@hublo/sentinel 1.0.2 → 1.1.0-alpha.2

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.
@@ -0,0 +1,570 @@
1
+ {
2
+ "$schema": "./node_modules/oxlint/configuration_schema.json",
3
+ "plugins": [
4
+ "typescript",
5
+ "react",
6
+ "jsx-a11y",
7
+ "jest",
8
+ "import",
9
+ "unicorn",
10
+ "oxc"
11
+ ],
12
+ "env": {
13
+ "browser": true,
14
+ "es2022": true,
15
+ "node": true,
16
+ "jest": true
17
+ },
18
+ "categories": {
19
+ "correctness": "off",
20
+ "suspicious": "off",
21
+ "pedantic": "off",
22
+ "perf": "off",
23
+ "style": "off",
24
+ "restriction": "off",
25
+ "nursery": "off"
26
+ },
27
+ "ignorePatterns": [
28
+ "**/dist/**",
29
+ "**/node_modules/**",
30
+ "**/coverage/**",
31
+ "**/*.mock.ts"
32
+ ],
33
+ "rules": {
34
+ "typescript/ban-ts-comment": "warn",
35
+ "typescript/no-array-constructor": "warn",
36
+ "typescript/no-duplicate-enum-values": "warn",
37
+ "typescript/no-empty-object-type": "warn",
38
+ "typescript/no-extra-non-null-assertion": "warn",
39
+ "typescript/no-misused-new": "warn",
40
+ "typescript/no-namespace": "warn",
41
+ "typescript/no-non-null-asserted-optional-chain": "warn",
42
+ "typescript/no-this-alias": "warn",
43
+ "typescript/no-unnecessary-type-constraint": "warn",
44
+ "typescript/no-unsafe-declaration-merging": "warn",
45
+ "typescript/no-unsafe-function-type": "warn",
46
+ "typescript/no-unused-expressions": [
47
+ "warn",
48
+ {
49
+ "allowShortCircuit": false,
50
+ "allowTaggedTemplates": false,
51
+ "allowTernary": false
52
+ }
53
+ ],
54
+ "typescript/no-wrapper-object-types": "warn",
55
+ "typescript/prefer-as-const": "warn",
56
+ "typescript/prefer-namespace-keyword": "warn",
57
+ "typescript/triple-slash-reference": "warn",
58
+ "no-var": "warn",
59
+ "prefer-const": [
60
+ "warn",
61
+ {
62
+ "destructuring": "any",
63
+ "ignoreReadBeforeAssign": false
64
+ }
65
+ ],
66
+ "prefer-rest-params": "warn",
67
+ "prefer-spread": "warn",
68
+ "curly": [
69
+ "error",
70
+ "all"
71
+ ],
72
+ "for-direction": "error",
73
+ "no-async-promise-executor": "error",
74
+ "no-case-declarations": "error",
75
+ "no-compare-neg-zero": "error",
76
+ "no-cond-assign": [
77
+ "error",
78
+ "except-parens"
79
+ ],
80
+ "no-constant-binary-expression": "error",
81
+ "no-constant-condition": [
82
+ "error",
83
+ {
84
+ "checkLoops": "allExceptWhileTrue"
85
+ }
86
+ ],
87
+ "no-control-regex": "error",
88
+ "no-debugger": "error",
89
+ "no-delete-var": "error",
90
+ "no-dupe-else-if": "error",
91
+ "no-duplicate-case": "error",
92
+ "no-empty": [
93
+ "error",
94
+ {
95
+ "allowEmptyCatch": false
96
+ }
97
+ ],
98
+ "no-empty-character-class": "error",
99
+ "no-empty-pattern": [
100
+ "error",
101
+ {
102
+ "allowObjectPatternsAsParameters": false
103
+ }
104
+ ],
105
+ "no-empty-static-block": "error",
106
+ "no-ex-assign": "error",
107
+ "no-extra-boolean-cast": [
108
+ "error",
109
+ {}
110
+ ],
111
+ "no-fallthrough": [
112
+ "error",
113
+ {
114
+ "allowEmptyCase": false,
115
+ "reportUnusedFallthroughComment": false
116
+ }
117
+ ],
118
+ "no-global-assign": [
119
+ "error",
120
+ {
121
+ "exceptions": []
122
+ }
123
+ ],
124
+ "no-invalid-regexp": [
125
+ "error",
126
+ {}
127
+ ],
128
+ "no-irregular-whitespace": [
129
+ "error",
130
+ {
131
+ "skipComments": false,
132
+ "skipJSXText": false,
133
+ "skipRegExps": false,
134
+ "skipStrings": true,
135
+ "skipTemplates": false
136
+ }
137
+ ],
138
+ "no-loss-of-precision": "error",
139
+ "no-misleading-character-class": "error",
140
+ "no-nonoctal-decimal-escape": "error",
141
+ "no-prototype-builtins": "error",
142
+ "no-regex-spaces": "error",
143
+ "no-restricted-imports": [
144
+ "error",
145
+ {
146
+ "patterns": [
147
+ {
148
+ "group": [
149
+ "next/*"
150
+ ],
151
+ "message": "Importing from next/* is not allowed in the admin project."
152
+ },
153
+ {
154
+ "group": [
155
+ "storybook/*"
156
+ ],
157
+ "message": "Importing from storybook/* is not allowed in the admin project."
158
+ },
159
+ {
160
+ "group": [
161
+ "@nestjs/*"
162
+ ],
163
+ "message": "Importing from @nestjs/* is not allowed in the admin project."
164
+ },
165
+ {
166
+ "group": [
167
+ "next-i18next"
168
+ ],
169
+ "message": "Importing from next-i18next is not allowed in the admin project."
170
+ },
171
+ {
172
+ "group": [
173
+ "@hublo/style/colors",
174
+ "@hublo/style/colors/*"
175
+ ],
176
+ "message": "Importing runtime colors directly is not allowed. Use the MUI theme, theme-derived CSS variables, or an explicit product-specific mapping."
177
+ }
178
+ ]
179
+ }
180
+ ],
181
+ "no-self-assign": [
182
+ "error",
183
+ {
184
+ "props": true
185
+ }
186
+ ],
187
+ "no-shadow-restricted-names": [
188
+ "error",
189
+ {
190
+ "reportGlobalThis": false
191
+ }
192
+ ],
193
+ "no-sparse-arrays": "error",
194
+ "no-unexpected-multiline": "error",
195
+ "no-unsafe-finally": "error",
196
+ "no-unused-labels": "error",
197
+ "no-unused-private-class-members": "error",
198
+ "no-useless-backreference": "error",
199
+ "no-useless-catch": "error",
200
+ "no-useless-escape": [
201
+ "error",
202
+ {
203
+ "allowRegexCharacters": []
204
+ }
205
+ ],
206
+ "require-yield": "error",
207
+ "use-isnan": [
208
+ "error",
209
+ {
210
+ "enforceForIndexOf": false,
211
+ "enforceForSwitchCase": true
212
+ }
213
+ ],
214
+ "valid-typeof": [
215
+ "error",
216
+ {
217
+ "requireStringLiterals": false
218
+ }
219
+ ],
220
+ "styled-components-a11y/alt-text": "error",
221
+ "styled-components-a11y/anchor-has-content": "error",
222
+ "styled-components-a11y/anchor-is-valid": "error",
223
+ "styled-components-a11y/aria-activedescendant-has-tabindex": "error",
224
+ "styled-components-a11y/aria-props": "error",
225
+ "styled-components-a11y/aria-proptypes": "error",
226
+ "styled-components-a11y/aria-role": "error",
227
+ "styled-components-a11y/aria-unsupported-elements": "error",
228
+ "styled-components-a11y/autocomplete-valid": "error",
229
+ "styled-components-a11y/click-events-have-key-events": "error",
230
+ "styled-components-a11y/control-has-associated-label": [
231
+ "off",
232
+ {
233
+ "ignoreElements": [
234
+ "audio",
235
+ "canvas",
236
+ "embed",
237
+ "input",
238
+ "textarea",
239
+ "tr",
240
+ "video"
241
+ ],
242
+ "ignoreRoles": [
243
+ "grid",
244
+ "listbox",
245
+ "menu",
246
+ "menubar",
247
+ "radiogroup",
248
+ "row",
249
+ "tablist",
250
+ "toolbar",
251
+ "tree",
252
+ "treegrid"
253
+ ],
254
+ "includeRoles": [
255
+ "alert",
256
+ "dialog"
257
+ ]
258
+ }
259
+ ],
260
+ "styled-components-a11y/heading-has-content": "error",
261
+ "styled-components-a11y/html-has-lang": "error",
262
+ "styled-components-a11y/iframe-has-title": "error",
263
+ "styled-components-a11y/img-redundant-alt": "error",
264
+ "styled-components-a11y/interactive-supports-focus": [
265
+ "error",
266
+ {
267
+ "tabbable": [
268
+ "button",
269
+ "checkbox",
270
+ "link",
271
+ "progressbar",
272
+ "searchbox",
273
+ "slider",
274
+ "spinbutton",
275
+ "switch",
276
+ "textbox"
277
+ ]
278
+ }
279
+ ],
280
+ "styled-components-a11y/label-has-associated-control": "error",
281
+ "styled-components-a11y/media-has-caption": "error",
282
+ "styled-components-a11y/mouse-events-have-key-events": "error",
283
+ "styled-components-a11y/no-access-key": "error",
284
+ "styled-components-a11y/no-autofocus": "error",
285
+ "styled-components-a11y/no-distracting-elements": "error",
286
+ "styled-components-a11y/no-interactive-element-to-noninteractive-role": "error",
287
+ "styled-components-a11y/no-noninteractive-element-interactions": [
288
+ "error",
289
+ {
290
+ "body": [
291
+ "onError",
292
+ "onLoad"
293
+ ],
294
+ "iframe": [
295
+ "onError",
296
+ "onLoad"
297
+ ],
298
+ "img": [
299
+ "onError",
300
+ "onLoad"
301
+ ]
302
+ }
303
+ ],
304
+ "styled-components-a11y/no-noninteractive-element-to-interactive-role": "error",
305
+ "styled-components-a11y/no-noninteractive-tabindex": "error",
306
+ "styled-components-a11y/no-redundant-roles": "error",
307
+ "styled-components-a11y/no-static-element-interactions": "error",
308
+ "styled-components-a11y/role-has-required-aria-props": "error",
309
+ "styled-components-a11y/role-supports-aria-props": "error",
310
+ "styled-components-a11y/scope": "error",
311
+ "styled-components-a11y/tabindex-no-positive": "error",
312
+ "jsx-a11y/alt-text": "error",
313
+ "jsx-a11y/anchor-has-content": "error",
314
+ "jsx-a11y/anchor-is-valid": "error",
315
+ "jsx-a11y/aria-activedescendant-has-tabindex": "error",
316
+ "jsx-a11y/aria-props": "error",
317
+ "jsx-a11y/aria-proptypes": "error",
318
+ "jsx-a11y/aria-role": "error",
319
+ "jsx-a11y/aria-unsupported-elements": "error",
320
+ "jsx-a11y/autocomplete-valid": "error",
321
+ "jsx-a11y/click-events-have-key-events": "error",
322
+ "jsx-a11y/heading-has-content": "error",
323
+ "jsx-a11y/html-has-lang": "error",
324
+ "jsx-a11y/iframe-has-title": "error",
325
+ "jsx-a11y/img-redundant-alt": "off",
326
+ "jsx-a11y/interactive-supports-focus": [
327
+ "error",
328
+ {
329
+ "tabbable": [
330
+ "button",
331
+ "checkbox",
332
+ "link",
333
+ "progressbar",
334
+ "searchbox",
335
+ "slider",
336
+ "spinbutton",
337
+ "switch",
338
+ "textbox"
339
+ ]
340
+ }
341
+ ],
342
+ "jsx-a11y/label-has-associated-control": "error",
343
+ "jsx-a11y/media-has-caption": "error",
344
+ "jsx-a11y/mouse-events-have-key-events": "error",
345
+ "jsx-a11y/no-access-key": "error",
346
+ "jsx-a11y/no-autofocus": "error",
347
+ "jsx-a11y/no-distracting-elements": "error",
348
+ "jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
349
+ "jsx-a11y/no-noninteractive-element-interactions": [
350
+ "error",
351
+ {
352
+ "body": [
353
+ "onError",
354
+ "onLoad"
355
+ ],
356
+ "iframe": [
357
+ "onError",
358
+ "onLoad"
359
+ ],
360
+ "img": [
361
+ "onError",
362
+ "onLoad"
363
+ ]
364
+ }
365
+ ],
366
+ "jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
367
+ "jsx-a11y/no-noninteractive-tabindex": "error",
368
+ "jsx-a11y/no-redundant-roles": "error",
369
+ "jsx-a11y/no-static-element-interactions": "error",
370
+ "jsx-a11y/role-has-required-aria-props": "error",
371
+ "jsx-a11y/role-supports-aria-props": "error",
372
+ "jsx-a11y/scope": "error",
373
+ "jsx-a11y/tabindex-no-positive": "error",
374
+ "typescript/await-thenable": "error",
375
+ "typescript/no-array-delete": "error",
376
+ "typescript/no-base-to-string": "error",
377
+ "typescript/no-duplicate-type-constituents": "error",
378
+ "typescript/no-floating-promises": [
379
+ "error",
380
+ {
381
+ "ignoreVoid": true
382
+ }
383
+ ],
384
+ "typescript/no-for-in-array": "error",
385
+ "typescript/no-implied-eval": "error",
386
+ "typescript/no-inferrable-types": "error",
387
+ "typescript/no-misused-promises": [
388
+ "error",
389
+ {
390
+ "checksVoidReturn": {
391
+ "attributes": false
392
+ }
393
+ }
394
+ ],
395
+ "typescript/no-non-null-assertion": "warn",
396
+ "typescript/no-redundant-type-constituents": "error",
397
+ "typescript/no-require-imports": "error",
398
+ "typescript/no-unsafe-unary-minus": "error",
399
+ "typescript/prefer-promise-reject-errors": "error",
400
+ "typescript/restrict-plus-operands": "error",
401
+ "typescript/restrict-template-expressions": "error",
402
+ "typescript/switch-exhaustiveness-check": "warn",
403
+ "typescript/unbound-method": "error",
404
+ "react-hooks/exhaustive-deps": "warn",
405
+ "react-hooks/rules-of-hooks": "error",
406
+ "no-barrel-files/no-barrel-files": "warn",
407
+ "nx/enforce-module-boundaries": [
408
+ "error",
409
+ {
410
+ "enforceBuildableLibDependency": true,
411
+ "allow": [],
412
+ "depConstraints": [
413
+ {
414
+ "sourceTag": "*",
415
+ "onlyDependOnLibsWithTags": [
416
+ "*"
417
+ ]
418
+ }
419
+ ]
420
+ }
421
+ ],
422
+ "import-js/order": [
423
+ "error",
424
+ {
425
+ "newlines-between": "always",
426
+ "groups": [
427
+ "builtin",
428
+ "external",
429
+ "internal",
430
+ [
431
+ "parent",
432
+ "index"
433
+ ],
434
+ "sibling"
435
+ ],
436
+ "alphabetize": {
437
+ "order": "asc"
438
+ },
439
+ "pathGroups": [
440
+ {
441
+ "pattern": "@nestjs/**",
442
+ "group": "external"
443
+ },
444
+ {
445
+ "pattern": "{@admin/**,@authentication/**,@featureToggles/**,@institution/**,@hublo/**,@network/**,@mission/**,@notification/**,@shared/**,@talent/**,@worker/**,@contract/**,@config/**,@front/**}",
446
+ "group": "internal",
447
+ "position": "before"
448
+ }
449
+ ],
450
+ "pathGroupsExcludedImportTypes": [
451
+ "builtin"
452
+ ]
453
+ }
454
+ ],
455
+ "react-refresh/only-export-components": [
456
+ "warn",
457
+ {
458
+ "allowConstantExport": true
459
+ }
460
+ ],
461
+ "jest/expect-expect": "off",
462
+ "jest/no-alias-methods": "off",
463
+ "jest/no-commented-out-tests": "off",
464
+ "jest/no-conditional-expect": "off",
465
+ "jest/no-deprecated-functions": "off",
466
+ "jest/no-disabled-tests": "off",
467
+ "jest/no-done-callback": "off",
468
+ "jest/no-export": "off",
469
+ "jest/no-focused-tests": "off",
470
+ "jest/no-identical-title": "off",
471
+ "jest/no-interpolation-in-snapshots": "off",
472
+ "jest/no-jasmine-globals": "off",
473
+ "jest/no-mocks-import": "off",
474
+ "jest/no-standalone-expect": "off",
475
+ "jest/no-test-prefixes": "off",
476
+ "jest/prefer-to-be": "off",
477
+ "jest/prefer-to-contain": "off",
478
+ "jest/prefer-to-have-length": "off",
479
+ "jest/valid-describe-callback": "off",
480
+ "jest/valid-expect": "off",
481
+ "jest/valid-expect-in-promise": "off",
482
+ "jest/valid-title": "off",
483
+ "@tanstack/query/exhaustive-deps": "off",
484
+ "@tanstack/query/infinite-query-property-order": "off",
485
+ "@tanstack/query/mutation-property-order": "off",
486
+ "@tanstack/query/no-rest-destructuring": "off",
487
+ "@tanstack/query/no-unstable-deps": "off",
488
+ "@tanstack/query/no-void-query-fn": "off",
489
+ "@tanstack/query/stable-query-client": "off",
490
+ "typescript/no-empty-function": "off",
491
+ "typescript/no-explicit-any": "off",
492
+ "typescript/no-unnecessary-type-assertion": "off",
493
+ "typescript/no-unsafe-argument": "off",
494
+ "typescript/no-unsafe-assignment": "off",
495
+ "typescript/no-unsafe-call": "off",
496
+ "typescript/no-unsafe-enum-comparison": "off",
497
+ "typescript/no-unsafe-member-access": "off",
498
+ "typescript/no-unsafe-return": "off",
499
+ "typescript/no-unused-vars": "off",
500
+ "typescript/only-throw-error": "off",
501
+ "typescript/require-await": "off",
502
+ "no-console": "off",
503
+ "no-unsafe-optional-chaining": "off",
504
+ "react/jsx-curly-brace-presence": "off",
505
+ "styled-components-a11y/label-has-for": "off",
506
+ "hublo/no-palette-common": "off"
507
+ },
508
+ "jsPlugins": [
509
+ "eslint-plugin-no-barrel-files",
510
+ {
511
+ "name": "nx",
512
+ "specifier": "@nx/eslint-plugin"
513
+ },
514
+ {
515
+ "name": "import-js",
516
+ "specifier": "eslint-plugin-import"
517
+ },
518
+ {
519
+ "name": "@tanstack/query",
520
+ "specifier": "@tanstack/eslint-plugin-query"
521
+ },
522
+ {
523
+ "name": "styled-components-a11y",
524
+ "specifier": "eslint-plugin-styled-components-a11y"
525
+ },
526
+ {
527
+ "name": "react-refresh",
528
+ "specifier": "eslint-plugin-react-refresh"
529
+ },
530
+ "../plugins/hublo.js"
531
+ ],
532
+ "settings": {
533
+ "import/extensions": [
534
+ ".ts",
535
+ ".cts",
536
+ ".mts",
537
+ ".tsx",
538
+ ".js",
539
+ ".jsx",
540
+ ".mjs",
541
+ ".cjs"
542
+ ],
543
+ "import/external-module-folders": [
544
+ "node_modules",
545
+ "node_modules/@types"
546
+ ],
547
+ "import/parsers": {
548
+ "@typescript-eslint/parser": [
549
+ ".ts",
550
+ ".cts",
551
+ ".mts",
552
+ ".tsx"
553
+ ]
554
+ },
555
+ "import/resolver": {
556
+ "node": {
557
+ "extensions": [
558
+ ".ts",
559
+ ".cts",
560
+ ".mts",
561
+ ".tsx",
562
+ ".js",
563
+ ".jsx",
564
+ ".mjs",
565
+ ".cjs"
566
+ ]
567
+ }
568
+ }
569
+ }
570
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hublo/sentinel",
3
- "version": "1.0.2",
3
+ "version": "1.1.0-alpha.2",
4
4
  "description": "One CLI that guards code health across Hublo repos: shared lint/typescript/build/test presets, static & dynamic analysis, and architecture checks.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -28,15 +28,22 @@
28
28
  },
