@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.
@@ -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::b90bcf0b2fb6429faae59c7a454e3260>>
3
+ * @codegen <<SignedSource::d404a0bd62a78d4bad7ee376df0bd475>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -78,6 +78,7 @@ import useSimpleForm from './use-simple-form';
78
78
  import useSpotlightPackage from './use-spotlight-package';
79
79
  import useTagGroupLabel from './use-tag-group-label';
80
80
  import useTextfieldAutocomplete from './use-textfield-autocomplete';
81
+ import useTokensMotion from './use-tokens-motion';
81
82
  import useTokensShape from './use-tokens-shape';
82
83
  import useTokensSpace from './use-tokens-space';
83
84
  import useTokensTypography from './use-tokens-typography';
@@ -157,6 +158,7 @@ export const rules = {
157
158
  'use-spotlight-package': useSpotlightPackage,
158
159
  'use-tag-group-label': useTagGroupLabel,
159
160
  'use-textfield-autocomplete': useTextfieldAutocomplete,
161
+ 'use-tokens-motion': useTokensMotion,
160
162
  'use-tokens-shape': useTokensShape,
161
163
  'use-tokens-space': useTokensSpace,
162
164
  'use-tokens-typography': useTokensTypography,
@@ -0,0 +1,489 @@
1
+ import tokenDefaultValues from '@atlaskit/tokens/token-default-values';
2
+ import { createLintRule } from '../utils/create-lint-rule';
3
+ const 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'];
4
+ function parseDurationMs(value) {
5
+ const ms = value.match(/^(\d+(?:\.\d+)?)ms$/);
6
+ if (ms) {
7
+ return parseFloat(ms[1]);
8
+ }
9
+ const s = value.match(/^(\d+(?:\.\d+)?)s$/);
10
+ if (s) {
11
+ return parseFloat(s[1]) * 1000;
12
+ }
13
+ return null;
14
+ }
15
+ const DURATION_TOKENS = DURATION_TOKEN_NAMES.map(name => {
16
+ const rawValue = tokenDefaultValues[name];
17
+ const ms = parseDurationMs(rawValue);
18
+ if (ms === null) {
19
+ throw new Error(`use-tokens-motion: could not parse duration for token ${name}: ${rawValue}`);
20
+ }
21
+ return {
22
+ ms,
23
+ token: name
24
+ };
25
+ }).sort((a, b) => a.ms - b.ms);
26
+ const EASING_TOKEN_NAMES = ['motion.easing.in.practical', 'motion.easing.inout.bold', 'motion.easing.out.practical', 'motion.easing.out.bold'];
27
+ function parseCubicBezierParams(value) {
28
+ const match = value.match(/^cubic-bezier\(\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*,\s*([\d.]+)\s*\)$/);
29
+ if (!match) {
30
+ return null;
31
+ }
32
+ return [parseFloat(match[1]), parseFloat(match[2]), parseFloat(match[3]), parseFloat(match[4])];
33
+ }
34
+ const EASING_TOKENS = EASING_TOKEN_NAMES.map(name => {
35
+ const rawValue = tokenDefaultValues[name];
36
+ const params = parseCubicBezierParams(rawValue);
37
+ if (!params) {
38
+ throw new Error(`use-tokens-motion: could not parse cubic-bezier for token ${name}: ${rawValue}`);
39
+ }
40
+ return {
41
+ value: rawValue,
42
+ token: name,
43
+ params
44
+ };
45
+ });
46
+
47
+ // Splits on top-level commas (outside function parens) — preserves cubic-bezier(...) commas.
48
+ function splitOnTopLevelCommas(value) {
49
+ const parts = [];
50
+ let depth = 0;
51
+ let current = '';
52
+ for (const ch of value) {
53
+ if (ch === '(') {
54
+ depth++;
55
+ current += ch;
56
+ } else if (ch === ')') {
57
+ depth--;
58
+ current += ch;
59
+ } else if (ch === ',' && depth === 0) {
60
+ parts.push(current.trim());
61
+ current = '';
62
+ } else {
63
+ current += ch;
64
+ }
65
+ }
66
+ if (current.trim().length > 0) {
67
+ parts.push(current.trim());
68
+ }
69
+ return parts;
70
+ }
71
+ const DURATION_PROPERTIES = new Set(['transitionDuration', 'animationDuration']);
72
+ const EASING_PROPERTIES = new Set(['transitionTimingFunction', 'animationTimingFunction']);
73
+
74
+ // Explicit semantic mappings for CSS keyword easings to motion tokens.
75
+ // Pinned by design intent, confirmed with design system team (Alex + Akshay).
76
+ const CSS_KEYWORD_EASING_TOKEN_MAP = {
77
+ ease: 'motion.easing.out.practical',
78
+ 'ease-out': 'motion.easing.out.practical',
79
+ 'ease-in': 'motion.easing.in.practical',
80
+ 'ease-in-out': 'motion.easing.inout.bold'
81
+ // linear (0,0,1,1) — warn only, no autofix (per Akshay: too generic, no good token match)
82
+ };
83
+
84
+ // Non-curve easing values with no meaningful cubic-bezier representation — skip entirely
85
+ const SKIP_EASING_VALUES = new Set(['step-start', 'step-end', 'inherit', 'initial', 'unset', 'none']);
86
+ function euclideanDistance(a, b) {
87
+ return Math.sqrt(a.reduce((sum, val, i) => sum + Math.pow(val - b[i], 2), 0));
88
+ }
89
+
90
+ // Maximum Euclidean distance for easing autofix — beyond this threshold, we report-only
91
+ const EASING_AUTOFIX_THRESHOLD = 0.5;
92
+ function findClosestEasingToken(params) {
93
+ let minDist = Infinity;
94
+ let closest = EASING_TOKENS[0];
95
+ for (const entry of EASING_TOKENS) {
96
+ const dist = euclideanDistance(params, entry.params);
97
+ if (dist < minDist) {
98
+ minDist = dist;
99
+ closest = entry;
100
+ }
101
+ }
102
+ if (minDist > EASING_AUTOFIX_THRESHOLD) {
103
+ return null;
104
+ }
105
+ return {
106
+ token: closest.token,
107
+ value: closest.value,
108
+ dist: minDist
109
+ };
110
+ }
111
+ function findClosestDurationTokens(ms) {
112
+ const exact = DURATION_TOKENS.find(t => t.ms === ms);
113
+ if (exact) {
114
+ return [exact];
115
+ }
116
+ let minDist = Infinity;
117
+ for (const entry of DURATION_TOKENS) {
118
+ const dist = Math.abs(entry.ms - ms);
119
+ if (dist < minDist) {
120
+ minDist = dist;
121
+ }
122
+ }
123
+ const closest = DURATION_TOKENS.filter(t => Math.abs(t.ms - ms) === minDist);
124
+ return closest;
125
+ }
126
+ const useTokensMotion = createLintRule({
127
+ meta: {
128
+ name: 'use-tokens-motion',
129
+ type: 'suggestion',
130
+ hasSuggestions: true,
131
+ docs: {
132
+ description: 'Enforces usage of motion design tokens rather than hard-coded duration and easing values.',
133
+ recommended: false,
134
+ severity: 'warn'
135
+ },
136
+ messages: {
137
+ useMotionDurationToken: "Use a motion duration token instead of the hard-coded value '{{ value }}'.",
138
+ useMotionDurationTokenSuggest: 'Replace with {{ suggestion }}.',
139
+ useMotionDurationTokenNearest: "No exact token match for '{{ value }}'. Nearest: {{ suggestion1 }} or {{ suggestion2 }}.",
140
+ useMotionDurationTokenSingleNearest: "No exact token match for '{{ value }}'. Nearest: {{ suggestion }}.",
141
+ useMotionEasingToken: "Use a motion easing token instead of the hard-coded value '{{ value }}'.",
142
+ useMotionEasingTokenSuggest: 'Replace with {{ suggestion }}.',
143
+ useMotionEasingTokenUnknown: "Use a motion easing token from @atlaskit/tokens instead of the hard-coded value '{{ value }}'."
144
+ }
145
+ },
146
+ create(context) {
147
+ let tokensImportNode = null;
148
+ let hasTokenSpecifier = false;
149
+ function buildTokenCall(tokenName, fallback) {
150
+ return `token('${tokenName}', '${fallback}')`;
151
+ }
152
+ function getImportFix(fixer) {
153
+ var _context$sourceCode;
154
+ if (hasTokenSpecifier) {
155
+ return [];
156
+ }
157
+ if (tokensImportNode) {
158
+ // @atlaskit/tokens is imported but without `token` — add `token` to existing import
159
+ const lastSpecifier = tokensImportNode.specifiers[tokensImportNode.specifiers.length - 1];
160
+ if (lastSpecifier) {
161
+ return [fixer.insertTextAfter(lastSpecifier, ', token')];
162
+ }
163
+ // Empty import — replace the whole declaration
164
+ return [fixer.replaceText(tokensImportNode, `import { token } from '@atlaskit/tokens';`)];
165
+ }
166
+ const sourceCode = (_context$sourceCode = context.sourceCode) !== null && _context$sourceCode !== void 0 ? _context$sourceCode : context.getSourceCode();
167
+ const programBody = sourceCode.ast.body;
168
+ // Insert after the last existing import, or at top if no imports exist
169
+ const lastImport = [...programBody].reverse().find(n => n.type === 'ImportDeclaration');
170
+ if (lastImport) {
171
+ return [fixer.insertTextAfter(lastImport, `\nimport { token } from '@atlaskit/tokens';`)];
172
+ }
173
+ if (programBody.length > 0) {
174
+ return [fixer.insertTextBefore(programBody[0], `import { token } from '@atlaskit/tokens';\n`)];
175
+ }
176
+ return [];
177
+ }
178
+
179
+ // Returns autofix string for a single duration value, or null if ambiguous (equidistant)
180
+ function resolveDurationToken(value) {
181
+ const ms = parseDurationMs(value);
182
+ if (ms === null) {
183
+ return null;
184
+ }
185
+ const exact = DURATION_TOKENS.find(t => t.ms === ms);
186
+ if (exact) {
187
+ return buildTokenCall(exact.token, value);
188
+ }
189
+ return null;
190
+ }
191
+ function handleDurationProperty(node, rawValue) {
192
+ const segments = splitOnTopLevelCommas(rawValue);
193
+ if (segments.length === 1) {
194
+ const ms = parseDurationMs(rawValue);
195
+ if (ms === null) {
196
+ return;
197
+ }
198
+ const exactMatch = DURATION_TOKENS.find(t => t.ms === ms);
199
+ if (exactMatch) {
200
+ const suggestion = buildTokenCall(exactMatch.token, rawValue);
201
+ context.report({
202
+ node,
203
+ messageId: 'useMotionDurationToken',
204
+ data: {
205
+ value: rawValue
206
+ },
207
+ suggest: [{
208
+ messageId: 'useMotionDurationTokenSuggest',
209
+ data: {
210
+ suggestion
211
+ },
212
+ fix(fixer) {
213
+ return [...getImportFix(fixer), fixer.replaceText(node.value, suggestion)];
214
+ }
215
+ }]
216
+ });
217
+ } else {
218
+ const result = findClosestDurationTokens(ms);
219
+ if (result.length >= 2) {
220
+ const suggestion1 = buildTokenCall(result[0].token, rawValue);
221
+ const suggestion2 = buildTokenCall(result[1].token, rawValue);
222
+ context.report({
223
+ node,
224
+ messageId: 'useMotionDurationTokenNearest',
225
+ data: {
226
+ value: rawValue,
227
+ suggestion1: `${suggestion1} (${result[0].ms}ms)`,
228
+ suggestion2: `${suggestion2} (${result[1].ms}ms)`
229
+ }
230
+ });
231
+ } else {
232
+ const suggestion = buildTokenCall(result[0].token, rawValue);
233
+ context.report({
234
+ node,
235
+ messageId: 'useMotionDurationTokenSingleNearest',
236
+ data: {
237
+ value: rawValue,
238
+ suggestion: `${suggestion} (${result[0].ms}ms)`
239
+ }
240
+ });
241
+ }
242
+ }
243
+ return;
244
+ }
245
+ const resolved = segments.map(resolveDurationToken);
246
+ if (resolved.some(s => s === null)) {
247
+ return;
248
+ }
249
+ const templateLiteral = '`' + resolved.map(s => `\${${s}}`).join(', ') + '`';
250
+ context.report({
251
+ node,
252
+ messageId: 'useMotionDurationToken',
253
+ data: {
254
+ value: rawValue
255
+ },
256
+ suggest: [{
257
+ messageId: 'useMotionDurationTokenSuggest',
258
+ data: {
259
+ suggestion: templateLiteral
260
+ },
261
+ fix(fixer) {
262
+ return [...getImportFix(fixer), fixer.replaceText(node.value, templateLiteral)];
263
+ }
264
+ }]
265
+ });
266
+ }
267
+
268
+ // Returns autofix string for a single easing value, or null if no token suggestion is possible
269
+ function resolveEasingToken(value) {
270
+ const trimmed = value.trim();
271
+ if (SKIP_EASING_VALUES.has(trimmed)) {
272
+ return null;
273
+ }
274
+ if (trimmed in CSS_KEYWORD_EASING_TOKEN_MAP) {
275
+ return buildTokenCall(CSS_KEYWORD_EASING_TOKEN_MAP[trimmed], trimmed);
276
+ }
277
+ // linear has no curve (0,0,1,1) — warn only, no autofix
278
+ if (trimmed === 'linear') {
279
+ return null;
280
+ }
281
+ if (trimmed.startsWith('linear(')) {
282
+ // linear() is used for spring animations — motion.easing.spring is experimental, skip
283
+ return null;
284
+ }
285
+ const params = parseCubicBezierParams(trimmed);
286
+ if (!params) {
287
+ return null;
288
+ }
289
+ const exact = EASING_TOKENS.find(t => t.value === trimmed);
290
+ if (exact) {
291
+ return buildTokenCall(exact.token, trimmed);
292
+ }
293
+ const closest = findClosestEasingToken(params);
294
+ return closest ? buildTokenCall(closest.token, trimmed) : null;
295
+ }
296
+ function handleEasingProperty(node, rawValue) {
297
+ const segments = splitOnTopLevelCommas(rawValue);
298
+
299
+ // Multi-value path: resolve each segment, autofix only if all resolve cleanly
300
+ if (segments.length > 1) {
301
+ const resolved = segments.map(resolveEasingToken);
302
+ if (resolved.some(s => s === null)) {
303
+ return;
304
+ }
305
+ const templateLiteral = '`' + resolved.map(s => `\${${s}}`).join(', ') + '`';
306
+ context.report({
307
+ node,
308
+ messageId: 'useMotionEasingToken',
309
+ data: {
310
+ value: rawValue
311
+ },
312
+ suggest: [{
313
+ messageId: 'useMotionEasingTokenSuggest',
314
+ data: {
315
+ suggestion: templateLiteral
316
+ },
317
+ fix(fixer) {
318
+ return [...getImportFix(fixer), fixer.replaceText(node.value, templateLiteral)];
319
+ }
320
+ }]
321
+ });
322
+ return;
323
+ }
324
+ const trimmed = rawValue.trim();
325
+ if (SKIP_EASING_VALUES.has(trimmed)) {
326
+ return;
327
+ }
328
+
329
+ // CSS keyword easings: convert to cubic-bezier equivalent and find closest token
330
+ if (trimmed in CSS_KEYWORD_EASING_TOKEN_MAP) {
331
+ const suggestion = buildTokenCall(CSS_KEYWORD_EASING_TOKEN_MAP[trimmed], trimmed);
332
+ context.report({
333
+ node,
334
+ messageId: 'useMotionEasingToken',
335
+ data: {
336
+ value: trimmed
337
+ },
338
+ suggest: [{
339
+ messageId: 'useMotionEasingTokenSuggest',
340
+ data: {
341
+ suggestion
342
+ },
343
+ fix(fixer) {
344
+ return [...getImportFix(fixer), fixer.replaceText(node.value, suggestion)];
345
+ }
346
+ }]
347
+ });
348
+ return;
349
+ }
350
+ // linear has no curve (0,0,1,1) — warn only, no autofix
351
+ if (trimmed === 'linear') {
352
+ context.report({
353
+ node,
354
+ messageId: 'useMotionEasingTokenUnknown',
355
+ data: {
356
+ value: trimmed
357
+ }
358
+ });
359
+ return;
360
+ }
361
+ if (trimmed.startsWith('linear(')) {
362
+ // linear() is used for spring animations — motion.easing.spring is experimental, skip
363
+ return;
364
+ }
365
+ const params = parseCubicBezierParams(trimmed);
366
+ if (!params) {
367
+ context.report({
368
+ node,
369
+ messageId: 'useMotionEasingTokenUnknown',
370
+ data: {
371
+ value: rawValue
372
+ }
373
+ });
374
+ return;
375
+ }
376
+ const exact = EASING_TOKENS.find(t => t.value === trimmed);
377
+ if (exact) {
378
+ const suggestion = buildTokenCall(exact.token, rawValue);
379
+ context.report({
380
+ node,
381
+ messageId: 'useMotionEasingToken',
382
+ data: {
383
+ value: rawValue
384
+ },
385
+ suggest: [{
386
+ messageId: 'useMotionEasingTokenSuggest',
387
+ data: {
388
+ suggestion
389
+ },
390
+ fix(fixer) {
391
+ return [...getImportFix(fixer), fixer.replaceText(node.value, suggestion)];
392
+ }
393
+ }]
394
+ });
395
+ return;
396
+ }
397
+ const closest = findClosestEasingToken(params);
398
+ if (closest) {
399
+ const suggestion = buildTokenCall(closest.token, rawValue);
400
+ context.report({
401
+ node,
402
+ messageId: 'useMotionEasingToken',
403
+ data: {
404
+ value: rawValue
405
+ },
406
+ suggest: [{
407
+ messageId: 'useMotionEasingTokenSuggest',
408
+ data: {
409
+ suggestion
410
+ },
411
+ fix(fixer) {
412
+ return [...getImportFix(fixer), fixer.replaceText(node.value, suggestion)];
413
+ }
414
+ }]
415
+ });
416
+ } else {
417
+ context.report({
418
+ node,
419
+ messageId: 'useMotionEasingTokenUnknown',
420
+ data: {
421
+ value: rawValue
422
+ }
423
+ });
424
+ }
425
+ }
426
+ function handleProperty(node) {
427
+ const key = node.key;
428
+ if (key.type !== 'Identifier') {
429
+ return;
430
+ }
431
+ const isDuration = DURATION_PROPERTIES.has(key.name);
432
+ const isEasing = EASING_PROPERTIES.has(key.name);
433
+ if (!isDuration && !isEasing) {
434
+ return;
435
+ }
436
+ const value = node.value;
437
+ if (value.type === 'TemplateLiteral') {
438
+ // Only handle no-interpolation template literals (e.g. `200ms`) — treat as string
439
+ const tl = value;
440
+ if (tl.expressions.length === 0 && tl.quasis.length === 1) {
441
+ var _tl$quasis$0$value$co;
442
+ const 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;
443
+ if (isDuration) {
444
+ handleDurationProperty(node, rawValue);
445
+ } else {
446
+ handleEasingProperty(node, rawValue);
447
+ }
448
+ }
449
+ return;
450
+ }
451
+ if (value.type === 'CallExpression') {
452
+ const ce = value;
453
+ if (ce.callee.type === 'Identifier' && ce.callee.name === 'token') {
454
+ return;
455
+ }
456
+ return;
457
+ }
458
+ if (value.type === 'Literal') {
459
+ const lit = value;
460
+ let rawValue;
461
+ if (typeof lit.value === 'string') {
462
+ rawValue = lit.value;
463
+ } else if (typeof lit.value === 'number') {
464
+ // Treat bare numbers as ms
465
+ rawValue = `${lit.value}ms`;
466
+ } else {
467
+ return;
468
+ }
469
+ if (isDuration) {
470
+ handleDurationProperty(node, rawValue);
471
+ } else {
472
+ handleEasingProperty(node, rawValue);
473
+ }
474
+ }
475
+ }
476
+ return {
477
+ ImportDeclaration(node) {
478
+ if (node.source.value === '@atlaskit/tokens') {
479
+ tokensImportNode = node;
480
+ hasTokenSpecifier = node.specifiers.some(s => s.type === 'ImportSpecifier' && s.local.name === 'token');
481
+ }
482
+ },
483
+ Property(node) {
484
+ handleProperty(node);
485
+ }
486
+ };
487
+ }
488
+ });
489
+ export 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 @@ var 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 @@ var 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',
@@ -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::b90bcf0b2fb6429faae59c7a454e3260>>
3
+ * @codegen <<SignedSource::d404a0bd62a78d4bad7ee376df0bd475>>
4
4
  * @codegenCommand yarn workspace @atlaskit/eslint-plugin-design-system codegen
5
5
  */
6
6
 
@@ -78,6 +78,7 @@ import useSimpleForm from './use-simple-form';
78
78
  import useSpotlightPackage from './use-spotlight-package';
79
79
  import useTagGroupLabel from './use-tag-group-label';
80
80
  import useTextfieldAutocomplete from './use-textfield-autocomplete';
81
+ import useTokensMotion from './use-tokens-motion';
81
82
  import useTokensShape from './use-tokens-shape';
82
83
  import useTokensSpace from './use-tokens-space';
83
84
  import useTokensTypography from './use-tokens-typography';
@@ -157,6 +158,7 @@ export var rules = {
157
158
  'use-spotlight-package': useSpotlightPackage,
158
159
  'use-tag-group-label': useTagGroupLabel,
159
160
  'use-textfield-autocomplete': useTextfieldAutocomplete,
161
+ 'use-tokens-motion': useTokensMotion,
160
162
  'use-tokens-shape': useTokensShape,
161
163
  'use-tokens-space': useTokensSpace,
162
164
  'use-tokens-typography': useTokensTypography,