@atlaskit/eslint-plugin-design-system 8.38.0 → 9.1.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 +19 -0
- package/constellation/consistent-css-prop-usage/usage.mdx +3 -19
- package/dist/cjs/rules/consistent-css-prop-usage/index.js +53 -85
- package/dist/cjs/rules/use-tokens-typography/transformers/style-object.js +47 -6
- package/dist/cjs/rules/use-tokens-typography/utils.js +39 -3
- package/dist/cjs/rules/utils/is-supported-import.js +12 -3
- package/dist/es2019/rules/consistent-css-prop-usage/index.js +21 -48
- package/dist/es2019/rules/use-tokens-typography/transformers/style-object.js +49 -8
- package/dist/es2019/rules/use-tokens-typography/utils.js +37 -5
- package/dist/es2019/rules/utils/is-supported-import.js +15 -2
- package/dist/esm/rules/consistent-css-prop-usage/index.js +53 -85
- package/dist/esm/rules/use-tokens-typography/transformers/style-object.js +49 -8
- package/dist/esm/rules/use-tokens-typography/utils.js +37 -4
- package/dist/esm/rules/utils/is-supported-import.js +11 -2
- package/dist/types/rules/consistent-css-prop-usage/types.d.ts +1 -2
- package/dist/types/rules/use-tokens-typography/transformers/style-object.d.ts +4 -0
- package/dist/types/rules/use-tokens-typography/utils.d.ts +8 -4
- package/dist/types/rules/utils/is-supported-import.d.ts +5 -4
- package/dist/types-ts4.5/rules/consistent-css-prop-usage/types.d.ts +1 -2
- package/dist/types-ts4.5/rules/use-tokens-typography/transformers/style-object.d.ts +4 -0
- package/dist/types-ts4.5/rules/use-tokens-typography/utils.d.ts +8 -4
- package/dist/types-ts4.5/rules/utils/is-supported-import.d.ts +5 -4
- package/package.json +3 -3
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
4
|
import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
|
|
5
|
-
import { getValueForPropertyNode,
|
|
5
|
+
import { getValueForPropertyNode, normaliseValue } from '../../ensure-design-token-usage/utils';
|
|
6
6
|
import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
|
|
7
|
-
import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, isValidPropertyNode, notUndefined } from '../utils';
|
|
7
|
+
import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, notUndefined } from '../utils';
|
|
8
8
|
export const StyleObject = {
|
|
9
9
|
lint(node, {
|
|
10
10
|
context
|
|
@@ -29,7 +29,9 @@ export const StyleObject = {
|
|
|
29
29
|
const {
|
|
30
30
|
fontSizeNode,
|
|
31
31
|
fontSizeRaw,
|
|
32
|
-
tokensImportNode
|
|
32
|
+
tokensImportNode,
|
|
33
|
+
themeImportNode,
|
|
34
|
+
shouldAddFallbackImport
|
|
33
35
|
} = refs;
|
|
34
36
|
const fontSizeValue = normaliseValue('fontSize', fontSizeRaw);
|
|
35
37
|
|
|
@@ -117,6 +119,8 @@ export const StyleObject = {
|
|
|
117
119
|
matchingToken,
|
|
118
120
|
nodesToReplace,
|
|
119
121
|
tokensImportNode,
|
|
122
|
+
themeImportNode,
|
|
123
|
+
shouldAddFallbackImport,
|
|
120
124
|
fontWeightReplacement,
|
|
121
125
|
fontFamilyReplacement,
|
|
122
126
|
fontStyleReplacement
|
|
@@ -164,20 +168,47 @@ export const StyleObject = {
|
|
|
164
168
|
success: false
|
|
165
169
|
};
|
|
166
170
|
}
|
|
167
|
-
const
|
|
171
|
+
const tokensImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
|
|
168
172
|
|
|
169
173
|
// If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
|
|
170
|
-
if (
|
|
174
|
+
if (tokensImportDeclaration.length > 1) {
|
|
171
175
|
return {
|
|
172
176
|
success: false
|
|
173
177
|
};
|
|
174
178
|
}
|
|
179
|
+
|
|
180
|
+
// This exists purely because we're not inlining the fallback values
|
|
181
|
+
// and instead referencing a `fontFallback` object that exists in @atlaskit/theme/typography.
|
|
182
|
+
// This is a temporary measure until fallbacks are no longer required
|
|
183
|
+
let shouldAddFallbackImport = 'full';
|
|
184
|
+
const themeImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/theme/typography');
|
|
185
|
+
if (themeImportDeclaration.length) {
|
|
186
|
+
// Import exists, check if specifier exists
|
|
187
|
+
shouldAddFallbackImport = 'specifier';
|
|
188
|
+
const fallbackImport = themeImportDeclaration[0].specifiers.find(specifier => {
|
|
189
|
+
// @atlaskit/theme/typography has no default export so we can safely narrow this type
|
|
190
|
+
if (!isNodeOfType(specifier, 'ImportSpecifier')) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
if (specifier.imported.name === 'fontFallback') {
|
|
194
|
+
return true;
|
|
195
|
+
}
|
|
196
|
+
return false;
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Exact import already exists, no need to add
|
|
200
|
+
if (fallbackImport) {
|
|
201
|
+
shouldAddFallbackImport = false;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
175
204
|
return {
|
|
176
205
|
success: true,
|
|
177
206
|
refs: {
|
|
178
207
|
fontSizeNode,
|
|
179
208
|
fontSizeRaw,
|
|
180
|
-
tokensImportNode:
|
|
209
|
+
tokensImportNode: tokensImportDeclaration[0],
|
|
210
|
+
themeImportNode: themeImportDeclaration[0],
|
|
211
|
+
shouldAddFallbackImport
|
|
181
212
|
}
|
|
182
213
|
};
|
|
183
214
|
},
|
|
@@ -187,15 +218,25 @@ export const StyleObject = {
|
|
|
187
218
|
matchingToken,
|
|
188
219
|
nodesToReplace,
|
|
189
220
|
tokensImportNode,
|
|
221
|
+
themeImportNode,
|
|
222
|
+
shouldAddFallbackImport,
|
|
190
223
|
fontWeightReplacement,
|
|
191
224
|
fontFamilyReplacement,
|
|
192
225
|
fontStyleReplacement
|
|
193
226
|
} = refs;
|
|
194
227
|
const fontSizeNode = nodesToReplace[0];
|
|
195
|
-
|
|
228
|
+
const root = context.getSourceCode().ast.body;
|
|
229
|
+
let fallbackImport;
|
|
230
|
+
if (shouldAddFallbackImport === 'full') {
|
|
231
|
+
fallbackImport = insertFallbackImportFull(root, fixer);
|
|
232
|
+
} else if (shouldAddFallbackImport === 'specifier') {
|
|
233
|
+
fallbackImport = insertFallbackImportSpecifier(fixer, themeImportNode);
|
|
234
|
+
}
|
|
235
|
+
const fallbackName = (matchingToken.tokenName === 'font.body' ? 'font.body.medium' : matchingToken.tokenName).replace('font', 'fontFallback');
|
|
236
|
+
return (!tokensImportNode ? [insertTokensImport(root, fixer)] : []).concat(fallbackImport ? [fallbackImport] : [], nodesToReplace.map((node, index) => {
|
|
196
237
|
// Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
|
|
197
238
|
if (index === 0) {
|
|
198
|
-
return fixer.replaceText(node, `${getTokenProperty('font', matchingToken.tokenName,
|
|
239
|
+
return fixer.replaceText(node, `${getTokenProperty('font', matchingToken.tokenName, fallbackName, true)}`);
|
|
199
240
|
}
|
|
200
241
|
|
|
201
242
|
// We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { callExpression, identifier, isNodeOfType, literal, property } from 'eslint-codemod-utils';
|
|
1
|
+
import { callExpression, identifier, isNodeOfType, literal, memberExpression, property } from 'eslint-codemod-utils';
|
|
2
2
|
import { typographyPalette } from '@atlaskit/tokens/palettes-raw';
|
|
3
3
|
import { typographyAdg3 as typographyTokens } from '@atlaskit/tokens/tokens-raw';
|
|
4
|
+
import { Import, Root } from '../../ast-nodes';
|
|
4
5
|
export const typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
|
|
5
6
|
export const isTypographyProperty = propertyName => {
|
|
6
7
|
return typographyProperties.includes(propertyName);
|
|
@@ -68,21 +69,37 @@ export function isValidPropertyNode(node) {
|
|
|
68
69
|
}
|
|
69
70
|
return true;
|
|
70
71
|
}
|
|
71
|
-
function getTokenNode(tokenName, tokenValue) {
|
|
72
|
+
function getTokenNode(tokenName, tokenValue, isFallbackMember) {
|
|
73
|
+
let fallback;
|
|
74
|
+
if (isFallbackMember) {
|
|
75
|
+
fallback = createMemberExpressionFromArray(tokenValue.split('.'));
|
|
76
|
+
} else {
|
|
77
|
+
fallback = literal(tokenValue);
|
|
78
|
+
}
|
|
72
79
|
return callExpression({
|
|
73
80
|
callee: identifier({
|
|
74
81
|
name: 'token'
|
|
75
82
|
}),
|
|
76
83
|
arguments: [literal({
|
|
77
84
|
value: `'${tokenName}'`
|
|
78
|
-
}),
|
|
85
|
+
}), fallback],
|
|
79
86
|
optional: false
|
|
80
87
|
});
|
|
81
88
|
}
|
|
82
|
-
|
|
89
|
+
function createMemberExpressionFromArray(array) {
|
|
90
|
+
if (array.length === 1) {
|
|
91
|
+
return identifier(array[0]);
|
|
92
|
+
}
|
|
93
|
+
const property = array.pop();
|
|
94
|
+
return memberExpression({
|
|
95
|
+
object: createMemberExpressionFromArray(array),
|
|
96
|
+
property: identifier(property)
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
export function getTokenProperty(propertyName, tokenName, tokenFallback, isFallbackMember = false) {
|
|
83
100
|
return property({
|
|
84
101
|
key: identifier(propertyName),
|
|
85
|
-
value: getTokenNode(tokenName, tokenFallback)
|
|
102
|
+
value: getTokenNode(tokenName, tokenFallback, isFallbackMember)
|
|
86
103
|
});
|
|
87
104
|
}
|
|
88
105
|
export function getLiteralProperty(propertyName, propertyValue) {
|
|
@@ -96,4 +113,19 @@ export function convertPropertyNodeToStringableNode(node) {
|
|
|
96
113
|
key: node.key,
|
|
97
114
|
value: node.value
|
|
98
115
|
});
|
|
116
|
+
}
|
|
117
|
+
export function insertTokensImport(root, fixer) {
|
|
118
|
+
return Root.insertImport(root, {
|
|
119
|
+
module: '@atlaskit/tokens',
|
|
120
|
+
specifiers: ['token']
|
|
121
|
+
}, fixer);
|
|
122
|
+
}
|
|
123
|
+
export function insertFallbackImportFull(root, fixer) {
|
|
124
|
+
return Root.insertImport(root, {
|
|
125
|
+
module: '@atlaskit/theme/typography',
|
|
126
|
+
specifiers: ['fontFallback']
|
|
127
|
+
}, fixer);
|
|
128
|
+
}
|
|
129
|
+
export function insertFallbackImportSpecifier(fixer, themeImportNode) {
|
|
130
|
+
return Import.insertNamedSpecifiers(themeImportNode, ['fontFallback'], fixer);
|
|
99
131
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// This should be kept in sync with
|
|
2
|
+
// packages/design-system/eslint-plugin-ui-styling-standard/src/rules/utils/is-supported-import.tsx
|
|
3
|
+
// whenever possible.
|
|
4
|
+
//
|
|
5
|
+
// TODO: would having an @atlassian/eslint-plugin-design-system-common
|
|
6
|
+
// package be useful?
|
|
7
|
+
|
|
1
8
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
9
|
|
|
3
10
|
export const CSS_IN_JS_IMPORTS = {
|
|
@@ -109,8 +116,14 @@ export const isCssMap = isSupportedImportWrapper('cssMap');
|
|
|
109
116
|
export const isKeyframes = isSupportedImportWrapper('keyframes');
|
|
110
117
|
// `styled` is also the explicit default of `styled-components` and `@emotion/styled`, so we also match on default imports generally
|
|
111
118
|
export const isStyled = isSupportedImportWrapper('styled', ['styled-components', '@emotion/styled']);
|
|
112
|
-
export const
|
|
113
|
-
|
|
119
|
+
export const isXcss = isSupportedImportWrapper('xcss');
|
|
120
|
+
export const isImportedFrom = (moduleName, exactMatch = true) => (nodeToCheck, referencesInScope,
|
|
121
|
+
/**
|
|
122
|
+
* If we strictly have specific import sources in the config scope, pass them to make this more performant.
|
|
123
|
+
* Pass `null` if you don't care if its configured or not.
|
|
124
|
+
*/
|
|
125
|
+
importSources = null) => {
|
|
126
|
+
if (importSources && !importSources.some(importSource => importSource === moduleName || !exactMatch && importSource.startsWith(moduleName))) {
|
|
114
127
|
// Don't go through the trouble of checking the import sources does not include this
|
|
115
128
|
// We'll assume this is skipped elsewhere.
|
|
116
129
|
return false;
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
3
2
|
import _classCallCheck from "@babel/runtime/helpers/classCallCheck";
|
|
4
3
|
import _createClass from "@babel/runtime/helpers/createClass";
|
|
@@ -33,6 +32,18 @@ var getProgramNode = function getProgramNode(expression) {
|
|
|
33
32
|
}
|
|
34
33
|
return expression.parent;
|
|
35
34
|
};
|
|
35
|
+
var isDeclaredInsideComponent = function isDeclaredInsideComponent(expression) {
|
|
36
|
+
// These nodes imply that there is a distinct own scope (function scope / block scope),
|
|
37
|
+
// and so the presence of them means that expression was not defined in the module scope.
|
|
38
|
+
var NOT_MODULE_SCOPE = ['ArrowFunctionExpression', 'BlockStatement', 'ClassDeclaration', 'FunctionExpression'];
|
|
39
|
+
while (expression.type !== 'Program') {
|
|
40
|
+
if (NOT_MODULE_SCOPE.includes(expression.type)) {
|
|
41
|
+
return true;
|
|
42
|
+
}
|
|
43
|
+
expression = expression.parent;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
};
|
|
36
47
|
var JSXExpressionLinter = /*#__PURE__*/function () {
|
|
37
48
|
// File-level tracking of styles hoisted from the cssAtTopOfModule/cssAtBottomOfModule fixers.
|
|
38
49
|
|
|
@@ -112,13 +123,13 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
|
|
|
112
123
|
});
|
|
113
124
|
return;
|
|
114
125
|
}
|
|
115
|
-
if (identifier
|
|
126
|
+
if (isDeclaredInsideComponent(identifier)) {
|
|
116
127
|
// When variable is declared inside the component
|
|
117
128
|
this.context.report({
|
|
118
129
|
node: sourceIdentifier,
|
|
119
130
|
messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
|
|
120
131
|
fix: function fix(fixer) {
|
|
121
|
-
if (_this.configuration.
|
|
132
|
+
if (!_this.configuration.autoFix) {
|
|
122
133
|
return [];
|
|
123
134
|
}
|
|
124
135
|
return _this.fixCssNotInModuleScope(fixer, identifier, false);
|
|
@@ -140,7 +151,7 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
|
|
|
140
151
|
node: identifier,
|
|
141
152
|
messageId: 'cssObjectTypeOnly',
|
|
142
153
|
fix: function fix(fixer) {
|
|
143
|
-
if (_this.configuration.
|
|
154
|
+
if (!_this.configuration.autoFix) {
|
|
144
155
|
return [];
|
|
145
156
|
}
|
|
146
157
|
return _this.addCssFunctionCall(fixer, identifier.parent);
|
|
@@ -316,10 +327,11 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
|
|
|
316
327
|
// The last value is the bottom of the file
|
|
317
328
|
fixerNodePlacement = programNode.body[programNode.body.length - 1];
|
|
318
329
|
} else {
|
|
330
|
+
var _ref3;
|
|
319
331
|
// Place after the last ImportDeclaration
|
|
320
|
-
fixerNodePlacement = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(function (node) {
|
|
332
|
+
fixerNodePlacement = (_ref3 = programNode.body.length === 1 ? programNode.body[0] : programNode.body.find(function (node) {
|
|
321
333
|
return node.type !== 'ImportDeclaration';
|
|
322
|
-
});
|
|
334
|
+
})) !== null && _ref3 !== void 0 ? _ref3 : fixerNodePlacement;
|
|
323
335
|
}
|
|
324
336
|
var moduleString;
|
|
325
337
|
var fixes = [];
|
|
@@ -403,7 +415,7 @@ var JSXExpressionLinter = /*#__PURE__*/function () {
|
|
|
403
415
|
node: expression,
|
|
404
416
|
messageId: this.configuration.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule',
|
|
405
417
|
fix: function fix(fixer) {
|
|
406
|
-
if (_this3.configuration.
|
|
418
|
+
if (!_this3.configuration.autoFix) {
|
|
407
419
|
return [];
|
|
408
420
|
}
|
|
409
421
|
|
|
@@ -448,8 +460,7 @@ var defaultConfig = {
|
|
|
448
460
|
cssImportSource: CSS_IN_JS_IMPORTS.compiled,
|
|
449
461
|
xcssImportSource: CSS_IN_JS_IMPORTS.atlaskitPrimitives,
|
|
450
462
|
excludeReactComponents: false,
|
|
451
|
-
|
|
452
|
-
autoFixNames: true
|
|
463
|
+
autoFix: true
|
|
453
464
|
};
|
|
454
465
|
var rule = createLintRule({
|
|
455
466
|
meta: {
|
|
@@ -468,8 +479,7 @@ var rule = createLintRule({
|
|
|
468
479
|
cssObjectTypeOnly: "Create styles using objects passed to a css function call, e.g. `css({ textAlign: 'center'; })`.",
|
|
469
480
|
cssInModule: "Imported styles should not be used; instead define in the module, import a component, or use a design token.",
|
|
470
481
|
cssArrayStylesOnly: "Compose styles with an array on the css prop instead of using object spread.",
|
|
471
|
-
noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles')."
|
|
472
|
-
shouldEndInStyles: 'Declared styles should end in "styles".'
|
|
482
|
+
noMemberExpressions: "Styles should be a regular variable (e.g. 'buttonStyles'), not a member of an object (e.g. 'myObject.styles')."
|
|
473
483
|
},
|
|
474
484
|
schema: [{
|
|
475
485
|
type: 'object',
|
|
@@ -493,10 +503,7 @@ var rule = createLintRule({
|
|
|
493
503
|
excludeReactComponents: {
|
|
494
504
|
type: 'boolean'
|
|
495
505
|
},
|
|
496
|
-
|
|
497
|
-
type: 'boolean'
|
|
498
|
-
},
|
|
499
|
-
fixNamesOnly: {
|
|
506
|
+
autoFix: {
|
|
500
507
|
type: 'boolean'
|
|
501
508
|
}
|
|
502
509
|
},
|
|
@@ -504,80 +511,41 @@ var rule = createLintRule({
|
|
|
504
511
|
}]
|
|
505
512
|
},
|
|
506
513
|
create: function create(context) {
|
|
507
|
-
var _ref3;
|
|
508
514
|
var mergedConfig = assign({}, defaultConfig, context.options[0]);
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
}
|
|
524
|
-
if (!mergedConfig.autoFixNames) {
|
|
525
|
-
return;
|
|
526
|
-
}
|
|
527
|
-
context.report({
|
|
528
|
-
node: identifier,
|
|
529
|
-
messageId: 'shouldEndInStyles',
|
|
530
|
-
fix: function fix(fixer) {
|
|
531
|
-
var _context$getScope$var;
|
|
532
|
-
var identifierName = identifier.type === 'Identifier' ? identifier.name : '';
|
|
533
|
-
var references = ((_context$getScope$var = context.getScope().variables.find(function (varb) {
|
|
534
|
-
return varb.name === identifierName;
|
|
535
|
-
})) === null || _context$getScope$var === void 0 ? void 0 : _context$getScope$var.references) || [];
|
|
536
|
-
var newIdentifierName = "".concat(identifierName
|
|
537
|
-
// Remove "Style" if it is already defined.
|
|
538
|
-
.replace(/Style$/, '')).concat(declarationSuffix);
|
|
539
|
-
return references.filter(function (ref) {
|
|
540
|
-
return ref.identifier.name === identifierName;
|
|
541
|
-
}).map(function (ref) {
|
|
542
|
-
if (ref.identifier.parent && ref.identifier.parent.shorthand) {
|
|
543
|
-
return fixer.replaceText(ref.identifier, "".concat(identifierName, ": ").concat(newIdentifierName));
|
|
544
|
-
}
|
|
545
|
-
return fixer.replaceText(ref.identifier, newIdentifierName);
|
|
546
|
-
});
|
|
547
|
-
}
|
|
548
|
-
});
|
|
549
|
-
}), _defineProperty(_ref3, "JSXAttribute", function JSXAttribute(nodeOriginal) {
|
|
550
|
-
var node = nodeOriginal;
|
|
551
|
-
var name = node.name,
|
|
552
|
-
value = node.value;
|
|
553
|
-
if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
|
|
554
|
-
// e.g. <item.before />
|
|
555
|
-
if (node.parent.name.type === 'JSXMemberExpression') {
|
|
556
|
-
return;
|
|
557
|
-
}
|
|
558
|
-
// e.g. <div />, <MenuItem />
|
|
559
|
-
if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
|
|
560
|
-
return;
|
|
561
|
-
}
|
|
562
|
-
}
|
|
563
|
-
if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
|
|
564
|
-
// When not a jsx expression. For eg. css=""
|
|
565
|
-
if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
|
|
566
|
-
context.report({
|
|
567
|
-
node: node,
|
|
568
|
-
messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
|
|
569
|
-
});
|
|
570
|
-
return;
|
|
515
|
+
return {
|
|
516
|
+
JSXAttribute: function JSXAttribute(nodeOriginal) {
|
|
517
|
+
var node = nodeOriginal;
|
|
518
|
+
var name = node.name,
|
|
519
|
+
value = node.value;
|
|
520
|
+
if (mergedConfig.excludeReactComponents && node.parent.type === 'JSXOpeningElement') {
|
|
521
|
+
// e.g. <item.before />
|
|
522
|
+
if (node.parent.name.type === 'JSXMemberExpression') {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
// e.g. <div />, <MenuItem />
|
|
526
|
+
if (node.parent.name.type === 'JSXIdentifier' && !isDOMElementName(node.parent.name.name)) {
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
571
529
|
}
|
|
572
|
-
if (
|
|
573
|
-
//
|
|
574
|
-
|
|
575
|
-
|
|
530
|
+
if (name.type === 'JSXIdentifier' && mergedConfig.cssFunctions.includes(name.name)) {
|
|
531
|
+
// When not a jsx expression. For eg. css=""
|
|
532
|
+
if ((value === null || value === void 0 ? void 0 : value.type) !== 'JSXExpressionContainer') {
|
|
533
|
+
context.report({
|
|
534
|
+
node: node,
|
|
535
|
+
messageId: mergedConfig.stylesPlacement === 'bottom' ? 'cssAtBottomOfModule' : 'cssAtTopOfModule'
|
|
536
|
+
});
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (value.expression.type === 'JSXEmptyExpression') {
|
|
540
|
+
// e.g. the comment in
|
|
541
|
+
// <div css={/* Hello there */} />
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
var linter = new JSXExpressionLinter(context, name.name, mergedConfig, value.expression);
|
|
545
|
+
linter.run();
|
|
576
546
|
}
|
|
577
|
-
var linter = new JSXExpressionLinter(context, name.name, mergedConfig, value.expression);
|
|
578
|
-
linter.run();
|
|
579
547
|
}
|
|
580
|
-
}
|
|
548
|
+
};
|
|
581
549
|
}
|
|
582
550
|
});
|
|
583
551
|
export default rule;
|
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
import { isNodeOfType } from 'eslint-codemod-utils';
|
|
4
4
|
import { Object as ASTObject, ObjectEntry, Root } from '../../../ast-nodes';
|
|
5
|
-
import { getValueForPropertyNode,
|
|
5
|
+
import { getValueForPropertyNode, normaliseValue } from '../../ensure-design-token-usage/utils';
|
|
6
6
|
import { isDecendantOfGlobalToken, isDecendantOfStyleBlock, isDecendantOfType } from '../../utils/is-node';
|
|
7
|
-
import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, isValidPropertyNode, notUndefined } from '../utils';
|
|
7
|
+
import { convertPropertyNodeToStringableNode, defaultFontWeight, findFontFamilyValueForToken, findFontWeightTokenForValue, findTypographyTokenForValues, fontWeightMap, getLiteralProperty, getTokenProperty, insertFallbackImportFull, insertFallbackImportSpecifier, insertTokensImport, isValidPropertyNode, notUndefined } from '../utils';
|
|
8
8
|
export var StyleObject = {
|
|
9
9
|
lint: function lint(node, _ref) {
|
|
10
10
|
var context = _ref.context;
|
|
@@ -26,7 +26,9 @@ export var StyleObject = {
|
|
|
26
26
|
}
|
|
27
27
|
var fontSizeNode = refs.fontSizeNode,
|
|
28
28
|
fontSizeRaw = refs.fontSizeRaw,
|
|
29
|
-
tokensImportNode = refs.tokensImportNode
|
|
29
|
+
tokensImportNode = refs.tokensImportNode,
|
|
30
|
+
themeImportNode = refs.themeImportNode,
|
|
31
|
+
shouldAddFallbackImport = refs.shouldAddFallbackImport;
|
|
30
32
|
var fontSizeValue = normaliseValue('fontSize', fontSizeRaw);
|
|
31
33
|
|
|
32
34
|
// -- Font weight --
|
|
@@ -117,6 +119,8 @@ export var StyleObject = {
|
|
|
117
119
|
matchingToken: matchingToken,
|
|
118
120
|
nodesToReplace: nodesToReplace,
|
|
119
121
|
tokensImportNode: tokensImportNode,
|
|
122
|
+
themeImportNode: themeImportNode,
|
|
123
|
+
shouldAddFallbackImport: shouldAddFallbackImport,
|
|
120
124
|
fontWeightReplacement: fontWeightReplacement,
|
|
121
125
|
fontFamilyReplacement: fontFamilyReplacement,
|
|
122
126
|
fontStyleReplacement: fontStyleReplacement
|
|
@@ -163,20 +167,47 @@ export var StyleObject = {
|
|
|
163
167
|
success: false
|
|
164
168
|
};
|
|
165
169
|
}
|
|
166
|
-
var
|
|
170
|
+
var tokensImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/tokens');
|
|
167
171
|
|
|
168
172
|
// If there is more than one `@atlaskit/tokens` import, then it becomes difficult to determine which import to transform
|
|
169
|
-
if (
|
|
173
|
+
if (tokensImportDeclaration.length > 1) {
|
|
170
174
|
return {
|
|
171
175
|
success: false
|
|
172
176
|
};
|
|
173
177
|
}
|
|
178
|
+
|
|
179
|
+
// This exists purely because we're not inlining the fallback values
|
|
180
|
+
// and instead referencing a `fontFallback` object that exists in @atlaskit/theme/typography.
|
|
181
|
+
// This is a temporary measure until fallbacks are no longer required
|
|
182
|
+
var shouldAddFallbackImport = 'full';
|
|
183
|
+
var themeImportDeclaration = Root.findImportsByModule(context.getSourceCode().ast.body, '@atlaskit/theme/typography');
|
|
184
|
+
if (themeImportDeclaration.length) {
|
|
185
|
+
// Import exists, check if specifier exists
|
|
186
|
+
shouldAddFallbackImport = 'specifier';
|
|
187
|
+
var fallbackImport = themeImportDeclaration[0].specifiers.find(function (specifier) {
|
|
188
|
+
// @atlaskit/theme/typography has no default export so we can safely narrow this type
|
|
189
|
+
if (!isNodeOfType(specifier, 'ImportSpecifier')) {
|
|
190
|
+
return false;
|
|
191
|
+
}
|
|
192
|
+
if (specifier.imported.name === 'fontFallback') {
|
|
193
|
+
return true;
|
|
194
|
+
}
|
|
195
|
+
return false;
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Exact import already exists, no need to add
|
|
199
|
+
if (fallbackImport) {
|
|
200
|
+
shouldAddFallbackImport = false;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
174
203
|
return {
|
|
175
204
|
success: true,
|
|
176
205
|
refs: {
|
|
177
206
|
fontSizeNode: fontSizeNode,
|
|
178
207
|
fontSizeRaw: fontSizeRaw,
|
|
179
|
-
tokensImportNode:
|
|
208
|
+
tokensImportNode: tokensImportDeclaration[0],
|
|
209
|
+
themeImportNode: themeImportDeclaration[0],
|
|
210
|
+
shouldAddFallbackImport: shouldAddFallbackImport
|
|
180
211
|
}
|
|
181
212
|
};
|
|
182
213
|
},
|
|
@@ -185,14 +216,24 @@ export var StyleObject = {
|
|
|
185
216
|
var matchingToken = refs.matchingToken,
|
|
186
217
|
nodesToReplace = refs.nodesToReplace,
|
|
187
218
|
tokensImportNode = refs.tokensImportNode,
|
|
219
|
+
themeImportNode = refs.themeImportNode,
|
|
220
|
+
shouldAddFallbackImport = refs.shouldAddFallbackImport,
|
|
188
221
|
fontWeightReplacement = refs.fontWeightReplacement,
|
|
189
222
|
fontFamilyReplacement = refs.fontFamilyReplacement,
|
|
190
223
|
fontStyleReplacement = refs.fontStyleReplacement;
|
|
191
224
|
var fontSizeNode = nodesToReplace[0];
|
|
192
|
-
|
|
225
|
+
var root = context.getSourceCode().ast.body;
|
|
226
|
+
var fallbackImport;
|
|
227
|
+
if (shouldAddFallbackImport === 'full') {
|
|
228
|
+
fallbackImport = insertFallbackImportFull(root, fixer);
|
|
229
|
+
} else if (shouldAddFallbackImport === 'specifier') {
|
|
230
|
+
fallbackImport = insertFallbackImportSpecifier(fixer, themeImportNode);
|
|
231
|
+
}
|
|
232
|
+
var fallbackName = (matchingToken.tokenName === 'font.body' ? 'font.body.medium' : matchingToken.tokenName).replace('font', 'fontFallback');
|
|
233
|
+
return (!tokensImportNode ? [insertTokensImport(root, fixer)] : []).concat(fallbackImport ? [fallbackImport] : [], nodesToReplace.map(function (node, index) {
|
|
193
234
|
// Replace first node with token, delete remaining nodes. Guaranteed to be fontSize
|
|
194
235
|
if (index === 0) {
|
|
195
|
-
return fixer.replaceText(node, "".concat(getTokenProperty('font', matchingToken.tokenName,
|
|
236
|
+
return fixer.replaceText(node, "".concat(getTokenProperty('font', matchingToken.tokenName, fallbackName, true)));
|
|
196
237
|
}
|
|
197
238
|
|
|
198
239
|
// We don't replace fontWeight/fontFamily/fontStyle here in case it occurs before the font property.
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { callExpression, identifier, isNodeOfType, literal, property } from 'eslint-codemod-utils';
|
|
1
|
+
import { callExpression, identifier, isNodeOfType, literal, memberExpression, property } from 'eslint-codemod-utils';
|
|
2
2
|
import { typographyPalette } from '@atlaskit/tokens/palettes-raw';
|
|
3
3
|
import { typographyAdg3 as typographyTokens } from '@atlaskit/tokens/tokens-raw';
|
|
4
|
+
import { Import, Root } from '../../ast-nodes';
|
|
4
5
|
export var typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
|
|
5
6
|
export var isTypographyProperty = function isTypographyProperty(propertyName) {
|
|
6
7
|
return typographyProperties.includes(propertyName);
|
|
@@ -98,21 +99,38 @@ export function isValidPropertyNode(node) {
|
|
|
98
99
|
}
|
|
99
100
|
return true;
|
|
100
101
|
}
|
|
101
|
-
function getTokenNode(tokenName, tokenValue) {
|
|
102
|
+
function getTokenNode(tokenName, tokenValue, isFallbackMember) {
|
|
103
|
+
var fallback;
|
|
104
|
+
if (isFallbackMember) {
|
|
105
|
+
fallback = createMemberExpressionFromArray(tokenValue.split('.'));
|
|
106
|
+
} else {
|
|
107
|
+
fallback = literal(tokenValue);
|
|
108
|
+
}
|
|
102
109
|
return callExpression({
|
|
103
110
|
callee: identifier({
|
|
104
111
|
name: 'token'
|
|
105
112
|
}),
|
|
106
113
|
arguments: [literal({
|
|
107
114
|
value: "'".concat(tokenName, "'")
|
|
108
|
-
}),
|
|
115
|
+
}), fallback],
|
|
109
116
|
optional: false
|
|
110
117
|
});
|
|
111
118
|
}
|
|
119
|
+
function createMemberExpressionFromArray(array) {
|
|
120
|
+
if (array.length === 1) {
|
|
121
|
+
return identifier(array[0]);
|
|
122
|
+
}
|
|
123
|
+
var property = array.pop();
|
|
124
|
+
return memberExpression({
|
|
125
|
+
object: createMemberExpressionFromArray(array),
|
|
126
|
+
property: identifier(property)
|
|
127
|
+
});
|
|
128
|
+
}
|
|
112
129
|
export function getTokenProperty(propertyName, tokenName, tokenFallback) {
|
|
130
|
+
var isFallbackMember = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
|
|
113
131
|
return property({
|
|
114
132
|
key: identifier(propertyName),
|
|
115
|
-
value: getTokenNode(tokenName, tokenFallback)
|
|
133
|
+
value: getTokenNode(tokenName, tokenFallback, isFallbackMember)
|
|
116
134
|
});
|
|
117
135
|
}
|
|
118
136
|
export function getLiteralProperty(propertyName, propertyValue) {
|
|
@@ -126,4 +144,19 @@ export function convertPropertyNodeToStringableNode(node) {
|
|
|
126
144
|
key: node.key,
|
|
127
145
|
value: node.value
|
|
128
146
|
});
|
|
147
|
+
}
|
|
148
|
+
export function insertTokensImport(root, fixer) {
|
|
149
|
+
return Root.insertImport(root, {
|
|
150
|
+
module: '@atlaskit/tokens',
|
|
151
|
+
specifiers: ['token']
|
|
152
|
+
}, fixer);
|
|
153
|
+
}
|
|
154
|
+
export function insertFallbackImportFull(root, fixer) {
|
|
155
|
+
return Root.insertImport(root, {
|
|
156
|
+
module: '@atlaskit/theme/typography',
|
|
157
|
+
specifiers: ['fontFallback']
|
|
158
|
+
}, fixer);
|
|
159
|
+
}
|
|
160
|
+
export function insertFallbackImportSpecifier(fixer, themeImportNode) {
|
|
161
|
+
return Import.insertNamedSpecifiers(themeImportNode, ['fontFallback'], fixer);
|
|
129
162
|
}
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
// This should be kept in sync with
|
|
2
|
+
// packages/design-system/eslint-plugin-ui-styling-standard/src/rules/utils/is-supported-import.tsx
|
|
3
|
+
// whenever possible.
|
|
4
|
+
//
|
|
5
|
+
// TODO: would having an @atlassian/eslint-plugin-design-system-common
|
|
6
|
+
// package be useful?
|
|
7
|
+
|
|
1
8
|
// eslint-disable-next-line import/no-extraneous-dependencies
|
|
2
9
|
|
|
3
10
|
export var CSS_IN_JS_IMPORTS = {
|
|
@@ -112,10 +119,12 @@ export var isCssMap = isSupportedImportWrapper('cssMap');
|
|
|
112
119
|
export var isKeyframes = isSupportedImportWrapper('keyframes');
|
|
113
120
|
// `styled` is also the explicit default of `styled-components` and `@emotion/styled`, so we also match on default imports generally
|
|
114
121
|
export var isStyled = isSupportedImportWrapper('styled', ['styled-components', '@emotion/styled']);
|
|
122
|
+
export var isXcss = isSupportedImportWrapper('xcss');
|
|
115
123
|
export var isImportedFrom = function isImportedFrom(moduleName) {
|
|
116
124
|
var exactMatch = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;
|
|
117
|
-
return function (nodeToCheck, referencesInScope
|
|
118
|
-
|
|
125
|
+
return function (nodeToCheck, referencesInScope) {
|
|
126
|
+
var importSources = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : null;
|
|
127
|
+
if (importSources && !importSources.some(function (importSource) {
|
|
119
128
|
return importSource === moduleName || !exactMatch && importSource.startsWith(moduleName);
|
|
120
129
|
})) {
|
|
121
130
|
// Don't go through the trouble of checking the import sources does not include this
|
|
@@ -8,6 +8,8 @@ interface Refs {
|
|
|
8
8
|
fontSizeNode: Property;
|
|
9
9
|
fontSizeRaw: string | number;
|
|
10
10
|
tokensImportNode: ImportDeclaration | undefined;
|
|
11
|
+
themeImportNode: ImportDeclaration | undefined;
|
|
12
|
+
shouldAddFallbackImport: 'full' | 'specifier' | false;
|
|
11
13
|
}
|
|
12
14
|
type Check = {
|
|
13
15
|
success: boolean;
|
|
@@ -17,6 +19,8 @@ interface FixerRefs {
|
|
|
17
19
|
matchingToken: TokenValueMap;
|
|
18
20
|
nodesToReplace: Property[];
|
|
19
21
|
tokensImportNode: ImportDeclaration | undefined;
|
|
22
|
+
themeImportNode: ImportDeclaration | undefined;
|
|
23
|
+
shouldAddFallbackImport: Refs['shouldAddFallbackImport'];
|
|
20
24
|
fontWeightReplacement: StringableASTNode<Property> | undefined;
|
|
21
25
|
fontFamilyReplacement: StringableASTNode<Property> | undefined;
|
|
22
26
|
fontStyleReplacement: StringableASTNode<Property> | undefined;
|