diff --git a/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts b/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts index 27507aa09..1b24092af 100644 --- a/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts +++ b/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts @@ -33,6 +33,8 @@ export const warningRules = [ "notificationBadge-warn-markup-change", "notificationDrawerHeader-warn-update-markup", "overflowMenu-warn-updated-dropdownItem", + "page-warn-updated-markup", + "pageBreadcrumbAndSection-warn-updated-wrapperLogic", "pageSection-warn-variantClasses-applied", "popover-warn-appendTo-default", "popper-update-appendTo-default", diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts index 8ce9983ec..3d0f26ca6 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/JSXAttributes.ts @@ -16,6 +16,22 @@ export function getAttribute( ) as JSXAttribute | undefined; } +export function getAnyAttribute( + node: JSXElement | JSXOpeningElement, + attributeNames: string[] +) { + let foundAttribute = undefined; + for (const attribute of attributeNames) { + foundAttribute = getAttribute(node, attribute); + + if (foundAttribute) { + break; + } + } + + return foundAttribute; +} + export function getAttributeValue( context: Rule.RuleContext, node?: JSXAttribute["value"] diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.md new file mode 100644 index 000000000..f8b076cfa --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.md @@ -0,0 +1,17 @@ +### pageBreadcrumbAndSection-warn-updated-wrapperLogic [(#10650)](https://github.com/patternfly/patternfly-react/pull/10650) + +The `isWidthLimited` prop on PageBreadcrumb and PageSection will no longer determine whether the children of either component are wrapped in a PageBody. Instead the new `hasBodyWrapper` prop must be used. By default this new prop is set to true. Running the fix for this rule will apply `hasBodyWrapper` with the same value as the `isWidthLimited` prop or false if `isWidthLimited` is not passed. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.test.ts new file mode 100644 index 000000000..1fae61b22 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.test.ts @@ -0,0 +1,82 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./pageBreadcrumbAndSection-warn-updated-wrapperLogic"; +import { + ValidTests, + InvalidTests, + createInvalidTest, + createValidTest, +} from "../../helpers/testHelpers"; + +const validTests: ValidTests = []; +const invalidTests: InvalidTests = []; +const applicableComponents = ["PageBreadcrumb", "PageSection"]; +for (const component of applicableComponents) { + validTests.push(createValidTest(`<${component} isWidthLimited />`)); + + const message = `The isWidthLimited prop on ${component} will no longer determine whether the children are wrapped in a PageBody. Instead the new hasBodyWrapper prop must be used. By default this new prop is set to true. Running the fix for this rule will apply hasBodyWrapper with the same value as the isWidthLimited prop or false if isWidthLimited is not passed.`; + const errorObject = { + message, + type: "JSXOpeningElement", + }; + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core'; <${component} isWidthLimited />`, + `import { ${component} } from '@patternfly/react-core'; <${component} hasBodyWrapper isWidthLimited />`, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core'; <${component} />`, + `import { ${component} } from '@patternfly/react-core'; <${component} hasBodyWrapper={false} />`, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core'; <${component} isWidthLimited={someVar} />`, + `import { ${component} } from '@patternfly/react-core'; <${component} hasBodyWrapper={someVar} isWidthLimited={someVar} />`, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core'; <${component} isWidthLimited={() => someCallback()} />`, + `import { ${component} } from '@patternfly/react-core'; <${component} hasBodyWrapper={() => someCallback()} isWidthLimited={() => someCallback()} />`, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} as CustomThing } from '@patternfly/react-core'; `, + `import { ${component} as CustomThing } from '@patternfly/react-core'; `, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core/dist/esm/components/Page/index.js'; <${component} isWidthLimited />`, + `import { ${component} } from '@patternfly/react-core/dist/esm/components/Page/index.js'; <${component} hasBodyWrapper isWidthLimited />`, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core/dist/js/components/Page/index.js'; <${component} isWidthLimited />`, + `import { ${component} } from '@patternfly/react-core/dist/js/components/Page/index.js'; <${component} hasBodyWrapper isWidthLimited />`, + [errorObject] + ) + ); + invalidTests.push( + createInvalidTest( + `import { ${component} } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; <${component} isWidthLimited />`, + `import { ${component} } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; <${component} hasBodyWrapper isWidthLimited />`, + [errorObject] + ) + ); +} + +ruleTester.run("pageBreadcrumbAndSection-warn-updated-wrapperLogic", rule, { + valid: validTests, + invalid: invalidTests, +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.ts new file mode 100644 index 000000000..9943ba756 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSection-warn-updated-wrapperLogic.ts @@ -0,0 +1,57 @@ +import { Rule } from "eslint"; +import { JSXOpeningElement } from "estree-jsx"; +import { + getFromPackage, + getAttribute, + getAttributeValueText, +} from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10650 +module.exports = { + meta: { fixable: "code" }, + create: function (context: Rule.RuleContext) { + const { imports } = getFromPackage(context, "@patternfly/react-core"); + + const componentImports = imports.filter((specifier) => + ["PageBreadcrumb", "PageSection"].includes(specifier.imported.name) + ); + + return !componentImports.length + ? {} + : { + JSXOpeningElement(node: JSXOpeningElement) { + const applicableComponent = componentImports.find( + (imp) => + node.name.type === "JSXIdentifier" && + imp.local.name === node.name.name + ); + if (applicableComponent) { + const isWidthLimitedProp = getAttribute(node, "isWidthLimited"); + const isWidthLimitedValueText = getAttributeValueText( + context, + isWidthLimitedProp + ); + const hadBodyWrapperProp = getAttribute(node, "hasBodyWrapper"); + + if (!hadBodyWrapperProp) { + context.report({ + node, + message: `The isWidthLimited prop on ${applicableComponent.imported.name} will no longer determine whether the children are wrapped in a PageBody. Instead the new hasBodyWrapper prop must be used. By default this new prop is set to true. Running the fix for this rule will apply hasBodyWrapper with the same value as the isWidthLimited prop or false if isWidthLimited is not passed.`, + fix(fixer) { + const hasBodyWrapperValue = isWidthLimitedProp + ? isWidthLimitedValueText + : `{false}`; + return fixer.insertTextAfter( + node.name, + ` hasBodyWrapper${ + hasBodyWrapperValue ? `=${hasBodyWrapperValue}` : "" + }` + ); + }, + }); + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSectionWarnUpdatedWrapperLogicInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSectionWarnUpdatedWrapperLogicInput.tsx new file mode 100644 index 000000000..d0391e8b8 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSectionWarnUpdatedWrapperLogicInput.tsx @@ -0,0 +1,12 @@ +import { PageBreadcrumb, PageSection } from "@patternfly/react-core"; + +export const PageBreadcrumbAndSectionWarnUpdatedWrapperLogicInput = () => ( + <> + + + someCallback()} /> + + + someCallback()} /> + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSectionWarnUpdatedWrapperLogicOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSectionWarnUpdatedWrapperLogicOutput.tsx new file mode 100644 index 000000000..93408ed9a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageBreadcrumbAndSectionWarnUpdatedWrapperLogic/pageBreadcrumbAndSectionWarnUpdatedWrapperLogicOutput.tsx @@ -0,0 +1,12 @@ +import { PageBreadcrumb, PageSection } from "@patternfly/react-core"; + +export const PageBreadcrumbAndSectionWarnUpdatedWrapperLogicInput = () => ( + <> + + + someCallback()} isWidthLimited={() => someCallback()} /> + + + someCallback()} isWidthLimited={() => someCallback()} /> + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.md new file mode 100644 index 000000000..4422f3684 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.md @@ -0,0 +1,18 @@ +### pageNavigation-remove-component [(#10650)](https://github.com/patternfly/patternfly-react/pull/10650) + +The PageNavigation component has been removed from PatternFly. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` + diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.test.ts new file mode 100644 index 000000000..ffbcba573 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.test.ts @@ -0,0 +1,141 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./pageNavigation-remove-component"; + +ruleTester.run("pageNavigation-remove-component", rule, { + valid: [ + { + code: ``, + }, + { + code: ``, + }, + { + code: `import { PageNavigation } from '@patternfly/someOtherPackage'; `, + }, + { + code: `import { PageNavigation } from '@patternfly/someOtherPackage'; `, + }, + ], + invalid: [ + { + code: `import { PageNavigation } from '@patternfly/react-core'; `, + output: `import { PageNavigation } from '@patternfly/react-core'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core'; `, + output: `import { PageNavigation } from '@patternfly/react-core'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core';
Some internal content
`, + output: `import { PageNavigation } from '@patternfly/react-core';
Some internal content
`, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core';
Wrapper content
Adjacent Content
Some internal content
`, + output: `import { PageNavigation } from '@patternfly/react-core';
Wrapper content
Adjacent Content
Some internal content
`, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + { + code: `export { PageNavigation } from '@patternfly/react-core';`, + output: ``, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `export { PageNavigation, Button } from '@patternfly/react-core';`, + output: `export { Button } from '@patternfly/react-core';`, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `export { Alert, PageNavigation, Button } from '@patternfly/react-core';`, + output: `export { Alert, Button } from '@patternfly/react-core';`, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core'; export { PageNavigation }`, + output: `import { PageNavigation } from '@patternfly/react-core'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "ExportNamedDeclaration", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core'; export default PageNavigation`, + output: `import { PageNavigation } from '@patternfly/react-core'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "ExportDefaultDeclaration", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core/dist/esm/components/Page/index.js'; `, + output: `import { PageNavigation } from '@patternfly/react-core/dist/esm/components/Page/index.js'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core/dist/js/components/Page/index.js'; `, + output: `import { PageNavigation } from '@patternfly/react-core/dist/js/components/Page/index.js'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + { + code: `import { PageNavigation } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; `, + output: `import { PageNavigation } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; `, + errors: [ + { + message: `The PageNavigation component has been removed from PatternFly.`, + type: "JSXElement", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.ts new file mode 100644 index 000000000..df8bfcaf6 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigation-remove-component.ts @@ -0,0 +1,103 @@ +import { Rule, AST } from "eslint"; +import { + JSXElement, + ExportNamedDeclaration, + ExportDefaultDeclaration, +} from "estree-jsx"; +import { getFromPackage } from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10650 +module.exports = { + meta: { fixable: "code" }, + create: function (context: Rule.RuleContext) { + const { imports, exports } = getFromPackage( + context, + "@patternfly/react-core" + ); + + const pageNavImport = imports.find( + (specifier) => specifier.imported.name === "PageNavigation" + ); + const pageNavExport = exports.find( + (specifier) => specifier.local.name === "PageNavigation" + ); + + return !pageNavImport && !pageNavExport + ? {} + : { + JSXElement(node: JSXElement) { + if ( + pageNavImport && + node.openingElement.name.type === "JSXIdentifier" && + pageNavImport.local.name === node.openingElement.name.name + ) { + context.report({ + node, + message: + "The PageNavigation component has been removed from PatternFly.", + fix(fixer) { + const fixes = [fixer.remove(node.openingElement)]; + if (node.closingElement) { + fixes.push(fixer.remove(node.closingElement)); + } + return fixes; + }, + }); + } + }, + ExportNamedDeclaration(node: ExportNamedDeclaration) { + const pageNavSpecifier = node.specifiers.find((specifier) => { + const { name: localName } = specifier.local; + return pageNavExport + ? localName === pageNavExport.local.name + : localName === pageNavImport?.local.name; + }); + if (pageNavSpecifier) { + context.report({ + node, + message: + "The PageNavigation component has been removed from PatternFly.", + fix(fixer) { + if (node.specifiers.length === 1) { + return fixer.remove(node); + } + + if (!pageNavSpecifier.range) { + return []; + } + const tokenAfter = context + .getSourceCode() + .getTokenAfter(pageNavSpecifier); + const isCommaAfter = tokenAfter && tokenAfter.value === ","; + const rangeToRemove: AST.Range = [ + pageNavSpecifier.range[0], + isCommaAfter + ? tokenAfter.range[1] + : pageNavSpecifier.range[1], + ]; + return fixer.removeRange(rangeToRemove); + }, + }); + } + }, + ExportDefaultDeclaration(node: ExportDefaultDeclaration) { + if (!pageNavImport) { + return; + } + const exportName = + node.declaration.type === "Identifier" && node.declaration.name; + const isPageNavExport = pageNavImport.local.name === exportName; + + if (isPageNavExport) { + context.report({ + node, + message: `The PageNavigation component has been removed from PatternFly.`, + fix(fixer) { + return fixer.remove(node); + }, + }); + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigationRemoveComponentInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigationRemoveComponentInput.tsx new file mode 100644 index 000000000..965ec5007 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigationRemoveComponentInput.tsx @@ -0,0 +1,15 @@ +import { PageNavigation } from "@patternfly/react-core"; + +export const PageNavigationRemoveComponentInput = () => ( +
+ +
Some adjacent content
+ +
Some internal content
+
+
+); + +export { PageNavigation } from "@patternfly/react-core"; +export { PageNavigation as CustomNav }; +export default PageNavigation; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigationRemoveComponentOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigationRemoveComponentOutput.tsx new file mode 100644 index 000000000..f2c7b72c4 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageNavigationRemoveComponent/pageNavigationRemoveComponentOutput.tsx @@ -0,0 +1,15 @@ +import { PageNavigation } from "@patternfly/react-core"; + +export const PageNavigationRemoveComponentInput = () => ( +
+ +
Some adjacent content
+ +
Some internal content
+ +
+); + + + + diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.md new file mode 100644 index 000000000..cb8c3f6c4 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.md @@ -0,0 +1,18 @@ +### pageSection-remove-nav-type [(#10650)](https://github.com/patternfly/patternfly-react/pull/10650) + +The "nav" type for PageSection has been removed. + +#### Examples + +In: + +```jsx +%inputExample% +``` + +Out: + +```jsx +%outputExample% +``` + diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.test.ts new file mode 100644 index 000000000..2e4cef9e3 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.test.ts @@ -0,0 +1,105 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./pageSection-remove-nav-type"; + +ruleTester.run("pageSection-remove-nav-type", rule, { + valid: [ + { + code: ``, + }, + { + code: `import { PageSection } from '@patternfly/react-core'; `, + }, + ], + invalid: [ + { + code: `import { PageSection } from '@patternfly/react-core'; `, + output: `import { PageSection } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection, PageSectionTypes } from '@patternfly/react-core'; `, + output: `import { PageSection, PageSectionTypes } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection, PageSectionTypes } from '@patternfly/react-core'; const chosenType = PageSectionTypes.nav; `, + output: `import { PageSection, PageSectionTypes } from '@patternfly/react-core'; const chosenType = PageSectionTypes.nav; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection as CustomSection } from '@patternfly/react-core'; `, + output: `import { PageSection as CustomSection } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection, PageSectionTypes as CustomTypes } from '@patternfly/react-core'; `, + output: `import { PageSection, PageSectionTypes as CustomTypes } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection as CustomSection, PageSectionTypes as CustomTypes } from '@patternfly/react-core'; `, + output: `import { PageSection as CustomSection, PageSectionTypes as CustomTypes } from '@patternfly/react-core'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection } from '@patternfly/react-core/dist/esm/components/Page/index.js'; `, + output: `import { PageSection } from '@patternfly/react-core/dist/esm/components/Page/index.js'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection } from '@patternfly/react-core/dist/js/components/Page/index.js'; `, + output: `import { PageSection } from '@patternfly/react-core/dist/js/components/Page/index.js'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { PageSection } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; `, + output: `import { PageSection } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; `, + errors: [ + { + message: `The "nav" type for PageSection has been removed.`, + type: "JSXOpeningElement", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts new file mode 100644 index 000000000..d3b588abc --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSection-remove-nav-type.ts @@ -0,0 +1,51 @@ +import { Rule } from "eslint"; +import { JSXOpeningElement } from "estree-jsx"; +import { getFromPackage, getAttribute, getAttributeValue } from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10650 +module.exports = { + meta: { fixable: "code" }, + create: function (context: Rule.RuleContext) { + const { imports } = getFromPackage(context, "@patternfly/react-core"); + + const pageSectionImport = imports.find( + (specifier) => specifier.imported.name === "PageSection" + ); + const pageSectionTypeEnum = imports.find( + (specifier) => specifier.imported.name === "PageSectionTypes" + ); + + return !pageSectionImport + ? {} + : { + JSXOpeningElement(node: JSXOpeningElement) { + if ( + node.name.type === "JSXIdentifier" && + pageSectionImport.local.name === node.name.name + ) { + const typeProp = getAttribute(node, "type"); + if (!typeProp || !typeProp.value) { + return; + } + + const typeValue = getAttributeValue(context, typeProp.value); + const isEnumValueNav = + pageSectionTypeEnum && + typeValue.object?.name === pageSectionTypeEnum.local.name && + typeValue.property.name === "nav"; + if (typeValue !== "nav" && !isEnumValueNav) { + return; + } + + context.report({ + node, + message: 'The "nav" type for PageSection has been removed.', + fix(fixer) { + return fixer.remove(typeProp); + }, + }); + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSectionRemoveNavTypeInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSectionRemoveNavTypeInput.tsx new file mode 100644 index 000000000..4ef641d94 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSectionRemoveNavTypeInput.tsx @@ -0,0 +1,11 @@ +import { PageSection, PageSectionTypes } from "@patternfly/react-core"; + +const chosenType = PageSectionTypes.nav; + +export const PageSectionRemoveNavTypeInput = () => ( + <> + + + + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSectionRemoveNavTypeOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSectionRemoveNavTypeOutput.tsx new file mode 100644 index 000000000..c6cfef6fe --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageSectionRemoveNavType/pageSectionRemoveNavTypeOutput.tsx @@ -0,0 +1,11 @@ +import { PageSection, PageSectionTypes } from "@patternfly/react-core"; + +const chosenType = PageSectionTypes.nav; + +export const PageSectionRemoveNavTypeInput = () => ( + <> + + + + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.md new file mode 100644 index 000000000..a4f1e209c --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.md @@ -0,0 +1,3 @@ +### page-warn-updated-markup [(#10650)](https://github.com/patternfly/patternfly-react/pull/10650) + +The markup for Page has changed. When either the `horizontalSubnav` or `breadcrumb` props are passed, a PageBody component will always wrap the contents. diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.test.ts new file mode 100644 index 000000000..377fc5d2f --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.test.ts @@ -0,0 +1,85 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./page-warn-updated-markup"; + +ruleTester.run("page-warn-updated-markup", rule, { + valid: [ + { + code: ``, + }, + { + code: `import { Page } from '@patternfly/react-core'; `, + }, + ], + invalid: [ + { + code: `import { Page } from '@patternfly/react-core'; `, + output: `import { Page } from '@patternfly/react-core'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { Page } from '@patternfly/react-core'; `, + output: `import { Page } from '@patternfly/react-core'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { Page } from '@patternfly/react-core'; `, + output: `import { Page } from '@patternfly/react-core'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { Page as CustomThing } from '@patternfly/react-core'; `, + output: `import { Page as CustomThing } from '@patternfly/react-core'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { Page } from '@patternfly/react-core/dist/esm/components/Page/index.js'; `, + output: `import { Page } from '@patternfly/react-core/dist/esm/components/Page/index.js'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { Page } from '@patternfly/react-core/dist/js/components/Page/index.js'; `, + output: `import { Page } from '@patternfly/react-core/dist/js/components/Page/index.js'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + { + code: `import { Page } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; `, + output: `import { Page } from '@patternfly/react-core/dist/dynamic/components/Page/index.js'; `, + errors: [ + { + message: `The markup for Page has changed. When either the \`horizontalSubnav\` or \`breadcrumb\` props are passed, a PageBody component will always wrap the contents.`, + type: "JSXOpeningElement", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.ts new file mode 100644 index 000000000..dd889f16f --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/page-warn-updated-markup.ts @@ -0,0 +1,38 @@ +import { Rule } from "eslint"; +import { JSXOpeningElement } from "estree-jsx"; +import { getFromPackage, getAnyAttribute } from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10650 +module.exports = { + meta: {}, + create: function (context: Rule.RuleContext) { + const { imports } = getFromPackage(context, "@patternfly/react-core"); + + const pageImport = imports.find( + (specifier) => specifier.imported.name === "Page" + ); + + return !pageImport + ? {} + : { + JSXOpeningElement(node: JSXOpeningElement) { + if ( + node.name.type === "JSXIdentifier" && + pageImport.local.name === node.name.name + ) { + const applicableProps = getAnyAttribute(node, [ + "horizontalSubnav", + "breadcrumb", + ]); + if (applicableProps) { + context.report({ + node, + message: + "The markup for Page has changed. When either the `horizontalSubnav` or `breadcrumb` props are passed, a PageBody component will always wrap the contents.", + }); + } + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/pageWarnUpdatedMarkupInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/pageWarnUpdatedMarkupInput.tsx new file mode 100644 index 000000000..4fc74750a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/pageWarnUpdatedMarkupInput.tsx @@ -0,0 +1,5 @@ +import { Page } from "@patternfly/react-core"; + +export const PageWarnUpdatedMarkupInput = () => ( + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/pageWarnUpdatedMarkupOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/pageWarnUpdatedMarkupOutput.tsx new file mode 100644 index 000000000..4fc74750a --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/pageWarnUpdatedMarkup/pageWarnUpdatedMarkupOutput.tsx @@ -0,0 +1,5 @@ +import { Page } from "@patternfly/react-core"; + +export const PageWarnUpdatedMarkupInput = () => ( + +);