@atlaskit/eslint-plugin-design-system 8.18.0 → 8.19.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/README.md +1 -0
- package/constellation/index/usage.mdx +51 -0
- package/dist/cjs/presets/all.codegen.js +2 -1
- package/dist/cjs/presets/recommended.codegen.js +2 -1
- package/dist/cjs/rules/index.codegen.js +3 -1
- package/dist/cjs/rules/no-unsafe-style-overrides/index.js +74 -0
- package/dist/cjs/rules/use-primitives/index.js +40 -35
- package/dist/cjs/rules/use-primitives/transformers/css-to-xcss.js +10 -3
- package/dist/cjs/rules/use-primitives/transformers/index.js +8 -1
- package/dist/cjs/rules/use-primitives/transformers/styled-component-to-primitive.js +68 -0
- package/dist/cjs/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +16 -0
- package/dist/cjs/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.js +39 -0
- package/dist/cjs/rules/use-primitives/utils/find-valid-styled-component-call.js +52 -0
- package/dist/cjs/rules/use-primitives/utils/index.js +21 -0
- package/dist/cjs/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +38 -0
- package/dist/cjs/rules/use-primitives/utils/is-valid-tag-name.js +2 -2
- package/dist/cjs/rules/use-primitives/utils/update-jsx-element-name.js +1 -1
- package/dist/cjs/rules/utils/get-import-node-by-source.js +49 -1
- package/dist/cjs/rules/utils/jsx.js +17 -0
- package/dist/es2019/presets/all.codegen.js +2 -1
- package/dist/es2019/presets/recommended.codegen.js +2 -1
- package/dist/es2019/rules/index.codegen.js +3 -1
- package/dist/es2019/rules/no-unsafe-style-overrides/index.js +68 -0
- package/dist/es2019/rules/use-primitives/index.js +41 -37
- package/dist/es2019/rules/use-primitives/transformers/css-to-xcss.js +9 -6
- package/dist/es2019/rules/use-primitives/transformers/index.js +2 -1
- package/dist/es2019/rules/use-primitives/transformers/styled-component-to-primitive.js +59 -0
- package/dist/es2019/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +14 -0
- package/dist/es2019/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.js +32 -0
- package/dist/es2019/rules/use-primitives/utils/find-valid-styled-component-call.js +47 -0
- package/dist/es2019/rules/use-primitives/utils/index.js +3 -0
- package/dist/es2019/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +32 -0
- package/dist/es2019/rules/use-primitives/utils/is-valid-tag-name.js +1 -1
- package/dist/es2019/rules/use-primitives/utils/update-jsx-element-name.js +1 -1
- package/dist/es2019/rules/utils/get-import-node-by-source.js +27 -0
- package/dist/es2019/rules/utils/jsx.js +16 -0
- package/dist/esm/presets/all.codegen.js +2 -1
- package/dist/esm/presets/recommended.codegen.js +2 -1
- package/dist/esm/rules/index.codegen.js +3 -1
- package/dist/esm/rules/no-unsafe-style-overrides/index.js +68 -0
- package/dist/esm/rules/use-primitives/index.js +41 -37
- package/dist/esm/rules/use-primitives/transformers/css-to-xcss.js +9 -2
- package/dist/esm/rules/use-primitives/transformers/index.js +2 -1
- package/dist/esm/rules/use-primitives/transformers/styled-component-to-primitive.js +61 -0
- package/dist/esm/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js +16 -0
- package/dist/esm/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.js +34 -0
- package/dist/esm/rules/use-primitives/utils/find-valid-styled-component-call.js +47 -0
- package/dist/esm/rules/use-primitives/utils/index.js +3 -0
- package/dist/esm/rules/use-primitives/utils/is-valid-css-properties-to-transform.js +32 -0
- package/dist/esm/rules/use-primitives/utils/is-valid-tag-name.js +1 -1
- package/dist/esm/rules/use-primitives/utils/update-jsx-element-name.js +1 -1
- package/dist/esm/rules/utils/get-import-node-by-source.js +48 -0
- package/dist/esm/rules/utils/jsx.js +16 -0
- package/dist/types/index.codegen.d.ts +2 -0
- package/dist/types/presets/all.codegen.d.ts +2 -1
- package/dist/types/presets/recommended.codegen.d.ts +2 -1
- package/dist/types/rules/index.codegen.d.ts +1 -0
- package/dist/types/rules/no-unsafe-style-overrides/index.d.ts +2 -0
- package/dist/types/rules/use-primitives/transformers/css-to-xcss.d.ts +2 -1
- package/dist/types/rules/use-primitives/transformers/index.d.ts +1 -0
- package/dist/types/rules/use-primitives/transformers/styled-component-to-primitive.d.ts +13 -0
- package/dist/types/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +1 -1
- package/dist/types/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.d.ts +7 -0
- package/dist/types/rules/use-primitives/utils/find-valid-styled-component-call.d.ts +7 -0
- package/dist/types/rules/use-primitives/utils/index.d.ts +3 -0
- package/dist/types/rules/use-primitives/utils/is-valid-css-properties-to-transform.d.ts +3 -0
- package/dist/types/rules/use-primitives/utils/is-valid-tag-name.d.ts +1 -0
- package/dist/types/rules/use-primitives/utils/update-jsx-element-name.d.ts +1 -1
- package/dist/types/rules/utils/get-import-node-by-source.d.ts +9 -0
- package/dist/types/rules/utils/jsx.d.ts +2 -1
- package/dist/types-ts4.5/index.codegen.d.ts +2 -0
- package/dist/types-ts4.5/presets/all.codegen.d.ts +2 -1
- package/dist/types-ts4.5/presets/recommended.codegen.d.ts +2 -1
- package/dist/types-ts4.5/rules/index.codegen.d.ts +1 -0
- package/dist/types-ts4.5/rules/no-unsafe-style-overrides/index.d.ts +2 -0
- package/dist/types-ts4.5/rules/use-primitives/transformers/css-to-xcss.d.ts +2 -1
- package/dist/types-ts4.5/rules/use-primitives/transformers/index.d.ts +1 -0
- package/dist/types-ts4.5/rules/use-primitives/transformers/styled-component-to-primitive.d.ts +13 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.d.ts +1 -1
- package/dist/types-ts4.5/rules/use-primitives/utils/find-valid-jsx-usage-to-transform.d.ts +7 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/find-valid-styled-component-call.d.ts +7 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/index.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-css-properties-to-transform.d.ts +3 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/is-valid-tag-name.d.ts +1 -0
- package/dist/types-ts4.5/rules/use-primitives/utils/update-jsx-element-name.d.ts +1 -1
- package/dist/types-ts4.5/rules/utils/get-import-node-by-source.d.ts +9 -0
- package/dist/types-ts4.5/rules/utils/jsx.d.ts +2 -1
- package/package.json +1 -1
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.isValidCssPropertiesToTransform = void 0;
|
|
7
|
+
var _eslintCodemodUtils = require("eslint-codemod-utils");
|
|
8
|
+
var _cssToXcss = require("../transformers/css-to-xcss");
|
|
9
|
+
var _convertAstObjectExpressionToJsObject = require("./convert-ast-object-expression-to-js-object");
|
|
10
|
+
var isValidCssPropertiesToTransform = exports.isValidCssPropertiesToTransform = function isValidCssPropertiesToTransform(node) {
|
|
11
|
+
var cssObjectExpression = node.arguments[0];
|
|
12
|
+
// Bail on empty object calls
|
|
13
|
+
if (!cssObjectExpression || !(0, _eslintCodemodUtils.isNodeOfType)(cssObjectExpression, 'ObjectExpression')) {
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
var cssObject = (0, _convertAstObjectExpressionToJsObject.convertASTObjectExpressionToJSObject)(cssObjectExpression);
|
|
17
|
+
// Bail if there are less or more than 1 styles defined
|
|
18
|
+
if (!cssObject || Object.keys(cssObject).length !== 1) {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// NOTE: Our approach with this lint rule is to strictly whitelist css properties we can map.
|
|
23
|
+
// It means we have to provide mappings for everything (e.g. `display: block`).
|
|
24
|
+
// However, from a maker's experience, it's much better that the rule doesn't report (if we miss a mapping)
|
|
25
|
+
// than the rule reporting on things that can't be mapped.
|
|
26
|
+
var containsOnlyValidStyles = Object.keys(cssObject).every(function (styleProperty) {
|
|
27
|
+
var styleValue = cssObject[styleProperty];
|
|
28
|
+
return _cssToXcss.supportedStylesMap[styleProperty] &&
|
|
29
|
+
// Is the key something we can map
|
|
30
|
+
_cssToXcss.supportedStylesMap[styleProperty][styleValue] // Is the value something we can map
|
|
31
|
+
;
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
if (!containsOnlyValidStyles) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
return true;
|
|
38
|
+
};
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.isValidTagName = void 0;
|
|
7
|
-
var validPrimitiveElements = new Set(['div']);
|
|
6
|
+
exports.validPrimitiveElements = exports.isValidTagName = void 0;
|
|
7
|
+
var validPrimitiveElements = exports.validPrimitiveElements = new Set(['div']);
|
|
8
8
|
var isValidTagName = exports.isValidTagName = function isValidTagName(node) {
|
|
9
9
|
if (node.openingElement.name.type !== 'JSXIdentifier') {
|
|
10
10
|
return false;
|
|
@@ -12,5 +12,5 @@ var updateJSXElementName = exports.updateJSXElementName = function updateJSXElem
|
|
|
12
12
|
var newClosingElement = closingElement &&
|
|
13
13
|
// Self closing tags, like `<div />` don't need to have the closing tag updated
|
|
14
14
|
fixer.replaceText(closingElement.name, (0, _eslintCodemodUtils.jsxIdentifier)(newName).toString());
|
|
15
|
-
return [newOpeningElement, newClosingElement];
|
|
15
|
+
return [newOpeningElement, newClosingElement || undefined];
|
|
16
16
|
};
|
|
@@ -3,7 +3,10 @@
|
|
|
3
3
|
Object.defineProperty(exports, "__esModule", {
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
|
-
exports.getImportedNodeBySource = void 0;
|
|
6
|
+
exports.getModuleOfIdentifier = exports.getImportedNodeBySource = void 0;
|
|
7
|
+
function _createForOfIteratorHelper(o, allowArrayLike) { var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"]; if (!it) { if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") { if (it) o = it; var i = 0; var F = function F() {}; return { s: F, n: function n() { if (i >= o.length) return { done: true }; return { done: false, value: o[i++] }; }, e: function e(_e) { throw _e; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var normalCompletion = true, didErr = false, err; return { s: function s() { it = it.call(o); }, n: function n() { var step = it.next(); normalCompletion = step.done; return step; }, e: function e(_e2) { didErr = true; err = _e2; }, f: function f() { try { if (!normalCompletion && it.return != null) it.return(); } finally { if (didErr) throw err; } } }; }
|
|
8
|
+
function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); }
|
|
9
|
+
function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; }
|
|
7
10
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
8
11
|
|
|
9
12
|
/**
|
|
@@ -17,4 +20,49 @@ var getImportedNodeBySource = exports.getImportedNodeBySource = function getImpo
|
|
|
17
20
|
}).find(function (node) {
|
|
18
21
|
return node.source.value === path;
|
|
19
22
|
});
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Returns the module name of an identifier, if one exists.
|
|
27
|
+
*
|
|
28
|
+
* getModuleOfIdentifier(source, 'Button'); // "@atlaskit/button"
|
|
29
|
+
*/
|
|
30
|
+
var getModuleOfIdentifier = exports.getModuleOfIdentifier = function getModuleOfIdentifier(source, identifierName) {
|
|
31
|
+
var _iterator = _createForOfIteratorHelper(source.ast.body),
|
|
32
|
+
_step;
|
|
33
|
+
try {
|
|
34
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
35
|
+
var node = _step.value;
|
|
36
|
+
if (node.type === 'ImportDeclaration') {
|
|
37
|
+
var _iterator2 = _createForOfIteratorHelper(node.specifiers),
|
|
38
|
+
_step2;
|
|
39
|
+
try {
|
|
40
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
41
|
+
var spec = _step2.value;
|
|
42
|
+
if (spec.type === 'ImportDefaultSpecifier' && spec.local.name === identifierName) {
|
|
43
|
+
return {
|
|
44
|
+
moduleName: node.source.value + '',
|
|
45
|
+
importName: identifierName
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
if (spec.type === 'ImportSpecifier' && spec.local.name === identifierName) {
|
|
49
|
+
return {
|
|
50
|
+
moduleName: node.source.value + '',
|
|
51
|
+
importName: spec.imported.name
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
} catch (err) {
|
|
56
|
+
_iterator2.e(err);
|
|
57
|
+
} finally {
|
|
58
|
+
_iterator2.f();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
} catch (err) {
|
|
63
|
+
_iterator.e(err);
|
|
64
|
+
} finally {
|
|
65
|
+
_iterator.f();
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
20
68
|
};
|
|
@@ -4,9 +4,26 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
4
4
|
value: true
|
|
5
5
|
});
|
|
6
6
|
exports.findProp = findProp;
|
|
7
|
+
exports.getJSXElementName = getJSXElementName;
|
|
7
8
|
function findProp(jsx, propName) {
|
|
8
9
|
var labelProp = jsx.openingElement.attributes.find(function (attr) {
|
|
9
10
|
return attr.type === 'JSXAttribute' && attr.name.name === propName;
|
|
10
11
|
});
|
|
11
12
|
return labelProp;
|
|
13
|
+
}
|
|
14
|
+
function unrollMemberExpression(exp) {
|
|
15
|
+
if (exp.type === 'JSXIdentifier') {
|
|
16
|
+
return exp.name;
|
|
17
|
+
}
|
|
18
|
+
return unrollMemberExpression(exp.object);
|
|
19
|
+
}
|
|
20
|
+
function getJSXElementName(jsx) {
|
|
21
|
+
switch (jsx.name.type) {
|
|
22
|
+
case 'JSXIdentifier':
|
|
23
|
+
return jsx.name.name;
|
|
24
|
+
case 'JSXMemberExpression':
|
|
25
|
+
return unrollMemberExpression(jsx.name.object);
|
|
26
|
+
case 'JSXNamespacedName':
|
|
27
|
+
return jsx.name.namespace.name;
|
|
28
|
+
}
|
|
12
29
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::6efa1e48692b3e287d6dfcd500a5f0ab>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
export default {
|
|
@@ -19,6 +19,7 @@ export default {
|
|
|
19
19
|
'@atlaskit/design-system/no-nested-styles': 'error',
|
|
20
20
|
'@atlaskit/design-system/no-physical-properties': 'error',
|
|
21
21
|
'@atlaskit/design-system/no-unsafe-design-token-usage': 'error',
|
|
22
|
+
'@atlaskit/design-system/no-unsafe-style-overrides': 'warn',
|
|
22
23
|
'@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
|
|
23
24
|
'@atlaskit/design-system/prefer-primitives': 'warn',
|
|
24
25
|
'@atlaskit/design-system/use-drawer-label': 'warn',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::be810d87ec2d253e3b053dc06ff1b99a>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
export default {
|
|
@@ -15,6 +15,7 @@ export default {
|
|
|
15
15
|
'@atlaskit/design-system/no-deprecated-imports': 'error',
|
|
16
16
|
'@atlaskit/design-system/no-nested-styles': 'error',
|
|
17
17
|
'@atlaskit/design-system/no-unsafe-design-token-usage': 'error',
|
|
18
|
+
'@atlaskit/design-system/no-unsafe-style-overrides': 'warn',
|
|
18
19
|
'@atlaskit/design-system/no-unsupported-drag-and-drop-libraries': 'error',
|
|
19
20
|
'@atlaskit/design-system/use-drawer-label': 'warn',
|
|
20
21
|
'@atlaskit/design-system/use-heading-level-in-spotlight-card': 'warn',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
3
|
-
* @codegen <<SignedSource::
|
|
3
|
+
* @codegen <<SignedSource::ab1f5b129d07027c228dbd79da5f3572>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
import consistentCssPropUsage from './consistent-css-prop-usage';
|
|
@@ -16,6 +16,7 @@ import noMargin from './no-margin';
|
|
|
16
16
|
import noNestedStyles from './no-nested-styles';
|
|
17
17
|
import noPhysicalProperties from './no-physical-properties';
|
|
18
18
|
import noUnsafeDesignTokenUsage from './no-unsafe-design-token-usage';
|
|
19
|
+
import noUnsafeStyleOverrides from './no-unsafe-style-overrides';
|
|
19
20
|
import noUnsupportedDragAndDropLibraries from './no-unsupported-drag-and-drop-libraries';
|
|
20
21
|
import preferPrimitives from './prefer-primitives';
|
|
21
22
|
import useDrawerLabel from './use-drawer-label';
|
|
@@ -37,6 +38,7 @@ export default {
|
|
|
37
38
|
'no-nested-styles': noNestedStyles,
|
|
38
39
|
'no-physical-properties': noPhysicalProperties,
|
|
39
40
|
'no-unsafe-design-token-usage': noUnsafeDesignTokenUsage,
|
|
41
|
+
'no-unsafe-style-overrides': noUnsafeStyleOverrides,
|
|
40
42
|
'no-unsupported-drag-and-drop-libraries': noUnsupportedDragAndDropLibraries,
|
|
41
43
|
'prefer-primitives': preferPrimitives,
|
|
42
44
|
'use-drawer-label': useDrawerLabel,
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { createLintRule } from '../utils/create-rule';
|
|
3
|
+
import { getModuleOfIdentifier } from '../utils/get-import-node-by-source';
|
|
4
|
+
import { getJSXElementName } from '../utils/jsx';
|
|
5
|
+
const unsafeOverrides = ['css', 'className', 'theme', 'cssFn', 'styles'];
|
|
6
|
+
const rule = createLintRule({
|
|
7
|
+
meta: {
|
|
8
|
+
docs: {
|
|
9
|
+
recommended: true,
|
|
10
|
+
// This should be an error but for now we're rolling it out as warn so we can actually get it into codebases.
|
|
11
|
+
severity: 'warn',
|
|
12
|
+
description: 'Discourage usage of unsafe style overrides used against the Atlassian Design System.'
|
|
13
|
+
},
|
|
14
|
+
name: 'no-unsafe-style-overrides',
|
|
15
|
+
messages: {
|
|
16
|
+
noUnsafeStyledOverride: 'Wrapping {{componentName}} in a styled component encourages unsafe style overrides which cause friction and incidents when its internals change.',
|
|
17
|
+
noUnsafeOverrides: 'The {{propName}} prop encourages unsafe style overrides which cause friction and incidents when {{componentName}} internals change.'
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
create(context) {
|
|
21
|
+
return {
|
|
22
|
+
CallExpression(node) {
|
|
23
|
+
if (node.callee.type !== 'Identifier' || !node.callee.name.toLowerCase().includes('styled')) {
|
|
24
|
+
// Ignore functions that don't look like styled().
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const firstArgument = node.arguments[0];
|
|
28
|
+
if (!firstArgument || firstArgument.type !== 'Identifier') {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const moduleName = getModuleOfIdentifier(context.getSourceCode(), firstArgument.name);
|
|
32
|
+
if (!moduleName || !moduleName.moduleName.startsWith('@atlaskit')) {
|
|
33
|
+
// Ignore styled calls with non-atlaskit components.
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
context.report({
|
|
37
|
+
node: firstArgument,
|
|
38
|
+
messageId: 'noUnsafeStyledOverride',
|
|
39
|
+
data: {
|
|
40
|
+
componentName: moduleName.importName
|
|
41
|
+
}
|
|
42
|
+
});
|
|
43
|
+
},
|
|
44
|
+
JSXAttribute(node) {
|
|
45
|
+
if (!isNodeOfType(node, 'JSXAttribute') || !(node.parent && isNodeOfType(node.parent, 'JSXOpeningElement'))) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
const elementName = getJSXElementName(node.parent);
|
|
49
|
+
const moduleName = getModuleOfIdentifier(context.getSourceCode(), elementName);
|
|
50
|
+
if (!moduleName || !moduleName.moduleName.startsWith('@atlaskit')) {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const propName = typeof node.name.name === 'string' ? node.name.name : node.name.name.name;
|
|
54
|
+
if (unsafeOverrides.includes(propName)) {
|
|
55
|
+
context.report({
|
|
56
|
+
node,
|
|
57
|
+
messageId: 'noUnsafeOverrides',
|
|
58
|
+
data: {
|
|
59
|
+
propName,
|
|
60
|
+
componentName: moduleName.importName
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
export default rule;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
import { getIdentifierInParentScope, isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import assign from 'lodash/assign';
|
|
2
3
|
import { createLintRule } from '../utils/create-rule';
|
|
3
|
-
import { jsxElementToBoxTransformer,
|
|
4
|
-
import { containsOnlySupportedAttrs,
|
|
4
|
+
import { jsxElementToBoxTransformer, styledComponentToPrimitive } from './transformers';
|
|
5
|
+
import { containsOnlySupportedAttrs, findValidJsxUsageToTransform, findValidStyledComponentCall, getAttributeValueIdentifier, getJSXAttributeByName, getVariableDefinitionValue, getVariableUsagesCount, isFunctionNamed, isValidCssPropertiesToTransform, isValidTagName } from './utils';
|
|
5
6
|
const boxDocsUrl = 'https://atlassian.design/components/primitives/box';
|
|
7
|
+
const defaultConfig = {
|
|
8
|
+
preview: false
|
|
9
|
+
};
|
|
6
10
|
const rule = createLintRule({
|
|
7
11
|
meta: {
|
|
8
12
|
name: 'use-primitives',
|
|
@@ -15,11 +19,43 @@ const rule = createLintRule({
|
|
|
15
19
|
severity: 'warn'
|
|
16
20
|
},
|
|
17
21
|
messages: {
|
|
18
|
-
preferPrimitivesBox: `This
|
|
22
|
+
preferPrimitivesBox: `This element can be replaced with a "Box" primitive. See ${boxDocsUrl} for additional guidance.`
|
|
19
23
|
}
|
|
20
24
|
},
|
|
21
25
|
create(context) {
|
|
26
|
+
const mergedConfig = assign({}, defaultConfig, context.options[0]);
|
|
22
27
|
return {
|
|
28
|
+
// transforms styled.<html>(...) usages
|
|
29
|
+
CallExpression(node) {
|
|
30
|
+
if (!mergedConfig.preview) {
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
if (!isNodeOfType(node, 'CallExpression')) {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
const styledComponentVariableRef = findValidStyledComponentCall(node);
|
|
37
|
+
if (!styledComponentVariableRef || !isNodeOfType(styledComponentVariableRef.id, 'Identifier') || !isValidCssPropertiesToTransform(node)) {
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const styledComponentJsxRef = findValidJsxUsageToTransform(styledComponentVariableRef.id.name, context.getScope());
|
|
41
|
+
if (!styledComponentJsxRef) {
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// if we have both references at this point then we can offer a fix \o/
|
|
46
|
+
context.report({
|
|
47
|
+
node: styledComponentVariableRef,
|
|
48
|
+
messageId: 'preferPrimitivesBox',
|
|
49
|
+
suggest: [{
|
|
50
|
+
desc: `Convert ${styledComponentVariableRef.id.name} to Box`,
|
|
51
|
+
fix: styledComponentToPrimitive({
|
|
52
|
+
stylesRef: styledComponentVariableRef,
|
|
53
|
+
jsxRef: styledComponentJsxRef
|
|
54
|
+
}, context)
|
|
55
|
+
}]
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
// transforms <div css={...}> usages
|
|
23
59
|
JSXOpeningElement(node) {
|
|
24
60
|
if (!isNodeOfType(node, 'JSXOpeningElement')) {
|
|
25
61
|
return;
|
|
@@ -91,42 +127,10 @@ const shouldSuggestBox = (node, context
|
|
|
91
127
|
// Find where `cssVariableName` is defined. We're looking for `const myStyles = css({...})`
|
|
92
128
|
const cssVariableDefinition = getIdentifierInParentScope(context.getScope(), cssVariableName);
|
|
93
129
|
const cssVariableValue = getVariableDefinitionValue(cssVariableDefinition);
|
|
94
|
-
|
|
95
130
|
// 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
|
-
const cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
|
|
103
|
-
|
|
104
|
-
// Bail on empty `css()` calls
|
|
105
|
-
if (!cssObjectExpression) {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
const 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
|
-
const containsOnlyValidStyles = Object.keys(cssObject).every(styleProperty => {
|
|
120
|
-
const 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) {
|
|
131
|
+
if (!cssVariableValue || !isFunctionNamed(cssVariableValue, 'css')) {
|
|
128
132
|
return false;
|
|
129
133
|
}
|
|
130
|
-
return
|
|
134
|
+
return isValidCssPropertiesToTransform(cssVariableValue.node.init);
|
|
131
135
|
};
|
|
132
136
|
export default rule;
|
|
@@ -28,11 +28,14 @@ export const cssToXcssTransformer = (node, context, fixer) => {
|
|
|
28
28
|
const cssObjectExpression = getFunctionArgumentAtPos(cssVariableValue, 0);
|
|
29
29
|
return [
|
|
30
30
|
// Update `css` function name to `xcss`.
|
|
31
|
-
fixer.replaceText(cssVariableValue.node.init.callee, identifier('xcss').toString()),
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
fixer.replaceText(cssVariableValue.node.init.callee, identifier('xcss').toString()), ...styledObjectToXcssTokens(cssObjectExpression, fixer)];
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// Update css object values to xcss values. e.g. `'8px'` -> `'space.100'`
|
|
35
|
+
// Note: `properties` in this context is a group of AST nodes that make up a key/value pair in an object.
|
|
36
|
+
// e.g. `padding: '8px'`. For clarity, it's renamed to `entry` inside the `.map()`.
|
|
37
|
+
export const styledObjectToXcssTokens = (styles, fixer) => {
|
|
38
|
+
return styles.properties.map(entry => {
|
|
36
39
|
if (!isNodeOfType(entry, 'Property')) {
|
|
37
40
|
return;
|
|
38
41
|
}
|
|
@@ -47,7 +50,7 @@ export const cssToXcssTransformer = (node, context, fixer) => {
|
|
|
47
50
|
return;
|
|
48
51
|
}
|
|
49
52
|
return fixer.replaceText(entry.value, literal(`'${supportedStylesMap[entry.key.name][value]}'`).toString());
|
|
50
|
-
})
|
|
53
|
+
});
|
|
51
54
|
};
|
|
52
55
|
export const spaceTokenMap = {
|
|
53
56
|
'0px': 'space.0',
|
|
@@ -1,2 +1,3 @@
|
|
|
1
1
|
export { cssToXcssTransformer, supportedStylesMap, spaceTokenMap } from './css-to-xcss';
|
|
2
|
-
export { jsxElementToBoxTransformer } from './jsx-element-to-box';
|
|
2
|
+
export { jsxElementToBoxTransformer } from './jsx-element-to-box';
|
|
3
|
+
export { styledComponentToPrimitive } from './styled-component-to-primitive';
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { updateJSXElementName, upsertImportDeclaration } from '../utils';
|
|
3
|
+
import { styledObjectToXcssTokens } from './css-to-xcss';
|
|
4
|
+
/**
|
|
5
|
+
* All required validation steps have been taken care of before this
|
|
6
|
+
* transformer is called, so it just goes ahead providing all necessary fixes
|
|
7
|
+
*/
|
|
8
|
+
export const styledComponentToPrimitive = ({
|
|
9
|
+
stylesRef,
|
|
10
|
+
jsxRef
|
|
11
|
+
}, context) => {
|
|
12
|
+
return fixer => {
|
|
13
|
+
// generates the new variable name: MyComponent -> myComponentStyles
|
|
14
|
+
const calculatedStylesVariableName = isNodeOfType(stylesRef.id, 'Identifier') && `${stylesRef.id.name.replace(stylesRef.id.name[0], stylesRef.id.name[0].toLowerCase())}Styles`;
|
|
15
|
+
if (!calculatedStylesVariableName) {
|
|
16
|
+
return [];
|
|
17
|
+
}
|
|
18
|
+
const importFixes = upsertImportDeclaration({
|
|
19
|
+
packageName: '@atlaskit/primitives',
|
|
20
|
+
specifiers: ['Box', 'xcss']
|
|
21
|
+
}, context, fixer);
|
|
22
|
+
const stylesFixes = convertStyledComponentToXcss(stylesRef, calculatedStylesVariableName, fixer);
|
|
23
|
+
const jsxFixes = convertJsxCallSite(jsxRef, calculatedStylesVariableName, fixer);
|
|
24
|
+
return [importFixes, ...stylesFixes, ...jsxFixes].filter(fix => Boolean(fix)); // Some of the transformers can return arrays with undefined, so filter them out
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const convertStyledComponentToXcss = (stylesRef, newStylesVariableName, fixer) => {
|
|
29
|
+
const fixes = [];
|
|
30
|
+
|
|
31
|
+
// renames the variable from MyComponent to myComponentStyles
|
|
32
|
+
fixes.push(fixer.replaceText(stylesRef.id, newStylesVariableName));
|
|
33
|
+
|
|
34
|
+
// renames the function call from styled.<tag> to xcss
|
|
35
|
+
if (stylesRef.init && isNodeOfType(stylesRef.init, 'CallExpression')) {
|
|
36
|
+
fixes.push(fixer.replaceText(stylesRef.init.callee, 'xcss'));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// converts CSS values to XCSS-compatible tokens
|
|
40
|
+
if (stylesRef.init && isNodeOfType(stylesRef.init, 'CallExpression')) {
|
|
41
|
+
const objectExpression = stylesRef.init.arguments[0];
|
|
42
|
+
if (isNodeOfType(objectExpression, 'ObjectExpression')) {
|
|
43
|
+
fixes.push(...styledObjectToXcssTokens(objectExpression, fixer));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return fixes;
|
|
47
|
+
};
|
|
48
|
+
const convertJsxCallSite = (jsxRef, newStylesVariableName, fixer) => {
|
|
49
|
+
const fixes = [];
|
|
50
|
+
|
|
51
|
+
// renames the JSX call site
|
|
52
|
+
if (isNodeOfType(jsxRef.parent, 'JSXElement')) {
|
|
53
|
+
fixes.push(...updateJSXElementName(jsxRef.parent, 'Box', fixer));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// adds xcss prop
|
|
57
|
+
fixes.push(fixer.insertTextAfter(jsxRef.name, ` xcss={${newStylesVariableName}}`));
|
|
58
|
+
return fixes;
|
|
59
|
+
};
|
package/dist/es2019/rules/use-primitives/utils/convert-ast-object-expression-to-js-object.js
CHANGED
|
@@ -7,6 +7,20 @@ import { isNodeOfType } from 'eslint-codemod-utils';
|
|
|
7
7
|
*/
|
|
8
8
|
export const convertASTObjectExpressionToJSObject = styles => {
|
|
9
9
|
const styleObj = {};
|
|
10
|
+
|
|
11
|
+
// if we see any spread props we stop and return false to indicate this is unsupported
|
|
12
|
+
if (!styles.properties.every(prop => isNodeOfType(prop, 'Property'))) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// TODO: We need to harden this logic.
|
|
17
|
+
// It currently generates a false positive for:
|
|
18
|
+
// styled.div({
|
|
19
|
+
// marginTop: "0px",
|
|
20
|
+
// marginBottom: token("space.100", "8px"),
|
|
21
|
+
// })
|
|
22
|
+
// as the value for `marginBottom` is not a string, so it is just skipped
|
|
23
|
+
// from the resulting map and this causes the rule to trigger when it shouldn't
|
|
10
24
|
styles.properties.forEach(prop => {
|
|
11
25
|
if (!isNodeOfType(prop, 'Property')) {
|
|
12
26
|
return;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Given a component name finds its JSX usages and performs some
|
|
5
|
+
* additional validations to ensure transformation can be done correctly
|
|
6
|
+
*/
|
|
7
|
+
export const findValidJsxUsageToTransform = (componentName, scope) => {
|
|
8
|
+
const variableDeclaration = scope.variables.find(v => v.name === componentName);
|
|
9
|
+
if (!variableDeclaration) {
|
|
10
|
+
return;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// length here should be exactly 2 to indicate only two references:
|
|
14
|
+
// one being the variable declaration itself
|
|
15
|
+
// second being the JSX call site
|
|
16
|
+
// we might consider handling multiple local JSX call sites in the future
|
|
17
|
+
// but "this is good enough for now"™️
|
|
18
|
+
if (variableDeclaration.references.length !== 2) {
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
const jsxUsage = variableDeclaration.references[1].identifier;
|
|
22
|
+
if (!isNodeOfType(jsxUsage, 'JSXIdentifier')) {
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
const jsxOpeningElement = jsxUsage.parent;
|
|
26
|
+
// we could relatively easily support some safe attributes like
|
|
27
|
+
// "id" or "testId" but support will be expanded as we go
|
|
28
|
+
if (!isNodeOfType(jsxOpeningElement, 'JSXOpeningElement') || jsxOpeningElement.attributes.length > 0) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
return jsxOpeningElement;
|
|
32
|
+
};
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { closestOfType, isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { validPrimitiveElements } from './is-valid-tag-name';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* returns a variable reference if preconditions are favourable for
|
|
6
|
+
* the transformation to proceed, undefined otherwise.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const findValidStyledComponentCall = node => {
|
|
10
|
+
// halts unless we are dealing with a styled component
|
|
11
|
+
if (!isStyledCallExpression(node)) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
// halts if the component is being exported directly
|
|
15
|
+
if (closestOfType(node, 'ExportNamedDeclaration')) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
const styledComponentVariableRef = node.parent;
|
|
19
|
+
// halts if the styled component is not assigned to a variable immediately
|
|
20
|
+
if (!isNodeOfType(styledComponentVariableRef, 'VariableDeclarator')) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
return styledComponentVariableRef;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Some verbose precondition checks but all it does is check
|
|
28
|
+
* a call expression is of form `styled.div` or `styled2.div`
|
|
29
|
+
*
|
|
30
|
+
* In the future it could be enhanced to double check `styled` and `styled2`
|
|
31
|
+
* are Compiled imports but as is should work for the majority of use cases
|
|
32
|
+
*/
|
|
33
|
+
const isStyledCallExpression = call => {
|
|
34
|
+
if (!isNodeOfType(call, 'CallExpression')) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
if (!isNodeOfType(call.callee, 'MemberExpression')) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
if (!isNodeOfType(call.callee.object, 'Identifier') || !isNodeOfType(call.callee.property, 'Identifier')) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
if (/^styled2?$/.test(call.callee.object.name) && validPrimitiveElements.has(call.callee.property.name)) {
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
return false;
|
|
47
|
+
};
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export { containsOnlySupportedAttrs } from './contains-only-supported-attrs';
|
|
2
2
|
export { convertASTObjectExpressionToJSObject } from './convert-ast-object-expression-to-js-object';
|
|
3
|
+
export { findValidJsxUsageToTransform } from './find-valid-jsx-usage-to-transform';
|
|
4
|
+
export { findValidStyledComponentCall } from './find-valid-styled-component-call';
|
|
3
5
|
export { getAttributeValueIdentifier } from './get-attribute-value-identifier';
|
|
4
6
|
export { getFunctionArgumentAtPos } from './get-function-argument-at-pos';
|
|
5
7
|
export { getJSXAttributeByName } from './get-jsx-attribute-by-name';
|
|
6
8
|
export { getVariableDefinitionValue } from './get-variable-definition-value';
|
|
7
9
|
export { getVariableUsagesCount } from './get-variable-usage-count';
|
|
8
10
|
export { isFunctionNamed } from './is-function-named';
|
|
11
|
+
export { isValidCssPropertiesToTransform } from './is-valid-css-properties-to-transform';
|
|
9
12
|
export { isValidTagName } from './is-valid-tag-name';
|
|
10
13
|
export { updateJSXAttributeByName } from './update-jsx-attribute-by-name';
|
|
11
14
|
export { updateJSXElementName } from './update-jsx-element-name';
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
2
|
+
import { supportedStylesMap } from '../transformers/css-to-xcss';
|
|
3
|
+
import { convertASTObjectExpressionToJSObject } from './convert-ast-object-expression-to-js-object';
|
|
4
|
+
export const isValidCssPropertiesToTransform = node => {
|
|
5
|
+
const cssObjectExpression = node.arguments[0];
|
|
6
|
+
// Bail on empty object calls
|
|
7
|
+
if (!cssObjectExpression || !isNodeOfType(cssObjectExpression, 'ObjectExpression')) {
|
|
8
|
+
return false;
|
|
9
|
+
}
|
|
10
|
+
const cssObject = convertASTObjectExpressionToJSObject(cssObjectExpression);
|
|
11
|
+
// Bail if there are less or more than 1 styles defined
|
|
12
|
+
if (!cssObject || Object.keys(cssObject).length !== 1) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// NOTE: Our approach with this lint rule is to strictly whitelist css properties we can map.
|
|
17
|
+
// It means we have to provide mappings for everything (e.g. `display: block`).
|
|
18
|
+
// However, from a maker's experience, it's much better that the rule doesn't report (if we miss a mapping)
|
|
19
|
+
// than the rule reporting on things that can't be mapped.
|
|
20
|
+
const containsOnlyValidStyles = Object.keys(cssObject).every(styleProperty => {
|
|
21
|
+
const styleValue = cssObject[styleProperty];
|
|
22
|
+
return supportedStylesMap[styleProperty] &&
|
|
23
|
+
// Is the key something we can map
|
|
24
|
+
supportedStylesMap[styleProperty][styleValue] // Is the value something we can map
|
|
25
|
+
;
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!containsOnlyValidStyles) {
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
};
|
|
@@ -8,5 +8,5 @@ export const updateJSXElementName = (node, newName, fixer) => {
|
|
|
8
8
|
const newClosingElement = closingElement &&
|
|
9
9
|
// Self closing tags, like `<div />` don't need to have the closing tag updated
|
|
10
10
|
fixer.replaceText(closingElement.name, jsxIdentifier(newName).toString());
|
|
11
|
-
return [newOpeningElement, newClosingElement];
|
|
11
|
+
return [newOpeningElement, newClosingElement || undefined];
|
|
12
12
|
};
|