eslint-plugin-primer-react 5.5.0-rc.40574df → 6.0.0-rc.0df8090

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,6 +1,10 @@
1
1
  # eslint-plugin-primer-react
2
2
 
3
- ## 5.5.0
3
+ ## 6.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - [#207](https://github.com/primer/eslint-plugin-primer-react/pull/207) [`baff792`](https://github.com/primer/eslint-plugin-primer-react/commit/baff792c0aa01a29374e44e8aa770dbe2cb889a1) Thanks [@iansan5653](https://github.com/iansan5653)! - [Breaking] Adds `no-unnecessary-components` lint rule and enables it by default. This may raise new (typically autofixable) lint errors in existing codebases.
4
8
 
5
9
  ### Minor Changes
6
10
 
@@ -0,0 +1,69 @@
1
+ # Disallow unnecessary use of `Box` and `Text` components (no-unnecessary-components)
2
+
3
+ 🔧 The `--fix` option on the [ESLint CLI](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can
4
+ automatically fix some of the problems reported by this rule.
5
+
6
+ ## Rule details
7
+
8
+ The [`Box`](https://primer.style/components/box) and [`Text`](https://primer.style/components/text)
9
+ Primer React components are utilities that exist solely to provide access to `sx` or styled-system
10
+ props.
11
+
12
+ If these props are not being used, plain HTML element provide better performance, simpler code,
13
+ and improved linting support.
14
+
15
+ This rule is auto-fixable in nearly all cases. Autofixing respects the presence of an `as` prop.
16
+
17
+ 👎 Examples of **incorrect** code for this rule:
18
+
19
+ ```jsx
20
+ /* eslint primer-react/no-unnecessary-components: "error" */
21
+ import {Box, Text} from '@primer/react'
22
+
23
+ <Box>Content</Box>
24
+ <Box style={{padding: '16px'}} className="danger-box">Content</Box>
25
+ <Box as="section">Content</Box>
26
+
27
+ <Text>Content</Text>
28
+ <Text style={{fontSize: '24px'}} className="large-text">Content</Text>
29
+ <Text as="p">Content</Text>
30
+ ```
31
+
32
+ 👍 Examples of **correct** code for this rule:
33
+
34
+ ```jsx
35
+ /* eslint primer-react/no-system-props: "error" */
36
+ import {Box, Text} from '@primer/react'
37
+
38
+ // Prefer plain HTML elements (autofixable)
39
+ <div>Content</div>
40
+ <div style={{padding: '16px'}} className="danger-box">Content</div>
41
+ <section>Content</section>
42
+
43
+ <span>Content</span>
44
+ <span style={{fontSize: '24px'}} className="large-text">Content</span>
45
+ <p>Content</p>
46
+
47
+ // sx props are allowed
48
+ <Box sx={{p: 2}}>Content</Box>
49
+ <Text sx={{mt: 2}} as="p">Content</Text>
50
+
51
+ // styled-system props are allowed
52
+ <Box p={2}>Content</Box>
53
+ <Text mt={2} as="p">Content</Text>
54
+ ```
55
+
56
+ ```jsx
57
+ /* eslint primer-react/no-system-props: ["error", {skipImportCheck: false}] */
58
+ import {Box, Text} from '@primer/brand'
59
+
60
+ // Other components with the same name are allowed
61
+ <Box>Content</Box>
62
+ <Text>Content</Text>
63
+ ```
64
+
65
+ ## Options
66
+
67
+ - `skipImportCheck` (default: `false`)
68
+
69
+ By default, the rule will only check for incorrect uses of `Box` and `Text` components that are are imported from `@primer/react`. You can disable this behavior (checking all components with these names regardless of import source) by setting `skipImportCheck` to `true`.
package/jest.config.js ADDED
@@ -0,0 +1,7 @@
1
+ // @ts-check
2
+
3
+ /** @type {import('jest').Config} **/
4
+ module.exports = {
5
+ testEnvironment: 'node',
6
+ testMatch: ['**/__tests__/*.test.js'],
7
+ }
package/package-lock.json CHANGED
@@ -10,17 +10,21 @@
10
10
  "license": "MIT",
11
11
  "dependencies": {
12
12
  "@styled-system/props": "^5.1.5",
13
+ "@typescript-eslint/utils": "7.16.0",
13
14
  "eslint-plugin-github": "^5.0.1",
14
15
  "eslint-plugin-jsx-a11y": "^6.7.1",
15
16
  "eslint-traverse": "^1.0.0",
16
17
  "lodash": "^4.17.21",
17
- "styled-system": "^5.1.5"
18
+ "styled-system": "^5.1.5",
19
+ "typescript": "^5.5.3"
18
20
  },
19
21
  "devDependencies": {
20
22
  "@changesets/changelog-github": "^0.5.0",
21
23
  "@changesets/cli": "^2.16.0",
22
24
  "@github/markdownlint-github": "^0.6.0",
23
25
  "@github/prettier-config": "0.0.6",
26
+ "@types/jest": "^29.5.12",
27
+ "@typescript-eslint/rule-tester": "7.16.0",
24
28
  "eslint": "^8.42.0",
25
29
  "eslint-plugin-prettier": "^5.0.1",
26
30
  "jest": "^29.7.0",
@@ -2120,10 +2124,22 @@
2120
2124
  "@types/istanbul-lib-report": "*"
2121
2125
  }
2122
2126
  },
2127
+ "node_modules/@types/jest": {
2128
+ "version": "29.5.12",
2129
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz",
2130
+ "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==",
2131
+ "dev": true,
2132
+ "license": "MIT",
2133
+ "dependencies": {
2134
+ "expect": "^29.0.0",
2135
+ "pretty-format": "^29.0.0"
2136
+ }
2137
+ },
2123
2138
  "node_modules/@types/json-schema": {
2124
2139
  "version": "7.0.15",
2125
2140
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
2126
- "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="
2141
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
2142
+ "license": "MIT"
2127
2143
  },
2128
2144
  "node_modules/@types/json5": {
2129
2145
  "version": "0.0.29",
@@ -2199,6 +2215,31 @@
2199
2215
  }
2200
2216
  }
2201
2217
  },
