@atlaskit/eslint-plugin-design-system 15.0.0 → 15.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 +9 -0
- package/README.md +1 -0
- package/dist/cjs/presets/all-flat.codegen.js +2 -1
- package/dist/cjs/presets/all.codegen.js +2 -1
- package/dist/cjs/rules/index.codegen.js +3 -1
- package/dist/cjs/rules/use-tokens-motion/index.js +555 -0
- package/dist/es2019/presets/all-flat.codegen.js +2 -1
- package/dist/es2019/presets/all.codegen.js +2 -1
- package/dist/es2019/rules/index.codegen.js +3 -1
- package/dist/es2019/rules/use-tokens-motion/index.js +489 -0
- package/dist/esm/presets/all-flat.codegen.js +2 -1
- package/dist/esm/presets/all.codegen.js +2 -1
- package/dist/esm/rules/index.codegen.js +3 -1
- package/dist/esm/rules/use-tokens-motion/index.js +548 -0
- package/dist/types/presets/all-flat.codegen.d.ts +1 -1
- package/dist/types/presets/all.codegen.d.ts +1 -1
- package/dist/types/rules/index.codegen.d.ts +1 -1
- package/dist/types/rules/use-tokens-motion/index.d.ts +3 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# @atlaskit/eslint-plugin-design-system
|
|
2
2
|
|
|
3
|
+
## 15.1.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`c6fce4a43355c`](https://bitbucket.org/atlassian/atlassian-frontend-monorepo/commits/c6fce4a43355c) -
|
|
8
|
+
Add new `use-tokens-motion` ESLint rule to enforce the use of motion design tokens for durations
|
|
9
|
+
and easing values. This rule was moved from `@atlaskit/eslint-plugin-platform`
|
|
10
|
+
(`compiled/use-motion-token-values`) to the design-system plugin as a first-class rule.
|
|
11
|
+
|
|
3
12
|
## 15.0.0
|
|
4
13
|
|
|
5
14
|
### Major Changes
|
package/README.md
CHANGED
|
@@ -123,6 +123,7 @@ module.exports = {
|
|
|
123
123
|
| <a href="./src/rules/use-spotlight-package/README.md">use-spotlight-package</a> | Discourage the use of @atlaskit/onboarding in favor of @atlaskit/spotlight. | | Yes | Yes |
|
|
124
124
|
| <a href="./src/rules/use-tag-group-label/README.md">use-tag-group-label</a> | Ensures tag groups are described to assistive technology by a direct label or by another element. | Yes | | Yes |
|
|
125
125
|
| <a href="./src/rules/use-textfield-autocomplete/README.md">use-textfield-autocomplete</a> | Enforce that Textfield components with type="email", "tel", or "url" have an appropriate autocomplete value for WCAG 2.2 SC 1.3.5 compliance (Identify Input Purpose). | Yes | Yes | |
|
|
126
|
+
| <a href="./src/rules/use-tokens-motion/README.md">use-tokens-motion</a> | Enforces usage of motion design tokens rather than hard-coded duration and easing values. | | | Yes |
|
|
126
127
|
| <a href="./src/rules/use-tokens-shape/README.md">use-tokens-shape</a> | Enforces usage of shape design tokens rather than hard-coded values. | | Yes | Yes |
|
|
127
128
|
| <a href="./src/rules/use-tokens-space/README.md">use-tokens-space</a> | Enforces usage of space design tokens rather than hard-coded values. | | Yes | Yes |
|
|
128
129
|
| <a href="./src/rules/use-tokens-typography/README.md">use-tokens-typography</a> | Enforces usage of design tokens for typography properties rather than hard-coded values. | Yes | Yes | Yes |
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
9
|
-
* @codegen <<SignedSource::
|
|
9
|
+
* @codegen <<SignedSource::eb0cc6324a0dfa4a3a11b16ab0a6cd4f>>
|
|
10
10
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -87,6 +87,7 @@ var rules = {
|
|
|
87
87
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
88
88
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
89
89
|
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
90
|
+
'@atlaskit/design-system/use-tokens-motion': 'warn',
|
|
90
91
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
91
92
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
92
93
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
@@ -6,7 +6,7 @@ Object.defineProperty(exports, "__esModule", {
|
|
|
6
6
|
exports.default = void 0;
|
|
7
7
|
/**
|
|
8
8
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
9
|
-
* @codegen <<SignedSource::
|
|
9
|
+
* @codegen <<SignedSource::80cbd12c57061c4fb2660da2e808210f>>
|
|
10
10
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
11
11
|
*/
|
|
12
12
|
|
|
@@ -86,6 +86,7 @@ var rules = {
|
|
|
86
86
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
87
87
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
88
88
|
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
89
|
+
'@atlaskit/design-system/use-tokens-motion': 'warn',
|
|
89
90
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
90
91
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
91
92
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|
|
@@ -79,13 +79,14 @@ var _useSimpleForm = _interopRequireDefault(require("./use-simple-form"));
|
|
|
79
79
|
var _useSpotlightPackage = _interopRequireDefault(require("./use-spotlight-package"));
|
|
80
80
|
var _useTagGroupLabel = _interopRequireDefault(require("./use-tag-group-label"));
|
|
81
81
|
var _useTextfieldAutocomplete = _interopRequireDefault(require("./use-textfield-autocomplete"));
|
|
82
|
+
var _useTokensMotion = _interopRequireDefault(require("./use-tokens-motion"));
|
|
82
83
|
var _useTokensShape = _interopRequireDefault(require("./use-tokens-shape"));
|
|
83
84
|
var _useTokensSpace = _interopRequireDefault(require("./use-tokens-space"));
|
|
84
85
|
var _useTokensTypography = _interopRequireDefault(require("./use-tokens-typography"));
|
|
85
86
|
var _useVisuallyHidden = _interopRequireDefault(require("./use-visually-hidden"));
|
|
86
87
|
/**
|
|
87
88
|
* THIS FILE WAS CREATED VIA CODEGEN DO NOT MODIFY {@see http://go/af-codegen}
|
|
88
|
-
* @codegen <<SignedSource::
|
|
89
|
+
* @codegen <<SignedSource::d404a0bd62a78d4bad7ee376df0bd475>>
|
|
89
90
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
90
91
|
*/
|
|
91
92
|
|
|
@@ -164,6 +165,7 @@ var rules = exports.rules = {
|
|
|
164
165
|
'use-spotlight-package': _useSpotlightPackage.default,
|
|
165
166
|
'use-tag-group-label': _useTagGroupLabel.default,
|
|
166
167
|
'use-textfield-autocomplete': _useTextfieldAutocomplete.default,
|
|
168
|
+
'use-tokens-motion': _useTokensMotion.default,
|
|
167
169
|
'use-tokens-shape': _useTokensShape.default,
|
|
168
170
|
'use-tokens-space': _useTokensSpace.default,
|
|
169
171
|
'use-tokens-typography': _useTokensTypography.default,
|
|
@@ -0,0 +1,555 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
var _interopRequireDefault = require("@babel/runtime/helpers/interopRequireDefault");
|
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
|
5
|
+
value: true
|
|
6
|
+
});
|
|
7
|
+
exports.default = void 0;
|
|
8
|
+
var _toConsumableArray2 = _interopRequireDefault(require("@babel/runtime/helpers/toConsumableArray"));
|
|
9
|
+
var _tokenDefaultValues = _interopRequireDefault(require("@atlaskit/tokens/token-default-values"));
|
|
10
|
+
var _createLintRule = require("../utils/create-lint-rule");
|
|
11
|
+
function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e && r && "number" == typeof r.length) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: !0 } : { done: !1, value: r[_n++] }; }, e: function e(r) { throw r; }, 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 o, a = !0, u = !1; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = !0, o = r; }, f: function f() { try { a || null == t.return || t.return(); } finally { if (u) throw o; } } }; }
|
|
12
|
+
function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
|
|
13
|
+
function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
|
|
14
|
+
var DURATION_TOKEN_NAMES = ['motion.duration.instant', 'motion.duration.xxshort', 'motion.duration.xshort', 'motion.duration.short', 'motion.duration.medium', 'motion.duration.long', 'motion.duration.xlong', 'motion.duration.xxlong'];
|
|
15
|
+
function parseDurationMs(value) {
|
|
16
|
+
var ms = value.match(/^(\d+(?:\.\d+)?)ms$/);
|
|
17
|
+
if (ms) {
|
|
18
|
+
return parseFloat(ms[1]);
|
|
19
|
+
}
|
|
20
|
+
var s = value.match(/^(\d+(?:\.\d+)?)s$/);
|
|
21
|
+
if (s) {
|
|
22
|
+
return parseFloat(s[1]) * 1000;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
var DURATION_TOKENS = DURATION_TOKEN_NAMES.map(function (name) {
|
|
27
|
+
var rawValue = _tokenDefaultValues.default[name];
|
|
28
|
+
var ms = parseDurationMs(rawValue);
|
|
29
|
+
if (ms === null) {
|
|
30
|
+
throw new Error("use-tokens-motion: could not parse duration for token ".concat(name, ": ").concat(rawValue));
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
ms: ms,
|
|
34
|
+
token: name
|
|
35
|
+
};
|
|
36
|
+
}).sort(function (a, b) {
|
|
37
|
+
return a.ms - b.ms;
|
|
38
|
+
});
|
|
39
|
+
var EASING_TOKEN_NAMES = ['motion.easing.in.practical', 'motion.easing.inout.bold', 'motion.easing.out.practical', 'motion.easing.out.bold'];
|
|
40
|
+
function parseCubicBezierParams(value) {
|
|
41
|
+
var match = value.match(/^cubic-bezier\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
|
|
42
|
+
if (!match) {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
return [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]), parseFloat(match[4])];
|
|
46
|
+
}
|
|
47
|
+
var EASING_TOKENS = EASING_TOKEN_NAMES.map(function (name) {
|
|
48
|
+
var rawValue = _tokenDefaultValues.default[name];
|
|
49
|
+
var params = parseCubicBezierParams(rawValue);
|
|
50
|
+
if (!params) {
|
|
51
|
+
throw new Error("use-tokens-motion: could not parse cubic-bezier for token ".concat(name, ": ").concat(rawValue));
|
|
52
|
+
}
|
|
53
|
+
return {
|
|
54
|
+
value: rawValue,
|
|
55
|
+
token: name,
|
|
56
|
+
params: params
|
|
57
|
+
};
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Splits on top-level commas (outside function parens) — preserves cubic-bezier(...) commas.
|
|
61
|
+
function splitOnTopLevelCommas(value) {
|
|
62
|
+
var parts = [];
|
|
63
|
+
var depth = 0;
|
|
64
|
+
var current = '';
|
|
65
|
+
var _iterator = _createForOfIteratorHelper(value),
|
|
66
|
+
_step;
|
|
67
|
+
try {
|
|
68
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
69
|
+
var ch = _step.value;
|
|
70
|
+
if (ch === '(') {
|
|
71
|
+
depth++;
|
|
72
|
+
current += ch;
|
|
73
|
+
} else if (ch === ')') {
|
|
74
|
+
depth--;
|
|
75
|
+
current += ch;
|
|
76
|
+
} else if (ch === ',' && depth === 0) {
|
|
77
|
+
parts.push(current.trim());
|
|
78
|
+
current = '';
|
|
79
|
+
} else {
|
|
80
|
+
current += ch;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
} catch (err) {
|
|
84
|
+
_iterator.e(err);
|
|
85
|
+
} finally {
|
|
86
|
+
_iterator.f();
|
|
87
|
+
}
|
|
88
|
+
if (current.trim().length > 0) {
|
|
89
|
+
parts.push(current.trim());
|
|
90
|
+
}
|
|
91
|
+
return parts;
|
|
92
|
+
}
|
|
93
|
+
var DURATION_PROPERTIES = new Set(['transitionDuration', 'animationDuration']);
|
|
94
|
+
var EASING_PROPERTIES = new Set(['transitionTimingFunction', 'animationTimingFunction']);
|
|
95
|
+
|
|
96
|
+
// Explicit semantic mappings for CSS keyword easings to motion tokens.
|
|
97
|
+
// Pinned by design intent, confirmed with design system team (Alex + Akshay).
|
|
98
|
+
var CSS_KEYWORD_EASING_TOKEN_MAP = {
|
|
99
|
+
ease: 'motion.easing.out.practical',
|
|
100
|
+
'ease-out': 'motion.easing.out.practical',
|
|
101
|
+
'ease-in': 'motion.easing.in.practical',
|
|
102
|
+
'ease-in-out': 'motion.easing.inout.bold'
|
|
103
|
+
// linear (0,0,1,1) — warn only, no autofix (per Akshay: too generic, no good token match)
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
// Non-curve easing values with no meaningful cubic-bezier representation — skip entirely
|
|
107
|
+
var SKIP_EASING_VALUES = new Set(['step-start', 'step-end', 'inherit', 'initial', 'unset', 'none']);
|
|
108
|
+
function euclideanDistance(a, b) {
|
|
109
|
+
return Math.sqrt(a.reduce(function (sum, val, i) {
|
|
110
|
+
return sum + Math.pow(val - b[i], 2);
|
|
111
|
+
}, 0));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Maximum Euclidean distance for easing autofix — beyond this threshold, we report-only
|
|
115
|
+
var EASING_AUTOFIX_THRESHOLD = 0.5;
|
|
116
|
+
function findClosestEasingToken(params) {
|
|
117
|
+
var minDist = Infinity;
|
|
118
|
+
var closest = EASING_TOKENS[0];
|
|
119
|
+
var _iterator2 = _createForOfIteratorHelper(EASING_TOKENS),
|
|
120
|
+
_step2;
|
|
121
|
+
try {
|
|
122
|
+
for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
|
|
123
|
+
var entry = _step2.value;
|
|
124
|
+
var dist = euclideanDistance(params, entry.params);
|
|
125
|
+
if (dist < minDist) {
|
|
126
|
+
minDist = dist;
|
|
127
|
+
closest = entry;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
} catch (err) {
|
|
131
|
+
_iterator2.e(err);
|
|
132
|
+
} finally {
|
|
133
|
+
_iterator2.f();
|
|
134
|
+
}
|
|
135
|
+
if (minDist > EASING_AUTOFIX_THRESHOLD) {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
return {
|
|
139
|
+
token: closest.token,
|
|
140
|
+
value: closest.value,
|
|
141
|
+
dist: minDist
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
function findClosestDurationTokens(ms) {
|
|
145
|
+
var exact = DURATION_TOKENS.find(function (t) {
|
|
146
|
+
return t.ms === ms;
|
|
147
|
+
});
|
|
148
|
+
if (exact) {
|
|
149
|
+
return [exact];
|
|
150
|
+
}
|
|
151
|
+
var minDist = Infinity;
|
|
152
|
+
var _iterator3 = _createForOfIteratorHelper(DURATION_TOKENS),
|
|
153
|
+
_step3;
|
|
154
|
+
try {
|
|
155
|
+
for (_iterator3.s(); !(_step3 = _iterator3.n()).done;) {
|
|
156
|
+
var entry = _step3.value;
|
|
157
|
+
var dist = Math.abs(entry.ms - ms);
|
|
158
|
+
if (dist < minDist) {
|
|
159
|
+
minDist = dist;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
_iterator3.e(err);
|
|
164
|
+
} finally {
|
|
165
|
+
_iterator3.f();
|
|
166
|
+
}
|
|
167
|
+
var closest = DURATION_TOKENS.filter(function (t) {
|
|
168
|
+
return Math.abs(t.ms - ms) === minDist;
|
|
169
|
+
});
|
|
170
|
+
return closest;
|
|
171
|
+
}
|
|
172
|
+
var useTokensMotion = (0, _createLintRule.createLintRule)({
|
|
173
|
+
meta: {
|
|
174
|
+
name: 'use-tokens-motion',
|
|
175
|
+
type: 'suggestion',
|
|
176
|
+
hasSuggestions: true,
|
|
177
|
+
docs: {
|
|
178
|
+
description: 'Enforces usage of motion design tokens rather than hard-coded duration and easing values.',
|
|
179
|
+
recommended: false,
|
|
180
|
+
severity: 'warn'
|
|
181
|
+
},
|
|
182
|
+
messages: {
|
|
183
|
+
useMotionDurationToken: "Use a motion duration token instead of the hard-coded value '{{ value }}'.",
|
|
184
|
+
useMotionDurationTokenSuggest: 'Replace with {{ suggestion }}.',
|
|
185
|
+
useMotionDurationTokenNearest: "No exact token match for '{{ value }}'. Nearest: {{ suggestion1 }} or {{ suggestion2 }}.",
|
|
186
|
+
useMotionDurationTokenSingleNearest: "No exact token match for '{{ value }}'. Nearest: {{ suggestion }}.",
|
|
187
|
+
useMotionEasingToken: "Use a motion easing token instead of the hard-coded value '{{ value }}'.",
|
|
188
|
+
useMotionEasingTokenSuggest: 'Replace with {{ suggestion }}.',
|
|
189
|
+
useMotionEasingTokenUnknown: "Use a motion easing token from @atlaskit/tokens instead of the hard-coded value '{{ value }}'."
|
|
190
|
+
}
|
|
191
|
+
},
|
|
192
|
+
create: function create(context) {
|
|
193
|
+
var tokensImportNode = null;
|
|
194
|
+
var hasTokenSpecifier = false;
|
|
195
|
+
function buildTokenCall(tokenName, fallback) {
|
|
196
|
+
return "token('".concat(tokenName, "', '").concat(fallback, "')");
|
|
197
|
+
}
|
|
198
|
+
function getImportFix(fixer) {
|
|
199
|
+
var _context$sourceCode;
|
|
200
|
+
if (hasTokenSpecifier) {
|
|
201
|
+
return [];
|
|
202
|
+
}
|
|
203
|
+
if (tokensImportNode) {
|
|
204
|
+
// @atlaskit/tokens is imported but without `token` — add `token` to existing import
|
|
205
|
+
var lastSpecifier = tokensImportNode.specifiers[tokensImportNode.specifiers.length - 1];
|
|
206
|
+
if (lastSpecifier) {
|
|
207
|
+
return [fixer.insertTextAfter(lastSpecifier, ', token')];
|
|
208
|
+
}
|
|
209
|
+
// Empty import — replace the whole declaration
|
|
210
|
+
return [fixer.replaceText(tokensImportNode, "import { token } from '@atlaskit/tokens';")];
|
|
211
|
+
}
|
|
212
|
+
var sourceCode = (_context$sourceCode = context.sourceCode) !== null && _context$sourceCode !== void 0 ? _context$sourceCode : context.getSourceCode();
|
|
213
|
+
var programBody = sourceCode.ast.body;
|
|
214
|
+
// Insert after the last existing import, or at top if no imports exist
|
|
215
|
+
var lastImport = (0, _toConsumableArray2.default)(programBody).reverse().find(function (n) {
|
|
216
|
+
return n.type === 'ImportDeclaration';
|
|
217
|
+
});
|
|
218
|
+
if (lastImport) {
|
|
219
|
+
return [fixer.insertTextAfter(lastImport, "\nimport { token } from '@atlaskit/tokens';")];
|
|
220
|
+
}
|
|
221
|
+
if (programBody.length > 0) {
|
|
222
|
+
return [fixer.insertTextBefore(programBody[0], "import { token } from '@atlaskit/tokens';\n")];
|
|
223
|
+
}
|
|
224
|
+
return [];
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Returns autofix string for a single duration value, or null if ambiguous (equidistant)
|
|
228
|
+
function resolveDurationToken(value) {
|
|
229
|
+
var ms = parseDurationMs(value);
|
|
230
|
+
if (ms === null) {
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
var exact = DURATION_TOKENS.find(function (t) {
|
|
234
|
+
return t.ms === ms;
|
|
235
|
+
});
|
|
236
|
+
if (exact) {
|
|
237
|
+
return buildTokenCall(exact.token, value);
|
|
238
|
+
}
|
|
239
|
+
return null;
|
|
240
|
+
}
|
|
241
|
+
function handleDurationProperty(node, rawValue) {
|
|
242
|
+
var segments = splitOnTopLevelCommas(rawValue);
|
|
243
|
+
if (segments.length === 1) {
|
|
244
|
+
var ms = parseDurationMs(rawValue);
|
|
245
|
+
if (ms === null) {
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
var exactMatch = DURATION_TOKENS.find(function (t) {
|
|
249
|
+
return t.ms === ms;
|
|
250
|
+
});
|
|
251
|
+
if (exactMatch) {
|
|
252
|
+
var suggestion = buildTokenCall(exactMatch.token, rawValue);
|
|
253
|
+
context.report({
|
|
254
|
+
node: node,
|
|
255
|
+
messageId: 'useMotionDurationToken',
|
|
256
|
+
data: {
|
|
257
|
+
value: rawValue
|
|
258
|
+
},
|
|
259
|
+
suggest: [{
|
|
260
|
+
messageId: 'useMotionDurationTokenSuggest',
|
|
261
|
+
data: {
|
|
262
|
+
suggestion: suggestion
|
|
263
|
+
},
|
|
264
|
+
fix: function fix(fixer) {
|
|
265
|
+
return [].concat((0, _toConsumableArray2.default)(getImportFix(fixer)), [fixer.replaceText(node.value, suggestion)]);
|
|
266
|
+
}
|
|
267
|
+
}]
|
|
268
|
+
});
|
|
269
|
+
} else {
|
|
270
|
+
var result = findClosestDurationTokens(ms);
|
|
271
|
+
if (result.length >= 2) {
|
|
272
|
+
var suggestion1 = buildTokenCall(result[0].token, rawValue);
|
|
273
|
+
var suggestion2 = buildTokenCall(result[1].token, rawValue);
|
|
274
|
+
context.report({
|
|
275
|
+
node: node,
|
|
276
|
+
messageId: 'useMotionDurationTokenNearest',
|
|
277
|
+
data: {
|
|
278
|
+
value: rawValue,
|
|
279
|
+
suggestion1: "".concat(suggestion1, " (").concat(result[0].ms, "ms)"),
|
|
280
|
+
suggestion2: "".concat(suggestion2, " (").concat(result[1].ms, "ms)")
|
|
281
|
+
}
|
|
282
|
+
});
|
|
283
|
+
} else {
|
|
284
|
+
var _suggestion = buildTokenCall(result[0].token, rawValue);
|
|
285
|
+
context.report({
|
|
286
|
+
node: node,
|
|
287
|
+
messageId: 'useMotionDurationTokenSingleNearest',
|
|
288
|
+
data: {
|
|
289
|
+
value: rawValue,
|
|
290
|
+
suggestion: "".concat(_suggestion, " (").concat(result[0].ms, "ms)")
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
297
|
+
var resolved = segments.map(resolveDurationToken);
|
|
298
|
+
if (resolved.some(function (s) {
|
|
299
|
+
return s === null;
|
|
300
|
+
})) {
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
var templateLiteral = '`' + resolved.map(function (s) {
|
|
304
|
+
return "${".concat(s, "}");
|
|
305
|
+
}).join(', ') + '`';
|
|
306
|
+
context.report({
|
|
307
|
+
node: node,
|
|
308
|
+
messageId: 'useMotionDurationToken',
|
|
309
|
+
data: {
|
|
310
|
+
value: rawValue
|
|
311
|
+
},
|
|
312
|
+
suggest: [{
|
|
313
|
+
messageId: 'useMotionDurationTokenSuggest',
|
|
314
|
+
data: {
|
|
315
|
+
suggestion: templateLiteral
|
|
316
|
+
},
|
|
317
|
+
fix: function fix(fixer) {
|
|
318
|
+
return [].concat((0, _toConsumableArray2.default)(getImportFix(fixer)), [fixer.replaceText(node.value, templateLiteral)]);
|
|
319
|
+
}
|
|
320
|
+
}]
|
|
321
|
+
});
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Returns autofix string for a single easing value, or null if no token suggestion is possible
|
|
325
|
+
function resolveEasingToken(value) {
|
|
326
|
+
var trimmed = value.trim();
|
|
327
|
+
if (SKIP_EASING_VALUES.has(trimmed)) {
|
|
328
|
+
return null;
|
|
329
|
+
}
|
|
330
|
+
if (trimmed in CSS_KEYWORD_EASING_TOKEN_MAP) {
|
|
331
|
+
return buildTokenCall(CSS_KEYWORD_EASING_TOKEN_MAP[trimmed], trimmed);
|
|
332
|
+
}
|
|
333
|
+
// linear has no curve (0,0,1,1) — warn only, no autofix
|
|
334
|
+
if (trimmed === 'linear') {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
if (trimmed.startsWith('linear(')) {
|
|
338
|
+
// linear() is used for spring animations — motion.easing.spring is experimental, skip
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
var params = parseCubicBezierParams(trimmed);
|
|
342
|
+
if (!params) {
|
|
343
|
+
return null;
|
|
344
|
+
}
|
|
345
|
+
var exact = EASING_TOKENS.find(function (t) {
|
|
346
|
+
return t.value === trimmed;
|
|
347
|
+
});
|
|
348
|
+
if (exact) {
|
|
349
|
+
return buildTokenCall(exact.token, trimmed);
|
|
350
|
+
}
|
|
351
|
+
var closest = findClosestEasingToken(params);
|
|
352
|
+
return closest ? buildTokenCall(closest.token, trimmed) : null;
|
|
353
|
+
}
|
|
354
|
+
function handleEasingProperty(node, rawValue) {
|
|
355
|
+
var segments = splitOnTopLevelCommas(rawValue);
|
|
356
|
+
|
|
357
|
+
// Multi-value path: resolve each segment, autofix only if all resolve cleanly
|
|
358
|
+
if (segments.length > 1) {
|
|
359
|
+
var resolved = segments.map(resolveEasingToken);
|
|
360
|
+
if (resolved.some(function (s) {
|
|
361
|
+
return s === null;
|
|
362
|
+
})) {
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
var templateLiteral = '`' + resolved.map(function (s) {
|
|
366
|
+
return "${".concat(s, "}");
|
|
367
|
+
}).join(', ') + '`';
|
|
368
|
+
context.report({
|
|
369
|
+
node: node,
|
|
370
|
+
messageId: 'useMotionEasingToken',
|
|
371
|
+
data: {
|
|
372
|
+
value: rawValue
|
|
373
|
+
},
|
|
374
|
+
suggest: [{
|
|
375
|
+
messageId: 'useMotionEasingTokenSuggest',
|
|
376
|
+
data: {
|
|
377
|
+
suggestion: templateLiteral
|
|
378
|
+
},
|
|
379
|
+
fix: function fix(fixer) {
|
|
380
|
+
return [].concat((0, _toConsumableArray2.default)(getImportFix(fixer)), [fixer.replaceText(node.value, templateLiteral)]);
|
|
381
|
+
}
|
|
382
|
+
}]
|
|
383
|
+
});
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
var trimmed = rawValue.trim();
|
|
387
|
+
if (SKIP_EASING_VALUES.has(trimmed)) {
|
|
388
|
+
return;
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// CSS keyword easings: convert to cubic-bezier equivalent and find closest token
|
|
392
|
+
if (trimmed in CSS_KEYWORD_EASING_TOKEN_MAP) {
|
|
393
|
+
var suggestion = buildTokenCall(CSS_KEYWORD_EASING_TOKEN_MAP[trimmed], trimmed);
|
|
394
|
+
context.report({
|
|
395
|
+
node: node,
|
|
396
|
+
messageId: 'useMotionEasingToken',
|
|
397
|
+
data: {
|
|
398
|
+
value: trimmed
|
|
399
|
+
},
|
|
400
|
+
suggest: [{
|
|
401
|
+
messageId: 'useMotionEasingTokenSuggest',
|
|
402
|
+
data: {
|
|
403
|
+
suggestion: suggestion
|
|
404
|
+
},
|
|
405
|
+
fix: function fix(fixer) {
|
|
406
|
+
return [].concat((0, _toConsumableArray2.default)(getImportFix(fixer)), [fixer.replaceText(node.value, suggestion)]);
|
|
407
|
+
}
|
|
408
|
+
}]
|
|
409
|
+
});
|
|
410
|
+
return;
|
|
411
|
+
}
|
|
412
|
+
// linear has no curve (0,0,1,1) — warn only, no autofix
|
|
413
|
+
if (trimmed === 'linear') {
|
|
414
|
+
context.report({
|
|
415
|
+
node: node,
|
|
416
|
+
messageId: 'useMotionEasingTokenUnknown',
|
|
417
|
+
data: {
|
|
418
|
+
value: trimmed
|
|
419
|
+
}
|
|
420
|
+
});
|
|
421
|
+
return;
|
|
422
|
+
}
|
|
423
|
+
if (trimmed.startsWith('linear(')) {
|
|
424
|
+
// linear() is used for spring animations — motion.easing.spring is experimental, skip
|
|
425
|
+
return;
|
|
426
|
+
}
|
|
427
|
+
var params = parseCubicBezierParams(trimmed);
|
|
428
|
+
if (!params) {
|
|
429
|
+
context.report({
|
|
430
|
+
node: node,
|
|
431
|
+
messageId: 'useMotionEasingTokenUnknown',
|
|
432
|
+
data: {
|
|
433
|
+
value: rawValue
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
438
|
+
var exact = EASING_TOKENS.find(function (t) {
|
|
439
|
+
return t.value === trimmed;
|
|
440
|
+
});
|
|
441
|
+
if (exact) {
|
|
442
|
+
var _suggestion2 = buildTokenCall(exact.token, rawValue);
|
|
443
|
+
context.report({
|
|
444
|
+
node: node,
|
|
445
|
+
messageId: 'useMotionEasingToken',
|
|
446
|
+
data: {
|
|
447
|
+
value: rawValue
|
|
448
|
+
},
|
|
449
|
+
suggest: [{
|
|
450
|
+
messageId: 'useMotionEasingTokenSuggest',
|
|
451
|
+
data: {
|
|
452
|
+
suggestion: _suggestion2
|
|
453
|
+
},
|
|
454
|
+
fix: function fix(fixer) {
|
|
455
|
+
return [].concat((0, _toConsumableArray2.default)(getImportFix(fixer)), [fixer.replaceText(node.value, _suggestion2)]);
|
|
456
|
+
}
|
|
457
|
+
}]
|
|
458
|
+
});
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
var closest = findClosestEasingToken(params);
|
|
462
|
+
if (closest) {
|
|
463
|
+
var _suggestion3 = buildTokenCall(closest.token, rawValue);
|
|
464
|
+
context.report({
|
|
465
|
+
node: node,
|
|
466
|
+
messageId: 'useMotionEasingToken',
|
|
467
|
+
data: {
|
|
468
|
+
value: rawValue
|
|
469
|
+
},
|
|
470
|
+
suggest: [{
|
|
471
|
+
messageId: 'useMotionEasingTokenSuggest',
|
|
472
|
+
data: {
|
|
473
|
+
suggestion: _suggestion3
|
|
474
|
+
},
|
|
475
|
+
fix: function fix(fixer) {
|
|
476
|
+
return [].concat((0, _toConsumableArray2.default)(getImportFix(fixer)), [fixer.replaceText(node.value, _suggestion3)]);
|
|
477
|
+
}
|
|
478
|
+
}]
|
|
479
|
+
});
|
|
480
|
+
} else {
|
|
481
|
+
context.report({
|
|
482
|
+
node: node,
|
|
483
|
+
messageId: 'useMotionEasingTokenUnknown',
|
|
484
|
+
data: {
|
|
485
|
+
value: rawValue
|
|
486
|
+
}
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
function handleProperty(node) {
|
|
491
|
+
var key = node.key;
|
|
492
|
+
if (key.type !== 'Identifier') {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
var isDuration = DURATION_PROPERTIES.has(key.name);
|
|
496
|
+
var isEasing = EASING_PROPERTIES.has(key.name);
|
|
497
|
+
if (!isDuration && !isEasing) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
var value = node.value;
|
|
501
|
+
if (value.type === 'TemplateLiteral') {
|
|
502
|
+
// Only handle no-interpolation template literals (e.g. `200ms`) — treat as string
|
|
503
|
+
var tl = value;
|
|
504
|
+
if (tl.expressions.length === 0 && tl.quasis.length === 1) {
|
|
505
|
+
var _tl$quasis$0$value$co;
|
|
506
|
+
var rawValue = (_tl$quasis$0$value$co = tl.quasis[0].value.cooked) !== null && _tl$quasis$0$value$co !== void 0 ? _tl$quasis$0$value$co : tl.quasis[0].value.raw;
|
|
507
|
+
if (isDuration) {
|
|
508
|
+
handleDurationProperty(node, rawValue);
|
|
509
|
+
} else {
|
|
510
|
+
handleEasingProperty(node, rawValue);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (value.type === 'CallExpression') {
|
|
516
|
+
var ce = value;
|
|
517
|
+
if (ce.callee.type === 'Identifier' && ce.callee.name === 'token') {
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
if (value.type === 'Literal') {
|
|
523
|
+
var lit = value;
|
|
524
|
+
var _rawValue;
|
|
525
|
+
if (typeof lit.value === 'string') {
|
|
526
|
+
_rawValue = lit.value;
|
|
527
|
+
} else if (typeof lit.value === 'number') {
|
|
528
|
+
// Treat bare numbers as ms
|
|
529
|
+
_rawValue = "".concat(lit.value, "ms");
|
|
530
|
+
} else {
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (isDuration) {
|
|
534
|
+
handleDurationProperty(node, _rawValue);
|
|
535
|
+
} else {
|
|
536
|
+
handleEasingProperty(node, _rawValue);
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
}
|
|
540
|
+
return {
|
|
541
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
542
|
+
if (node.source.value === '@atlaskit/tokens') {
|
|
543
|
+
tokensImportNode = node;
|
|
544
|
+
hasTokenSpecifier = node.specifiers.some(function (s) {
|
|
545
|
+
return s.type === 'ImportSpecifier' && s.local.name === 'token';
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
},
|
|
549
|
+
Property: function Property(node) {
|
|
550
|
+
handleProperty(node);
|
|
551
|
+
}
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
var _default = exports.default = useTokensMotion;
|
|
@@ -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::eb0cc6324a0dfa4a3a11b16ab0a6cd4f>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -81,6 +81,7 @@ const rules = {
|
|
|
81
81
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
82
82
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
83
83
|
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
84
|
+
'@atlaskit/design-system/use-tokens-motion': 'warn',
|
|
84
85
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
85
86
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
86
87
|
'@atlaskit/design-system/use-tokens-typography': '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::80cbd12c57061c4fb2660da2e808210f>>
|
|
4
4
|
* @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
|
|
5
5
|
*/
|
|
6
6
|
|
|
@@ -80,6 +80,7 @@ const rules = {
|
|
|
80
80
|
'@atlaskit/design-system/use-spotlight-package': 'warn',
|
|
81
81
|
'@atlaskit/design-system/use-tag-group-label': 'warn',
|
|
82
82
|
'@atlaskit/design-system/use-textfield-autocomplete': 'warn',
|
|
83
|
+
'@atlaskit/design-system/use-tokens-motion': 'warn',
|
|
83
84
|
'@atlaskit/design-system/use-tokens-shape': 'error',
|
|
84
85
|
'@atlaskit/design-system/use-tokens-space': 'error',
|
|
85
86
|
'@atlaskit/design-system/use-tokens-typography': 'warn',
|