@atlaskit/eslint-plugin-design-system 13.30.1 → 13.32.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +21 -0
- package/README.md +75 -75
- package/dist/cjs/presets/all-flat.codegen.js +3 -3
- package/dist/cjs/presets/all.codegen.js +3 -3
- package/dist/cjs/presets/recommended-flat.codegen.js +2 -2
- package/dist/cjs/presets/recommended.codegen.js +2 -2
- package/dist/cjs/rules/index.codegen.js +5 -5
- package/dist/cjs/rules/lozenge-badge-tag-labelling-system-migration/index.js +712 -0
- package/dist/cjs/rules/no-placeholder/index.js +162 -0
- package/dist/es2019/presets/all-flat.codegen.js +3 -3
- package/dist/es2019/presets/all.codegen.js +3 -3
- package/dist/es2019/presets/recommended-flat.codegen.js +2 -2
- package/dist/es2019/presets/recommended.codegen.js +2 -2
- package/dist/es2019/rules/index.codegen.js +5 -5
- package/dist/es2019/rules/lozenge-badge-tag-labelling-system-migration/index.js +702 -0
- package/dist/es2019/rules/no-placeholder/index.js +142 -0
- package/dist/esm/presets/all-flat.codegen.js +3 -3
- package/dist/esm/presets/all.codegen.js +3 -3
- package/dist/esm/presets/recommended-flat.codegen.js +2 -2
- package/dist/esm/presets/recommended.codegen.js +2 -2
- package/dist/esm/rules/index.codegen.js +5 -5
- package/dist/esm/rules/lozenge-badge-tag-labelling-system-migration/index.js +705 -0
- package/dist/esm/rules/no-placeholder/index.js +154 -0
- package/dist/types/presets/all-flat.codegen.d.ts +1 -1
- package/dist/types/presets/all.codegen.d.ts +1 -1
- package/dist/types/presets/recommended-flat.codegen.d.ts +1 -1
- package/dist/types/presets/recommended.codegen.d.ts +1 -1
- package/dist/types/rules/index.codegen.d.ts +1 -1
- package/dist/types/rules/no-placeholder/index.d.ts +6 -0
- package/dist/types-ts4.5/presets/all-flat.codegen.d.ts +1 -1
- package/dist/types-ts4.5/presets/all.codegen.d.ts +1 -1
- package/dist/types-ts4.5/presets/recommended-flat.codegen.d.ts +1 -1
- package/dist/types-ts4.5/presets/recommended.codegen.d.ts +1 -1
- package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -1
- package/dist/types-ts4.5/rules/no-placeholder/index.d.ts +6 -0
- package/package.json +2 -2
- package/dist/cjs/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.js +0 -332
- package/dist/cjs/rules/no-utility-icons/checks.js +0 -246
- package/dist/cjs/rules/no-utility-icons/index.js +0 -49
- package/dist/es2019/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.js +0 -324
- package/dist/es2019/rules/no-utility-icons/checks.js +0 -177
- package/dist/es2019/rules/no-utility-icons/index.js +0 -44
- package/dist/esm/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.js +0 -326
- package/dist/esm/rules/no-utility-icons/checks.js +0 -239
- package/dist/esm/rules/no-utility-icons/index.js +0 -43
- package/dist/types/rules/lozenge-isBold-and-lozenge-badge-appearance-migration/index.d.ts +0 -3
- package/dist/types/rules/no-utility-icons/checks.d.ts +0 -10
- package/dist/types/rules/no-utility-icons/index.d.ts +0 -3
- package/dist/types-ts4.5/rules/no-utility-icons/checks.d.ts +0 -10
- /package/dist/{types-ts4.5/rules/no-utility-icons → types/rules/lozenge-badge-tag-labelling-system-migration}/index.d.ts +0 -0
- /package/dist/types-ts4.5/rules/{lozenge-isBold-and-lozenge-badge-appearance-migration → lozenge-badge-tag-labelling-system-migration}/index.d.ts +0 -0
|
@@ -1,177 +0,0 @@
|
|
|
1
|
-
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
-
const specialCases = {
|
|
3
|
-
'@atlaskit/icon/utility/migration/cross--editor-close': '@atlaskit/icon/core/migration/cross--editor-close'
|
|
4
|
-
};
|
|
5
|
-
const iconPropsinNewButton = ['icon', 'iconBefore', 'iconAfter'];
|
|
6
|
-
export const createChecks = context => {
|
|
7
|
-
const importStatementsUtility = {};
|
|
8
|
-
const importStatementsCore = {};
|
|
9
|
-
const newButtonImports = new Set();
|
|
10
|
-
const errors = {};
|
|
11
|
-
|
|
12
|
-
/**
|
|
13
|
-
* Gets the value of a boolean configuration flag
|
|
14
|
-
* @param key the key of the configuration flag
|
|
15
|
-
* @param defaultValue The default value of the configuration flag
|
|
16
|
-
* @returns defaultValue if the configuration flag is not set, the defaultValue of the configuration flag otherwise
|
|
17
|
-
*/
|
|
18
|
-
const getConfigFlag = (key, defaultValue) => {
|
|
19
|
-
if (context.options && context.options.length > 0 && context.options[0] && context.options[0].hasOwnProperty(key)) {
|
|
20
|
-
return context.options[0][key] === !defaultValue ? !defaultValue : defaultValue;
|
|
21
|
-
}
|
|
22
|
-
return defaultValue;
|
|
23
|
-
};
|
|
24
|
-
const checkImportDeclarations = node => {
|
|
25
|
-
const moduleSource = node.source.value;
|
|
26
|
-
if (typeof moduleSource !== 'string') {
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
29
|
-
if (moduleSource.startsWith('@atlaskit/icon/utility/')) {
|
|
30
|
-
for (const specifier of node.specifiers) {
|
|
31
|
-
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
32
|
-
importStatementsUtility[specifier.local.name] = node;
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
} else if (moduleSource.startsWith('@atlaskit/icon/core/')) {
|
|
36
|
-
for (const specifier of node.specifiers) {
|
|
37
|
-
if (specifier.type === 'ImportDefaultSpecifier') {
|
|
38
|
-
importStatementsCore[moduleSource] = {
|
|
39
|
-
node,
|
|
40
|
-
localName: specifier.local.name
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
} else if (moduleSource.startsWith('@atlaskit/button/')) {
|
|
45
|
-
for (const specifier of node.specifiers) {
|
|
46
|
-
newButtonImports.add(specifier.local.name);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
};
|
|
50
|
-
const checkJSXElement = node => {
|
|
51
|
-
if (!(isNodeOfType(node, 'JSXElement') && isNodeOfType(node.openingElement.name, 'JSXIdentifier') && importStatementsUtility.hasOwnProperty(node.openingElement.name.name) && typeof importStatementsUtility[node.openingElement.name.name].source.value === 'string')) {
|
|
52
|
-
return;
|
|
53
|
-
}
|
|
54
|
-
if (!errors.hasOwnProperty(node.openingElement.name.name)) {
|
|
55
|
-
errors[node.openingElement.name.name] = [];
|
|
56
|
-
}
|
|
57
|
-
errors[node.openingElement.name.name].push({
|
|
58
|
-
node,
|
|
59
|
-
messageId: 'noUtilityIconsJSXElement',
|
|
60
|
-
localName: node.openingElement.name.name,
|
|
61
|
-
fixable: true,
|
|
62
|
-
inNewButton: false
|
|
63
|
-
});
|
|
64
|
-
};
|
|
65
|
-
|
|
66
|
-
// Cases: As Props, In Function Calls, In Arrays, In Maps, In Exports
|
|
67
|
-
const checkIconReference = node => {
|
|
68
|
-
//if this is an import statement then exit early
|
|
69
|
-
if (node.parent && (isNodeOfType(node.parent, 'ImportSpecifier') || isNodeOfType(node.parent, 'ImportDefaultSpecifier'))) {
|
|
70
|
-
return;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
//check the reference to see if it's a utility icon, if not exit early
|
|
74
|
-
if (!importStatementsUtility.hasOwnProperty(node.name)) {
|
|
75
|
-
return;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// if it is in new Button then we can fix it
|
|
79
|
-
if (node.parent && node.parent.parent && node.parent.parent.parent && isNodeOfType(node.parent, 'JSXExpressionContainer') && isNodeOfType(node.parent.parent, 'JSXAttribute') && isNodeOfType(node.parent.parent.name, 'JSXIdentifier') && iconPropsinNewButton.includes(node.parent.parent.name.name) && isNodeOfType(node.parent.parent.parent, 'JSXOpeningElement') && isNodeOfType(node.parent.parent.parent.name, 'JSXIdentifier') && newButtonImports.has(node.parent.parent.parent.name.name)) {
|
|
80
|
-
// if it is in new Button then we can fix it
|
|
81
|
-
if (!errors.hasOwnProperty(node.name)) {
|
|
82
|
-
errors[node.name] = [];
|
|
83
|
-
}
|
|
84
|
-
errors[node.name].push({
|
|
85
|
-
node,
|
|
86
|
-
messageId: 'noUtilityIconsReference',
|
|
87
|
-
localName: node.name,
|
|
88
|
-
fixable: true,
|
|
89
|
-
inNewButton: true
|
|
90
|
-
});
|
|
91
|
-
return;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
// manually error
|
|
95
|
-
if (!errors.hasOwnProperty(node.name)) {
|
|
96
|
-
errors[node.name] = [];
|
|
97
|
-
}
|
|
98
|
-
errors[node.name].push({
|
|
99
|
-
node,
|
|
100
|
-
messageId: 'noUtilityIconsReference',
|
|
101
|
-
localName: node.name,
|
|
102
|
-
fixable: false,
|
|
103
|
-
inNewButton: false
|
|
104
|
-
});
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Throws the relevant errors in the correct order.
|
|
109
|
-
*/
|
|
110
|
-
const throwErrors = () => {
|
|
111
|
-
const shouldAutoFix = getConfigFlag('enableAutoFixer', false);
|
|
112
|
-
for (const utilityIcon of Object.keys(errors)) {
|
|
113
|
-
const allFixable = errors[utilityIcon].every(x => x.fixable); // Check if ALL errors for a giving import are fixable
|
|
114
|
-
const originalImportNode = importStatementsUtility[utilityIcon];
|
|
115
|
-
const oldImportName = importStatementsUtility[utilityIcon].source.value;
|
|
116
|
-
const newImportName = specialCases.hasOwnProperty(oldImportName) ? specialCases[oldImportName] : oldImportName.replace('@atlaskit/icon/utility/', '@atlaskit/icon/core/');
|
|
117
|
-
const existingImport = importStatementsCore.hasOwnProperty(newImportName) ? importStatementsCore[newImportName].localName : null;
|
|
118
|
-
let replacementImportName = existingImport ? existingImport : allFixable ? utilityIcon : `${utilityIcon}Core`;
|
|
119
|
-
let importFixAdded = false;
|
|
120
|
-
for (const [index, error] of errors[utilityIcon].entries()) {
|
|
121
|
-
if (error.fixable && shouldAutoFix) {
|
|
122
|
-
context.report({
|
|
123
|
-
node: error.node,
|
|
124
|
-
messageId: error.messageId,
|
|
125
|
-
fix: fixer => {
|
|
126
|
-
const fixes = [];
|
|
127
|
-
|
|
128
|
-
// Add a new import statement if it doesn't already exist
|
|
129
|
-
if (!existingImport && !importFixAdded) {
|
|
130
|
-
importFixAdded = true;
|
|
131
|
-
fixes.push(fixer.insertTextBefore(originalImportNode, `import ${replacementImportName} from '${newImportName}';\n`));
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
// Handle JSX elements differently if they are in a "new Button"
|
|
135
|
-
if (error.inNewButton) {
|
|
136
|
-
// Replace the icon with a function that wraps it with iconProps and size="small"
|
|
137
|
-
const wrappedIcon = `(iconProps) => <${replacementImportName} {...iconProps} size="small" />`;
|
|
138
|
-
fixes.push(fixer.replaceText(error.node, wrappedIcon));
|
|
139
|
-
} else if (isNodeOfType(error.node, 'JSXElement') && isNodeOfType(error.node.openingElement.name, 'JSXIdentifier')) {
|
|
140
|
-
// Replace the JSX element's closing tag with size="small"
|
|
141
|
-
const newOpeningElementText = context.sourceCode.getText(error.node.openingElement).replace(/\s*\/\s*>$/, ` size="small"\/>`).replace(new RegExp('<s*' + error.node.openingElement.name.name), `<${replacementImportName}`);
|
|
142
|
-
fixes.push(fixer.replaceText(error.node.openingElement, newOpeningElementText));
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
// Handle the first fixable error for import removal if all fixable for this import
|
|
146
|
-
if (index === 0 && allFixable) {
|
|
147
|
-
fixes.push(fixer.remove(originalImportNode));
|
|
148
|
-
}
|
|
149
|
-
return fixes;
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
} else {
|
|
153
|
-
// Report non-fixable errors
|
|
154
|
-
context.report({
|
|
155
|
-
node: error.node,
|
|
156
|
-
messageId: error.messageId
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// If other utility icons are imported but there were no errors for them - (this should only be unused imports but good to have as a backup), report them
|
|
163
|
-
const otherUtilityImportsWithoutErrors = Object.keys(importStatementsUtility).filter(utilityIcon => !errors.hasOwnProperty(utilityIcon));
|
|
164
|
-
for (const utilityIcon of otherUtilityImportsWithoutErrors) {
|
|
165
|
-
context.report({
|
|
166
|
-
node: importStatementsUtility[utilityIcon],
|
|
167
|
-
messageId: 'noUtilityIconsImport'
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
return {
|
|
172
|
-
checkImportDeclarations,
|
|
173
|
-
checkJSXElement,
|
|
174
|
-
checkIconReference,
|
|
175
|
-
throwErrors
|
|
176
|
-
};
|
|
177
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
import { createLintRule } from '../utils/create-rule';
|
|
2
|
-
import { createChecks } from './checks';
|
|
3
|
-
const rule = createLintRule({
|
|
4
|
-
meta: {
|
|
5
|
-
name: 'no-utility-icons',
|
|
6
|
-
fixable: 'code',
|
|
7
|
-
hasSuggestions: true,
|
|
8
|
-
type: 'problem',
|
|
9
|
-
schema: [{
|
|
10
|
-
type: 'object',
|
|
11
|
-
properties: {
|
|
12
|
-
enableAutoFixer: {
|
|
13
|
-
type: 'boolean'
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
additionalProperties: false
|
|
17
|
-
}],
|
|
18
|
-
docs: {
|
|
19
|
-
description: 'Disallow use of deprecated utility icons, in favor of core icons with `size="small"`.',
|
|
20
|
-
recommended: true,
|
|
21
|
-
severity: 'warn'
|
|
22
|
-
},
|
|
23
|
-
messages: {
|
|
24
|
-
noUtilityIconsJSXElement: `Utility icons are deprecated. Please use core icons instead with the size prop set to small.`,
|
|
25
|
-
noUtilityIconsImport: `Utility icons are deprecated. Please do not import them, use core icons instead.`,
|
|
26
|
-
noUtilityIconsReference: `Utility icons are deprecated. To replace them, please use core icons with the size prop set to small instead.`
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
|
-
create(context) {
|
|
30
|
-
const {
|
|
31
|
-
checkImportDeclarations,
|
|
32
|
-
checkJSXElement,
|
|
33
|
-
checkIconReference,
|
|
34
|
-
throwErrors
|
|
35
|
-
} = createChecks(context);
|
|
36
|
-
return {
|
|
37
|
-
ImportDeclaration: checkImportDeclarations,
|
|
38
|
-
JSXElement: checkJSXElement,
|
|
39
|
-
Identifier: checkIconReference,
|
|
40
|
-
'Program:exit': throwErrors
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
});
|
|
44
|
-
export default rule;
|
|
@@ -1,326 +0,0 @@
|
|
|
1
|
-
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
-
import { createLintRule } from '../utils/create-rule';
|
|
3
|
-
var rule = createLintRule({
|
|
4
|
-
meta: {
|
|
5
|
-
name: 'lozenge-isBold-and-lozenge-badge-appearance-migration',
|
|
6
|
-
fixable: 'code',
|
|
7
|
-
type: 'suggestion',
|
|
8
|
-
docs: {
|
|
9
|
-
description: 'Helps migrate Lozenge isBold prop and appearance values (for both Lozenge and Badge components) as part of the Labelling System Phase 1 migration.',
|
|
10
|
-
recommended: false,
|
|
11
|
-
severity: 'warn'
|
|
12
|
-
},
|
|
13
|
-
messages: {
|
|
14
|
-
updateAppearance: 'Update appearance value to new semantic value.',
|
|
15
|
-
migrateTag: 'Non-bold <Lozenge> variants should migrate to <Tag> component.',
|
|
16
|
-
manualReview: "Dynamic 'isBold' props require manual review before migration.",
|
|
17
|
-
updateBadgeAppearance: 'Update Badge appearance value "{{oldValue}}" to new semantic value "{{newValue}}".',
|
|
18
|
-
dynamicBadgeAppearance: 'Dynamic appearance prop values require manual review to ensure they use the new semantic values: neutral, information, inverse, danger, success.'
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
create: function create(context) {
|
|
22
|
-
/**
|
|
23
|
-
* Contains a map of imported Lozenge components.
|
|
24
|
-
*/
|
|
25
|
-
var lozengeImports = {}; // local name -> import source
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Contains a map of imported Badge components.
|
|
29
|
-
*/
|
|
30
|
-
var badgeImports = {}; // local name -> import source
|
|
31
|
-
|
|
32
|
-
/**
|
|
33
|
-
* Check if a JSX attribute value is a literal false
|
|
34
|
-
*/
|
|
35
|
-
function isLiteralFalse(node) {
|
|
36
|
-
return node && node.type === 'JSXExpressionContainer' && node.expression && node.expression.type === 'Literal' && node.expression.value === false;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Check if a JSX attribute value is dynamic (not a static literal value)
|
|
41
|
-
* Can be used for any prop type (boolean, string, etc.)
|
|
42
|
-
*/
|
|
43
|
-
function isDynamicExpression(node) {
|
|
44
|
-
if (!node) {
|
|
45
|
-
return false;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// If it's a plain literal (e.g., appearance="value"), it's not dynamic
|
|
49
|
-
if (node.type === 'Literal') {
|
|
50
|
-
return false;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
// If it's an expression container with a non-literal expression, it's dynamic
|
|
54
|
-
if (node.type === 'JSXExpressionContainer') {
|
|
55
|
-
var expr = node.expression;
|
|
56
|
-
return expr && expr.type !== 'Literal';
|
|
57
|
-
}
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Get all attributes as an object for easier manipulation
|
|
63
|
-
*/
|
|
64
|
-
function getAttributesMap(attributes) {
|
|
65
|
-
var map = {};
|
|
66
|
-
attributes.forEach(function (attr) {
|
|
67
|
-
if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
|
|
68
|
-
map[attr.name.name] = attr;
|
|
69
|
-
}
|
|
70
|
-
});
|
|
71
|
-
return map;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
/**
|
|
75
|
-
* Map old appearance values to new semantic appearance values
|
|
76
|
-
* Both Lozenge and Tag now use the same appearance prop with new semantic values
|
|
77
|
-
*/
|
|
78
|
-
function mapToNewAppearanceValue(oldValue) {
|
|
79
|
-
var mapping = {
|
|
80
|
-
success: 'success',
|
|
81
|
-
default: 'default',
|
|
82
|
-
removed: 'removed',
|
|
83
|
-
inprogress: 'inprogress',
|
|
84
|
-
new: 'new',
|
|
85
|
-
moved: 'moved'
|
|
86
|
-
};
|
|
87
|
-
// TODO: Update this mapping based on actual new semantic values when provided
|
|
88
|
-
return mapping[oldValue] || oldValue;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
/**
|
|
92
|
-
* Map Badge old appearance values to new semantic appearance values
|
|
93
|
-
*/
|
|
94
|
-
function mapBadgeToNewAppearanceValue(oldValue) {
|
|
95
|
-
var mapping = {
|
|
96
|
-
added: 'success',
|
|
97
|
-
removed: 'danger',
|
|
98
|
-
default: 'neutral',
|
|
99
|
-
primary: 'information',
|
|
100
|
-
primaryInverted: 'inverse',
|
|
101
|
-
important: 'danger'
|
|
102
|
-
};
|
|
103
|
-
return mapping[oldValue] || oldValue;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Extract the string value from a JSX attribute value
|
|
108
|
-
*/
|
|
109
|
-
function extractStringValue(attrValue) {
|
|
110
|
-
if (!attrValue) {
|
|
111
|
-
return null;
|
|
112
|
-
}
|
|
113
|
-
if (attrValue.type === 'Literal') {
|
|
114
|
-
return attrValue.value;
|
|
115
|
-
}
|
|
116
|
-
if (attrValue.type === 'JSXExpressionContainer' && attrValue.expression && attrValue.expression.type === 'Literal') {
|
|
117
|
-
return attrValue.expression.value;
|
|
118
|
-
}
|
|
119
|
-
return null;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Create a fixer function to replace an appearance prop value
|
|
124
|
-
* Handles both Literal and JSXExpressionContainer with Literal
|
|
125
|
-
*/
|
|
126
|
-
function createAppearanceFixer(attrValue, newValue) {
|
|
127
|
-
return function (fixer) {
|
|
128
|
-
if (!attrValue) {
|
|
129
|
-
return null;
|
|
130
|
-
}
|
|
131
|
-
if (attrValue.type === 'Literal') {
|
|
132
|
-
return fixer.replaceText(attrValue, "\"".concat(newValue, "\""));
|
|
133
|
-
}
|
|
134
|
-
if (attrValue.type === 'JSXExpressionContainer' && 'expression' in attrValue && attrValue.expression && attrValue.expression.type === 'Literal') {
|
|
135
|
-
return fixer.replaceText(attrValue.expression, "\"".concat(newValue, "\""));
|
|
136
|
-
}
|
|
137
|
-
return null;
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
/**
|
|
142
|
-
* Generate the replacement JSX element text
|
|
143
|
-
*/
|
|
144
|
-
function generateTagReplacement(node) {
|
|
145
|
-
var sourceCode = context.getSourceCode();
|
|
146
|
-
var attributes = node.openingElement.attributes;
|
|
147
|
-
|
|
148
|
-
// Build new attributes array, excluding isBold and mapping appearance values to new semantics
|
|
149
|
-
var newAttributes = [];
|
|
150
|
-
attributes.forEach(function (attr) {
|
|
151
|
-
if (attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier') {
|
|
152
|
-
var attrName = attr.name.name;
|
|
153
|
-
if (attrName === 'isBold') {
|
|
154
|
-
// Skip isBold attribute
|
|
155
|
-
return;
|
|
156
|
-
}
|
|
157
|
-
if (attrName === 'appearance') {
|
|
158
|
-
// Map appearance value to new semantic value but keep the prop name as appearance
|
|
159
|
-
var stringValue = extractStringValue(attr.value);
|
|
160
|
-
if (stringValue && typeof stringValue === 'string') {
|
|
161
|
-
var mappedAppearance = mapToNewAppearanceValue(stringValue);
|
|
162
|
-
newAttributes.push("appearance=\"".concat(mappedAppearance, "\""));
|
|
163
|
-
} else {
|
|
164
|
-
// If we can't extract the string value, keep as-is with appearance prop
|
|
165
|
-
var value = attr.value ? sourceCode.getText(attr.value) : '';
|
|
166
|
-
newAttributes.push("appearance".concat(value ? "=".concat(value) : ''));
|
|
167
|
-
}
|
|
168
|
-
return;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
// Keep all other attributes
|
|
172
|
-
newAttributes.push(sourceCode.getText(attr));
|
|
173
|
-
} else if (attr.type === 'JSXSpreadAttribute') {
|
|
174
|
-
// Keep spread attributes
|
|
175
|
-
newAttributes.push(sourceCode.getText(attr));
|
|
176
|
-
}
|
|
177
|
-
});
|
|
178
|
-
var attributesText = newAttributes.length > 0 ? " ".concat(newAttributes.join(' ')) : '';
|
|
179
|
-
var children = node.children.length > 0 ? sourceCode.getText().slice(node.openingElement.range[1], node.closingElement ? node.closingElement.range[0] : node.range[1]) : '';
|
|
180
|
-
if (node.closingElement) {
|
|
181
|
-
return "<Tag".concat(attributesText, ">").concat(children, "</Tag>");
|
|
182
|
-
} else {
|
|
183
|
-
return "<Tag".concat(attributesText, " />");
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return {
|
|
187
|
-
ImportDeclaration: function ImportDeclaration(node) {
|
|
188
|
-
var moduleSource = node.source.value;
|
|
189
|
-
if (typeof moduleSource === 'string') {
|
|
190
|
-
// Track Lozenge imports
|
|
191
|
-
if (moduleSource === '@atlaskit/lozenge' || moduleSource.startsWith('@atlaskit/lozenge')) {
|
|
192
|
-
node.specifiers.forEach(function (spec) {
|
|
193
|
-
if (spec.type === 'ImportDefaultSpecifier') {
|
|
194
|
-
lozengeImports[spec.local.name] = moduleSource;
|
|
195
|
-
} else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
|
|
196
|
-
if (spec.imported.name === 'Lozenge') {
|
|
197
|
-
lozengeImports[spec.local.name] = moduleSource;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
});
|
|
201
|
-
}
|
|
202
|
-
// Track Badge imports
|
|
203
|
-
if (moduleSource === '@atlaskit/badge' || moduleSource.startsWith('@atlaskit/badge')) {
|
|
204
|
-
node.specifiers.forEach(function (spec) {
|
|
205
|
-
if (spec.type === 'ImportDefaultSpecifier') {
|
|
206
|
-
badgeImports[spec.local.name] = moduleSource;
|
|
207
|
-
} else if (spec.type === 'ImportSpecifier' && spec.imported.type === 'Identifier') {
|
|
208
|
-
if (spec.imported.name === 'Badge' || spec.imported.name === 'default') {
|
|
209
|
-
badgeImports[spec.local.name] = moduleSource;
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
},
|
|
216
|
-
JSXElement: function JSXElement(node) {
|
|
217
|
-
if (!isNodeOfType(node, 'JSXElement')) {
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
if (!isNodeOfType(node.openingElement.name, 'JSXIdentifier')) {
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
var elementName = node.openingElement.name.name;
|
|
224
|
-
|
|
225
|
-
// Handle Badge components
|
|
226
|
-
if (badgeImports[elementName]) {
|
|
227
|
-
// Find the appearance prop
|
|
228
|
-
var _appearanceProp = node.openingElement.attributes.find(function (attr) {
|
|
229
|
-
return attr.type === 'JSXAttribute' && attr.name.type === 'JSXIdentifier' && attr.name.name === 'appearance';
|
|
230
|
-
});
|
|
231
|
-
if (!_appearanceProp || _appearanceProp.type !== 'JSXAttribute') {
|
|
232
|
-
// No appearance prop or it's a spread attribute, nothing to migrate
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
// Check if it's a dynamic expression
|
|
237
|
-
if (isDynamicExpression(_appearanceProp.value)) {
|
|
238
|
-
context.report({
|
|
239
|
-
node: _appearanceProp,
|
|
240
|
-
messageId: 'dynamicBadgeAppearance'
|
|
241
|
-
});
|
|
242
|
-
return;
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// Extract the string value
|
|
246
|
-
var stringValue = extractStringValue(_appearanceProp.value);
|
|
247
|
-
if (stringValue && typeof stringValue === 'string') {
|
|
248
|
-
var mappedValue = mapBadgeToNewAppearanceValue(stringValue);
|
|
249
|
-
if (mappedValue !== stringValue) {
|
|
250
|
-
context.report({
|
|
251
|
-
node: _appearanceProp,
|
|
252
|
-
messageId: 'updateBadgeAppearance',
|
|
253
|
-
data: {
|
|
254
|
-
oldValue: stringValue,
|
|
255
|
-
newValue: mappedValue
|
|
256
|
-
},
|
|
257
|
-
fix: createAppearanceFixer(_appearanceProp.value, mappedValue)
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
}
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Only process if this is a Lozenge component we've imported
|
|
265
|
-
if (!lozengeImports[elementName]) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
var attributesMap = getAttributesMap(node.openingElement.attributes);
|
|
269
|
-
var appearanceProp = attributesMap.appearance;
|
|
270
|
-
var isBoldProp = attributesMap.isBold;
|
|
271
|
-
|
|
272
|
-
// Handle appearance prop value migration
|
|
273
|
-
if (appearanceProp) {
|
|
274
|
-
var shouldMigrateToTag = !isBoldProp || isLiteralFalse(isBoldProp.value);
|
|
275
|
-
if (!shouldMigrateToTag) {
|
|
276
|
-
// Only update appearance values for Lozenge components that stay as Lozenge
|
|
277
|
-
var _stringValue = extractStringValue(appearanceProp.value);
|
|
278
|
-
if (_stringValue && typeof _stringValue === 'string') {
|
|
279
|
-
var _mappedValue = mapToNewAppearanceValue(_stringValue);
|
|
280
|
-
if (_mappedValue !== _stringValue) {
|
|
281
|
-
context.report({
|
|
282
|
-
node: appearanceProp,
|
|
283
|
-
messageId: 'updateAppearance',
|
|
284
|
-
fix: createAppearanceFixer(appearanceProp.value, _mappedValue)
|
|
285
|
-
});
|
|
286
|
-
}
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// Handle isBold prop and Tag migration
|
|
292
|
-
if (isBoldProp) {
|
|
293
|
-
if (isLiteralFalse(isBoldProp.value)) {
|
|
294
|
-
// isBold={false} should migrate to Tag
|
|
295
|
-
context.report({
|
|
296
|
-
node: node,
|
|
297
|
-
messageId: 'migrateTag',
|
|
298
|
-
fix: function fix(fixer) {
|
|
299
|
-
var replacement = generateTagReplacement(node);
|
|
300
|
-
return fixer.replaceText(node, replacement);
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
} else if (isDynamicExpression(isBoldProp.value)) {
|
|
304
|
-
// Dynamic isBold requires manual review
|
|
305
|
-
context.report({
|
|
306
|
-
node: isBoldProp,
|
|
307
|
-
messageId: 'manualReview'
|
|
308
|
-
});
|
|
309
|
-
}
|
|
310
|
-
// isBold={true} or isBold (implicit true) - no action needed
|
|
311
|
-
} else {
|
|
312
|
-
// No isBold prop means implicit false, should migrate to Tag
|
|
313
|
-
context.report({
|
|
314
|
-
node: node,
|
|
315
|
-
messageId: 'migrateTag',
|
|
316
|
-
fix: function fix(fixer) {
|
|
317
|
-
var replacement = generateTagReplacement(node);
|
|
318
|
-
return fixer.replaceText(node, replacement);
|
|
319
|
-
}
|
|
320
|
-
});
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
};
|
|
324
|
-
}
|
|
325
|
-
});
|
|
326
|
-
export default rule;
|