2218
+ "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/utils": {
2219
+ "version": "7.1.1",
2220
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
2221
+ "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
2222
+ "license": "MIT",
2223
+ "dependencies": {
2224
+ "@eslint-community/eslint-utils": "^4.4.0",
2225
+ "@types/json-schema": "^7.0.12",
2226
+ "@types/semver": "^7.5.0",
2227
+ "@typescript-eslint/scope-manager": "7.1.1",
2228
+ "@typescript-eslint/types": "7.1.1",
2229
+ "@typescript-eslint/typescript-estree": "7.1.1",
2230
+ "semver": "^7.5.4"
2231
+ },
2232
+ "engines": {
2233
+ "node": "^16.0.0 || >=18.0.0"
2234
+ },
2235
+ "funding": {
2236
+ "type": "opencollective",
2237
+ "url": "https://opencollective.com/typescript-eslint"
2238
+ },
2239
+ "peerDependencies": {
2240
+ "eslint": "^8.56.0"
2241
+ }
2242
+ },
2202
2243
  "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": {
2203
2244
  "version": "7.6.0",
2204
2245
  "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -2240,6 +2281,132 @@
2240
2281
  }
2241
2282
  }
2242
2283
  },
2284
+ "node_modules/@typescript-eslint/rule-tester": {
2285
+ "version": "7.16.0",
2286
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/rule-tester/-/rule-tester-7.16.0.tgz",
2287
+ "integrity": "sha512-MLDeDEY8BVZiWkIhtMnEkhiwH7CXDOLKDnHntFZp//WHYdso+1gS/8F60+oTb+Xjw4LkWz4D8sJ4js3ZxMoctA==",
2288
+ "dev": true,
2289
+ "license": "MIT",
2290
+ "dependencies": {
2291
+ "@typescript-eslint/typescript-estree": "7.16.0",
2292
+ "@typescript-eslint/utils": "7.16.0",
2293
+ "ajv": "^6.12.6",
2294
+ "json-stable-stringify-without-jsonify": "^1.0.1",
2295
+ "lodash.merge": "4.6.2",
2296
+ "semver": "^7.6.0"
2297
+ },
2298
+ "engines": {
2299
+ "node": "^18.18.0 || >=20.0.0"
2300
+ },
2301
+ "funding": {
2302
+ "type": "opencollective",
2303
+ "url": "https://opencollective.com/typescript-eslint"
2304
+ },
2305
+ "peerDependencies": {
2306
+ "@eslint/eslintrc": ">=2",
2307
+ "eslint": "^8.56.0"
2308
+ }
2309
+ },
2310
+ "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/types": {
2311
+ "version": "7.16.0",
2312
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz",
2313
+ "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==",
2314
+ "dev": true,
2315
+ "license": "MIT",
2316
+ "engines": {
2317
+ "node": "^18.18.0 || >=20.0.0"
2318
+ },
2319
+ "funding": {
2320
+ "type": "opencollective",
2321
+ "url": "https://opencollective.com/typescript-eslint"
2322
+ }
2323
+ },
2324
+ "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/typescript-estree": {
2325
+ "version": "7.16.0",
2326
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz",
2327
+ "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==",
2328
+ "dev": true,
2329
+ "license": "BSD-2-Clause",
2330
+ "dependencies": {
2331
+ "@typescript-eslint/types": "7.16.0",
2332
+ "@typescript-eslint/visitor-keys": "7.16.0",
2333
+ "debug": "^4.3.4",
2334
+ "globby": "^11.1.0",
2335
+ "is-glob": "^4.0.3",
2336
+ "minimatch": "^9.0.4",
2337
+ "semver": "^7.6.0",
2338
+ "ts-api-utils": "^1.3.0"
2339
+ },
2340
+ "engines": {
2341
+ "node": "^18.18.0 || >=20.0.0"
2342
+ },
2343
+ "funding": {
2344
+ "type": "opencollective",
2345
+ "url": "https://opencollective.com/typescript-eslint"
2346
+ },
2347
+ "peerDependenciesMeta": {
2348
+ "typescript": {
2349
+ "optional": true
2350
+ }
2351
+ }
2352
+ },
2353
+ "node_modules/@typescript-eslint/rule-tester/node_modules/@typescript-eslint/visitor-keys": {
2354
+ "version": "7.16.0",
2355
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz",
2356
+ "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==",
2357
+ "dev": true,
2358
+ "license": "MIT",
2359
+ "dependencies": {
2360
+ "@typescript-eslint/types": "7.16.0",
2361
+ "eslint-visitor-keys": "^3.4.3"
2362
+ },
2363
+ "engines": {
2364
+ "node": "^18.18.0 || >=20.0.0"
2365
+ },
2366
+ "funding": {
2367
+ "type": "opencollective",
2368
+ "url": "https://opencollective.com/typescript-eslint"
2369
+ }
2370
+ },
2371
+ "node_modules/@typescript-eslint/rule-tester/node_modules/brace-expansion": {
2372
+ "version": "2.0.1",
2373
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
2374
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
2375
+ "dev": true,
2376
+ "license": "MIT",
2377
+ "dependencies": {
2378
+ "balanced-match": "^1.0.0"
2379
+ }
2380
+ },
2381
+ "node_modules/@typescript-eslint/rule-tester/node_modules/minimatch": {
2382
+ "version": "9.0.5",
2383
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2384
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2385
+ "dev": true,
2386
+ "license": "ISC",
2387
+ "dependencies": {
2388
+ "brace-expansion": "^2.0.1"
2389
+ },
2390
+ "engines": {
2391
+ "node": ">=16 || 14 >=14.17"
2392
+ },
2393
+ "funding": {
2394
+ "url": "https://github.com/sponsors/isaacs"
2395
+ }
2396
+ },
2397
+ "node_modules/@typescript-eslint/rule-tester/node_modules/semver": {
2398
+ "version": "7.6.3",
2399
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
2400
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
2401
+ "dev": true,
2402
+ "license": "ISC",
2403
+ "bin": {
2404
+ "semver": "bin/semver.js"
2405
+ },
2406
+ "engines": {
2407
+ "node": ">=10"
2408
+ }
2409
+ },
2243
2410
  "node_modules/@typescript-eslint/scope-manager": {
2244
2411
  "version": "7.1.1",
2245
2412
  "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.1.tgz",
@@ -2282,6 +2449,43 @@
2282
2449
  }
