diff --git a/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts b/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts index 662bbc3c6..487f2cb0a 100644 --- a/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts +++ b/packages/eslint-plugin-pf-codemods/src/ruleCustomization.ts @@ -27,6 +27,7 @@ export const warningRules = [ "horizontalSubnav-warn-ariaLabel", "jumpLinksItem-warn-markup-change", "label-warn-truncated-default", + "loginMainHeader-warn-updated-markup", "logViewer-moved-styles", "menuItemAction-warn-update-markup", "menuToggle-warn-iconOnly-toggle", diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts index e719e884d..314f1deb5 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/index.ts @@ -24,7 +24,8 @@ export * from "./interfaces"; export * from "./isReactIcon"; export * from "./JSXAttributes"; export * from "./makeJSXElementSelfClosing"; -export * from "./nodeMatches"; +export * from "./nodeMatches/checkMatchingImportDeclaration"; +export * from "./nodeMatches/checkMatchingJSXOpeningElement"; export * from "./pfPackageMatches"; export * from "./removeElement"; export * from "./removeEmptyLineAfter"; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches/checkMatchingImportDeclaration.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches/checkMatchingImportDeclaration.ts new file mode 100644 index 000000000..848497b83 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches/checkMatchingImportDeclaration.ts @@ -0,0 +1,30 @@ +import { ImportDeclaration, ImportSpecifier } from "estree-jsx"; +import { pfPackageMatches } from "../pfPackageMatches"; + +function checkSpecifierExists( + node: ImportDeclaration, + importSpecifier: ImportSpecifier +) { + return node.specifiers.some( + (specifier) => + specifier.type === "ImportSpecifier" && + specifier.imported.name === importSpecifier.imported.name + ); +} + +/** Used to check whether the current ImportDeclaration node matches at least 1 of the import specifiers. */ +export function checkMatchingImportDeclaration( + node: ImportDeclaration, + imports: ImportSpecifier | ImportSpecifier[], + packageName: string = "@patternfly/react-core" +) { + if (!pfPackageMatches(packageName, node.source.value)) { + return false; + } + + if (Array.isArray(imports)) { + return imports.some((imp) => checkSpecifierExists(node, imp)); + } + + return checkSpecifierExists(node, imports); +} diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches/checkMatchingJSXOpeningElement.ts similarity index 83% rename from packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches.ts rename to packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches/checkMatchingJSXOpeningElement.ts index 1d3657947..0863a56b1 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/nodeMatches/checkMatchingJSXOpeningElement.ts @@ -4,6 +4,7 @@ import { ImportDefaultSpecifier, } from "estree-jsx"; +/** Used to check whether the current JSXOpeningElement node matches at least 1 of the import specifiers. */ export function checkMatchingJSXOpeningElement( node: JSXOpeningElement, imports: diff --git a/packages/eslint-plugin-pf-codemods/src/rules/helpers/pfPackageMatches.ts b/packages/eslint-plugin-pf-codemods/src/rules/helpers/pfPackageMatches.ts index e60b8d21f..f6889abd7 100644 --- a/packages/eslint-plugin-pf-codemods/src/rules/helpers/pfPackageMatches.ts +++ b/packages/eslint-plugin-pf-codemods/src/rules/helpers/pfPackageMatches.ts @@ -1,5 +1,6 @@ import { ImportDeclaration } from "estree-jsx"; +// TODO: Swap the params so that packageName has a default value of pf/react-core and can be optional export function pfPackageMatches( packageName: string, nodeSrc: ImportDeclaration["source"]["value"] diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.md b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.md new file mode 100644 index 000000000..b0a39161d --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.md @@ -0,0 +1,3 @@ +### loginMainHeader-warn-updated-markup [(#10880)](https://github.com/patternfly/patternfly-react/pull/10880) + +The markup for LoginMainHeader - which is used internally within LoginPage - has been updated, now using a `div` wrapper instead of a `header` element wrapper. diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.test.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.test.ts new file mode 100644 index 000000000..9ffcb28d7 --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.test.ts @@ -0,0 +1,91 @@ +const ruleTester = require("../../ruletester"); +import * as rule from "./loginMainHeader-warn-updated-markup"; + +ruleTester.run("loginMainHeader-warn-updated-markup", rule, { + valid: [ + { + code: ``, + }, + { + code: `import { LoginMainHeader } from 'somewhere-else'`, + }, + { + code: ``, + }, + { + code: `import { LoginPage } from 'somewhere-else'`, + }, + ], + invalid: [ + { + code: `import { LoginMainHeader } from '@patternfly/react-core';`, + output: `import { LoginMainHeader } from '@patternfly/react-core';`, + errors: [ + { + message: `The markup for LoginMainHeader has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + { + code: `import { LoginPage } from '@patternfly/react-core';`, + output: `import { LoginPage } from '@patternfly/react-core';`, + errors: [ + { + message: `The markup for LoginMainHeader (which is used internally within LoginPage) has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + { + code: `import { LoginPage, LoginMainHeader, Button } from '@patternfly/react-core';`, + output: `import { LoginPage, LoginMainHeader, Button } from '@patternfly/react-core';`, + errors: [ + { + message: `The markup for LoginMainHeader (which is used internally within LoginPage) has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + { + code: `import { LoginMainHeader as CustomThing } from '@patternfly/react-core';`, + output: `import { LoginMainHeader as CustomThing } from '@patternfly/react-core';`, + errors: [ + { + message: `The markup for LoginMainHeader has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + { + code: `import { LoginMainHeader } from '@patternfly/react-core/dist/esm/components/LoginPage/index.js';`, + output: `import { LoginMainHeader } from '@patternfly/react-core/dist/esm/components/LoginPage/index.js';`, + errors: [ + { + message: `The markup for LoginMainHeader has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + { + code: `import { LoginMainHeader } from '@patternfly/react-core/dist/js/components/LoginPage/index.js';`, + output: `import { LoginMainHeader } from '@patternfly/react-core/dist/js/components/LoginPage/index.js';`, + errors: [ + { + message: `The markup for LoginMainHeader has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + { + code: `import { LoginMainHeader } from '@patternfly/react-core/dist/dynamic/components/LoginPage/index.js';`, + output: `import { LoginMainHeader } from '@patternfly/react-core/dist/dynamic/components/LoginPage/index.js';`, + errors: [ + { + message: `The markup for LoginMainHeader has been updated, now using a div wrapper instead of a header element wrapper.`, + type: "ImportDeclaration", + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.ts b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.ts new file mode 100644 index 000000000..e3d4479ec --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeader-warn-updated-markup.ts @@ -0,0 +1,35 @@ +import { Rule } from "eslint"; +import { ImportDeclaration } from "estree-jsx"; +import { getFromPackage, checkMatchingImportDeclaration } from "../../helpers"; + +// https://github.com/patternfly/patternfly-react/pull/10880 +module.exports = { + meta: {}, + create: function (context: Rule.RuleContext) { + const basePackage = "@patternfly/react-core"; + const { imports: loginImports } = getFromPackage(context, basePackage, [ + "LoginPage", + "LoginMainHeader", + ]); + + return !loginImports.length + ? {} + : { + ImportDeclaration(node: ImportDeclaration) { + if (checkMatchingImportDeclaration(node, loginImports)) { + const hasLoginPageImport = loginImports.find( + (imp) => imp.imported.name === "LoginPage" + ); + context.report({ + node, + message: `The markup for LoginMainHeader${ + hasLoginPageImport + ? " (which is used internally within LoginPage)" + : "" + } has been updated, now using a div wrapper instead of a header element wrapper.`, + }); + } + }, + }; + }, +}; diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeaderWarnUpdatedMarkupInput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeaderWarnUpdatedMarkupInput.tsx new file mode 100644 index 000000000..5b5c377cf --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeaderWarnUpdatedMarkupInput.tsx @@ -0,0 +1,8 @@ +import { LoginMainHeader, LoginPage } from "@patternfly/react-core"; + +export const LoginMainHeaderWarnUpdatedMarkupInput = () => ( + <> + + + +); diff --git a/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeaderWarnUpdatedMarkupOutput.tsx b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeaderWarnUpdatedMarkupOutput.tsx new file mode 100644 index 000000000..5b5c377cf --- /dev/null +++ b/packages/eslint-plugin-pf-codemods/src/rules/v6/loginMainHeaderWarnUpdatedMarkup/loginMainHeaderWarnUpdatedMarkupOutput.tsx @@ -0,0 +1,8 @@ +import { LoginMainHeader, LoginPage } from "@patternfly/react-core"; + +export const LoginMainHeaderWarnUpdatedMarkupInput = () => ( + <> + + + +);