-
Notifications
You must be signed in to change notification settings - Fork 9
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add use-deprecated-from-deprecated (#204)
* feat: add use-deprecated-from-deprecated * chore: add changeset
- Loading branch information
Showing
5 changed files
with
227 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
--- | ||
'eslint-plugin-primer-react': minor | ||
--- | ||
|
||
Add use-deprecated-from-deprecated rule |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Use Deprecated from Deprecated | ||
|
||
## Rule Details | ||
|
||
This rule enforces the usage of deprecated imports from `@primer/react/deprecated`. | ||
|
||
👎 Examples of **incorrect** code for this rule | ||
|
||
```jsx | ||
import {Dialog} from '@primer/react' | ||
|
||
function ExampleComponent() { | ||
return <Dialog>{/* ... */}</Dialog> | ||
} | ||
``` | ||
|
||
👍 Examples of **correct** code for this rule: | ||
|
||
```jsx | ||
import {Dialog} from '@primer/react/deprecated' | ||
|
||
function ExampleComponent() { | ||
return <Dialog>{/* ... */}</Dialog> | ||
} | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
66 changes: 66 additions & 0 deletions
66
src/rules/__tests__/use-deprecated-from-deprecated.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
'use strict' | ||
|
||
const {RuleTester} = require('eslint') | ||
const rule = require('../use-deprecated-from-deprecated') | ||
|
||
const ruleTester = new RuleTester({ | ||
parserOptions: { | ||
ecmaVersion: 'latest', | ||
sourceType: 'module', | ||
ecmaFeatures: { | ||
jsx: true, | ||
}, | ||
}, | ||
}) | ||
|
||
ruleTester.run('use-deprecated-from-deprecated', rule, { | ||
valid: [], | ||
invalid: [ | ||
// Single deprecated import | ||
{ | ||
code: `import {Tooltip} from '@primer/react'`, | ||
output: `import {Tooltip} from '@primer/react/deprecated'`, | ||
errors: ['Import deprecated components from @primer/react/deprecated'], | ||
}, | ||
|
||
// Single deprecated import with existing deprecated entrypoint | ||
{ | ||
code: `import {Tooltip} from '@primer/react' | ||
import {Dialog} from '@primer/react/deprecated'`, | ||
output: `\nimport {Dialog, Tooltip} from '@primer/react/deprecated'`, | ||
errors: ['Import deprecated components from @primer/react/deprecated'], | ||
}, | ||
|
||
// Multiple deprecated imports | ||
{ | ||
code: `import {Dialog, Tooltip} from '@primer/react'`, | ||
output: `import {Dialog, Tooltip} from '@primer/react/deprecated'`, | ||
errors: ['Import deprecated components from @primer/react/deprecated'], | ||
}, | ||
|
||
// Mixed deprecated and non-deprecated imports | ||
{ | ||
code: `import {Button, Tooltip} from '@primer/react'`, | ||
output: `import {Button, } from '@primer/react' | ||
import {Tooltip} from '@primer/react/deprecated'`, | ||
errors: ['Import deprecated components from @primer/react/deprecated'], | ||
}, | ||
|
||
// Mixed deprecated and non-deprecated imports with existing deprecated | ||
{ | ||
code: `import {Button, Tooltip} from '@primer/react' | ||
import {Dialog} from '@primer/react/deprecated'`, | ||
output: `import {Button, } from '@primer/react' | ||
import {Dialog, Tooltip} from '@primer/react/deprecated'`, | ||
errors: ['Import deprecated components from @primer/react/deprecated'], | ||
}, | ||
|
||
// Multiple mixed deprecated and non-deprecated imports | ||
{ | ||
code: `import {Button, Dialog, Tooltip} from '@primer/react'`, | ||
output: `import {Button, } from '@primer/react' | ||
import {Dialog, Tooltip} from '@primer/react/deprecated'`, | ||
errors: ['Import deprecated components from @primer/react/deprecated'], | ||
}, | ||
], | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
'use strict' | ||
|
||
const url = require('../url') | ||
|
||
const components = [ | ||
{ | ||
identifier: 'Dialog', | ||
entrypoint: '@primer/react', | ||
}, | ||
{ | ||
identifier: 'Octicon', | ||
entrypoint: '@primer/react', | ||
}, | ||
{ | ||
identifier: 'Pagehead', | ||
entrypoint: '@primer/react', | ||
}, | ||
{ | ||
identifier: 'TabNav', | ||
entrypoint: '@primer/react', | ||
}, | ||
{ | ||
identifier: 'Tooltip', | ||
entrypoint: '@primer/react', | ||
}, | ||
] | ||
|
||
const entrypoints = new Map() | ||
|
||
for (const component of components) { | ||
if (!entrypoints.has(component.entrypoint)) { | ||
entrypoints.set(component.entrypoint, new Set()) | ||
} | ||
entrypoints.get(component.entrypoint).add(component.identifier) | ||
} | ||
|
||
/** | ||
* @type {import('eslint').Rule.RuleModule} | ||
*/ | ||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'Use deprecated components from the `@primer/react/deprecated` entrypoint', | ||
recommended: true, | ||
url: url(module), | ||
}, | ||
fixable: true, | ||
schema: [], | ||
}, | ||
create(context) { | ||
const sourceCode = context.getSourceCode() | ||
|
||
return { | ||
ImportDeclaration(node) { | ||
if (!entrypoints.has(node.source.value)) { | ||
return | ||
} | ||
|
||
const entrypoint = entrypoints.get(node.source.value) | ||
const deprecated = node.specifiers.filter(specifier => { | ||
return entrypoint.has(specifier.imported.name) | ||
}) | ||
|
||
if (deprecated.length === 0) { | ||
return | ||
} | ||
|
||
const deprecatedEntrypoint = node.parent.body.find(node => { | ||
if (node.type !== 'ImportDeclaration') { | ||
return false | ||
} | ||
|
||
return node.source.value === '@primer/react/deprecated' | ||
}) | ||
|
||
// All imports are deprecated | ||
if (deprecated.length === node.specifiers.length) { | ||
context.report({ | ||
node, | ||
message: 'Import deprecated components from @primer/react/deprecated', | ||
*fix(fixer) { | ||
if (deprecatedEntrypoint) { | ||
const lastSpecifier = deprecatedEntrypoint.specifiers[deprecatedEntrypoint.specifiers.length - 1] | ||
|
||
yield fixer.remove(node) | ||
yield fixer.insertTextAfter( | ||
lastSpecifier, | ||
`, ${node.specifiers.map(specifier => specifier.imported.name).join(', ')}`, | ||
) | ||
} else { | ||
yield fixer.replaceText(node.source, `'@primer/react/deprecated'`) | ||
} | ||
}, | ||
}) | ||
} else { | ||
// There is a mix of deprecated and non-deprecated imports | ||
context.report({ | ||
node, | ||
message: 'Import deprecated components from @primer/react/deprecated', | ||
*fix(fixer) { | ||
for (const specifier of deprecated) { | ||
yield fixer.remove(specifier) | ||
const comma = sourceCode.getTokenAfter(specifier) | ||
if (comma.value === ',') { | ||
yield fixer.remove(comma) | ||
} | ||
} | ||
|
||
if (deprecatedEntrypoint) { | ||
const lastSpecifier = deprecatedEntrypoint.specifiers[deprecatedEntrypoint.specifiers.length - 1] | ||
yield fixer.insertTextAfter( | ||
lastSpecifier, | ||
`, ${deprecated.map(specifier => specifier.imported.name).join(', ')}`, | ||
) | ||
} else { | ||
yield fixer.insertTextAfter( | ||
node, | ||
`\nimport {${deprecated | ||
.map(specifier => specifier.imported.name) | ||
.join(', ')}} from '@primer/react/deprecated'`, | ||
) | ||
} | ||
}, | ||
}) | ||
} | ||
}, | ||
} | ||
}, | ||
} |