2283
2450
  }
2284
2451
  },
2452
+ "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/utils": {
2453
+ "version": "7.1.1",
2454
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
2455
+ "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
2456
+ "license": "MIT",
2457
+ "dependencies": {
2458
+ "@eslint-community/eslint-utils": "^4.4.0",
2459
+ "@types/json-schema": "^7.0.12",
2460
+ "@types/semver": "^7.5.0",
2461
+ "@typescript-eslint/scope-manager": "7.1.1",
2462
+ "@typescript-eslint/types": "7.1.1",
2463
+ "@typescript-eslint/typescript-estree": "7.1.1",
2464
+ "semver": "^7.5.4"
2465
+ },
2466
+ "engines": {
2467
+ "node": "^16.0.0 || >=18.0.0"
2468
+ },
2469
+ "funding": {
2470
+ "type": "opencollective",
2471
+ "url": "https://opencollective.com/typescript-eslint"
2472
+ },
2473
+ "peerDependencies": {
2474
+ "eslint": "^8.56.0"
2475
+ }
2476
+ },
2477
+ "node_modules/@typescript-eslint/type-utils/node_modules/semver": {
2478
+ "version": "7.6.3",
2479
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
2480
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
2481
+ "license": "ISC",
2482
+ "bin": {
2483
+ "semver": "bin/semver.js"
2484
+ },
2485
+ "engines": {
2486
+ "node": ">=10"
2487
+ }
2488
+ },
2285
2489
  "node_modules/@typescript-eslint/types": {
2286
2490
  "version": "7.1.1",
2287
2491
  "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.1.1.tgz",
@@ -2358,20 +2562,18 @@
2358
2562
  }
2359
2563
  },
2360
2564
  "node_modules/@typescript-eslint/utils": {
2361
- "version": "7.1.1",
2362
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
2363
- "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
2565
+ "version": "7.16.0",
2566
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz",
2567
+ "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==",
2568
+ "license": "MIT",
2364
2569
  "dependencies": {
2365
2570
  "@eslint-community/eslint-utils": "^4.4.0",
2366
- "@types/json-schema": "^7.0.12",
2367
- "@types/semver": "^7.5.0",
2368
- "@typescript-eslint/scope-manager": "7.1.1",
2369
- "@typescript-eslint/types": "7.1.1",
2370
- "@typescript-eslint/typescript-estree": "7.1.1",
2371
- "semver": "^7.5.4"
2571
+ "@typescript-eslint/scope-manager": "7.16.0",
2572
+ "@typescript-eslint/types": "7.16.0",
2573
+ "@typescript-eslint/typescript-estree": "7.16.0"
2372
2574
  },
2373
2575
  "engines": {
2374
- "node": "^16.0.0 || >=18.0.0"
2576
+ "node": "^18.18.0 || >=20.0.0"
2375
2577
  },
2376
2578
  "funding": {
2377
2579
  "type": "opencollective",
@@ -2381,13 +2583,110 @@
2381
2583
  "eslint": "^8.56.0"
2382
2584
  }
2383
2585
  },
2384
- "node_modules/@typescript-eslint/utils/node_modules/semver": {
2385
- "version": "7.6.0",
2386
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
2387
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
2586
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
2587
+ "version": "7.16.0",
2588
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz",
2589
+ "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==",
2590
+ "license": "MIT",
2388
2591
  "dependencies": {
2389
- "lru-cache": "^6.0.0"
2592
+ "@typescript-eslint/types": "7.16.0",
2593
+ "@typescript-eslint/visitor-keys": "7.16.0"
2594
+ },
2595
+ "engines": {
2596
+ "node": "^18.18.0 || >=20.0.0"
2597
+ },
2598
+ "funding": {
2599
+ "type": "opencollective",
2600
+ "url": "https://opencollective.com/typescript-eslint"
2601
+ }
2602
+ },
2603
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
2604
+ "version": "7.16.0",
2605
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz",
2606
+ "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==",
2607
+ "license": "MIT",
2608
+ "engines": {
2609
+ "node": "^18.18.0 || >=20.0.0"
2610
+ },
2611
+ "funding": {
2612
+ "type": "opencollective",
2613
+ "url": "https://opencollective.com/typescript-eslint"
2614
+ }
2615
+ },
2616
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
2617
+ "version": "7.16.0",
2618
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz",
2619
+ "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==",
2620
+ "license": "BSD-2-Clause",
2621
+ "dependencies": {
2622
+ "@typescript-eslint/types": "7.16.0",
2623
+ "@typescript-eslint/visitor-keys": "7.16.0",
2624
+ "debug": "^4.3.4",
2625
+ "globby": "^11.1.0",
2626
+ "is-glob": "^4.0.3",
2627
+ "minimatch": "^9.0.4",
2628
+ "semver": "^7.6.0",
2629
+ "ts-api-utils": "^1.3.0"
2630
+ },
2631
+ "engines": {
2632
+ "node": "^18.18.0 || >=20.0.0"
2633
+ },
2634
+ "funding": {
2635
+ "type": "opencollective",
2636
+ "url": "https://opencollective.com/typescript-eslint"
2637
+ },
2638
+ "peerDependenciesMeta": {
2639
+ "typescript": {
2640
+ "optional": true
2641
+ }
2642
+ }
2643
+ },
2644
+ "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
2645
+ "version": "7.16.0",
2646
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz",
2647
+ "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==",
2648
+ "license": "MIT",
2649
+ "dependencies": {
2650
+ "@typescript-eslint/types": "7.16.0",
2651
+ "eslint-visitor-keys": "^3.4.3"
2390
2652
  },
