@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 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::4cd2dfd73ed3cdd152ee6481087c49ee>>
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::8efae2c2e169f84381b978b4bc47433d>>
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::b90bcf0b2fb6429faae59c7a454e3260>>
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::4cd2dfd73ed3cdd152ee6481087c49ee>>
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::8efae2c2e169f84381b978b4bc47433d>>
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',