@atlaskit/eslint-plugin-design-system 8.15.5 → 8.17.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 +12 -0
- package/constellation/index/usage.mdx +62 -16
- package/dist/cjs/rules/prefer-primitives/index.js +2 -3
- package/dist/cjs/rules/prefer-primitives/utils.js +16 -5
- package/dist/cjs/rules/use-primitives/index.js +89 -58
- package/dist/cjs/rules/use-primitives/transformers/css-to-xcss.js +95 -0
- package/dist/cjs/rules/use-primitives/transformers/index.js +31 -0
- package/dist/cjs/rules/use-primitives/transformers/jsx-element-to-box.js +26 -0
- package/dist/cjs/rules/use-primitives/utils/contains-only-supported-attrs.js +19 -0
- package/dist/cjs/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +31 -0
- package/dist/cjs/rules/use-primitives/utils/get-attribute-value-identifier.js +37 -0
- package/dist/cjs/rules/use-primitives/utils/get-function-argument-at-pos.js +10 -0
- package/dist/cjs/rules/use-primitives/utils/get-jsx-attribute-by-name.js +16 -0
- package/dist/cjs/rules/use-primitives/utils/get-variable-definition-value.js +29 -0
- package/dist/cjs/rules/use-primitives/utils/get-variable-usage-count.js +21 -0
- package/dist/cjs/rules/use-primitives/utils/index.js +89 -0
- package/dist/cjs/rules/use-primitives/utils/is-function-named.js +19 -0
- package/dist/cjs/rules/use-primitives/utils/is-valid-tag-name.js +13 -0
- package/dist/cjs/rules/use-primitives/utils/update-jsx-attribute-by-name.js +31 -0
- package/dist/cjs/rules/use-primitives/utils/update-jsx-element-name.js +16 -0
- package/dist/cjs/rules/use-primitives/utils/upsert-import-declaration.js +80 -0
- package/dist/es2019/rules/prefer-primitives/index.js +1 -2
- package/dist/es2019/rules/prefer-primitives/utils.js +11 -2
- package/dist/es2019/rules/use-primitives/index.js +91 -60
- package/dist/es2019/rules/use-primitives/transformers/css-to-xcss.js +91 -0
- package/dist/es2019/rules/use-primitives/transformers/index.js +2 -0
- package/dist/es2019/rules/use-primitives/transformers/jsx-element-to-box.js +16 -0
- package/dist/es2019/rules/use-primitives/utils/contains-only-supported-attrs.js +13 -0
- package/dist/es2019/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +26 -0
- package/dist/es2019/rules/use-primitives/utils/get-attribute-value-identifier.js +32 -0
- package/dist/es2019/rules/use-primitives/utils/get-function-argument-at-pos.js +4 -0
- package/dist/es2019/rules/use-primitives/utils/get-jsx-attribute-by-name.js +10 -0
- package/dist/es2019/rules/use-primitives/utils/get-variable-definition-value.js +23 -0
- package/dist/es2019/rules/use-primitives/utils/get-variable-usage-count.js +15 -0
- package/dist/es2019/rules/use-primitives/utils/index.js +12 -0
- package/dist/es2019/rules/use-primitives/utils/is-function-named.js +13 -0
- package/dist/es2019/rules/use-primitives/utils/is-valid-tag-name.js +7 -0
- package/dist/es2019/rules/use-primitives/utils/update-jsx-attribute-by-name.js +26 -0
- package/dist/es2019/rules/use-primitives/utils/update-jsx-element-name.js +12 -0
- package/dist/es2019/rules/use-primitives/utils/upsert-import-declaration.js +76 -0
- package/dist/esm/rules/prefer-primitives/index.js +1 -2
- package/dist/esm/rules/prefer-primitives/utils.js +13 -2
- package/dist/esm/rules/use-primitives/index.js +91 -60
- package/dist/esm/rules/use-primitives/transformers/css-to-xcss.js +88 -0
- package/dist/esm/rules/use-primitives/transformers/index.js +2 -0
- package/dist/esm/rules/use-primitives/transformers/jsx-element-to-box.js +19 -0
- package/dist/esm/rules/use-primitives/utils/contains-only-supported-attrs.js +13 -0
- package/dist/esm/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +26 -0
- package/dist/esm/rules/use-primitives/utils/get-attribute-value-identifier.js +32 -0
- package/dist/esm/rules/use-primitives/utils/get-function-argument-at-pos.js +4 -0
- package/dist/esm/rules/use-primitives/utils/get-jsx-attribute-by-name.js +10 -0
- package/dist/esm/rules/use-primitives/utils/get-variable-definition-value.js +23 -0
- package/dist/esm/rules/use-primitives/utils/get-variable-usage-count.js +15 -0
- package/dist/esm/rules/use-primitives/utils/index.js +12 -0
- package/dist/esm/rules/use-primitives/utils/is-function-named.js +13 -0
- package/dist/esm/rules/use-primitives/utils/is-valid-tag-name.js +7 -0
- package/dist/esm/rules/use-primitives/utils/update-jsx-attribute-by-name.js +24 -0
- package/dist/esm/rules/use-primitives/utils/update-jsx-element-name.js +10 -0
- package/dist/esm/rules/use-primitives/utils/upsert-import-declaration.js +75 -0
- package/dist/types/rules/prefer-primitives/utils.d.ts +2 -1
- package/dist/types/rules/use-primitives/transformers/css-to-xcss.d.ts +9 -0
- package/dist/types/rules/use-primitives/transformers/index.d.ts +2 -0
- package/dist/types/rules/use-primitives/transformers/jsx-element-to-box.d.ts +3 -0
- package/dist/types/rules/use-primitives/utils/contains-only-supported-attrs.d.ts +2 -0
- package/dist/types/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +9 -0
- package/dist/types/rules/use-primitives/utils/get-attribute-value-identifier.d.ts +14 -0
- package/dist/types/rules/use-primitives/utils/get-function-argument-at-pos.d.ts +2 -0
- package/dist/types/rules/use-primitives/utils/get-jsx-attribute-by-name.d.ts +2 -0
- package/dist/types/rules/use-primitives/utils/get-variable-definition-value.d.ts +2 -0
- package/dist/types/rules/use-primitives/utils/get-variable-usage-count.d.ts +6 -0
- package/dist/types/rules/use-primitives/utils/index.d.ts +12 -0
- package/dist/types/rules/use-primitives/utils/is-function-named.d.ts +2 -0
- package/dist/types/rules/use-primitives/utils/is-valid-tag-name.d.ts +2 -0
- package/dist/types/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +3 -0
- package/dist/types/rules/use-primitives/utils/update-jsx-element-name.d.ts +3 -0
- package/dist/types/rules/use-primitives/utils/upsert-import-declaration.d.ts +11 -0
- package/dist/types-ts4.5/rules/prefer-primitives/utils.d.ts +2 -1
- package/dist/types-ts4.5/rules/use-primitives/transformers/css-to-xcss.d.ts +9 -0
- package/dist/types-ts4.5/rules/use-primitives/transformers/index.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/transformers/jsx-element-to-box.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/contains-only-supported-attrs.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +9 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/get-attribute-value-identifier.d.ts +14 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/get-function-argument-at-pos.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/get-jsx-attribute-by-name.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/get-variable-definition-value.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/get-variable-usage-count.d.ts +6 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/index.d.ts +12 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/is-function-named.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-tag-name.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-attribute-by-name.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-element-name.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/upsert-import-declaration.d.ts +11 -0
- package/package.json +3 -1
- package/dist/cjs/rules/use-primitives/utils.js +0 -364
- package/dist/es2019/rules/use-primitives/utils.js +0 -353
- package/dist/esm/rules/use-primitives/utils.js +0 -359
- package/dist/types/rules/use-primitives/utils.d.ts +0 -13
- package/dist/types-ts4.5/rules/use-primitives/utils.d.ts +0 -13
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
export const getJSXAttributeByName = (node, attrName) => {
|
|
3
|
+
return node.attributes.find(attr => {
|
|
4
|
+
// Ignore anything other than JSXAttribute
|
|
5
|
+
if (!isNodeOfType(attr, 'JSXAttribute')) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return attr.name.name === attrName;
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
export const getVariableDefinitionValue = variableDefinition => {
|
|
3
|
+
if (!variableDefinition) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
if (variableDefinition.defs.length !== 1) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
const variableValue = variableDefinition.defs[0];
|
|
10
|
+
|
|
11
|
+
// Note: unable to use `isNodeOfType` here
|
|
12
|
+
// because `variableDefinition` is necessarily of type `Scope.Variable`,
|
|
13
|
+
// which doesn't overlap properly with the `eslint-codemod-utils` types
|
|
14
|
+
if (variableValue.type !== 'Variable') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// TODO: is this necessary?
|
|
19
|
+
if (!isNodeOfType(variableValue.node, 'VariableDeclarator')) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return variableValue;
|
|
23
|
+
};
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Using Regex here because otherwise we'd need to traverse the entire AST
|
|
3
|
+
* We should harden this logic as we go.
|
|
4
|
+
*/
|
|
5
|
+
export const getVariableUsagesCount = (variableName, context) => {
|
|
6
|
+
if (!variableName) {
|
|
7
|
+
return 0;
|
|
8
|
+
}
|
|
9
|
+
const source = context.getSourceCode().text;
|
|
10
|
+
const matches = Array.from(source.matchAll(new RegExp(`[^a-z]${variableName}[^a-z]`, 'g')));
|
|
11
|
+
|
|
12
|
+
// subtract 1 because one of the matches is the variable definition:
|
|
13
|
+
// e.g. a regex will find two `beep`s in this: `const beep = 'hello'; console.log(beep)`
|
|
14
|
+
return matches.length - 1;
|
|
15
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export { containsOnlySupportedAttrs } from './contains-only-supported-attrs';
|
|
2
|
+
export { convertASTObjectExpressionToJSObject } from './convert-ast-object-expression-to-js-object';
|
|
3
|
+
export { getAttributeValueIdentifier } from './get-attribute-value-identifier';
|
|
4
|
+
export { getFunctionArgumentAtPos } from './get-function-argument-at-pos';
|
|
5
|
+
export { getJSXAttributeByName } from './get-jsx-attribute-by-name';
|
|
6
|
+
export { getVariableDefinitionValue } from './get-variable-definition-value';
|
|
7
|
+
export { getVariableUsagesCount } from './get-variable-usage-count';
|
|
8
|
+
export { isFunctionNamed } from './is-function-named';
|
|
9
|
+
export { isValidTagName } from './is-valid-tag-name';
|
|
10
|
+
export { updateJSXAttributeByName } from './update-jsx-attribute-by-name';
|
|
11
|
+
export { updateJSXElementName } from './update-jsx-element-name';
|
|
12
|
+
export { upsertImportDeclaration } from './upsert-import-declaration';
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
export const isFunctionNamed = (node, name) => {
|
|
3
|
+
if (!node) {
|
|
4
|
+
return false;
|
|
5
|
+
}
|
|
6
|
+
if (!isNodeOfType(node.node.init, 'CallExpression')) {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
if (!isNodeOfType(node.node.init.callee, 'Identifier')) {
|
|
10
|
+
return false;
|
|
11
|
+
}
|
|
12
|
+
return node.node.init.callee.name === name;
|
|
13
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { jsxAttribute, jsxIdentifier } from 'eslint-codemod-utils';
|
|
2
|
+
export const updateJSXAttributeByName = (oldName, newName, node, fixer) => {
|
|
3
|
+
const {
|
|
4
|
+
openingElement
|
|
5
|
+
} = node;
|
|
6
|
+
const {
|
|
7
|
+
attributes
|
|
8
|
+
} = openingElement;
|
|
9
|
+
const existingAttribute = attributes.find(attr => {
|
|
10
|
+
if (attr.type !== 'JSXAttribute') {
|
|
11
|
+
return false;
|
|
12
|
+
}
|
|
13
|
+
if (attr.name.type !== 'JSXIdentifier') {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
return attr.name.name === oldName;
|
|
17
|
+
});
|
|
18
|
+
if (!existingAttribute || existingAttribute.type !== 'JSXAttribute') {
|
|
19
|
+
return [];
|
|
20
|
+
}
|
|
21
|
+
const newAttribute = jsxAttribute({
|
|
22
|
+
...existingAttribute,
|
|
23
|
+
name: jsxIdentifier(newName)
|
|
24
|
+
});
|
|
25
|
+
return fixer.replaceText(existingAttribute, newAttribute.toString());
|
|
26
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsxIdentifier } from 'eslint-codemod-utils';
|
|
2
|
+
export const updateJSXElementName = (node, newName, fixer) => {
|
|
3
|
+
const {
|
|
4
|
+
openingElement,
|
|
5
|
+
closingElement
|
|
6
|
+
} = node;
|
|
7
|
+
const newOpeningElement = fixer.replaceText(openingElement.name, jsxIdentifier(newName).toString());
|
|
8
|
+
const newClosingElement = closingElement &&
|
|
9
|
+
// Self closing tags, like `<div />` don't need to have the closing tag updated
|
|
10
|
+
fixer.replaceText(closingElement.name, jsxIdentifier(newName).toString());
|
|
11
|
+
return [newOpeningElement, newClosingElement];
|
|
12
|
+
};
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { hasImportDeclaration, insertImportDeclaration, insertImportSpecifier, isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Note: Very naive implementation. Does not handle default imports, namespace imports, or aliased imports.
|
|
5
|
+
* Add that functionality when needed.
|
|
6
|
+
*
|
|
7
|
+
* However, does handle duplicate identifiers.
|
|
8
|
+
*/
|
|
9
|
+
export const upsertImportDeclaration = ({
|
|
10
|
+
packageName,
|
|
11
|
+
specifiers
|
|
12
|
+
}, context, fixer) => {
|
|
13
|
+
// Find any imports that match the packageName
|
|
14
|
+
const body = context.getSourceCode().ast.body;
|
|
15
|
+
const existingImports = body.filter(node => {
|
|
16
|
+
if (!isNodeOfType(node, 'ImportDeclaration')) {
|
|
17
|
+
return false;
|
|
18
|
+
}
|
|
19
|
+
if (!hasImportDeclaration(node, packageName)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return true;
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* This can happen for cases like:
|
|
27
|
+
* ```
|
|
28
|
+
* import { Stack } from '@atlaskit/primitives'
|
|
29
|
+
* import type { StackProps } from '@atlaskit/primitives'
|
|
30
|
+
* ```
|
|
31
|
+
*
|
|
32
|
+
* Ignore these cases for now to reduce scope creep
|
|
33
|
+
*
|
|
34
|
+
* TODO: Support multiple imports
|
|
35
|
+
*/
|
|
36
|
+
if (existingImports.length > 1) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// The import doesn't exist yet, we can just insert a whole new one
|
|
41
|
+
if (existingImports.length === 0) {
|
|
42
|
+
return fixer.insertTextBefore(body[0], `${insertImportDeclaration(packageName, specifiers)};\n`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// The import exists so, modify the existing one
|
|
46
|
+
const existingImport = existingImports[0]; // We have already validated that only one exists
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* `insertImportSpecifier()` has the unfortunate implementation detail of naively adding duplicate specifiers.
|
|
50
|
+
* e.g. calling
|
|
51
|
+
* `insertImportSpecifier(importDecl, 'xcss')`
|
|
52
|
+
* on
|
|
53
|
+
* `import { Inline, xcss } from '@atlaskit/primitives'`
|
|
54
|
+
* will result in:
|
|
55
|
+
* `import { Inline, xcss, xcss } from '@atlaskit/primitives'`.
|
|
56
|
+
* So, we need to filter out specifiers that are already imported.
|
|
57
|
+
*/
|
|
58
|
+
const uniqueImportSpecifiers = specifiers.filter(specifier => {
|
|
59
|
+
return !existingImport.specifiers.find(existingSpecifier => {
|
|
60
|
+
if (existingSpecifier.type !== 'ImportSpecifier') {
|
|
61
|
+
return false;
|
|
62
|
+
}
|
|
63
|
+
return existingSpecifier.imported.name === specifier;
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Ensures the import doesn't end up like: `import { Box, xcss, } from '@atlaskit/primitives';`
|
|
68
|
+
// which can happen if the import decl already contains all import specifiers
|
|
69
|
+
if (uniqueImportSpecifiers.length === 0) {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
return fixer.replaceText(existingImport, `${insertImportSpecifier(existingImport,
|
|
73
|
+
// `insertImportSpecifier` only accepts one specifier, and we can't call it multiple times on the same import, because fixes can't overlap
|
|
74
|
+
// So, join the array into a string literal, and hope for the best 🤞
|
|
75
|
+
uniqueImportSpecifiers.join(', '))};\n`);
|
|
76
|
+
};
|
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
2
|
|
|
3
3
|
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
|
-
import { validPrimitiveElements } from '../use-primitives/utils';
|
|
5
4
|
import { createLintRule } from '../utils/create-rule';
|
|
6
|
-
import { shouldSuggest } from './utils';
|
|
5
|
+
import { shouldSuggest, validPrimitiveElements } from './utils';
|
|
7
6
|
var primitiveDocsUrl = 'https://go.atlassian.com/dst-prefer-primitives';
|
|
8
7
|
var rule = createLintRule({
|
|
9
8
|
meta: {
|
|
@@ -1,4 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
export var validPrimitiveElements = new Set(['div', 'span', 'article', 'aside', 'dialog', 'footer', 'header', 'li', 'main', 'nav', 'ol', 'section', 'ul']);
|
|
3
|
+
var getChildrenByType = function getChildrenByType(node, types) {
|
|
4
|
+
return node.children.filter(function (child) {
|
|
5
|
+
return types.find(function (type) {
|
|
6
|
+
return isNodeOfType(child, type);
|
|
7
|
+
});
|
|
8
|
+
});
|
|
9
|
+
};
|
|
10
|
+
var isValidPrimitiveElement = function isValidPrimitiveElement(node) {
|
|
11
|
+
return validPrimitiveElements.has(node.openingElement.name.name);
|
|
12
|
+
};
|
|
2
13
|
export var shouldSuggest = function shouldSuggest(node) {
|
|
3
14
|
if (!node) {
|
|
4
15
|
return false;
|
|
@@ -17,7 +28,7 @@ export var shouldSuggest = function shouldSuggest(node) {
|
|
|
17
28
|
* ```
|
|
18
29
|
*/
|
|
19
30
|
var nonWhiteSpaceTextChildren = getChildrenByType(node, ['JSXText']).filter(function (child) {
|
|
20
|
-
return
|
|
31
|
+
return child.value.trim() !== '';
|
|
21
32
|
});
|
|
22
33
|
if (nonWhiteSpaceTextChildren.length > 0) {
|
|
23
34
|
return false;
|
|
@@ -1,11 +1,8 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
1
|
+
import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
|
|
4
2
|
import { createLintRule } from '../utils/create-rule';
|
|
5
|
-
import {
|
|
6
|
-
|
|
7
|
-
var
|
|
8
|
-
var stackDocsUrl = 'https://staging.atlassian.design/components/primitives/stack';
|
|
3
|
+
import { jsxElementToBoxTransformer, supportedStylesMap } from './transformers';
|
|
4
|
+
import { containsOnlySupportedAttrs, convertASTObjectExpressionToJSObject, getAttributeValueIdentifier, getFunctionArgumentAtPos, getJSXAttributeByName, getVariableDefinitionValue, getVariableUsagesCount, isFunctionNamed, isValidTagName } from './utils';
|
|
5
|
+
var boxDocsUrl = 'https://atlassian.design/components/primitives/box';
|
|
9
6
|
var rule = createLintRule({
|
|
10
7
|
meta: {
|
|
11
8
|
name: 'use-primitives',
|
|
@@ -18,21 +15,11 @@ var rule = createLintRule({
|
|
|
18
15
|
severity: 'warn'
|
|
19
16
|
},
|
|
20
17
|
messages: {
|
|
21
|
-
preferPrimitivesBox: "This \"{{element}}\" may be able to be replaced with a \"Box\". See ".concat(boxDocsUrl, " for guidance.")
|
|
22
|
-
preferPrimitivesInline: "This \"{{element}}\" may be able to be replaced with an \"Inline\". See ".concat(inlineDocsUrl, " for guidance."),
|
|
23
|
-
preferPrimitivesStack: "This \"{{element}}\" may be able to be replaced with a \"Stack\". See ".concat(stackDocsUrl, " for guidance.")
|
|
18
|
+
preferPrimitivesBox: "This \"{{element}}\" may be able to be replaced with a \"Box\". See ".concat(boxDocsUrl, " for guidance.")
|
|
24
19
|
}
|
|
25
20
|
},
|
|
26
21
|
create: function create(context) {
|
|
27
22
|
return {
|
|
28
|
-
/**
|
|
29
|
-
* Traverse file
|
|
30
|
-
* Look for any JSX opening element and check what it is
|
|
31
|
-
* a) it's already a component
|
|
32
|
-
* b) it's native HTML
|
|
33
|
-
*
|
|
34
|
-
* if b) suggest alternative use of primitives
|
|
35
|
-
*/
|
|
36
23
|
JSXOpeningElement: function JSXOpeningElement(node) {
|
|
37
24
|
if (!isNodeOfType(node, 'JSXOpeningElement')) {
|
|
38
25
|
return;
|
|
@@ -40,15 +27,10 @@ var rule = createLintRule({
|
|
|
40
27
|
if (!isNodeOfType(node.name, 'JSXIdentifier')) {
|
|
41
28
|
return;
|
|
42
29
|
}
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
// const suggestText = shouldSuggestText(
|
|
48
|
-
// node?.parent as any,
|
|
49
|
-
// // context.getScope(),
|
|
50
|
-
// );
|
|
51
|
-
|
|
30
|
+
if (!isNodeOfType(node.parent, 'JSXElement')) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
var suggestBox = shouldSuggestBox(node.parent, context);
|
|
52
34
|
if (suggestBox) {
|
|
53
35
|
context.report({
|
|
54
36
|
node: node,
|
|
@@ -58,39 +40,7 @@ var rule = createLintRule({
|
|
|
58
40
|
},
|
|
59
41
|
suggest: [{
|
|
60
42
|
desc: "Convert to Box",
|
|
61
|
-
fix:
|
|
62
|
-
}]
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
if (suggestInline) {
|
|
66
|
-
context.report({
|
|
67
|
-
node: node,
|
|
68
|
-
messageId: 'preferPrimitivesInline',
|
|
69
|
-
data: {
|
|
70
|
-
element: node.name.name
|
|
71
|
-
},
|
|
72
|
-
suggest: [{
|
|
73
|
-
desc: "Convert to Inline",
|
|
74
|
-
fix: primitiveFixer(node, 'Inline', context)
|
|
75
|
-
}, {
|
|
76
|
-
desc: "Convert to Flex",
|
|
77
|
-
fix: primitiveFixer(node, 'Flex', context)
|
|
78
|
-
}]
|
|
79
|
-
});
|
|
80
|
-
}
|
|
81
|
-
if (suggestStack) {
|
|
82
|
-
context.report({
|
|
83
|
-
node: node,
|
|
84
|
-
messageId: 'preferPrimitivesStack',
|
|
85
|
-
data: {
|
|
86
|
-
element: node.name.name
|
|
87
|
-
},
|
|
88
|
-
suggest: [{
|
|
89
|
-
desc: "Convert to Stack",
|
|
90
|
-
fix: primitiveFixer(node, 'Stack', context)
|
|
91
|
-
}, {
|
|
92
|
-
desc: "Convert to Flex",
|
|
93
|
-
fix: primitiveFixer(node, 'Flex', context)
|
|
43
|
+
fix: jsxElementToBoxTransformer(node.parent, context)
|
|
94
44
|
}]
|
|
95
45
|
});
|
|
96
46
|
}
|
|
@@ -98,4 +48,85 @@ var rule = createLintRule({
|
|
|
98
48
|
};
|
|
99
49
|
}
|
|
100
50
|
});
|
|
51
|
+
var shouldSuggestBox = function shouldSuggestBox(node, context
|
|
52
|
+
// scope: Scope.Scope,
|
|
53
|
+
) {
|
|
54
|
+
if (!node) {
|
|
55
|
+
return false;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Currently we only support `div`, but possibly more in future
|
|
59
|
+
if (!isValidTagName(node)) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Currently we only support elements that contain only a `css` attr, possibly more in future
|
|
64
|
+
if (!containsOnlySupportedAttrs(node)) {
|
|
65
|
+
return false;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Get the `css` attr
|
|
69
|
+
var cssAttr = getJSXAttributeByName(node.openingElement, 'css');
|
|
70
|
+
|
|
71
|
+
// Get the prop value, e.g. `myStyles` in `css={myStyles}`
|
|
72
|
+
var cssVariableName = getAttributeValueIdentifier(cssAttr);
|
|
73
|
+
|
|
74
|
+
// `cssVariableName` could be undefined if the maker has
|
|
75
|
+
// done something like `css={[styles1, styles2]}`, `css={...styles}`, etc
|
|
76
|
+
if (!cssVariableName) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Make sure the variable is not used in multiple JSX elements:
|
|
82
|
+
* ```
|
|
83
|
+
* <div css={paddingStyles}></div>
|
|
84
|
+
* <input css={paddingStyles}></input>
|
|
85
|
+
* ```
|
|
86
|
+
*/
|
|
87
|
+
if (getVariableUsagesCount(cssVariableName, context) !== 1) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Find where `cssVariableName` is defined. We're looking for `const myStyles = css({...})`
|
|
92
|
+
var cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
|
|
93
|
+
var cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
|
|
94
|
+
|
|
95
|
+
// Check if `cssVariableValue` is a function called `css()`
|
|
96
|
+
if (!isFunctionNamed(cssVariableValue, 'css')) {
|
|
97
|
+
return false;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// get the `{}` in `css({})`
|
|
101
|
+
// Zero indexed
|
|
102
|
+
var cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
|
|
103
|
+
|
|
104
|
+
// Bail on empty `css()` calls
|
|
105
|
+
if (!cssObjectExpression) {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
var cssObject = convertASTObjectExpressionToJSObject(cssObjectExpression);
|
|
109
|
+
|
|
110
|
+
// Bail if there are less or more than 1 styles defined
|
|
111
|
+
if (Object.keys(cssObject).length !== 1) {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// NOTE: Our approach with this lint rule is to strictly whitelist css properties we can map.
|
|
116
|
+
// It means we have to provide mappings for everything (e.g. `display: block`).
|
|
117
|
+
// However, from a maker's experience, it's much better that the rule doesn't report (if we miss a mapping)
|
|
118
|
+
// than the rule reporting on things that can't be mapped.
|
|
119
|
+
var containsOnlyValidStyles = Object.keys(cssObject).every(function (styleProperty) {
|
|
120
|
+
var styleValue = cssObject[styleProperty];
|
|
121
|
+
return supportedStylesMap[styleProperty] &&
|
|
122
|
+
// Is the key something we can map
|
|
123
|
+
supportedStylesMap[styleProperty][styleValue] // Is the value something we can map
|
|
124
|
+
;
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!containsOnlyValidStyles) {
|
|
128
|
+
return false;
|
|
129
|
+
}
|
|
130
|
+
return true;
|
|
131
|
+
};
|
|
101
132
|
export default rule;
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import { getIdentifierInParentScope, identifier, isNodeOfType, literal } from 'eslint-codemod-utils';
|
|
3
|
+
import { getAttributeValueIdentifier, getFunctionArgumentAtPos, getJSXAttributeByName, getVariableDefinitionValue } from '../utils';
|
|
4
|
+
export var cssToXcssTransformer = function cssToXcssTransformer(node, context, fixer) {
|
|
5
|
+
/**
|
|
6
|
+
* Note: The logic here is very similar to the logic in `shouldSuggestBox`. i.e.
|
|
7
|
+
* 1. Find the `css` attr
|
|
8
|
+
* 2. Find the variableName (`myStyles` in `css={myStyles}`)
|
|
9
|
+
* 3. Find the `const myStyles = css({ padding: '8px' })`
|
|
10
|
+
* 4. Find the style object `{ padding: '8px' }`
|
|
11
|
+
*
|
|
12
|
+
* The only difference is, we've already performed very defensive checks for these steps in `shouldSuggestBox`,
|
|
13
|
+
* so there's no need to do those checks here.
|
|
14
|
+
*
|
|
15
|
+
* The repetition could be avoided by combining the 'shouldSuggest' and 'fixCode' steps. However, there are tradeoffs
|
|
16
|
+
* to that approach (mainly poor separation of concerns). I'm un-opinionated about which strategy we use. I just opted
|
|
17
|
+
* for this because the original `use-primitives` rule did this.
|
|
18
|
+
*/
|
|
19
|
+
var cssAttr = getJSXAttributeByName(node.openingElement, 'css');
|
|
20
|
+
var cssVariableName = getAttributeValueIdentifier(cssAttr);
|
|
21
|
+
if (!cssVariableName) {
|
|
22
|
+
return [];
|
|
23
|
+
}
|
|
24
|
+
var cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
|
|
25
|
+
var cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
|
|
26
|
+
if (!cssVariableValue) {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
var cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
|
|
30
|
+
return [
|
|
31
|
+
// Update `css` function name to `xcss`.
|
|
32
|
+
fixer.replaceText(cssVariableValue.node.init.callee, identifier('xcss').toString())].concat(_toConsumableArray(cssObjectExpression.properties.map(function (entry) {
|
|
33
|
+
if (!isNodeOfType(entry, 'Property')) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (!isNodeOfType(entry.key, 'Identifier')) {
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
if (!isNodeOfType(entry.value, 'Literal')) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
var value = entry.value.value;
|
|
43
|
+
if (typeof value !== 'string') {
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
return fixer.replaceText(entry.value, literal("'".concat(supportedStylesMap[entry.key.name][value], "'")).toString());
|
|
47
|
+
})));
|
|
48
|
+
};
|
|
49
|
+
export var spaceTokenMap = {
|
|
50
|
+
'0px': 'space.0',
|
|
51
|
+
'2px': 'space.025',
|
|
52
|
+
'4px': 'space.050',
|
|
53
|
+
'6px': 'space.075',
|
|
54
|
+
'8px': 'space.100',
|
|
55
|
+
'12px': 'space.150',
|
|
56
|
+
'16px': 'space.200',
|
|
57
|
+
'20px': 'space.250',
|
|
58
|
+
'24px': 'space.300',
|
|
59
|
+
'32px': 'space.400',
|
|
60
|
+
'40px': 'space.500',
|
|
61
|
+
'48px': 'space.600',
|
|
62
|
+
'64px': 'space.800',
|
|
63
|
+
'80px': 'space.1000'
|
|
64
|
+
};
|
|
65
|
+
export var supportedStylesMap = {
|
|
66
|
+
padding: spaceTokenMap,
|
|
67
|
+
paddingBlock: spaceTokenMap,
|
|
68
|
+
paddingBlockEnd: spaceTokenMap,
|
|
69
|
+
paddingBlockStart: spaceTokenMap,
|
|
70
|
+
paddingBottom: spaceTokenMap,
|
|
71
|
+
paddingInline: spaceTokenMap,
|
|
72
|
+
paddingInlineEnd: spaceTokenMap,
|
|
73
|
+
paddingInlineStart: spaceTokenMap,
|
|
74
|
+
paddingLeft: spaceTokenMap,
|
|
75
|
+
paddingRight: spaceTokenMap,
|
|
76
|
+
paddingTop: spaceTokenMap,
|
|
77
|
+
margin: spaceTokenMap,
|
|
78
|
+
marginBlock: spaceTokenMap,
|
|
79
|
+
marginBlockEnd: spaceTokenMap,
|
|
80
|
+
marginBlockStart: spaceTokenMap,
|
|
81
|
+
marginBottom: spaceTokenMap,
|
|
82
|
+
marginInline: spaceTokenMap,
|
|
83
|
+
marginInlineEnd: spaceTokenMap,
|
|
84
|
+
marginInlineStart: spaceTokenMap,
|
|
85
|
+
marginLeft: spaceTokenMap,
|
|
86
|
+
marginRight: spaceTokenMap,
|
|
87
|
+
marginTop: spaceTokenMap
|
|
88
|
+
};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
2
|
+
import { updateJSXAttributeByName, updateJSXElementName, upsertImportDeclaration } from '../utils';
|
|
3
|
+
import { cssToXcssTransformer } from './css-to-xcss';
|
|
4
|
+
export var jsxElementToBoxTransformer = function jsxElementToBoxTransformer(node, context) {
|
|
5
|
+
return function (fixer) {
|
|
6
|
+
// Insert `import { Box, xcss } from '@atlaskit/primitives'`
|
|
7
|
+
// Or, if the import exists already, update it to include `Box`, and `xcss`
|
|
8
|
+
var importFixes = upsertImportDeclaration({
|
|
9
|
+
packageName: '@atlaskit/primitives',
|
|
10
|
+
specifiers: ['Box', 'xcss']
|
|
11
|
+
}, context, fixer);
|
|
12
|
+
var elementNameFixes = updateJSXElementName(node, 'Box', fixer);
|
|
13
|
+
var attributeFix = updateJSXAttributeByName('css', 'xcss', node, fixer);
|
|
14
|
+
var cssToXcssTransform = cssToXcssTransformer(node, context, fixer);
|
|
15
|
+
return [importFixes, attributeFix].concat(_toConsumableArray(elementNameFixes), _toConsumableArray(cssToXcssTransform)).filter(function (fix) {
|
|
16
|
+
return Boolean(fix);
|
|
17
|
+
}); // Some of the transformers can return arrays with undefined, so filter them out
|
|
18
|
+
};
|
|
19
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
var supportedAttributes = ['css'];
|
|
3
|
+
export var containsOnlySupportedAttrs = function containsOnlySupportedAttrs(node) {
|
|
4
|
+
return node.openingElement.attributes.every(function (attr) {
|
|
5
|
+
if (!isNodeOfType(attr, 'JSXAttribute')) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
if (!isNodeOfType(attr.name, 'JSXIdentifier')) {
|
|
9
|
+
return false;
|
|
10
|
+
}
|
|
11
|
+
return supportedAttributes.includes(attr.name.name);
|
|
12
|
+
});
|
|
13
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
|
|
3
|
+
// FIXME: This is only loosely typed
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Note: Not recursive. Only handles top level key/value pairs
|
|
7
|
+
*/
|
|
8
|
+
export var convertASTObjectExpressionToJSObject = function convertASTObjectExpressionToJSObject(styles) {
|
|
9
|
+
var styleObj = {};
|
|
10
|
+
styles.properties.forEach(function (prop) {
|
|
11
|
+
if (!isNodeOfType(prop, 'Property')) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
if (!isNodeOfType(prop.key, 'Identifier')) {
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
if (!isNodeOfType(prop.value, 'Literal')) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
if (typeof prop.value.value !== 'string') {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
styleObj[prop.key.name] = prop.value.value;
|
|
24
|
+
});
|
|
25
|
+
return styleObj;
|
|
26
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Bit of a weird name, but the functionality is quite specific, so this is the best I could do.
|
|
5
|
+
* This function looks at a JSXAttribute, and returns the string representation
|
|
6
|
+
* of the value if it's an identifier, e.g. `css={someStyles}` would return `'someStyles'`
|
|
7
|
+
*
|
|
8
|
+
* It returns undefined if it finds anything else, like:
|
|
9
|
+
* - `css={[styles1, styles2]}`
|
|
10
|
+
* - `css={...styles}`
|
|
11
|
+
* - `css={styleMap[key]}`
|
|
12
|
+
* - `css='what even is this'`
|
|
13
|
+
* - etc
|
|
14
|
+
*/
|
|
15
|
+
export var getAttributeValueIdentifier = function getAttributeValueIdentifier(attr) {
|
|
16
|
+
if (!attr) {
|
|
17
|
+
return undefined;
|
|
18
|
+
}
|
|
19
|
+
if (!isNodeOfType(attr, 'JSXAttribute')) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
if (!attr.value) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
if (!isNodeOfType(attr.value, 'JSXExpressionContainer')) {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (!isNodeOfType(attr.value.expression, 'Identifier')) {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
return attr.value.expression.name;
|
|
32
|
+
};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export var getFunctionArgumentAtPos = function getFunctionArgumentAtPos(node, pos) {
|
|
2
|
+
var _node$node;
|
|
3
|
+
return node === null || node === void 0 || (_node$node = node.node) === null || _node$node === void 0 || (_node$node = _node$node.init) === null || _node$node === void 0 || (_node$node = _node$node.arguments) === null || _node$node === void 0 ? void 0 : _node$node[pos];
|
|
4
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
export var getJSXAttributeByName = function getJSXAttributeByName(node, attrName) {
|
|
3
|
+
return node.attributes.find(function (attr) {
|
|
4
|
+
// Ignore anything other than JSXAttribute
|
|
5
|
+
if (!isNodeOfType(attr, 'JSXAttribute')) {
|
|
6
|
+
return false;
|
|
7
|
+
}
|
|
8
|
+
return attr.name.name === attrName;
|
|
9
|
+
});
|
|
10
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
export var getVariableDefinitionValue = function getVariableDefinitionValue(variableDefinition) {
|
|
3
|
+
if (!variableDefinition) {
|
|
4
|
+
return undefined;
|
|
5
|
+
}
|
|
6
|
+
if (variableDefinition.defs.length !== 1) {
|
|
7
|
+
return undefined;
|
|
8
|
+
}
|
|
9
|
+
var variableValue = variableDefinition.defs[0];
|
|
10
|
+
|
|
11
|
+
// Note: unable to use `isNodeOfType` here
|
|
12
|
+
// because `variableDefinition` is necessarily of type `Scope.Variable`,
|
|
13
|
+
// which doesn't overlap properly with the `eslint-codemod-utils` types
|
|
14
|
+
if (variableValue.type !== 'Variable') {
|
|
15
|
+
return undefined;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// TODO: is this necessary?
|
|
19
|
+
if (!isNodeOfType(variableValue.node, 'VariableDeclarator')) {
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
return variableValue;
|
|
23
|
+
};
|