2653
+ "engines": {
2654
+ "node": "^18.18.0 || >=20.0.0"
2655
+ },
2656
+ "funding": {
2657
+ "type": "opencollective",
2658
+ "url": "https://opencollective.com/typescript-eslint"
2659
+ }
2660
+ },
2661
+ "node_modules/@typescript-eslint/utils/node_modules/brace-expansion": {
2662
+ "version": "2.0.1",
2663
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
2664
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
2665
+ "license": "MIT",
2666
+ "dependencies": {
2667
+ "balanced-match": "^1.0.0"
2668
+ }
2669
+ },
2670
+ "node_modules/@typescript-eslint/utils/node_modules/minimatch": {
2671
+ "version": "9.0.5",
2672
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
2673
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
2674
+ "license": "ISC",
2675
+ "dependencies": {
2676
+ "brace-expansion": "^2.0.1"
2677
+ },
2678
+ "engines": {
2679
+ "node": ">=16 || 14 >=14.17"
2680
+ },
2681
+ "funding": {
2682
+ "url": "https://github.com/sponsors/isaacs"
2683
+ }
2684
+ },
2685
+ "node_modules/@typescript-eslint/utils/node_modules/semver": {
2686
+ "version": "7.6.3",
2687
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
2688
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
2689
+ "license": "ISC",
2391
2690
  "bin": {
2392
2691
  "semver": "bin/semver.js"
2393
2692
  },
@@ -7721,9 +8020,10 @@
7721
8020
  "dev": true
7722
8021
  },
7723
8022
  "node_modules/ts-api-utils": {
7724
- "version": "1.2.1",
7725
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
7726
- "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
8023
+ "version": "1.3.0",
8024
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
8025
+ "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
8026
+ "license": "MIT",
7727
8027
  "engines": {
7728
8028
  "node": ">=16"
7729
8029
  },
@@ -7857,10 +8157,10 @@
7857
8157
  }
7858
8158
  },
7859
8159
  "node_modules/typescript": {
7860
- "version": "5.4.2",
7861
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
7862
- "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
7863
- "peer": true,
8160
+ "version": "5.5.4",
8161
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
8162
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==",
8163
+ "license": "Apache-2.0",
7864
8164
  "bin": {
7865
8165
  "tsc": "bin/tsc",
7866
8166
  "tsserver": "bin/tsserver"
@@ -9900,6 +10200,16 @@
9900
10200
  "@types/istanbul-lib-report": "*"
9901
10201
  }
9902
10202
  },
10203
+ "@types/jest": {
10204
+ "version": "29.5.12",
10205
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-29.5.12.tgz",
10206
+ "integrity": "sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==",
10207
+ "dev": true,
10208
+ "requires": {
10209
+ "expect": "^29.0.0",
10210
+ "pretty-format": "^29.0.0"
10211
+ }
10212
+ },
9903
10213
  "@types/json-schema": {
9904
10214
  "version": "7.0.15",
9905
10215
  "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -9963,6 +10273,20 @@
9963
10273
  "ts-api-utils": "^1.0.1"
9964
10274
  },
9965
10275
  "dependencies": {
10276
+ "@typescript-eslint/utils": {
10277
+ "version": "7.1.1",
10278
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
10279
+ "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
10280
+ "requires": {
10281
+ "@eslint-community/eslint-utils": "^4.4.0",
10282
+ "@types/json-schema": "^7.0.12",
10283
+ "@types/semver": "^7.5.0",
10284
+ "@typescript-eslint/scope-manager": "7.1.1",
10285
+ "@typescript-eslint/types": "7.1.1",
10286
+ "@typescript-eslint/typescript-estree": "7.1.1",
10287
+ "semver": "^7.5.4"
10288
+ }
10289
+ },
9966
10290
  "semver": {
9967
10291
  "version": "7.6.0",
9968
10292
  "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
@@ -9985,6 +10309,78 @@
9985
10309
  "debug": "^4.3.4"
9986
10310
  }
9987
10311
  },
10312
+ "@typescript-eslint/rule-tester": {
10313
+ "version": "7.16.0",
10314
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/rule-tester/-/rule-tester-7.16.0.tgz",
10315
+ "integrity": "sha512-MLDeDEY8BVZiWkIhtMnEkhiwH7CXDOLKDnHntFZp//WHYdso+1gS/8F60+oTb+Xjw4LkWz4D8sJ4js3ZxMoctA==",
10316
+ "dev": true,
10317
+ "requires": {
10318
+ "@typescript-eslint/typescript-estree": "7.16.0",
10319
+ "@typescript-eslint/utils": "7.16.0",
10320
+ "ajv": "^6.12.6",
10321
+ "json-stable-stringify-without-jsonify": "^1.0.1",
10322
+ "lodash.merge": "4.6.2",
10323
+ "semver": "^7.6.0"
10324
+ },
10325
+ "dependencies": {
10326
+ "@typescript-eslint/types": {
10327
+ "version": "7.16.0",
10328
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz",
10329
+ "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw==",
10330
+ "dev": true
10331
+ },
10332
+ "@typescript-eslint/typescript-estree": {
10333
+ "version": "7.16.0",
10334
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz",
10335
+ "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==",
10336
+ "dev": true,
10337
+ "requires": {
10338
+ "@typescript-eslint/types": "7.16.0",
10339
+ "@typescript-eslint/visitor-keys": "7.16.0",
10340
+ "debug": "^4.3.4",
10341
+ "globby": "^11.1.0",
10342
+ "is-glob": "^4.0.3",
10343
+ "minimatch": "^9.0.4",
10344
+ "semver": "^7.6.0",
10345
+ "ts-api-utils": "^1.3.0"
10346
+ }
10347
+ },
10348
+ "@typescript-eslint/visitor-keys": {
10349
+ "version": "7.16.0",
10350
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz",
10351
+ "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==",
10352
+ "dev": true,
10353
+ "requires": {
10354
+ "@typescript-eslint/types": "7.16.0",
10355
+ "eslint-visitor-keys": "^3.4.3"
10356
+ }
10357
+ },
10358
+ "brace-expansion": {
10359
+ "version": "2.0.1",
10360
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
10361
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
10362
+ "dev": true,
10363
+ "requires": {
10364
+ "balanced-match": "^1.0.0"
10365
+ }
10366
+ },
10367
+ "minimatch": {
10368
+ "version": "9.0.5",
10369
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
10370
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
10371
+ "dev": true,
10372
+ "requires": {
10373
+ "brace-expansion": "^2.0.1"
10374
+ }
10375
+ },
10376
+ "semver": {
10377
+ "version": "7.6.3",
10378
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
10379
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==",
10380
+ "dev": true
10381
+ }
10382
+ }
10383
+ },
9988
10384
  "@typescript-eslint/scope-manager": {
9989
10385
  "version": "7.1.1",
9990
10386
  "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.1.1.tgz",
@@ -10003,6 +10399,27 @@
10003
10399
  "@typescript-eslint/utils": "7.1.1",
10004
10400
  "debug": "^4.3.4",
10005
10401
  "ts-api-utils": "^1.0.1"
10402
+ },
10403
+ "dependencies": {
10404
+ "@typescript-eslint/utils": {
10405
+ "version": "7.1.1",
10406
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
10407
+ "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
10408
+ "requires": {
10409
+ "@eslint-community/eslint-utils": "^4.4.0",
10410
+ "@types/json-schema": "^7.0.12",
10411
+ "@types/semver": "^7.5.0",
10412
+ "@typescript-eslint/scope-manager": "7.1.1",
10413
+ "@typescript-eslint/types": "7.1.1",
10414
+ "@typescript-eslint/typescript-estree": "7.1.1",
10415
+ "semver": "^7.5.4"
10416
+ }
10417
+ },
10418
+ "semver": {
10419
+ "version": "7.6.3",
10420
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
10421
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
10422
+ }
10006
10423
  }
