diff --git a/packages/eslint-plugin-pf-codemods/index.js b/packages/eslint-plugin-pf-codemods/index.js
index 1d79586a3..b1f34dc20 100644
--- a/packages/eslint-plugin-pf-codemods/index.js
+++ b/packages/eslint-plugin-pf-codemods/index.js
@@ -11,7 +11,7 @@ const rules = {
"select-rename-isExpanded": require('./lib/rules/select-rename-isExpanded'),
"title-require-heading-level": require('./lib/rules/title-require-heading-level'),
"title-size": require('./lib/rules/title-size'),
- "wizard-text": require('./lib/rules/wizard-text'),
+ "wizard-rename-text": require('./lib/rules/wizard-rename-text'),
"wizard-rename-hasBodyPadding": require('./lib/rules/wizard-rename-hasBodyPadding'),
"wizard-remove-props": require('./lib/rules/wizard-remove-props'),
"background-image-src-enum": require('./lib/rules/background-image-src-enum'),
@@ -30,6 +30,7 @@ const rules = {
"remove-isPseudo-props": require('./lib/rules/remove-isPseudo-props'),
"label-remove-isCompact": require('./lib/rules/label-remove-isCompact'),
"rename-noPadding": require('./lib/rules/rename-noPadding'),
+ "tab-title-text": require('./lib/rules/tab-title-text'),
};
module.exports = {
diff --git a/packages/eslint-plugin-pf-codemods/lib/helpers.js b/packages/eslint-plugin-pf-codemods/lib/helpers.js
index c6f432e02..b3316d114 100644
--- a/packages/eslint-plugin-pf-codemods/lib/helpers.js
+++ b/packages/eslint-plugin-pf-codemods/lib/helpers.js
@@ -98,9 +98,22 @@ function renameComponent(componentMap, message) {
}
}
+function addImport(context, fixer, package, currentImport, newImport) {
+ const specifierToAddTo = getPackageImports(context, package)
+ .filter(specifier => !currentImport || specifier.imported.name === currentImport)
+ .pop();
+
+ if (!specifierToAddTo) {
+ return [];
+ }
+
+ return fixer.insertTextAfter(specifierToAddTo, `, ${newImport}`);
+}
+
module.exports = {
getPackageImports,
renameProp,
renameProps,
- renameComponent
+ renameComponent,
+ addImport
}
\ No newline at end of file
diff --git a/packages/eslint-plugin-pf-codemods/lib/rules/chipgroup-remove-chipgrouptoolbaritem.js b/packages/eslint-plugin-pf-codemods/lib/rules/chipgroup-remove-chipgrouptoolbaritem.js
index 875e6ebea..81399fcd6 100644
--- a/packages/eslint-plugin-pf-codemods/lib/rules/chipgroup-remove-chipgrouptoolbaritem.js
+++ b/packages/eslint-plugin-pf-codemods/lib/rules/chipgroup-remove-chipgrouptoolbaritem.js
@@ -10,7 +10,7 @@ module.exports = {
return !chipGroupImport ? {} : {
JSXElement(node) {
- if (chipGroupToolbarItemImport.local.name === node.openingElement.name.name) {
+ if (chipGroupToolbarItemImport && chipGroupToolbarItemImport.local.name === node.openingElement.name.name) {
const hasSingleChipGroupParent = node.parent
&& node.parent.openingElement.name.name === chipGroupImport.local.name
&& node.parent.children.filter(child => child.type === 'JSXElement').length === 1;
diff --git a/packages/eslint-plugin-pf-codemods/lib/rules/dropdown-rename-icon.js b/packages/eslint-plugin-pf-codemods/lib/rules/dropdown-rename-icon.js
index 0db43b829..9f998747e 100644
--- a/packages/eslint-plugin-pf-codemods/lib/rules/dropdown-rename-icon.js
+++ b/packages/eslint-plugin-pf-codemods/lib/rules/dropdown-rename-icon.js
@@ -10,7 +10,7 @@ module.exports = {
return !imports ? {} : {
JSXElement(node) {
- if (dropdownItemImport.local.name === node.openingElement.name.name) {
+ if (dropdownItemImport && dropdownItemImport.local.name === node.openingElement.name.name) {
const dropdownIcons = node.children.filter(child =>
child.openingElement &&
child.openingElement.name.name === dropdownItemIconImport.local.name
diff --git a/packages/eslint-plugin-pf-codemods/lib/rules/tab-title-text.js b/packages/eslint-plugin-pf-codemods/lib/rules/tab-title-text.js
new file mode 100644
index 000000000..69e58b9d2
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/lib/rules/tab-title-text.js
@@ -0,0 +1,56 @@
+const { getPackageImports, addImport } = require('../helpers');
+
+// https://github.com/patternfly/patternfly-react/pull/4146
+module.exports = {
+ create: function(context) {
+ const tabImport = getPackageImports(context, '@patternfly/react-core')
+ .filter(specifier => specifier.imported.name === 'Tab');
+
+ return tabImport.length ? {
+ JSXOpeningElement(node) {
+ if (tabImport.map(imp => imp.local.name).includes(node.name.name)) {
+ const attribute = node.attributes.find(node => node.name.name === 'title');
+ if (attribute) {
+ const { value } = attribute;
+ let replacement;
+ if (value.type === 'Literal') {
+ // i.e. title="Title"
+ replacement = fixer => [
+ fixer.replaceText(attribute.value, `{${attribute.value.value}}`)
+ ];
+ } else if (value.type === 'JSXExpressionContainer') {
+ if (value.expression.type === 'Literal') {
+ // i.e. title={'Title'} or title={100}
+ replacement = fixer => [
+ fixer.replaceText(attribute.value, `{${attribute.value.expression.value}}`)
+ ];
+ } else if (value.expression.type === 'Identifier') {
+ // i.e. title={myVariable}
+ replacement = fixer => [
+ fixer.replaceText(attribute.value, `{{${attribute.value.expression.name}}}`)
+ ];
+ } else if (value.expression.type === 'JSXElement' && value.expression.openingElement.name.name !== 'TabTitleText') {
+ // i.e. title={}
+ replacement = fixer => [
+ fixer.insertTextBefore(attribute.value.expression, ``),
+ fixer.insertTextAfter(attribute.value.expression, ``)
+ ];
+ }
+ }
+ if (replacement) {
+ context.report({
+ node,
+ message: `title needs to be wrapped with the TabTitleText and/or TabTitleIcon component`,
+ fix(fixer) {
+ return replacement(fixer).concat(
+ addImport(context, fixer, '@patternfly/react-core', 'Tab', 'TabTitleText')
+ );
+ }
+ });
+ }
+ }
+ }
+ }
+ } : {};
+ }
+};
diff --git a/packages/eslint-plugin-pf-codemods/test/rules/tab-title-text.js b/packages/eslint-plugin-pf-codemods/test/rules/tab-title-text.js
new file mode 100644
index 000000000..9199ddfe2
--- /dev/null
+++ b/packages/eslint-plugin-pf-codemods/test/rules/tab-title-text.js
@@ -0,0 +1,24 @@
+const ruleTester = require('./ruletester');
+const rule = require('../../lib/rules/tab-title-text');
+
+ruleTester.run("tab-title-text", rule, {
+ valid: [
+ {
+ code: `import { Tab, TabTitleText } from '@patternfly/react-core'; Title}>Content`,
+ },
+ {
+ // No @patternfly/react-core import
+ code: `Content`,
+ }
+ ],
+ invalid: [
+ {
+ code: `import { Tab } from '@patternfly/react-core'; Content`,
+ output: `import { Tab, TabTitleText } from '@patternfly/react-core'; Title}>Content`,
+ errors: [{
+ message: `title needs to be wrapped with the TabTitleText and/or TabTitleIcon component`,
+ type: "JSXOpeningElement",
+ }]
+ },
+ ]
+});
diff --git a/test/test.tsx b/test/test.tsx
index f2ccec006..5132a1bdf 100644
--- a/test/test.tsx
+++ b/test/test.tsx
@@ -24,3 +24,13 @@ export const MyButtonWithCustomEnum = (
)
+import { Tab } from '@patternfly/react-core';
+const tabText = After;
+const TabTextComp = () => After;
+export const TestTab = Content;
+export const TestTab2 = hello}>Content;
+export const TestTab3 = Content;
+export const TestTab3a = Content;
+export const TestTab4 = Content;
+export const TestTab5 = }>Content;
+export const TestTab6 = hello}>Content;