29
29
  "./tsconfig/node": {
30
30
  "default": "./dist/tsconfig/node.json"
31
+ },
32
+ "./oxlint/nest": {
33
+ "default": "./oxlint/nest.json"
31
34
  }
32
35
  },
33
36
  "files": [
34
37
  "dist",
38
+ "oxlint",
39
+ "plugins",
35
40
  "!dist/**/*.map"
36
41
  ],
37
42
  "dependencies": {
38
43
  "commander": "^13.0.0",
39
- "jsonc-parser": "^3.3.1"
44
+ "jsonc-parser": "^3.3.1",
45
+ "oxlint": "1.77.0",
46
+ "oxlint-tsgolint": "7.0.2001"
40
47
  },
41
48
  "devDependencies": {
42
49
  "@eslint/js": "^10.0.1",
@@ -0,0 +1,88 @@
1
+ /**
2
+ * Local oxlint plugin: Hublo house rules that have no equivalent anywhere else.
3
+ *
4
+ * oxlint has no `no-restricted-syntax`, so the eight AST selectors that used to live in
5
+ * `restricted-syntax.cjs` cannot be carried across as configuration. They are expressed
6
+ * here as a real rule instead, which is what oxlint's JS plugin API is for.
7
+ *
8
+ * It ships INSIDE sentinel rather than being copied into each module. A house rule is code,
9
+ * not configuration: a per-module copy drifts, and drifting house rules are the problem
10
+ * sentinel exists to remove. The cost is that `@hublo/sentinel` carries a rule about Hublo's
11
+ * colour palette, which is honest for a package named after the company.
12
+ *
13
+ * The original selectors, and where each is handled below:
14
+ * 1-2 MemberExpression[object.property.name='palette'][property.name='common'] theme.palette.common
15
+ * …[computed=true][property.value='common'] theme.palette['common']
16
+ * 3-4 MemberExpression[object.name='palette']… palette.common
17
+ * 5-6 Property[key.name|key.value=/<colour props>/] > Literal[value=/^common\./] { color: 'common.white' }
18
+ * 7-8 JSXAttribute[name.name=/<colour props>/] > Literal | JSXExpressionContainer color="common.white"
19
+ */
20
+
21
+ const MESSAGE =
22
+ 'Do not use palette.common or common.* color paths in product code. Use semantic palette tokens instead.'
23
+
24
+ /** The MUI props that take a colour path, so `common.*` in them is the same mistake. */
25
+ const COLOUR_PROPS =
26
+ /^(color|bgcolor|background|backgroundColor|borderColor|borderTopColor|borderRightColor|borderBottomColor|borderLeftColor|outlineColor|fill|stroke|textDecorationColor|caretColor|accentColor)$/
27
+
28
+ /** `common.something` as a string value, e.g. `{ color: 'common.white' }`. */
29
+ const isCommonPath = (node) =>
30
+ node?.type === 'Literal' && typeof node.value === 'string' && /^common\./.test(node.value)
31
+
32
+ const plugin = {
33
+ meta: { name: 'hublo' },
34
+ rules: {
35
+ 'no-palette-common': {
36
+ meta: {
37
+ docs: {
38
+ description: 'Ban palette.common and common.* colour paths in product code.',
39
+ },
40
+ },
41
+ create(context) {
42
+ const report = (node) => context.report({ node, message: MESSAGE })
43
+
44
+ return {
45
+ // 1-4: reaching `common` off a palette, by property or by computed key.
46
+ MemberExpression(node) {
47
+ const onPalette =
48
+ node.object?.property?.name === 'palette' || node.object?.name === 'palette'
49
+ if (!onPalette) {
50
+ return
51
+ }
52
+ const reachesCommon =
53
+ node.property?.name === 'common' ||
54
+ (node.computed && node.property?.value === 'common')
55
+ if (reachesCommon) {
56
+ report(node)
57
+ }
58
+ },
59
+
60
+ // 5-6: `{ color: 'common.white' }` in an object literal, key written either way.
61
+ Property(node) {
62
+ const key = node.key?.name ?? node.key?.value
63
+ if (typeof key === 'string' && COLOUR_PROPS.test(key) && isCommonPath(node.value)) {
64
+ report(node.value)
65
+ }
66
+ },
67
+
68
+ // 7-8: `color="common.white"` and `color={'common.white'}` on a JSX element.
69
+ JSXAttribute(node) {
70
+ const name = node.name?.name
71
+ if (typeof name !== 'string' || !COLOUR_PROPS.test(name)) {
72
+ return
73
+ }
74
+ const value = node.value
75
+ if (isCommonPath(value)) {
76
+ return report(value)
77
+ }
78
+ if (value?.type === 'JSXExpressionContainer' && isCommonPath(value.expression)) {
79
+ report(value.expression)
80
+ }
81
+ },
82
+ }
83
+ },
84
+ },
85
+ },
86
+ }
87
+
88
+ export default plugin