10007
10424
  },
10008
10425
  "@typescript-eslint/types": {
@@ -10052,26 +10469,74 @@
10052
10469
  }
10053
10470
  },
10054
10471
  "@typescript-eslint/utils": {
10055
- "version": "7.1.1",
10056
- "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.1.1.tgz",
10057
- "integrity": "sha512-thOXM89xA03xAE0lW7alstvnyoBUbBX38YtY+zAUcpRPcq9EIhXPuJ0YTv948MbzmKh6e1AUszn5cBFK49Umqg==",
10472
+ "version": "7.16.0",
10473
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.16.0.tgz",
10474
+ "integrity": "sha512-PqP4kP3hb4r7Jav+NiRCntlVzhxBNWq6ZQ+zQwII1y/G/1gdIPeYDCKr2+dH6049yJQsWZiHU6RlwvIFBXXGNA==",
10058
10475
  "requires": {
10059
10476
  "@eslint-community/eslint-utils": "^4.4.0",
10060
- "@types/json-schema": "^7.0.12",
10061
- "@types/semver": "^7.5.0",
10062
- "@typescript-eslint/scope-manager": "7.1.1",
10063
- "@typescript-eslint/types": "7.1.1",
10064
- "@typescript-eslint/typescript-estree": "7.1.1",
10065
- "semver": "^7.5.4"
10477
+ "@typescript-eslint/scope-manager": "7.16.0",
10478
+ "@typescript-eslint/types": "7.16.0",
10479
+ "@typescript-eslint/typescript-estree": "7.16.0"
10066
10480
  },
10067
10481
  "dependencies": {
10068
- "semver": {
10069
- "version": "7.6.0",
10070
- "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
10071
- "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
10482
+ "@typescript-eslint/scope-manager": {
10483
+ "version": "7.16.0",
10484
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.16.0.tgz",
10485
+ "integrity": "sha512-8gVv3kW6n01Q6TrI1cmTZ9YMFi3ucDT7i7aI5lEikk2ebk1AEjrwX8MDTdaX5D7fPXMBLvnsaa0IFTAu+jcfOw==",
10072
10486
  "requires": {
10073
- "lru-cache": "^6.0.0"
10487
+ "@typescript-eslint/types": "7.16.0",
10488
+ "@typescript-eslint/visitor-keys": "7.16.0"
10489
+ }
10490
+ },
10491
+ "@typescript-eslint/types": {
10492
+ "version": "7.16.0",
10493
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.16.0.tgz",
10494
+ "integrity": "sha512-fecuH15Y+TzlUutvUl9Cc2XJxqdLr7+93SQIbcZfd4XRGGKoxyljK27b+kxKamjRkU7FYC6RrbSCg0ALcZn/xw=="
10495
+ },
10496
+ "@typescript-eslint/typescript-estree": {
10497
+ "version": "7.16.0",
10498
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.16.0.tgz",
10499
+ "integrity": "sha512-a5NTvk51ZndFuOLCh5OaJBELYc2O3Zqxfl3Js78VFE1zE46J2AaVuW+rEbVkQznjkmlzWsUI15BG5tQMixzZLw==",
10500
+ "requires": {
10501
+ "@typescript-eslint/types": "7.16.0",
10502
+ "@typescript-eslint/visitor-keys": "7.16.0",
10503
+ "debug": "^4.3.4",
10504
+ "globby": "^11.1.0",
10505
+ "is-glob": "^4.0.3",
10506
+ "minimatch": "^9.0.4",
10507
+ "semver": "^7.6.0",
10508
+ "ts-api-utils": "^1.3.0"
10509
+ }
10510
+ },
10511
+ "@typescript-eslint/visitor-keys": {
10512
+ "version": "7.16.0",
10513
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.16.0.tgz",
10514
+ "integrity": "sha512-rMo01uPy9C7XxG7AFsxa8zLnWXTF8N3PYclekWSrurvhwiw1eW88mrKiAYe6s53AUY57nTRz8dJsuuXdkAhzCg==",
10515
+ "requires": {
10516
+ "@typescript-eslint/types": "7.16.0",
10517
+ "eslint-visitor-keys": "^3.4.3"
10518
+ }
10519
+ },
10520
+ "brace-expansion": {
10521
+ "version": "2.0.1",
10522
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
10523
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
10524
+ "requires": {
10525
+ "balanced-match": "^1.0.0"
10074
10526
  }
10527
+ },
10528
+ "minimatch": {
10529
+ "version": "9.0.5",
10530
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz",
10531
+ "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==",
10532
+ "requires": {
10533
+ "brace-expansion": "^2.0.1"
10534
+ }
10535
+ },
10536
+ "semver": {
10537
+ "version": "7.6.3",
10538
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz",
10539
+ "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A=="
10075
10540
  }
10076
10541
  }
10077
10542
  },
@@ -13887,9 +14352,9 @@
13887
14352
  "dev": true
13888
14353
  },
