@atlaskit/eslint-plugin-design-system 4.16.1 → 4.16.3
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/dist/cjs/rules/ensure-design-token-usage-spacing/index.js +56 -19
- package/dist/cjs/rules/ensure-design-token-usage-spacing/utils.js +49 -8
- package/dist/cjs/rules/no-deprecated-apis/index.js +2 -4
- package/dist/cjs/rules/utils/create-rule.js +11 -0
- package/dist/cjs/version.json +1 -1
- package/dist/es2019/rules/ensure-design-token-usage-spacing/index.js +56 -19
- package/dist/es2019/rules/ensure-design-token-usage-spacing/utils.js +41 -9
- package/dist/es2019/rules/no-deprecated-apis/index.js +2 -2
- package/dist/es2019/rules/utils/create-rule.js +2 -0
- package/dist/es2019/version.json +1 -1
- package/dist/esm/rules/ensure-design-token-usage-spacing/index.js +57 -20
- package/dist/esm/rules/ensure-design-token-usage-spacing/utils.js +46 -8
- package/dist/esm/rules/no-deprecated-apis/index.js +2 -4
- package/dist/esm/rules/utils/create-rule.js +4 -0
- package/dist/esm/version.json +1 -1
- package/dist/types/index.d.ts +4 -1
- package/dist/types/rules/ensure-design-token-usage-spacing/index.d.ts +6 -2
- package/dist/types/rules/ensure-design-token-usage-spacing/utils.d.ts +19 -2
- package/dist/types/rules/utils/create-rule.d.ts +2 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-design-system
|
|
2
2
|
|
|
3
|
+
## 4.16.3
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [`ed34264c827`](https://bitbucket.org/atlassian/atlassian-frontend/commits/ed34264c827) - Fix errors on tagged template literals for eslint rule ensure-design-token-usage-spacing and handle edgecases ensuring seamless fallback on errors
|
|
8
|
+
|
|
9
|
+
## 4.16.2
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- [`3db6efeac0d`](https://bitbucket.org/atlassian/atlassian-frontend/commits/3db6efeac0d) - Improves internal configuration of spacing tokens rule.
|
|
14
|
+
|
|
3
15
|
## 4.16.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
|
@@ -5,34 +5,67 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
7
|
exports.default = void 0;
|
|
8
|
-
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
9
8
|
var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/slicedToArray"));
|
|
9
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
10
|
+
var _defineProperty2 = _interopRequireDefault(require("@babel/runtime/helpers/defineProperty"));
|
|
10
11
|
var _eslintCodemodUtils = require("eslint-codemod-utils");
|
|
12
|
+
var _createRule = require("../utils/create-rule");
|
|
11
13
|
var _isNode = require("../utils/is-node");
|
|
12
14
|
var _utils = require("./utils");
|
|
13
15
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
14
16
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { (0, _defineProperty2.default)(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
15
|
-
var rule = {
|
|
17
|
+
var rule = (0, _createRule.createRule)({
|
|
18
|
+
defaultOptions: [{
|
|
19
|
+
addons: ['spacing'],
|
|
20
|
+
applyImport: true
|
|
21
|
+
}],
|
|
22
|
+
name: 'ensure-design-token-usage-spacing',
|
|
16
23
|
meta: {
|
|
24
|
+
schema: {
|
|
25
|
+
type: 'array',
|
|
26
|
+
items: {
|
|
27
|
+
type: 'object',
|
|
28
|
+
properties: {
|
|
29
|
+
applyImport: {
|
|
30
|
+
type: 'boolean'
|
|
31
|
+
},
|
|
32
|
+
addons: {
|
|
33
|
+
type: 'array',
|
|
34
|
+
items: {
|
|
35
|
+
enum: ['spacing', 'typography', 'shape']
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
17
41
|
type: 'problem',
|
|
18
42
|
fixable: 'code',
|
|
19
43
|
docs: {
|
|
20
44
|
description: 'Rule ensures all spacing CSS properties apply a matching spacing token',
|
|
21
|
-
recommended:
|
|
45
|
+
recommended: 'error'
|
|
22
46
|
},
|
|
23
47
|
messages: {
|
|
24
48
|
noRawSpacingValues: 'The use of spacing primitives or tokens is preferred over the direct application of spacing properties.\n\n@meta <<{{payload}}>>',
|
|
25
49
|
autofixesPossible: 'Automated corrections available for spacing values. Apply autofix to replace values with appropriate tokens'
|
|
26
50
|
}
|
|
27
51
|
},
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
var
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
52
|
+
// @ts-expect-error
|
|
53
|
+
create: function create(context, options) {
|
|
54
|
+
var tokenNode = null;
|
|
55
|
+
|
|
56
|
+
// merge configs
|
|
57
|
+
var ruleConfig = _objectSpread(_objectSpread({}, options[0]), {}, {
|
|
58
|
+
addons: (0, _toConsumableArray2.default)(options[0].addons)
|
|
59
|
+
});
|
|
60
|
+
if (!ruleConfig.addons.includes('spacing')) {
|
|
61
|
+
ruleConfig.addons.push('spacing');
|
|
34
62
|
}
|
|
35
63
|
return {
|
|
64
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
65
|
+
if (node.source.value === '@atlaskit/tokens' && ruleConfig.applyImport) {
|
|
66
|
+
tokenNode = node;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
36
69
|
// CSSObjectExpression
|
|
37
70
|
// const styles = css({ color: 'red', margin: '4px' }), styled.div({ color: 'red', margin: '4px' })
|
|
38
71
|
'CallExpression[callee.name=css] > ObjectExpression, CallExpression[callee.object.name=styled] > ObjectExpression': function CallExpressionCalleeNameCssObjectExpressionCallExpressionCalleeObjectNameStyledObjectExpression(parentNode) {
|
|
@@ -66,7 +99,7 @@ var rule = {
|
|
|
66
99
|
if (!(0, _eslintCodemodUtils.isNodeOfType)(node.key, 'Identifier')) {
|
|
67
100
|
return;
|
|
68
101
|
}
|
|
69
|
-
if (!(0, _utils.shouldAnalyzeProperty)(node.key.name,
|
|
102
|
+
if (!(0, _utils.shouldAnalyzeProperty)(node.key.name, ruleConfig.addons)) {
|
|
70
103
|
return;
|
|
71
104
|
}
|
|
72
105
|
if ((0, _isNode.isDecendantOfGlobalToken)(node.value)) {
|
|
@@ -115,7 +148,7 @@ var rule = {
|
|
|
115
148
|
},
|
|
116
149
|
fix: function fix(fixer) {
|
|
117
150
|
var _node$loc;
|
|
118
|
-
if (!(0, _utils.shouldAnalyzeProperty)(propertyName,
|
|
151
|
+
if (!(0, _utils.shouldAnalyzeProperty)(propertyName, ruleConfig.addons)) {
|
|
119
152
|
return null;
|
|
120
153
|
}
|
|
121
154
|
var pixelValueString = "".concat(pixelValue, "px");
|
|
@@ -125,9 +158,9 @@ var rule = {
|
|
|
125
158
|
return null;
|
|
126
159
|
}
|
|
127
160
|
var replacementValue = (0, _utils.getTokenNodeForValue)(propertyName, lookupValue);
|
|
128
|
-
return [fixer.insertTextBefore(node, "// TODO Delete this comment after verifying spacing token -> previous value `".concat((0, _eslintCodemodUtils.node)(node.value), "`\n").concat(' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0))), fixer.replaceText(node, (0, _eslintCodemodUtils.property)(_objectSpread(_objectSpread({}, node), {}, {
|
|
161
|
+
return (!tokenNode && ruleConfig.applyImport ? [(0, _utils.insertTokensImport)(fixer)] : []).concat([fixer.insertTextBefore(node, "// TODO Delete this comment after verifying spacing token -> previous value `".concat((0, _eslintCodemodUtils.node)(node.value), "`\n").concat(' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0))), fixer.replaceText(node, (0, _eslintCodemodUtils.property)(_objectSpread(_objectSpread({}, node), {}, {
|
|
129
162
|
value: replacementValue
|
|
130
|
-
})).toString())];
|
|
163
|
+
})).toString())]);
|
|
131
164
|
}
|
|
132
165
|
});
|
|
133
166
|
}
|
|
@@ -154,11 +187,11 @@ var rule = {
|
|
|
154
187
|
if (!allResolvableValues) {
|
|
155
188
|
return null;
|
|
156
189
|
}
|
|
157
|
-
return fixer.replaceText(node.value, "`".concat(values.map(function (value) {
|
|
190
|
+
return (!tokenNode && ruleConfig.applyImport ? [(0, _utils.insertTokensImport)(fixer)] : []).concat([fixer.replaceText(node.value, "`".concat(values.map(function (value) {
|
|
158
191
|
var pixelValue = (0, _utils.emToPixels)(value, fontSize);
|
|
159
192
|
var pixelValueString = "".concat(pixelValue, "px");
|
|
160
193
|
return "${".concat((0, _utils.getTokenNodeForValue)(propertyName, pixelValueString), "}");
|
|
161
|
-
}).join(' '), "`"));
|
|
194
|
+
}).join(' '), "`"))]);
|
|
162
195
|
} : undefined
|
|
163
196
|
});
|
|
164
197
|
});
|
|
@@ -172,8 +205,12 @@ var rule = {
|
|
|
172
205
|
if (node.type !== 'TaggedTemplateExpression') {
|
|
173
206
|
return;
|
|
174
207
|
}
|
|
175
|
-
var parentNode = (0, _utils.findParentNodeForLine)(node);
|
|
176
208
|
var processedCssLines = (0, _utils.processCssNode)(node, context);
|
|
209
|
+
if (!processedCssLines) {
|
|
210
|
+
// if we can't get a processed css we bail
|
|
211
|
+
return;
|
|
212
|
+
}
|
|
213
|
+
var parentNode = (0, _utils.findParentNodeForLine)(node);
|
|
177
214
|
var globalFontSize = (0, _utils.getFontSizeValueInScope)(processedCssLines);
|
|
178
215
|
var textForSource = context.getSourceCode().getText(node.quasi);
|
|
179
216
|
var allReplacedValues = [];
|
|
@@ -192,7 +229,7 @@ var rule = {
|
|
|
192
229
|
var propertyName = (0, _utils.convertHyphenatedNameToCamelCase)(originalProperty);
|
|
193
230
|
var isFontFamily = /fontFamily/.test(propertyName);
|
|
194
231
|
var replacedValuesPerProperty = [originalProperty];
|
|
195
|
-
if (!(0, _utils.shouldAnalyzeProperty)(propertyName,
|
|
232
|
+
if (!(0, _utils.shouldAnalyzeProperty)(propertyName, ruleConfig.addons) || !resolvedCssValues || !(0, _utils.isValidSpacingValue)(resolvedCssValues, globalFontSize)) {
|
|
196
233
|
// in all of these cases no changes should be made to the current property
|
|
197
234
|
return currentSource;
|
|
198
235
|
}
|
|
@@ -278,13 +315,13 @@ var rule = {
|
|
|
278
315
|
node: node,
|
|
279
316
|
messageId: 'autofixesPossible',
|
|
280
317
|
fix: function fix(fixer) {
|
|
281
|
-
return [fixer.insertTextBefore(parentNode, replacementComments), fixer.replaceText(node.quasi, completeSource)];
|
|
318
|
+
return (!tokenNode && ruleConfig.applyImport ? [(0, _utils.insertTokensImport)(fixer)] : []).concat([fixer.insertTextBefore(parentNode, replacementComments), fixer.replaceText(node.quasi, completeSource)]);
|
|
282
319
|
}
|
|
283
320
|
});
|
|
284
321
|
}
|
|
285
322
|
}
|
|
286
323
|
};
|
|
287
324
|
}
|
|
288
|
-
};
|
|
325
|
+
});
|
|
289
326
|
var _default = rule;
|
|
290
327
|
exports.default = _default;
|
|
@@ -4,6 +4,7 @@ var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefau
|
|
|
4
4
|
Object.defineProperty(exports, "__esModule", {
|
|
5
5
|
value: true
|
|
6
6
|
});
|
|
7
|
+
exports.cleanComments = cleanComments;
|
|
7
8
|
exports.emToPixels = exports.convertHyphenatedNameToCamelCase = void 0;
|
|
8
9
|
exports.findIdentifierInParentScope = findIdentifierInParentScope;
|
|
9
10
|
exports.findParentNodeForLine = void 0;
|
|
@@ -11,7 +12,9 @@ exports.getFontSizeValueInScope = getFontSizeValueInScope;
|
|
|
11
12
|
exports.getRawExpression = void 0;
|
|
12
13
|
exports.getTokenNodeForValue = getTokenNodeForValue;
|
|
13
14
|
exports.getTokenReplacement = getTokenReplacement;
|
|
14
|
-
exports.
|
|
15
|
+
exports.getValueFromShorthand = exports.getValue = void 0;
|
|
16
|
+
exports.insertTokensImport = insertTokensImport;
|
|
17
|
+
exports.isSpacingProperty = void 0;
|
|
15
18
|
exports.isTokenValueString = isTokenValueString;
|
|
16
19
|
exports.onlyScaleTokens = exports.isValidSpacingValue = exports.isTypographyProperty = void 0;
|
|
17
20
|
exports.processCssNode = processCssNode;
|
|
@@ -65,6 +68,9 @@ function findIdentifierInParentScope(_ref) {
|
|
|
65
68
|
}
|
|
66
69
|
return null;
|
|
67
70
|
}
|
|
71
|
+
function insertTokensImport(fixer) {
|
|
72
|
+
return (0, _eslintCodemodUtils.insertAtStartOfFile)(fixer, "".concat((0, _eslintCodemodUtils.insertImportDeclaration)('@atlaskit/tokens', ['token']), "\n"));
|
|
73
|
+
}
|
|
68
74
|
var isSpacingProperty = function isSpacingProperty(propertyName) {
|
|
69
75
|
return properties.includes(propertyName);
|
|
70
76
|
};
|
|
@@ -72,10 +78,21 @@ exports.isSpacingProperty = isSpacingProperty;
|
|
|
72
78
|
var isTypographyProperty = function isTypographyProperty(propertyName) {
|
|
73
79
|
return typographyProperties.includes(propertyName);
|
|
74
80
|
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Accomplishes split str by whitespace but preserves expressions in between ${...}
|
|
84
|
+
* even if they might have whitepaces or nested brackets
|
|
85
|
+
* @param str
|
|
86
|
+
* @returns string[]
|
|
87
|
+
* @example
|
|
88
|
+
* Regex has two parts, first attempts to capture anything in between `${...}` in a capture group
|
|
89
|
+
* Whilst allowing nested brackets and non empty characters leading or traling wrapping expression e.g `${gridSize}`, `-${gridSize}px`
|
|
90
|
+
* second part is a white space delimiter
|
|
91
|
+
* For input `-${gridSize / 2}px ${token(...)} 18px -> [`-${gridSize / 2}px`, `${token(...)}`, `18px`]
|
|
92
|
+
*/
|
|
75
93
|
exports.isTypographyProperty = isTypographyProperty;
|
|
76
94
|
var splitShorthandValues = function splitShorthandValues(str) {
|
|
77
|
-
|
|
78
|
-
return str.split(/(\${[^}]*}\S*)|\s+/g).filter(Boolean);
|
|
95
|
+
return str.split(/(\S*\$\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}\S*)|\s+/g).filter(Boolean);
|
|
79
96
|
};
|
|
80
97
|
exports.splitShorthandValues = splitShorthandValues;
|
|
81
98
|
var getValueFromShorthand = function getValueFromShorthand(str) {
|
|
@@ -171,7 +188,7 @@ var getRawExpression = function getRawExpression(node, context) {
|
|
|
171
188
|
if (!(
|
|
172
189
|
// if not one of our recognized types or doesn't have a range prop, early return
|
|
173
190
|
|
|
174
|
-
(0, _eslintCodemodUtils.isNodeOfType)(node, 'Literal') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'Identifier') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'BinaryExpression') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'UnaryExpression') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'TemplateLiteral') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression')
|
|
191
|
+
(0, _eslintCodemodUtils.isNodeOfType)(node, 'Literal') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'Identifier') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'BinaryExpression') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'UnaryExpression') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'TemplateLiteral') || (0, _eslintCodemodUtils.isNodeOfType)(node, 'CallExpression')) || !Array.isArray(node.range)) {
|
|
175
192
|
return null;
|
|
176
193
|
}
|
|
177
194
|
var _node$range = (0, _slicedToArray2.default)(node.range, 2),
|
|
@@ -353,6 +370,14 @@ function shouldAnalyzeProperty(propertyName, targetOptions) {
|
|
|
353
370
|
return false;
|
|
354
371
|
}
|
|
355
372
|
|
|
373
|
+
/**
|
|
374
|
+
* Function that removes JS comments from a string of code,
|
|
375
|
+
* sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic
|
|
376
|
+
*/
|
|
377
|
+
function cleanComments(str) {
|
|
378
|
+
return str.replace(/\/\*([\s\S]*?)\*\//g, '').replace(/\/\/(.*)/g, '');
|
|
379
|
+
}
|
|
380
|
+
|
|
356
381
|
/**
|
|
357
382
|
* Returns an array of tuples representing a processed css within `TaggedTemplateExpression` node.
|
|
358
383
|
* each element of the array is a tuple `[string, string]`,
|
|
@@ -370,10 +395,14 @@ function processCssNode(node, context) {
|
|
|
370
395
|
return "".concat(q.value.raw).concat(node.quasi.expressions[i] ? getValue(node.quasi.expressions[i], context) : '');
|
|
371
396
|
}).join('');
|
|
372
397
|
var rawString = node.quasi.quasis.map(function (q, i) {
|
|
373
|
-
return "".concat(q.value.raw).concat(node.quasi.expressions[i] ? "${".concat(getRawExpression(node.quasi.expressions[i], context), "}") : '');
|
|
398
|
+
return "".concat(q.value.raw).concat(node.quasi.expressions[i] ? getRawExpression(node.quasi.expressions[i], context) ? "${".concat(getRawExpression(node.quasi.expressions[i], context), "}") : null : '');
|
|
374
399
|
}).join('');
|
|
375
|
-
var cssProperties = splitCssProperties(combinedString);
|
|
376
|
-
var unalteredCssProperties = splitCssProperties(rawString);
|
|
400
|
+
var cssProperties = splitCssProperties(cleanComments(combinedString));
|
|
401
|
+
var unalteredCssProperties = splitCssProperties(cleanComments(rawString));
|
|
402
|
+
if (cssProperties.length !== unalteredCssProperties.length) {
|
|
403
|
+
// this means something wen't wrong with the parsing, the original lines can't be reconciliated with the processed lines
|
|
404
|
+
return undefined;
|
|
405
|
+
}
|
|
377
406
|
return cssProperties.map(function (cssProperty, index) {
|
|
378
407
|
return [cssProperty, unalteredCssProperties[index]];
|
|
379
408
|
});
|
|
@@ -437,9 +466,21 @@ function getFontSizeValueInScope(cssProperties) {
|
|
|
437
466
|
function splitCssProperties(styleString) {
|
|
438
467
|
return styleString.split('\n').filter(function (line) {
|
|
439
468
|
return !line.trim().startsWith('@');
|
|
469
|
+
})
|
|
470
|
+
// sometimes makers will end a css line with `;` that's output from a function expression
|
|
471
|
+
// since we'll rely on `;` to split each line, we need to ensure it's there
|
|
472
|
+
.map(function (line) {
|
|
473
|
+
return line.endsWith(';') ? line : "".concat(line, ";");
|
|
440
474
|
}).join('\n').replace(/\n/g, '').split(/;|(?<!\$){|(?<!\${.+?)}/) // don't split on template literal expressions i.e. `${...}`
|
|
441
|
-
|
|
475
|
+
// filters lines that are completely null, this could be from function expressions that output both property and value
|
|
476
|
+
.filter(function (line) {
|
|
477
|
+
return line.trim() !== 'null' && line.trim() !== 'null;';
|
|
478
|
+
}).map(function (el) {
|
|
442
479
|
return el.trim() || '';
|
|
480
|
+
})
|
|
481
|
+
// we won't be able to reason about lines that don't have colon (:)
|
|
482
|
+
.filter(function (line) {
|
|
483
|
+
return line.split(':').length === 2;
|
|
443
484
|
}).filter(Boolean);
|
|
444
485
|
}
|
|
445
486
|
|
|
@@ -9,9 +9,7 @@ var _slicedToArray2 = _interopRequireDefault(require("@babel/runtime/helpers/sli
|
|
|
9
9
|
var _fs = _interopRequireDefault(require("fs"));
|
|
10
10
|
var _path = _interopRequireDefault(require("path"));
|
|
11
11
|
var _utils = require("@typescript-eslint/utils");
|
|
12
|
-
var
|
|
13
|
-
return name;
|
|
14
|
-
});
|
|
12
|
+
var _createRule = require("../utils/create-rule");
|
|
15
13
|
var noDeprecatedJSXAttributeMessageId = 'noDeprecatedJSXAttributes';
|
|
16
14
|
exports.noDeprecatedJSXAttributeMessageId = noDeprecatedJSXAttributeMessageId;
|
|
17
15
|
var isNodeOfType = function isNodeOfType(node, nodeType) {
|
|
@@ -38,7 +36,7 @@ var getConfig = function getConfig() {
|
|
|
38
36
|
};
|
|
39
37
|
var name = 'no-deprecated-apis';
|
|
40
38
|
exports.name = name;
|
|
41
|
-
var rule = createRule({
|
|
39
|
+
var rule = (0, _createRule.createRule)({
|
|
42
40
|
name: name,
|
|
43
41
|
defaultOptions: [{
|
|
44
42
|
deprecatedConfig: getConfig()
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
|
4
|
+
value: true
|
|
5
|
+
});
|
|
6
|
+
exports.createRule = void 0;
|
|
7
|
+
var _utils = require("@typescript-eslint/utils");
|
|
8
|
+
var createRule = _utils.ESLintUtils.RuleCreator(function (name) {
|
|
9
|
+
return "https://atlassian.design/components/eslint-plugin-design-system/examples#".concat(name);
|
|
10
|
+
});
|
|
11
|
+
exports.createRule = createRule;
|
package/dist/cjs/version.json
CHANGED
|
@@ -1,29 +1,62 @@
|
|
|
1
1
|
/* eslint-disable @atlassian/tangerine/import/entry-points */
|
|
2
2
|
|
|
3
3
|
import { isNodeOfType, node as nodeFn, property } from 'eslint-codemod-utils';
|
|
4
|
+
import { createRule } from '../utils/create-rule';
|
|
4
5
|
import { isDecendantOfGlobalToken } from '../utils/is-node';
|
|
5
|
-
import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getFontSizeValueInScope, getTokenNodeForValue, getTokenReplacement, getValue, getValueFromShorthand, isTokenValueString, isTypographyProperty, isValidSpacingValue, processCssNode, shouldAnalyzeProperty, spacingValueToToken, splitShorthandValues, typographyValueToToken } from './utils';
|
|
6
|
-
const rule = {
|
|
6
|
+
import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getFontSizeValueInScope, getTokenNodeForValue, getTokenReplacement, getValue, getValueFromShorthand, insertTokensImport, isTokenValueString, isTypographyProperty, isValidSpacingValue, processCssNode, shouldAnalyzeProperty, spacingValueToToken, splitShorthandValues, typographyValueToToken } from './utils';
|
|
7
|
+
const rule = createRule({
|
|
8
|
+
defaultOptions: [{
|
|
9
|
+
addons: ['spacing'],
|
|
10
|
+
applyImport: true
|
|
11
|
+
}],
|
|
12
|
+
name: 'ensure-design-token-usage-spacing',
|
|
7
13
|
meta: {
|
|
14
|
+
schema: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
applyImport: {
|
|
20
|
+
type: 'boolean'
|
|
21
|
+
},
|
|
22
|
+
addons: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: {
|
|
25
|
+
enum: ['spacing', 'typography', 'shape']
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
},
|
|
8
31
|
type: 'problem',
|
|
9
32
|
fixable: 'code',
|
|
10
33
|
docs: {
|
|
11
34
|
description: 'Rule ensures all spacing CSS properties apply a matching spacing token',
|
|
12
|
-
recommended:
|
|
35
|
+
recommended: 'error'
|
|
13
36
|
},
|
|
14
37
|
messages: {
|
|
15
38
|
noRawSpacingValues: 'The use of spacing primitives or tokens is preferred over the direct application of spacing properties.\n\n@meta <<{{payload}}>>',
|
|
16
39
|
autofixesPossible: 'Automated corrections available for spacing values. Apply autofix to replace values with appropriate tokens'
|
|
17
40
|
}
|
|
18
41
|
},
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
42
|
+
// @ts-expect-error
|
|
43
|
+
create(context, options) {
|
|
44
|
+
let tokenNode = null;
|
|
45
|
+
|
|
46
|
+
// merge configs
|
|
47
|
+
const ruleConfig = {
|
|
48
|
+
...options[0],
|
|
49
|
+
addons: [...options[0].addons]
|
|
50
|
+
};
|
|
51
|
+
if (!ruleConfig.addons.includes('spacing')) {
|
|
52
|
+
ruleConfig.addons.push('spacing');
|
|
25
53
|
}
|
|
26
54
|
return {
|
|
55
|
+
ImportDeclaration(node) {
|
|
56
|
+
if (node.source.value === '@atlaskit/tokens' && ruleConfig.applyImport) {
|
|
57
|
+
tokenNode = node;
|
|
58
|
+
}
|
|
59
|
+
},
|
|
27
60
|
// CSSObjectExpression
|
|
28
61
|
// const styles = css({ color: 'red', margin: '4px' }), styled.div({ color: 'red', margin: '4px' })
|
|
29
62
|
'CallExpression[callee.name=css] > ObjectExpression, CallExpression[callee.object.name=styled] > ObjectExpression': parentNode => {
|
|
@@ -57,7 +90,7 @@ const rule = {
|
|
|
57
90
|
if (!isNodeOfType(node.key, 'Identifier')) {
|
|
58
91
|
return;
|
|
59
92
|
}
|
|
60
|
-
if (!shouldAnalyzeProperty(node.key.name,
|
|
93
|
+
if (!shouldAnalyzeProperty(node.key.name, ruleConfig.addons)) {
|
|
61
94
|
return;
|
|
62
95
|
}
|
|
63
96
|
if (isDecendantOfGlobalToken(node.value)) {
|
|
@@ -105,7 +138,7 @@ const rule = {
|
|
|
105
138
|
},
|
|
106
139
|
fix: fixer => {
|
|
107
140
|
var _node$loc;
|
|
108
|
-
if (!shouldAnalyzeProperty(propertyName,
|
|
141
|
+
if (!shouldAnalyzeProperty(propertyName, ruleConfig.addons)) {
|
|
109
142
|
return null;
|
|
110
143
|
}
|
|
111
144
|
const pixelValueString = `${pixelValue}px`;
|
|
@@ -115,10 +148,10 @@ const rule = {
|
|
|
115
148
|
return null;
|
|
116
149
|
}
|
|
117
150
|
const replacementValue = getTokenNodeForValue(propertyName, lookupValue);
|
|
118
|
-
return [fixer.insertTextBefore(node, `// TODO Delete this comment after verifying spacing token -> previous value \`${nodeFn(node.value)}\`\n${' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0)}`), fixer.replaceText(node, property({
|
|
151
|
+
return (!tokenNode && ruleConfig.applyImport ? [insertTokensImport(fixer)] : []).concat([fixer.insertTextBefore(node, `// TODO Delete this comment after verifying spacing token -> previous value \`${nodeFn(node.value)}\`\n${' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0)}`), fixer.replaceText(node, property({
|
|
119
152
|
...node,
|
|
120
153
|
value: replacementValue
|
|
121
|
-
}).toString())];
|
|
154
|
+
}).toString())]);
|
|
122
155
|
}
|
|
123
156
|
});
|
|
124
157
|
}
|
|
@@ -143,11 +176,11 @@ const rule = {
|
|
|
143
176
|
if (!allResolvableValues) {
|
|
144
177
|
return null;
|
|
145
178
|
}
|
|
146
|
-
return fixer.replaceText(node.value, `\`${values.map(value => {
|
|
179
|
+
return (!tokenNode && ruleConfig.applyImport ? [insertTokensImport(fixer)] : []).concat([fixer.replaceText(node.value, `\`${values.map(value => {
|
|
147
180
|
const pixelValue = emToPixels(value, fontSize);
|
|
148
181
|
const pixelValueString = `${pixelValue}px`;
|
|
149
182
|
return `\${${getTokenNodeForValue(propertyName, pixelValueString)}}`;
|
|
150
|
-
}).join(' ')}\``);
|
|
183
|
+
}).join(' ')}\``)]);
|
|
151
184
|
} : undefined
|
|
152
185
|
});
|
|
153
186
|
});
|
|
@@ -161,8 +194,12 @@ const rule = {
|
|
|
161
194
|
if (node.type !== 'TaggedTemplateExpression') {
|
|
162
195
|
return;
|
|
163
196
|
}
|
|
164
|
-
const parentNode = findParentNodeForLine(node);
|
|
165
197
|
const processedCssLines = processCssNode(node, context);
|
|
198
|
+
if (!processedCssLines) {
|
|
199
|
+
// if we can't get a processed css we bail
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
const parentNode = findParentNodeForLine(node);
|
|
166
203
|
const globalFontSize = getFontSizeValueInScope(processedCssLines);
|
|
167
204
|
const textForSource = context.getSourceCode().getText(node.quasi);
|
|
168
205
|
const allReplacedValues = [];
|
|
@@ -172,7 +209,7 @@ const rule = {
|
|
|
172
209
|
const propertyName = convertHyphenatedNameToCamelCase(originalProperty);
|
|
173
210
|
const isFontFamily = /fontFamily/.test(propertyName);
|
|
174
211
|
const replacedValuesPerProperty = [originalProperty];
|
|
175
|
-
if (!shouldAnalyzeProperty(propertyName,
|
|
212
|
+
if (!shouldAnalyzeProperty(propertyName, ruleConfig.addons) || !resolvedCssValues || !isValidSpacingValue(resolvedCssValues, globalFontSize)) {
|
|
176
213
|
// in all of these cases no changes should be made to the current property
|
|
177
214
|
return currentSource;
|
|
178
215
|
}
|
|
@@ -252,12 +289,12 @@ const rule = {
|
|
|
252
289
|
node,
|
|
253
290
|
messageId: 'autofixesPossible',
|
|
254
291
|
fix: fixer => {
|
|
255
|
-
return [fixer.insertTextBefore(parentNode, replacementComments), fixer.replaceText(node.quasi, completeSource)];
|
|
292
|
+
return (!tokenNode && ruleConfig.applyImport ? [insertTokensImport(fixer)] : []).concat([fixer.insertTextBefore(parentNode, replacementComments), fixer.replaceText(node.quasi, completeSource)]);
|
|
256
293
|
}
|
|
257
294
|
});
|
|
258
295
|
}
|
|
259
296
|
}
|
|
260
297
|
};
|
|
261
298
|
}
|
|
262
|
-
};
|
|
299
|
+
});
|
|
263
300
|
export default rule;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { callExpression, identifier, isNodeOfType, literal } from 'eslint-codemod-utils';
|
|
1
|
+
import { callExpression, identifier, insertAtStartOfFile, insertImportDeclaration, isNodeOfType, literal } from 'eslint-codemod-utils';
|
|
2
2
|
import { spacing as spacingScale, typography as typographyTokens } from '@atlaskit/tokens/tokens-raw';
|
|
3
3
|
const typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
|
|
4
4
|
const properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingInline', 'paddingInlineStart', 'paddingInlineEnd', 'paddingBlock', 'paddingBlockStart', 'paddingBlockEnd', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'marginInline', 'marginInlineStart', 'marginInlineEnd', 'marginBlock', 'marginBlockStart', 'marginBlockEnd', 'margin', 'gap', 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap'];
|
|
@@ -30,15 +30,29 @@ export function findIdentifierInParentScope({
|
|
|
30
30
|
}
|
|
31
31
|
return null;
|
|
32
32
|
}
|
|
33
|
+
export function insertTokensImport(fixer) {
|
|
34
|
+
return insertAtStartOfFile(fixer, `${insertImportDeclaration('@atlaskit/tokens', ['token'])}\n`);
|
|
35
|
+
}
|
|
33
36
|
export const isSpacingProperty = propertyName => {
|
|
34
37
|
return properties.includes(propertyName);
|
|
35
38
|
};
|
|
36
39
|
export const isTypographyProperty = propertyName => {
|
|
37
40
|
return typographyProperties.includes(propertyName);
|
|
38
41
|
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Accomplishes split str by whitespace but preserves expressions in between ${...}
|
|
45
|
+
* even if they might have whitepaces or nested brackets
|
|
46
|
+
* @param str
|
|
47
|
+
* @returns string[]
|
|
48
|
+
* @example
|
|
49
|
+
* Regex has two parts, first attempts to capture anything in between `${...}` in a capture group
|
|
50
|
+
* Whilst allowing nested brackets and non empty characters leading or traling wrapping expression e.g `${gridSize}`, `-${gridSize}px`
|
|
51
|
+
* second part is a white space delimiter
|
|
52
|
+
* For input `-${gridSize / 2}px ${token(...)} 18px -> [`-${gridSize / 2}px`, `${token(...)}`, `18px`]
|
|
53
|
+
*/
|
|
39
54
|
export const splitShorthandValues = str => {
|
|
40
|
-
|
|
41
|
-
return str.split(/(\${[^}]*}\S*)|\s+/g).filter(Boolean);
|
|
55
|
+
return str.split(/(\S*\$\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}\S*)|\s+/g).filter(Boolean);
|
|
42
56
|
};
|
|
43
57
|
export const getValueFromShorthand = str => {
|
|
44
58
|
const valueString = String(str);
|
|
@@ -119,7 +133,7 @@ export const getRawExpression = (node, context) => {
|
|
|
119
133
|
if (!(
|
|
120
134
|
// if not one of our recognized types or doesn't have a range prop, early return
|
|
121
135
|
|
|
122
|
-
isNodeOfType(node, 'Literal') || isNodeOfType(node, 'Identifier') || isNodeOfType(node, 'BinaryExpression') || isNodeOfType(node, 'UnaryExpression') || isNodeOfType(node, 'TemplateLiteral') || isNodeOfType(node, 'CallExpression')
|
|
136
|
+
isNodeOfType(node, 'Literal') || isNodeOfType(node, 'Identifier') || isNodeOfType(node, 'BinaryExpression') || isNodeOfType(node, 'UnaryExpression') || isNodeOfType(node, 'TemplateLiteral') || isNodeOfType(node, 'CallExpression')) || !Array.isArray(node.range)) {
|
|
123
137
|
return null;
|
|
124
138
|
}
|
|
125
139
|
const [start, end] = node.range;
|
|
@@ -293,6 +307,14 @@ export function shouldAnalyzeProperty(propertyName, targetOptions) {
|
|
|
293
307
|
return false;
|
|
294
308
|
}
|
|
295
309
|
|
|
310
|
+
/**
|
|
311
|
+
* Function that removes JS comments from a string of code,
|
|
312
|
+
* sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic
|
|
313
|
+
*/
|
|
314
|
+
export function cleanComments(str) {
|
|
315
|
+
return str.replace(/\/\*([\s\S]*?)\*\//g, '').replace(/\/\/(.*)/g, '');
|
|
316
|
+
}
|
|
317
|
+
|
|
296
318
|
/**
|
|
297
319
|
* Returns an array of tuples representing a processed css within `TaggedTemplateExpression` node.
|
|
298
320
|
* each element of the array is a tuple `[string, string]`,
|
|
@@ -310,10 +332,14 @@ export function processCssNode(node, context) {
|
|
|
310
332
|
return `${q.value.raw}${node.quasi.expressions[i] ? getValue(node.quasi.expressions[i], context) : ''}`;
|
|
311
333
|
}).join('');
|
|
312
334
|
const rawString = node.quasi.quasis.map((q, i) => {
|
|
313
|
-
return `${q.value.raw}${node.quasi.expressions[i] ? `\${${getRawExpression(node.quasi.expressions[i], context)}}` : ''}`;
|
|
335
|
+
return `${q.value.raw}${node.quasi.expressions[i] ? getRawExpression(node.quasi.expressions[i], context) ? `\${${getRawExpression(node.quasi.expressions[i], context)}}` : null : ''}`;
|
|
314
336
|
}).join('');
|
|
315
|
-
const cssProperties = splitCssProperties(combinedString);
|
|
316
|
-
const unalteredCssProperties = splitCssProperties(rawString);
|
|
337
|
+
const cssProperties = splitCssProperties(cleanComments(combinedString));
|
|
338
|
+
const unalteredCssProperties = splitCssProperties(cleanComments(rawString));
|
|
339
|
+
if (cssProperties.length !== unalteredCssProperties.length) {
|
|
340
|
+
// this means something wen't wrong with the parsing, the original lines can't be reconciliated with the processed lines
|
|
341
|
+
return undefined;
|
|
342
|
+
}
|
|
317
343
|
return cssProperties.map((cssProperty, index) => [cssProperty, unalteredCssProperties[index]]);
|
|
318
344
|
}
|
|
319
345
|
|
|
@@ -365,8 +391,14 @@ export function getFontSizeValueInScope(cssProperties) {
|
|
|
365
391
|
* @param styleString string of css properties
|
|
366
392
|
*/
|
|
367
393
|
export function splitCssProperties(styleString) {
|
|
368
|
-
return styleString.split('\n').filter(line => !line.trim().startsWith('@'))
|
|
369
|
-
|
|
394
|
+
return styleString.split('\n').filter(line => !line.trim().startsWith('@'))
|
|
395
|
+
// sometimes makers will end a css line with `;` that's output from a function expression
|
|
396
|
+
// since we'll rely on `;` to split each line, we need to ensure it's there
|
|
397
|
+
.map(line => line.endsWith(';') ? line : `${line};`).join('\n').replace(/\n/g, '').split(/;|(?<!\$){|(?<!\${.+?)}/) // don't split on template literal expressions i.e. `${...}`
|
|
398
|
+
// filters lines that are completely null, this could be from function expressions that output both property and value
|
|
399
|
+
.filter(line => line.trim() !== 'null' && line.trim() !== 'null;').map(el => el.trim() || '')
|
|
400
|
+
// we won't be able to reason about lines that don't have colon (:)
|
|
401
|
+
.filter(line => line.split(':').length === 2).filter(Boolean);
|
|
370
402
|
}
|
|
371
403
|
|
|
372
404
|
/**
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
|
-
import { AST_NODE_TYPES, ASTUtils
|
|
4
|
-
|
|
3
|
+
import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
|
|
4
|
+
import { createRule } from '../utils/create-rule';
|
|
5
5
|
export const noDeprecatedJSXAttributeMessageId = 'noDeprecatedJSXAttributes';
|
|
6
6
|
const isNodeOfType = (node, nodeType) => ASTUtils.isNodeOfType(nodeType)(node);
|
|
7
7
|
const isImportDeclaration = programStatement => {
|
package/dist/es2019/version.json
CHANGED
|
@@ -1,33 +1,66 @@
|
|
|
1
|
-
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
2
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
+
import _toConsumableArray from "@babel/runtime/helpers/toConsumableArray";
|
|
3
|
+
import _defineProperty from "@babel/runtime/helpers/defineProperty";
|
|
3
4
|
function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); enumerableOnly && (symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; })), keys.push.apply(keys, symbols); } return keys; }
|
|
4
5
|
function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = null != arguments[i] ? arguments[i] : {}; i % 2 ? ownKeys(Object(source), !0).forEach(function (key) { _defineProperty(target, key, source[key]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)) : ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } return target; }
|
|
5
6
|
/* eslint-disable @atlassian/tangerine/import/entry-points */
|
|
6
7
|
|
|
7
8
|
import { isNodeOfType, node as nodeFn, property } from 'eslint-codemod-utils';
|
|
9
|
+
import { createRule } from '../utils/create-rule';
|
|
8
10
|
import { isDecendantOfGlobalToken } from '../utils/is-node';
|
|
9
|
-
import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getFontSizeValueInScope, getTokenNodeForValue, getTokenReplacement, getValue, getValueFromShorthand, isTokenValueString, isTypographyProperty, isValidSpacingValue, processCssNode, shouldAnalyzeProperty, spacingValueToToken, splitShorthandValues, typographyValueToToken } from './utils';
|
|
10
|
-
var rule = {
|
|
11
|
+
import { convertHyphenatedNameToCamelCase, emToPixels, findParentNodeForLine, getFontSizeValueInScope, getTokenNodeForValue, getTokenReplacement, getValue, getValueFromShorthand, insertTokensImport, isTokenValueString, isTypographyProperty, isValidSpacingValue, processCssNode, shouldAnalyzeProperty, spacingValueToToken, splitShorthandValues, typographyValueToToken } from './utils';
|
|
12
|
+
var rule = createRule({
|
|
13
|
+
defaultOptions: [{
|
|
14
|
+
addons: ['spacing'],
|
|
15
|
+
applyImport: true
|
|
16
|
+
}],
|
|
17
|
+
name: 'ensure-design-token-usage-spacing',
|
|
11
18
|
meta: {
|
|
19
|
+
schema: {
|
|
20
|
+
type: 'array',
|
|
21
|
+
items: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
applyImport: {
|
|
25
|
+
type: 'boolean'
|
|
26
|
+
},
|
|
27
|
+
addons: {
|
|
28
|
+
type: 'array',
|
|
29
|
+
items: {
|
|
30
|
+
enum: ['spacing', 'typography', 'shape']
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
},
|
|
12
36
|
type: 'problem',
|
|
13
37
|
fixable: 'code',
|
|
14
38
|
docs: {
|
|
15
39
|
description: 'Rule ensures all spacing CSS properties apply a matching spacing token',
|
|
16
|
-
recommended:
|
|
40
|
+
recommended: 'error'
|
|
17
41
|
},
|
|
18
42
|
messages: {
|
|
19
43
|
noRawSpacingValues: 'The use of spacing primitives or tokens is preferred over the direct application of spacing properties.\n\n@meta <<{{payload}}>>',
|
|
20
44
|
autofixesPossible: 'Automated corrections available for spacing values. Apply autofix to replace values with appropriate tokens'
|
|
21
45
|
}
|
|
22
46
|
},
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
var
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
47
|
+
// @ts-expect-error
|
|
48
|
+
create: function create(context, options) {
|
|
49
|
+
var tokenNode = null;
|
|
50
|
+
|
|
51
|
+
// merge configs
|
|
52
|
+
var ruleConfig = _objectSpread(_objectSpread({}, options[0]), {}, {
|
|
53
|
+
addons: _toConsumableArray(options[0].addons)
|
|
54
|
+
});
|
|
55
|
+
if (!ruleConfig.addons.includes('spacing')) {
|
|
56
|
+
ruleConfig.addons.push('spacing');
|
|
29
57
|
}
|
|
30
58
|
return {
|
|
59
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
60
|
+
if (node.source.value === '@atlaskit/tokens' && ruleConfig.applyImport) {
|
|
61
|
+
tokenNode = node;
|
|
62
|
+
}
|
|
63
|
+
},
|
|
31
64
|
// CSSObjectExpression
|
|
32
65
|
// const styles = css({ color: 'red', margin: '4px' }), styled.div({ color: 'red', margin: '4px' })
|
|
33
66
|
'CallExpression[callee.name=css] > ObjectExpression, CallExpression[callee.object.name=styled] > ObjectExpression': function CallExpressionCalleeNameCssObjectExpressionCallExpressionCalleeObjectNameStyledObjectExpression(parentNode) {
|
|
@@ -61,7 +94,7 @@ var rule = {
|
|
|
61
94
|
if (!isNodeOfType(node.key, 'Identifier')) {
|
|
62
95
|
return;
|
|
63
96
|
}
|
|
64
|
-
if (!shouldAnalyzeProperty(node.key.name,
|
|
97
|
+
if (!shouldAnalyzeProperty(node.key.name, ruleConfig.addons)) {
|
|
65
98
|
return;
|
|
66
99
|
}
|
|
67
100
|
if (isDecendantOfGlobalToken(node.value)) {
|
|
@@ -110,7 +143,7 @@ var rule = {
|
|
|
110
143
|
},
|
|
111
144
|
fix: function fix(fixer) {
|
|
112
145
|
var _node$loc;
|
|
113
|
-
if (!shouldAnalyzeProperty(propertyName,
|
|
146
|
+
if (!shouldAnalyzeProperty(propertyName, ruleConfig.addons)) {
|
|
114
147
|
return null;
|
|
115
148
|
}
|
|
116
149
|
var pixelValueString = "".concat(pixelValue, "px");
|
|
@@ -120,9 +153,9 @@ var rule = {
|
|
|
120
153
|
return null;
|
|
121
154
|
}
|
|
122
155
|
var replacementValue = getTokenNodeForValue(propertyName, lookupValue);
|
|
123
|
-
return [fixer.insertTextBefore(node, "// TODO Delete this comment after verifying spacing token -> previous value `".concat(nodeFn(node.value), "`\n").concat(' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0))), fixer.replaceText(node, property(_objectSpread(_objectSpread({}, node), {}, {
|
|
156
|
+
return (!tokenNode && ruleConfig.applyImport ? [insertTokensImport(fixer)] : []).concat([fixer.insertTextBefore(node, "// TODO Delete this comment after verifying spacing token -> previous value `".concat(nodeFn(node.value), "`\n").concat(' '.padStart(((_node$loc = node.loc) === null || _node$loc === void 0 ? void 0 : _node$loc.start.column) || 0))), fixer.replaceText(node, property(_objectSpread(_objectSpread({}, node), {}, {
|
|
124
157
|
value: replacementValue
|
|
125
|
-
})).toString())];
|
|
158
|
+
})).toString())]);
|
|
126
159
|
}
|
|
127
160
|
});
|
|
128
161
|
}
|
|
@@ -149,11 +182,11 @@ var rule = {
|
|
|
149
182
|
if (!allResolvableValues) {
|
|
150
183
|
return null;
|
|
151
184
|
}
|
|
152
|
-
return fixer.replaceText(node.value, "`".concat(values.map(function (value) {
|
|
185
|
+
return (!tokenNode && ruleConfig.applyImport ? [insertTokensImport(fixer)] : []).concat([fixer.replaceText(node.value, "`".concat(values.map(function (value) {
|
|
153
186
|
var pixelValue = emToPixels(value, fontSize);
|
|
154
187
|
var pixelValueString = "".concat(pixelValue, "px");
|
|
155
188
|
return "${".concat(getTokenNodeForValue(propertyName, pixelValueString), "}");
|
|
156
|
-
}).join(' '), "`"));
|
|
189
|
+
}).join(' '), "`"))]);
|
|
157
190
|
} : undefined
|
|
158
191
|
});
|
|
159
192
|
});
|
|
@@ -167,8 +200,12 @@ var rule = {
|
|
|
167
200
|
if (node.type !== 'TaggedTemplateExpression') {
|
|
168
201
|
return;
|
|
169
202
|
}
|
|
170
|
-
var parentNode = findParentNodeForLine(node);
|
|
171
203
|
var processedCssLines = processCssNode(node, context);
|
|
204
|
+
if (!processedCssLines) {
|
|
205
|
+
// if we can't get a processed css we bail
|
|
206
|
+
return;
|
|
207
|
+
}
|
|
208
|
+
var parentNode = findParentNodeForLine(node);
|
|
172
209
|
var globalFontSize = getFontSizeValueInScope(processedCssLines);
|
|
173
210
|
var textForSource = context.getSourceCode().getText(node.quasi);
|
|
174
211
|
var allReplacedValues = [];
|
|
@@ -187,7 +224,7 @@ var rule = {
|
|
|
187
224
|
var propertyName = convertHyphenatedNameToCamelCase(originalProperty);
|
|
188
225
|
var isFontFamily = /fontFamily/.test(propertyName);
|
|
189
226
|
var replacedValuesPerProperty = [originalProperty];
|
|
190
|
-
if (!shouldAnalyzeProperty(propertyName,
|
|
227
|
+
if (!shouldAnalyzeProperty(propertyName, ruleConfig.addons) || !resolvedCssValues || !isValidSpacingValue(resolvedCssValues, globalFontSize)) {
|
|
191
228
|
// in all of these cases no changes should be made to the current property
|
|
192
229
|
return currentSource;
|
|
193
230
|
}
|
|
@@ -273,12 +310,12 @@ var rule = {
|
|
|
273
310
|
node: node,
|
|
274
311
|
messageId: 'autofixesPossible',
|
|
275
312
|
fix: function fix(fixer) {
|
|
276
|
-
return [fixer.insertTextBefore(parentNode, replacementComments), fixer.replaceText(node.quasi, completeSource)];
|
|
313
|
+
return (!tokenNode && ruleConfig.applyImport ? [insertTokensImport(fixer)] : []).concat([fixer.insertTextBefore(parentNode, replacementComments), fixer.replaceText(node.quasi, completeSource)]);
|
|
277
314
|
}
|
|
278
315
|
});
|
|
279
316
|
}
|
|
280
317
|
}
|
|
281
318
|
};
|
|
282
319
|
}
|
|
283
|
-
};
|
|
320
|
+
});
|
|
284
321
|
export default rule;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
|
-
import { callExpression, identifier, isNodeOfType, literal } from 'eslint-codemod-utils';
|
|
2
|
+
import { callExpression, identifier, insertAtStartOfFile, insertImportDeclaration, isNodeOfType, literal } from 'eslint-codemod-utils';
|
|
3
3
|
import { spacing as spacingScale, typography as typographyTokens } from '@atlaskit/tokens/tokens-raw';
|
|
4
4
|
var typographyProperties = ['fontSize', 'fontWeight', 'fontFamily', 'lineHeight'];
|
|
5
5
|
var properties = ['padding', 'paddingBlock', 'paddingInline', 'paddingLeft', 'paddingTop', 'paddingRight', 'paddingBottom', 'paddingInline', 'paddingInlineStart', 'paddingInlineEnd', 'paddingBlock', 'paddingBlockStart', 'paddingBlockEnd', 'marginLeft', 'marginTop', 'marginRight', 'marginBottom', 'marginInline', 'marginInlineStart', 'marginInlineEnd', 'marginBlock', 'marginBlockStart', 'marginBlockEnd', 'margin', 'gap', 'rowGap', 'gridRowGap', 'columnGap', 'gridColumnGap'];
|
|
@@ -40,15 +40,29 @@ export function findIdentifierInParentScope(_ref) {
|
|
|
40
40
|
}
|
|
41
41
|
return null;
|
|
42
42
|
}
|
|
43
|
+
export function insertTokensImport(fixer) {
|
|
44
|
+
return insertAtStartOfFile(fixer, "".concat(insertImportDeclaration('@atlaskit/tokens', ['token']), "\n"));
|
|
45
|
+
}
|
|
43
46
|
export var isSpacingProperty = function isSpacingProperty(propertyName) {
|
|
44
47
|
return properties.includes(propertyName);
|
|
45
48
|
};
|
|
46
49
|
export var isTypographyProperty = function isTypographyProperty(propertyName) {
|
|
47
50
|
return typographyProperties.includes(propertyName);
|
|
48
51
|
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Accomplishes split str by whitespace but preserves expressions in between ${...}
|
|
55
|
+
* even if they might have whitepaces or nested brackets
|
|
56
|
+
* @param str
|
|
57
|
+
* @returns string[]
|
|
58
|
+
* @example
|
|
59
|
+
* Regex has two parts, first attempts to capture anything in between `${...}` in a capture group
|
|
60
|
+
* Whilst allowing nested brackets and non empty characters leading or traling wrapping expression e.g `${gridSize}`, `-${gridSize}px`
|
|
61
|
+
* second part is a white space delimiter
|
|
62
|
+
* For input `-${gridSize / 2}px ${token(...)} 18px -> [`-${gridSize / 2}px`, `${token(...)}`, `18px`]
|
|
63
|
+
*/
|
|
49
64
|
export var splitShorthandValues = function splitShorthandValues(str) {
|
|
50
|
-
|
|
51
|
-
return str.split(/(\${[^}]*}\S*)|\s+/g).filter(Boolean);
|
|
65
|
+
return str.split(/(\S*\$\{[^{}]*(?:\{[^{}]*\}[^{}]*)*\}\S*)|\s+/g).filter(Boolean);
|
|
52
66
|
};
|
|
53
67
|
export var getValueFromShorthand = function getValueFromShorthand(str) {
|
|
54
68
|
var valueString = String(str);
|
|
@@ -141,7 +155,7 @@ export var getRawExpression = function getRawExpression(node, context) {
|
|
|
141
155
|
if (!(
|
|
142
156
|
// if not one of our recognized types or doesn't have a range prop, early return
|
|
143
157
|
|
|
144
|
-
isNodeOfType(node, 'Literal') || isNodeOfType(node, 'Identifier') || isNodeOfType(node, 'BinaryExpression') || isNodeOfType(node, 'UnaryExpression') || isNodeOfType(node, 'TemplateLiteral') || isNodeOfType(node, 'CallExpression')
|
|
158
|
+
isNodeOfType(node, 'Literal') || isNodeOfType(node, 'Identifier') || isNodeOfType(node, 'BinaryExpression') || isNodeOfType(node, 'UnaryExpression') || isNodeOfType(node, 'TemplateLiteral') || isNodeOfType(node, 'CallExpression')) || !Array.isArray(node.range)) {
|
|
145
159
|
return null;
|
|
146
160
|
}
|
|
147
161
|
var _node$range = _slicedToArray(node.range, 2),
|
|
@@ -317,6 +331,14 @@ export function shouldAnalyzeProperty(propertyName, targetOptions) {
|
|
|
317
331
|
return false;
|
|
318
332
|
}
|
|
319
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Function that removes JS comments from a string of code,
|
|
336
|
+
* sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic
|
|
337
|
+
*/
|
|
338
|
+
export function cleanComments(str) {
|
|
339
|
+
return str.replace(/\/\*([\s\S]*?)\*\//g, '').replace(/\/\/(.*)/g, '');
|
|
340
|
+
}
|
|
341
|
+
|
|
320
342
|
/**
|
|
321
343
|
* Returns an array of tuples representing a processed css within `TaggedTemplateExpression` node.
|
|
322
344
|
* each element of the array is a tuple `[string, string]`,
|
|
@@ -334,10 +356,14 @@ export function processCssNode(node, context) {
|
|
|
334
356
|
return "".concat(q.value.raw).concat(node.quasi.expressions[i] ? getValue(node.quasi.expressions[i], context) : '');
|
|
335
357
|
}).join('');
|
|
336
358
|
var rawString = node.quasi.quasis.map(function (q, i) {
|
|
337
|
-
return "".concat(q.value.raw).concat(node.quasi.expressions[i] ? "${".concat(getRawExpression(node.quasi.expressions[i], context), "}") : '');
|
|
359
|
+
return "".concat(q.value.raw).concat(node.quasi.expressions[i] ? getRawExpression(node.quasi.expressions[i], context) ? "${".concat(getRawExpression(node.quasi.expressions[i], context), "}") : null : '');
|
|
338
360
|
}).join('');
|
|
339
|
-
var cssProperties = splitCssProperties(combinedString);
|
|
340
|
-
var unalteredCssProperties = splitCssProperties(rawString);
|
|
361
|
+
var cssProperties = splitCssProperties(cleanComments(combinedString));
|
|
362
|
+
var unalteredCssProperties = splitCssProperties(cleanComments(rawString));
|
|
363
|
+
if (cssProperties.length !== unalteredCssProperties.length) {
|
|
364
|
+
// this means something wen't wrong with the parsing, the original lines can't be reconciliated with the processed lines
|
|
365
|
+
return undefined;
|
|
366
|
+
}
|
|
341
367
|
return cssProperties.map(function (cssProperty, index) {
|
|
342
368
|
return [cssProperty, unalteredCssProperties[index]];
|
|
343
369
|
});
|
|
@@ -401,9 +427,21 @@ export function getFontSizeValueInScope(cssProperties) {
|
|
|
401
427
|
export function splitCssProperties(styleString) {
|
|
402
428
|
return styleString.split('\n').filter(function (line) {
|
|
403
429
|
return !line.trim().startsWith('@');
|
|
430
|
+
})
|
|
431
|
+
// sometimes makers will end a css line with `;` that's output from a function expression
|
|
432
|
+
// since we'll rely on `;` to split each line, we need to ensure it's there
|
|
433
|
+
.map(function (line) {
|
|
434
|
+
return line.endsWith(';') ? line : "".concat(line, ";");
|
|
404
435
|
}).join('\n').replace(/\n/g, '').split(/;|(?<!\$){|(?<!\${.+?)}/) // don't split on template literal expressions i.e. `${...}`
|
|
405
|
-
|
|
436
|
+
// filters lines that are completely null, this could be from function expressions that output both property and value
|
|
437
|
+
.filter(function (line) {
|
|
438
|
+
return line.trim() !== 'null' && line.trim() !== 'null;';
|
|
439
|
+
}).map(function (el) {
|
|
406
440
|
return el.trim() || '';
|
|
441
|
+
})
|
|
442
|
+
// we won't be able to reason about lines that don't have colon (:)
|
|
443
|
+
.filter(function (line) {
|
|
444
|
+
return line.split(':').length === 2;
|
|
407
445
|
}).filter(Boolean);
|
|
408
446
|
}
|
|
409
447
|
|
|
@@ -1,10 +1,8 @@
|
|
|
1
1
|
import _slicedToArray from "@babel/runtime/helpers/slicedToArray";
|
|
2
2
|
import fs from 'fs';
|
|
3
3
|
import path from 'path';
|
|
4
|
-
import { AST_NODE_TYPES, ASTUtils
|
|
5
|
-
|
|
6
|
-
return name;
|
|
7
|
-
});
|
|
4
|
+
import { AST_NODE_TYPES, ASTUtils } from '@typescript-eslint/utils';
|
|
5
|
+
import { createRule } from '../utils/create-rule';
|
|
8
6
|
export var noDeprecatedJSXAttributeMessageId = 'noDeprecatedJSXAttributes';
|
|
9
7
|
var isNodeOfType = function isNodeOfType(node, nodeType) {
|
|
10
8
|
return ASTUtils.isNodeOfType(nodeType)(node);
|
package/dist/esm/version.json
CHANGED
package/dist/types/index.d.ts
CHANGED
|
@@ -9,7 +9,10 @@ export declare const rules: {
|
|
|
9
9
|
'no-banned-imports': import("eslint").Rule.RuleModule;
|
|
10
10
|
'no-unsafe-design-token-usage': import("eslint").Rule.RuleModule;
|
|
11
11
|
'use-visually-hidden': import("eslint").Rule.RuleModule;
|
|
12
|
-
'ensure-design-token-usage-spacing': import("eslint").
|
|
12
|
+
'ensure-design-token-usage-spacing': import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noRawSpacingValues" | "autofixesPossible", [{
|
|
13
|
+
addons: ("spacing" | "typography" | "shape")[];
|
|
14
|
+
applyImport?: boolean | undefined;
|
|
15
|
+
}], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
13
16
|
};
|
|
14
17
|
export declare const configs: {
|
|
15
18
|
recommended: {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
declare
|
|
1
|
+
declare type Addon = 'spacing' | 'typography' | 'shape';
|
|
2
|
+
declare type RuleConfig = {
|
|
3
|
+
addons: Addon[];
|
|
4
|
+
applyImport?: boolean;
|
|
5
|
+
};
|
|
6
|
+
declare const rule: import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<"noRawSpacingValues" | "autofixesPossible", [RuleConfig], import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>;
|
|
3
7
|
export default rule;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { Rule, Scope } from 'eslint';
|
|
2
2
|
import { EslintNode, TaggedTemplateExpression } from 'eslint-codemod-utils';
|
|
3
3
|
export declare type ProcessedCSSLines = [string, string][];
|
|
4
|
-
export declare type TargetOptions = ('spacing' | 'typography')[];
|
|
4
|
+
export declare type TargetOptions = ('spacing' | 'typography' | 'shape')[];
|
|
5
5
|
/**
|
|
6
6
|
* Currently we have a wide range of experimental spacing tokens that we are testing.
|
|
7
7
|
* We only want transforms to apply to the stable scale values, not the rest.
|
|
@@ -70,8 +70,20 @@ export declare function findIdentifierInParentScope({ scope, identifierName, }:
|
|
|
70
70
|
scope: Scope.Scope;
|
|
71
71
|
identifierName: string;
|
|
72
72
|
}): Scope.Variable | null;
|
|
73
|
+
export declare function insertTokensImport(fixer: Rule.RuleFixer): Rule.Fix;
|
|
73
74
|
export declare const isSpacingProperty: (propertyName: string) => boolean;
|
|
74
75
|
export declare const isTypographyProperty: (propertyName: string) => boolean;
|
|
76
|
+
/**
|
|
77
|
+
* Accomplishes split str by whitespace but preserves expressions in between ${...}
|
|
78
|
+
* even if they might have whitepaces or nested brackets
|
|
79
|
+
* @param str
|
|
80
|
+
* @returns string[]
|
|
81
|
+
* @example
|
|
82
|
+
* Regex has two parts, first attempts to capture anything in between `${...}` in a capture group
|
|
83
|
+
* Whilst allowing nested brackets and non empty characters leading or traling wrapping expression e.g `${gridSize}`, `-${gridSize}px`
|
|
84
|
+
* second part is a white space delimiter
|
|
85
|
+
* For input `-${gridSize / 2}px ${token(...)} 18px -> [`-${gridSize / 2}px`, `${token(...)}`, `18px`]
|
|
86
|
+
*/
|
|
75
87
|
export declare const splitShorthandValues: (str: string) => string[];
|
|
76
88
|
export declare const getValueFromShorthand: (str: unknown) => any[];
|
|
77
89
|
export declare const getValue: (node: EslintNode, context: Rule.RuleContext) => string | number | any[] | null | undefined;
|
|
@@ -96,6 +108,11 @@ export declare const findParentNodeForLine: (node: Rule.Node) => Rule.Node;
|
|
|
96
108
|
* ```
|
|
97
109
|
*/
|
|
98
110
|
export declare function shouldAnalyzeProperty(propertyName: string, targetOptions: TargetOptions): boolean;
|
|
111
|
+
/**
|
|
112
|
+
* Function that removes JS comments from a string of code,
|
|
113
|
+
* sometimes makers will have single or multiline comments in their tagged template literals styles, this can mess with our parsing logic
|
|
114
|
+
*/
|
|
115
|
+
export declare function cleanComments(str: string): string;
|
|
99
116
|
/**
|
|
100
117
|
* Returns an array of tuples representing a processed css within `TaggedTemplateExpression` node.
|
|
101
118
|
* each element of the array is a tuple `[string, string]`,
|
|
@@ -108,7 +125,7 @@ export declare function shouldAnalyzeProperty(propertyName: string, targetOption
|
|
|
108
125
|
* `[['padding: 8', 'padding: ${gridSize()}'], ['margin: 6', 'margin: 6px' ]]`
|
|
109
126
|
* ```
|
|
110
127
|
*/
|
|
111
|
-
export declare function processCssNode(node: TaggedTemplateExpression & Rule.NodeParentExtension, context: Rule.RuleContext): ProcessedCSSLines;
|
|
128
|
+
export declare function processCssNode(node: TaggedTemplateExpression & Rule.NodeParentExtension, context: Rule.RuleContext): ProcessedCSSLines | undefined;
|
|
112
129
|
/**
|
|
113
130
|
* Returns a token node for a given value including fallbacks.
|
|
114
131
|
* @param propertyName camelCase CSS property
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { ESLintUtils } from '@typescript-eslint/utils';
|
|
2
|
+
export declare const createRule: <TOptions extends readonly unknown[], TMessageIds extends string, TRuleListener extends import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener = import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleListener>({ name, meta, ...rule }: Readonly<ESLintUtils.RuleWithMetaAndName<TOptions, TMessageIds, TRuleListener>>) => import("@typescript-eslint/utils/dist/ts-eslint/Rule").RuleModule<TMessageIds, TOptions, TRuleListener>;
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atlaskit/eslint-plugin-design-system",
|
|
3
3
|
"description": "The essential plugin for use with the Atlassian Design System.",
|
|
4
|
-
"version": "4.16.
|
|
4
|
+
"version": "4.16.3",
|
|
5
5
|
"author": "Atlassian Pty Ltd",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"registry": "https://registry.npmjs.org/"
|