eslint-plugin-primer-react 8.2.1 → 8.3.0-rc.4b58a28
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "eslint-plugin-primer-react",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.3.0-rc.4b58a28",
|
|
4
4
|
"description": "ESLint rules for Primer React",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"engines": {
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"@github/markdownlint-github": "^0.6.3",
|
|
47
47
|
"@github/prettier-config": "0.0.6",
|
|
48
48
|
"@types/jest": "^30.0.0",
|
|
49
|
-
"@typescript-eslint/rule-tester": "8.44.
|
|
49
|
+
"@typescript-eslint/rule-tester": "8.44.1",
|
|
50
50
|
"eslint": "^9.0.0",
|
|
51
51
|
"eslint-plugin-eslint-comments": "^3.2.0",
|
|
52
52
|
"eslint-plugin-filenames": "^1.3.2",
|
|
@@ -3,6 +3,7 @@ const {RuleTester} = require('eslint')
|
|
|
3
3
|
|
|
4
4
|
const ruleTester = new RuleTester({
|
|
5
5
|
languageOptions: {
|
|
6
|
+
parser: require(require.resolve('@typescript-eslint/parser', {paths: [require.resolve('eslint-plugin-github')]})),
|
|
6
7
|
ecmaVersion: 'latest',
|
|
7
8
|
sourceType: 'module',
|
|
8
9
|
parserOptions: {
|
|
@@ -25,6 +26,8 @@ ruleTester.run('use-styled-react-import', rule, {
|
|
|
25
26
|
|
|
26
27
|
// Valid: Utilities imported from styled-react
|
|
27
28
|
`import { sx } from '@primer/styled-react'`,
|
|
29
|
+
`import { useTheme } from '@primer/styled-react'`,
|
|
30
|
+
`import { sx, useTheme } from '@primer/styled-react'`,
|
|
28
31
|
|
|
29
32
|
// Valid: Component not in the styled list
|
|
30
33
|
`import { Avatar } from '@primer/react'
|
|
@@ -40,6 +43,14 @@ ruleTester.run('use-styled-react-import', rule, {
|
|
|
40
43
|
|
|
41
44
|
// Valid: Component without sx prop imported from styled-react (when not used)
|
|
42
45
|
`import { Button } from '@primer/styled-react'`,
|
|
46
|
+
|
|
47
|
+
// Valid: allowedComponents without sx prop imported from styled-react
|
|
48
|
+
`import { ThemeProvider, BaseStyles } from '@primer/styled-react'
|
|
49
|
+
const Component = ({children}) => <ThemeProvider><BaseStyles>{children}</BaseStyles></ThemeProvider>`,
|
|
50
|
+
|
|
51
|
+
// Valid: Component with sx prop AND allowedComponents
|
|
52
|
+
`import { ThemeProvider, Button } from '@primer/styled-react'
|
|
53
|
+
const Component = () => <ThemeProvider><Button sx={{ color: 'btn.bg'}}>Click me</Button></ThemeProvider>`,
|
|
43
54
|
],
|
|
44
55
|
invalid: [
|
|
45
56
|
// Invalid: Box with sx prop imported from @primer/react
|
|
@@ -205,6 +216,43 @@ import { Box } from '@primer/styled-react'
|
|
|
205
216
|
},
|
|
206
217
|
],
|
|
207
218
|
},
|
|
219
|
+
{
|
|
220
|
+
code: `import { useTheme } from '@primer/react'`,
|
|
221
|
+
output: `import { useTheme } from '@primer/styled-react'`,
|
|
222
|
+
errors: [
|
|
223
|
+
{
|
|
224
|
+
messageId: 'moveToStyledReact',
|
|
225
|
+
data: {importName: 'useTheme'},
|
|
226
|
+
},
|
|
227
|
+
],
|
|
228
|
+
},
|
|
229
|
+
{
|
|
230
|
+
code: `import { sx, useTheme } from '@primer/react'`,
|
|
231
|
+
output: `import { sx, useTheme } from '@primer/styled-react'`,
|
|
232
|
+
errors: [
|
|
233
|
+
{
|
|
234
|
+
messageId: 'moveToStyledReact',
|
|
235
|
+
data: {importName: 'sx'},
|
|
236
|
+
},
|
|
237
|
+
{
|
|
238
|
+
messageId: 'moveToStyledReact',
|
|
239
|
+
data: {importName: 'useTheme'},
|
|
240
|
+
},
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
|
|
244
|
+
// Invalid: Utility import from @primer/react that should be from styled-react, mixed with other imports
|
|
245
|
+
{
|
|
246
|
+
code: `import { sx, useAnchoredPosition } from '@primer/react'`,
|
|
247
|
+
output: `import { useAnchoredPosition } from '@primer/react'
|
|
248
|
+
import { sx } from '@primer/styled-react'`,
|
|
249
|
+
errors: [
|
|
250
|
+
{
|
|
251
|
+
messageId: 'moveToStyledReact',
|
|
252
|
+
data: {importName: 'sx'},
|
|
253
|
+
},
|
|
254
|
+
],
|
|
255
|
+
},
|
|
208
256
|
|
|
209
257
|
// Invalid: Button and Link, only Button uses sx
|
|
210
258
|
{
|
|
@@ -335,6 +383,61 @@ import { Button } from '@primer/react'
|
|
|
335
383
|
},
|
|
336
384
|
],
|
|
337
385
|
},
|
|
386
|
+
|
|
387
|
+
// Invalid: ThemeProvider and BaseStyles - should move to styled-react
|
|
388
|
+
{
|
|
389
|
+
code: `
|
|
390
|
+
import { ThemeProvider, BaseStyles } from '@primer/react'
|
|
391
|
+
`,
|
|
392
|
+
output: `
|
|
393
|
+
import { ThemeProvider, BaseStyles } from '@primer/styled-react'
|
|
394
|
+
`,
|
|
395
|
+
errors: [
|
|
396
|
+
{
|
|
397
|
+
messageId: 'moveToStyledReact',
|
|
398
|
+
data: {importName: 'ThemeProvider'},
|
|
399
|
+
},
|
|
400
|
+
{
|
|
401
|
+
messageId: 'moveToStyledReact',
|
|
402
|
+
data: {importName: 'BaseStyles'},
|
|
403
|
+
},
|
|
404
|
+
],
|
|
405
|
+
},
|
|
406
|
+
|
|
407
|
+
// Invalid: ThemeProvider, Button without sx prop - only ThemeProvider should be from styled-react
|
|
408
|
+
{
|
|
409
|
+
code: `
|
|
410
|
+
import { ThemeProvider, Button } from '@primer/react'
|
|
411
|
+
const Component = () => <ThemeProvider><Button>Click me</Button></ThemeProvider>
|
|
412
|
+
`,
|
|
413
|
+
output: `
|
|
414
|
+
import { Button } from '@primer/react'
|
|
415
|
+
import { ThemeProvider } from '@primer/styled-react'
|
|
416
|
+
const Component = () => <ThemeProvider><Button>Click me</Button></ThemeProvider>
|
|
417
|
+
`,
|
|
418
|
+
errors: [
|
|
419
|
+
{
|
|
420
|
+
messageId: 'moveToStyledReact',
|
|
421
|
+
data: {importName: 'ThemeProvider'},
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
},
|
|
425
|
+
|
|
426
|
+
// Invalid: Utility and type imports from @primer/react that should be from styled-react
|
|
427
|
+
{
|
|
428
|
+
code: `import { sx, type SxProp } from '@primer/react'`,
|
|
429
|
+
output: `import { sx, type SxProp } from '@primer/styled-react'`,
|
|
430
|
+
errors: [
|
|
431
|
+
{
|
|
432
|
+
messageId: 'moveToStyledReact',
|
|
433
|
+
data: {importName: 'sx'},
|
|
434
|
+
},
|
|
435
|
+
{
|
|
436
|
+
messageId: 'moveToStyledReact',
|
|
437
|
+
data: {importName: 'SxProp'},
|
|
438
|
+
},
|
|
439
|
+
],
|
|
440
|
+
},
|
|
338
441
|
],
|
|
339
442
|
})
|
|
340
443
|
|
|
@@ -23,13 +23,17 @@ const defaultStyledComponents = [
|
|
|
23
23
|
'Truncate',
|
|
24
24
|
'Octicon',
|
|
25
25
|
'Dialog',
|
|
26
|
+
'ThemeProvider',
|
|
27
|
+
'BaseStyles',
|
|
26
28
|
]
|
|
27
29
|
|
|
30
|
+
const componentsToAlwaysImportFromStyledReact = new Set(['ThemeProvider', 'BaseStyles'])
|
|
31
|
+
|
|
28
32
|
// Default types that should be imported from @primer/styled-react
|
|
29
33
|
const defaultStyledTypes = ['BoxProps', 'SxProp', 'BetterSystemStyleObject']
|
|
30
34
|
|
|
31
35
|
// Default utilities that should be imported from @primer/styled-react
|
|
32
|
-
const defaultStyledUtilities = ['sx']
|
|
36
|
+
const defaultStyledUtilities = ['sx', 'useTheme']
|
|
33
37
|
|
|
34
38
|
/**
|
|
35
39
|
* @type {import('eslint').Rule.RuleModule}
|
|
@@ -246,7 +250,11 @@ module.exports = {
|
|
|
246
250
|
// Report errors for components used WITHOUT sx prop that are imported from @primer/styled-react
|
|
247
251
|
for (const componentName of allUsedComponents) {
|
|
248
252
|
// If component is used but NOT with sx prop, and it's imported from styled-react
|
|
249
|
-
if (
|
|
253
|
+
if (
|
|
254
|
+
!componentsWithSx.has(componentName) &&
|
|
255
|
+
styledReactImports.has(componentName) &&
|
|
256
|
+
!componentsToAlwaysImportFromStyledReact.has(componentName)
|
|
257
|
+
) {
|
|
250
258
|
const importInfo = styledReactImports.get(componentName)
|
|
251
259
|
context.report({
|
|
252
260
|
node: importInfo.specifier,
|
|
@@ -337,53 +345,67 @@ module.exports = {
|
|
|
337
345
|
}
|
|
338
346
|
}
|
|
339
347
|
|
|
340
|
-
// Also report for types and
|
|
348
|
+
// Also report for types, utilities and components that should always be from styled-react
|
|
341
349
|
for (const [importName, importInfo] of primerReactImports) {
|
|
342
|
-
if (
|
|
350
|
+
if (
|
|
351
|
+
(styledTypes.has(importName) ||
|
|
352
|
+
styledUtilities.has(importName) ||
|
|
353
|
+
componentsToAlwaysImportFromStyledReact.has(importName)) &&
|
|
354
|
+
!styledReactImports.has(importName)
|
|
355
|
+
) {
|
|
343
356
|
context.report({
|
|
344
357
|
node: importInfo.specifier,
|
|
345
358
|
messageId: 'moveToStyledReact',
|
|
346
359
|
data: {importName},
|
|
347
360
|
fix(fixer) {
|
|
348
361
|
const {node: importNode, specifier, importSource} = importInfo
|
|
349
|
-
const otherSpecifiers = importNode.specifiers.filter(s => s !== specifier)
|
|
350
362
|
|
|
351
|
-
|
|
352
|
-
const styledReactPath = importSource.replace('@primer/react', '@primer/styled-react')
|
|
363
|
+
const fixes = []
|
|
353
364
|
|
|
354
|
-
//
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
return fixer.replaceText(importNode, `import { ${prefix}${importName} } from '${styledReactPath}'`)
|
|
358
|
-
}
|
|
365
|
+
// we consolidate all the fixes for the import in the first specifier
|
|
366
|
+
const isFirst = importNode.specifiers[0] === specifier
|
|
367
|
+
if (!isFirst) return null
|
|
359
368
|
|
|
360
|
-
|
|
361
|
-
|
|
369
|
+
const specifiersToMove = importNode.specifiers.filter(specifier => {
|
|
370
|
+
const name = specifier.imported.name
|
|
371
|
+
return (
|
|
372
|
+
styledUtilities.has(name) ||
|
|
373
|
+
styledTypes.has(name) ||
|
|
374
|
+
componentsToAlwaysImportFromStyledReact.has(name)
|
|
375
|
+
)
|
|
376
|
+
})
|
|
377
|
+
|
|
378
|
+
const remainingSpecifiers = importNode.specifiers.filter(specifier => {
|
|
379
|
+
return !specifiersToMove.includes(specifier)
|
|
380
|
+
})
|
|
381
|
+
|
|
382
|
+
// Convert @primer/react path to @primer/styled-react path
|
|
383
|
+
const styledReactPath = importSource.replace('@primer/react', '@primer/styled-react')
|
|
362
384
|
|
|
363
|
-
|
|
364
|
-
|
|
385
|
+
if (remainingSpecifiers.length === 0) {
|
|
386
|
+
// if there are no remaining specifiers, we can remove the whole import
|
|
365
387
|
fixes.push(fixer.remove(importNode))
|
|
366
388
|
} else {
|
|
367
|
-
const
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
} else if (isLast) {
|
|
374
|
-
const prevSpecifier = importNode.specifiers[importNode.specifiers.length - 2]
|
|
375
|
-
fixes.push(fixer.removeRange([prevSpecifier.range[1], specifier.range[1]]))
|
|
376
|
-
} else {
|
|
377
|
-
const nextSpecifier = importNode.specifiers[importNode.specifiers.indexOf(specifier) + 1]
|
|
378
|
-
fixes.push(fixer.removeRange([specifier.range[0], nextSpecifier.range[0]]))
|
|
379
|
-
}
|
|
389
|
+
const remainingNames = remainingSpecifiers.map(spec =>
|
|
390
|
+
spec.importKind === 'type' ? `type ${spec.imported.name}` : spec.imported.name,
|
|
391
|
+
)
|
|
392
|
+
fixes.push(
|
|
393
|
+
fixer.replaceText(importNode, `import { ${remainingNames.join(', ')} } from '${importSource}'`),
|
|
394
|
+
)
|
|
380
395
|
}
|
|
381
396
|
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
397
|
+
if (specifiersToMove.length > 0) {
|
|
398
|
+
const movedComponents = specifiersToMove.map(spec =>
|
|
399
|
+
spec.importKind === 'type' ? `type ${spec.imported.name}` : spec.imported.name,
|
|
400
|
+
)
|
|
401
|
+
const onNewLine = remainingSpecifiers.length > 0
|
|
402
|
+
fixes.push(
|
|
403
|
+
fixer.insertTextAfter(
|
|
404
|
+
importNode,
|
|
405
|
+
`${onNewLine ? '\n' : ''}import { ${movedComponents.join(', ')} } from '${styledReactPath}'`,
|
|
406
|
+
),
|
|
407
|
+
)
|
|
408
|
+
}
|
|
387
409
|
|
|
388
410
|
return fixes
|
|
389
411
|
},
|