13889
14354
  "ts-api-utils": {
13890
- "version": "1.2.1",
13891
- "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz",
13892
- "integrity": "sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA==",
14355
+ "version": "1.3.0",
14356
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
14357
+ "integrity": "sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==",
13893
14358
  "requires": {}
13894
14359
  },
13895
14360
  "tsconfig-paths": {
@@ -13984,10 +14449,9 @@
13984
14449
  }
13985
14450
  },
13986
14451
  "typescript": {
13987
- "version": "5.4.2",
13988
- "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.2.tgz",
13989
- "integrity": "sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==",
13990
- "peer": true
14452
+ "version": "5.5.4",
14453
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.5.4.tgz",
14454
+ "integrity": "sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q=="
13991
14455
  },
13992
14456
  "uc.micro": {
13993
14457
  "version": "2.1.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-primer-react",
3
- "version": "5.5.0-rc.40574df",
3
+ "version": "6.0.0-rc.0df8090",
4
4
  "description": "ESLint rules for Primer React",
5
5
  "main": "src/index.js",
6
6
  "scripts": {
@@ -33,7 +33,9 @@
33
33
  "eslint-plugin-jsx-a11y": "^6.7.1",
34
34
  "eslint-traverse": "^1.0.0",
35
35
  "lodash": "^4.17.21",
36
- "styled-system": "^5.1.5"
36
+ "styled-system": "^5.1.5",
37
+ "@typescript-eslint/utils": "7.16.0",
38
+ "typescript": "^5.5.3"
37
39
  },
38
40
  "devDependencies": {
39
41
  "@changesets/changelog-github": "^0.5.0",
@@ -44,7 +46,9 @@
44
46
  "eslint-plugin-prettier": "^5.0.1",
45
47
  "jest": "^29.7.0",
46
48
  "markdownlint-cli2": "^0.13.0",
47
- "markdownlint-cli2-formatter-pretty": "^0.0.6"
49
+ "markdownlint-cli2-formatter-pretty": "^0.0.6",
50
+ "@typescript-eslint/rule-tester": "7.16.0",
51
+ "@types/jest": "^29.5.12"
48
52
  },
49
53
  "prettier": "@github/prettier-config"
50
54
  }
@@ -18,6 +18,7 @@ module.exports = {
18
18
  'primer-react/no-deprecated-props': 'warn',
19
19
  'primer-react/a11y-remove-disable-tooltip': 'error',
20
20
  'primer-react/a11y-use-next-tooltip': 'error',
21
+ 'primer-react/no-unnecessary-components': 'error',
21
22
  },
22
23
  settings: {
23
24
  github: {
package/src/index.js CHANGED
@@ -11,6 +11,7 @@ module.exports = {
11
11
  'a11y-remove-disable-tooltip': require('./rules/a11y-remove-disable-tooltip'),
12
12
  'a11y-use-next-tooltip': require('./rules/a11y-use-next-tooltip'),
13
13
  'use-deprecated-from-deprecated': require('./rules/use-deprecated-from-deprecated'),
14
+ 'primer-react/no-unnecessary-components': require('./rules/no-unnecessary-components'),
14
15
  },
15
16
  configs: {
16
17
  recommended: require('./configs/recommended'),
@@ -0,0 +1 @@
1
+ // https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
@@ -0,0 +1 @@
1
+ // https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
@@ -0,0 +1,7 @@
1
+ // https://typescript-eslint.io/packages/rule-tester/#type-aware-testing
2
+ {
3
+ "compilerOptions": {
4
+ "strict": true
5
+ },
6
+ "include": ["file.ts", "File.tsx"]
7
+ }
@@ -0,0 +1,153 @@
1
+ // @ts-check
2
+
3
+ const {RuleTester} = require('@typescript-eslint/rule-tester')
4
+
5
+ const path = require('node:path')
6
+ const rule = require('../no-unnecessary-components')
7
+ const {components} = require('../no-unnecessary-components')
8
+
9
+ const prcImport = 'import React from "react"; import {Box, Text} from "@primer/react";'
10
+ const brandImport = 'import React from "react"; import {Box, Text} from "@primer/brand";'
11
+
12
+ /** @param {string} content */
13
+ const jsx = content => `export const Component = () => <>${content}</>`
14
+
15
+ const sxObjectDeclaration = `const props = {sx: {color: "red"}};`
16
+ const asObjectDeclaration = `const props = {as: "table"};`
17
+ const stringRecordDeclaration = `const props: Record<string, any> = {};`
18
+ const testIdObjectDeclaration = `const props = {'data-testid': 'xyz'};`
19
+ const componentDeclaration = `const OtherComponent = ({children}: {children: React.ReactNode}) => <>{children}</>;`
20
+ const asConstDeclaration = `const as = "p";`
21
+
22
+ const ruleTester = new RuleTester({
23
+ parser: '@typescript-eslint/parser',
24
+ parserOptions: {
25
+ tsconfigRootDir: path.resolve(__dirname, 'fixtures'),
26
+ project: path.resolve(__dirname, 'fixtures', 'tsconfig.json'),
27
+ },
28
+ defaultFilenames: {
29
+ ts: 'file.ts',
30
+ tsx: 'File.tsx',
31
+ },
32
+ })
33
+
34
+ jest.retryTimes(0, {logErrorsBeforeRetry: true})
35
+
36
+ const filename = 'File.tsx'
37
+
38
+ ruleTester.run('unnecessary-components', rule, {
39
+ valid: [
40
+ {name: 'Unrelated JSX', code: jsx('<span>Hello World</span>'), filename},
41
+ ...Object.keys(components).flatMap(component => [
42
+ {
43
+ name: `Non-PRC ${component}`,
44
+ code: `${brandImport}${jsx(`<${component}>Hello World</${component}>`)}`,
45
+ filename,
46
+ },
47
+ {
48
+ name: `${component} with sx prop`,
49
+ code: `${prcImport}${jsx(`<${component} sx={{color: "red"}}>Hello World</${component}>`)}`,
50
+ filename,
51
+ },
52
+ {
53
+ name: `${component} with any styled-system prop`,
54
+ code: `${prcImport}${jsx(`<${component} flex="row">Hello World</${component}>`)}`,
55
+ filename,
56
+ },
57
+ {
58
+ name: `${component} with spread sx prop`,
59
+ code: `${prcImport}${sxObjectDeclaration}${jsx(`<${component} {...props}>Hello World</${component}>`)}`,
60
+ filename,
61
+ },
62
+ {
63
+ name: `${component} with string index spread props`,
64
+ code: `${prcImport}${stringRecordDeclaration}${jsx(`<${component} {...props}>Hello World</${component}>`)}`,
65
+ filename,
66
+ },
67
+ ]),
68
+ {
69
+ name: `Text with weight prop`,
70
+ code: `${prcImport}${jsx(`<Text weight='medium'>Hello World</Text>`)}`,
71
+ filename,
72
+ },
73
+ {
74
+ name: `Text with size prop`,
75
+ code: `${prcImport}${jsx(`<Text size='small'>Hello World</Text>`)}`,
76
+ filename,
77
+ },
78
+ ],
79
+ invalid: Object.entries(components).flatMap(([component, {messageId, replacement}]) => [
80
+ {
81
+ name: `${component} without any styled-system props`,
82
+ code: `${prcImport}${jsx(`<${component}>Hello World</${component}>`)}`,
83
+ output: `${prcImport}${jsx(`<${replacement}>Hello World</${replacement}>`)}`,
84
+ errors: [{messageId}],
85
+ filename,
86
+ },
87
+ {
88
+ name: `Self-closing ${component} without any styled-system props`,
89
+ code: `${prcImport}${jsx(`<${component} />`)}`,
90
+ output: `${prcImport}${jsx(`<${replacement} />`)}`,
91
+ errors: [{messageId}],
92
+ filename,
93
+ },
94
+ {
95
+ name: `${component} with spread props without sx`,
96
+ code: `${prcImport}${testIdObjectDeclaration}${jsx(`<${component} {...props}>Hello World</${component}>`)}`,
97
+ output: `${prcImport}${testIdObjectDeclaration}${jsx(`<${replacement} {...props}>Hello World</${replacement}>`)}`,
98
+ errors: [{messageId}],
99
+ filename,
100
+ },
101
+ {
102
+ name: `${component} with string element 'as' prop`,
103
+ code: `${prcImport}${jsx(`<${component} as="code">Hello world</${component}>`)}`,
104
+ // There is extra whitespace here we don't worry about since formatters would get rid of it
105
+ output: `${prcImport}${jsx(`<code >Hello world</code>`)}`,
106
+ errors: [{messageId}],
107
+ filename,
108
+ },
109
+ {
110
+ name: `${component} with single-character 'as' prop`,
111
+ code: `${prcImport}${jsx(`<${component} as="p">Hello world</${component}>`)}`,
112
+ output: `${prcImport}${jsx(`<p >Hello world</p>`)}`,
113
+ errors: [{messageId}],
114
+ filename,
115
+ },
116
+ {
117
+ name: `${component} with string element 'as' prop surrounded by unnecessary braces`,
118
+ code: `${prcImport}${jsx(`<${component} as={"code"}>Hello world</${component}>`)}`,
119
+ output: `${prcImport}${jsx(`<code >Hello world</code>`)}`,
120
+ errors: [{messageId}],
121
+ filename,
122
+ },
123
+ {
124
+ name: `${component} with component reference 'as' prop`,
125
+ code: `${prcImport}${componentDeclaration}${jsx(`<${component} as={OtherComponent}>Hello world</${component}>`)}`,
126
+ output: `${prcImport}${componentDeclaration}${jsx(`<OtherComponent >Hello world</OtherComponent>`)}`,
127
+ errors: [{messageId}],
128
+ filename,
129
+ },
130
+ {
131
+ name: `${component} with spread 'as' prop`,
132
+ code: `${prcImport}${asObjectDeclaration}${jsx(`<${component} {...props}>Hello world</${component}>`)}`,
133
+ output: null,
134
+ errors: [{messageId}],
135
+ filename,
136
+ },
137
+ {
138
+ name: `${component} with unusable lowercase reference 'as' prop`,
139
+ code: `${prcImport}${asConstDeclaration}${jsx(`<${component} as={as}>Hello world</${component}>`)}`,
140
+ output: null,
141
+ errors: [{messageId}],
142
+ filename,
143
+ },
144
+ {
145
+ name: `Non-PRC ${component} when \`skipImportCheck\` is enabled`,
146
+ code: `${brandImport}${jsx(`<${component}>Hello World</${component}>`)}`,
147
+ output: `${brandImport}${jsx(`<${replacement}>Hello World</${replacement}>`)}`,
148
+ filename,
149
+ errors: [{messageId}],
150
+ options: [{skipImportCheck: true}],
151
+ },
152
+ ]),
153
+ })
@@ -0,0 +1,160 @@
1
+ // @ts-check
2
+
3
+ const {ESLintUtils} = require('@typescript-eslint/utils')
4
+ const {IndexKind} = require('typescript')
5
+ const {pick: pickStyledSystemProps} = require('@styled-system/props')
6
+ const {isPrimerComponent} = require('../utils/is-primer-component')
7
+
8
+ /** @typedef {import('@typescript-eslint/types').TSESTree.JSXAttribute} JSXAttribute */
9
+
10
+ const components = {
11
+ Box: {
12
+ replacement: 'div',
13
+ messageId: 'unecessaryBox',
14
+ message: 'Prefer plain HTML elements over `Box` when not using `sx` for styling.',
15
+ allowedProps: new Set(['sx']), // + styled-system props
16
+ },
17
+ Text: {
18
+ replacement: 'span',
19
+ messageId: 'unecessarySpan',
20
+ message: 'Prefer plain HTML elements over `Text` when not using `sx` for styling.',
21
+ allowedProps: new Set(['sx', 'size', 'weight']), // + styled-system props
22
+ },
23
+ }
24
+
25
+ const elementNameRegex = /^[a-z]\w*$/
26
+ const componentNameRegex = /^[A-Z][\w._]*$/
27
+
28
+ /** @param {string} propName */
29
+ const isStyledSystemProp = propName => propName in pickStyledSystemProps({[propName]: propName})
30
+
31
+ const rule = ESLintUtils.RuleCreator.withoutDocs({
32
+ meta: {
33
+ docs: {
34
+ description:
35
+ '`Box` and `Text` should only be used to provide access to the `sx` styling system and have a performance cost. If `sx` props are not being used, prefer `div` and `span` instead.',
36
+ },
37
+ messages: {
38
+ [components.Box.messageId]: components.Box.message,
39
+ [components.Text.messageId]: components.Text.message,
40
+ },
41
+ type: 'problem',
42
+ schema: [
43
+ {
44
+ type: 'object',
45
+ properties: {
46
+ skipImportCheck: {
47
+ type: 'boolean',
48
+ },
49
+ },
50
+ additionalProperties: false,
51
+ },
52
+ ],
53
+ fixable: 'code',
54
+ },
55
+ defaultOptions: [{skipImportCheck: false}],
56
+ create(context) {
57
+ return {
58
+ JSXElement({openingElement, closingElement}) {
59
+ const {name, attributes} = openingElement
60
+
61
+ // Ensure this is one of the components we are looking for. Note this doesn't account for import aliases; this
62
+ // is intentional to avoid having to do the scope tree traversal for every component of every name, which would
63
+ // be needlessly expensive. We just ignore aliased imports.
64
+ if (name.type !== 'JSXIdentifier' || !(name.name in components)) return
65
+ const componentConfig = components[/** @type {keyof typeof components} */ (name.name)]
66
+
67
+ // Only continue if the variable declaration is an import from @primer/react. Otherwise it could, for example,
68
+ // be an import from @primer/brand, which would be valid without sx.
69
+ const skipImportCheck = context.options[0]?.skipImportCheck
70
+ const isPrimer = skipImportCheck || isPrimerComponent(name, context.sourceCode.getScope(openingElement))
71
+ if (!isPrimer) return
72
+
73
+ /** @param {string} name */
74
+ const isAllowedProp = name => componentConfig.allowedProps.has(name) || isStyledSystemProp(name)
75
+
76
+ // Validate the attributes and ensure an allowed prop is present or spreaded in
77
+ /** @type {typeof attributes[number] | undefined | null} */
78
+ let asProp = undefined
79
+ for (const attribute of attributes) {
80
+ // If there is a spread type, check if the type of the spreaded value has an allowed property
81
+ if (attribute.type === 'JSXSpreadAttribute') {
82
+ const services = ESLintUtils.getParserServices(context)
83
+ const typeChecker = services.program.getTypeChecker()
84
+
85
+ const spreadType = services.getTypeAtLocation(attribute.argument)
86
+
87
+ // Check if the spread type has a string index signature - this could hide an allowed property
88
+ if (typeChecker.getIndexTypeOfType(spreadType, IndexKind.String) !== undefined) return
89
+
90
+ const spreadPropNames = typeChecker.getPropertiesOfType(spreadType).map(prop => prop.getName())
91
+
92
+ // If an allowed prop gets spread in, this is a valid use of the component
93
+ if (spreadPropNames.some(isAllowedProp)) return
94
+
95
+ // If there is an `as` inside the spread object, we can't autofix reliably
96
+ if (spreadPropNames.includes('as')) asProp = null
97
+
98
+ continue
99
+ }
100
+
101
+ // Has an allowed prop, so should keep using this component
102
+ if (attribute.name.type === 'JSXIdentifier' && isAllowedProp(attribute.name.name)) return
103
+
104
+ // If there is an `as` prop we will need to account for that when autofixing
105
+ if (attribute.name.type === 'JSXIdentifier' && attribute.name.name === 'as') asProp = attribute
106
+ }
107
+
108
+ // Determine a replacement component name accounting for the `as` prop if present
109
+ /** @type {string | null} */
110
+ let replacement = componentConfig.replacement
111
+ if (asProp === null) {
112
+ // {...{as: 'something-unusable'}}
113
+ replacement = null
114
+ } else if (asProp?.type === 'JSXAttribute') {
115
+ // as={ComponentReference}
116
+ if (asProp.value?.type === 'JSXExpressionContainer' && asProp.value.expression.type === 'Identifier') {
117
+ // can't just use expression.name here because we want the whole expression if it's A.B
118
+ const expressionStr = context.sourceCode.getText(asProp.value.expression)
119
+ replacement = componentNameRegex.test(expressionStr) ? expressionStr : null
120
+ }
121
+ // as={'tagName'} (surprisingly common, we really should enable `react/jsx-curly-brace-presence`)
122
+ else if (
123
+ asProp.value?.type === 'JSXExpressionContainer' &&
124
+ asProp.value.expression.type === 'Literal' &&
125
+ typeof asProp.value.expression.value === 'string' &&
126
+ elementNameRegex.test(asProp.value.expression.value)
127
+ ) {
128
+ replacement = asProp.value.expression.value
129
+ }
130
+ // as="tagName"
131
+ else if (
132
+ asProp.value?.type === 'Literal' &&
133
+ typeof asProp.value.value === 'string' &&
134
+ elementNameRegex.test(asProp.value.value)
135
+ ) {
136
+ replacement = asProp.value.value
137
+ }
138
+ // too complex to autofix
139
+ else {
140
+ replacement = null
141
+ }
142
+ }
143
+
144
+ context.report({
145
+ node: name,
146
+ messageId: componentConfig.messageId,
147
+ fix: replacement
148
+ ? function* (fixer) {
149
+ yield fixer.replaceText(name, replacement)
150
+ if (closingElement) yield fixer.replaceText(closingElement.name, replacement)
151
+ if (asProp) yield fixer.remove(asProp)
152
+ }
153
+ : undefined,
154
+ })
155
+ },
156
+ }
157
+ },
158
+ })
159
+
160
+ module.exports = {...rule, components}
@@ -1,5 +1,6 @@
1
1
  const {isImportedFrom} = require('./is-imported-from')
2
2
 
3
+ /** @returns {boolean} */
3
4
  function isPrimerComponent(name, scope) {
4
5
  let identifier
5
6