Skip to content

Commit 9ea1200

Browse files
committed
Add rule to enforce default import aliases
1 parent 5480240 commit 9ea1200

File tree

6 files changed

+308
-0
lines changed

6 files changed

+308
-0
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
44
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).
55

66
## [Unreleased]
7+
- Add [`rename-default-import`] rule: Enforce default import naming
78

89
## [2.13.0] - 2018-06-24
910
### Added
@@ -470,6 +471,7 @@ for info on changes for earlier releases.
470471
[`no-default-export`]: ./docs/rules/no-default-export.md
471472
[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md
472473
[`no-cycle`]: ./docs/rules/no-cycle.md
474+
[`rename-default-import`]: ./docs/rules/rename-default-import.md
473475

474476
[`memo-parser`]: ./memo-parser/README.md
475477

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
8989
* Forbid anonymous values as default exports ([`no-anonymous-default-export`])
9090
* Prefer named exports to be grouped together in a single export declaration ([`group-exports`])
9191
* Enforce a leading comment with the webpackChunkName for dynamic imports ([`dynamic-import-chunkname`])
92+
* Enforce an alias for the default package import ([`rename-default-import`])
9293

9394
[`first`]: ./docs/rules/first.md
9495
[`exports-last`]: ./docs/rules/exports-last.md
@@ -105,6 +106,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
105106
[`group-exports`]: ./docs/rules/group-exports.md
106107
[`no-default-export`]: ./docs/rules/no-default-export.md
107108
[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md
109+
[`rename-default-import`]: ./docs/rules/rename-default-import.md
108110

109111
## Installation
110112

docs/rules/rename-default-import.md

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# import/rename-default-import
2+
3+
This rule will enforce an alias for a default package import. Only ES6 imports are processed.
4+
5+
6+
## Rule Details
7+
8+
Given:
9+
10+
```js
11+
// ./foo.js
12+
export default function () { return 'Foo' }
13+
```
14+
15+
and
16+
17+
```yaml
18+
// .eslintrc
19+
rules:
20+
import/rename-default-import:
21+
- warn
22+
- prop-types: PropTypes
23+
Foo: Foo // default for package Foo should be aliased to Foo
24+
```
25+
26+
The following is considered valid:
27+
28+
```js
29+
import Foo from './foo'
30+
31+
import {default as PropTypes} from 'prop-types'
32+
33+
import PropTypes from 'prop-types'
34+
```
35+
36+
...and the following cases are reported:
37+
38+
```js
39+
import propTypes from 'prop-types';
40+
import {default as propTypes} from 'prop-types';
41+
```
42+
43+
44+
## When not to use it
45+
46+
As long as you don't want to enforce specific naming for default imports.
47+
48+
## Options
49+
50+
This rule accepts an object which is a mapping
51+
between package names and the aliases that should be used for default imports.
52+
For example, a configuration like the one below
53+
54+
`{'prop-types': 'PropTypes'}`
55+
56+
specifies that default import for the package `prop-types` should be aliased to `PropTypes`.

src/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export const rules = {
3737
'no-unassigned-import': require('./rules/no-unassigned-import'),
3838
'no-useless-path-segments': require('./rules/no-useless-path-segments'),
3939
'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'),
40+
'rename-default-import': require('./rules/rename-default-import'),
4041

4142
// export
4243
'exports-last': require('./rules/exports-last'),

src/rules/rename-default-import.js

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/**
2+
* @fileoverview Rule to enforce aliases for default imports
3+
* @author Michał Kołodziejski
4+
*/
5+
6+
import docsUrl from '../docsUrl'
7+
8+
9+
function isDefaultImport(specifier) {
10+
return specifier.type === 'ImportDefaultSpecifier' || specifier.imported.name === 'default'
11+
}
12+
13+
14+
function handleImport(context, specifier, source) {
15+
const {value: packageName} = source
16+
const {local: {name: importAlias}} = specifier
17+
const mappings = context.options[0]
18+
19+
if (!Object.keys(mappings).includes(packageName)) {
20+
return
21+
}
22+
23+
if (mappings[packageName] !== importAlias) {
24+
context.report({
25+
node: source,
26+
message: `Default import from '${packageName}' should be aliased to `
27+
+ `${mappings[packageName]}, not ${importAlias}`,
28+
fix: fixer => {
29+
let newAlias = mappings[packageName]
30+
if (specifier.imported && specifier.imported.name === 'default') {
31+
newAlias = `default as ${mappings[packageName]}`
32+
}
33+
34+
return fixer.replaceText(specifier, newAlias)
35+
},
36+
})
37+
38+
const declaredVariable = context.getDeclaredVariables(specifier)[0]
39+
for (const variableReference of declaredVariable.references) {
40+
context.report({
41+
node: variableReference.identifier,
42+
message: `Using incorrect alias '${variableReference.identifier.name}' `
43+
+ `instead of ${mappings[packageName]} for `
44+
+ `default import from package ${packageName}`,
45+
fix: fixer => {
46+
return fixer.replaceText(variableReference.identifier, mappings[packageName])
47+
},
48+
})
49+
}
50+
}
51+
}
52+
53+
54+
module.exports = {
55+
meta: {
56+
docs: {
57+
url: docsUrl('rename-default-import'),
58+
recommended: false,
59+
},
60+
schema: [
61+
{
62+
type: 'object',
63+
},
64+
],
65+
fixable: 'code',
66+
},
67+
create: function(context) {
68+
return {
69+
'ImportDeclaration': function(node) {
70+
const {source, specifiers} = node
71+
const options = context.options
72+
73+
if (options.length === 0 || Object.keys(options[0]).length === 0) {
74+
return
75+
}
76+
77+
for (const specifier of specifiers) {
78+
if (!isDefaultImport(specifier)) {
79+
continue
80+
}
81+
82+
handleImport(context, specifier, source)
83+
}
84+
},
85+
}
86+
},
87+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
import { test } from '../utils'
2+
3+
import { RuleTester } from 'eslint'
4+
5+
const ruleTester = new RuleTester(),
6+
rule = require('rules/rename-default-import')
7+
8+
ruleTester.run('rename-default-import', rule, {
9+
valid: [
10+
test({
11+
code: `
12+
import PropTypes from 'prop-types';
13+
`,
14+
options: [],
15+
}),
16+
test({
17+
code: `
18+
import PropTypes from 'prop-types';
19+
`,
20+
options: [{'prop-types': 'PropTypes'}],
21+
}),
22+
test({
23+
code: `
24+
import PropTypes, {Foo} from 'prop-types';
25+
`,
26+
options: [{'prop-types': 'PropTypes'}],
27+
}),
28+
test({
29+
code: `
30+
import {default as PropTypes} from 'prop-types';
31+
`,
32+
options: [{'prop-types': 'PropTypes'}],
33+
}),
34+
test({
35+
code: `
36+
import {Foo} from 'prop-types';
37+
`,
38+
options: [{'prop-types': 'PropTypes'}],
39+
}),
40+
],
41+
invalid: [
42+
test({
43+
code: `import propTypes from 'prop-types';`,
44+
options: [{'prop-types': 'PropTypes'}],
45+
output: `import PropTypes from 'prop-types';`,
46+
errors: [{
47+
ruleId: 'rename-default-import',
48+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
49+
}],
50+
}),
51+
test({
52+
code: `import propTypes, {B} from 'prop-types';`,
53+
options: [{'prop-types': 'PropTypes'}],
54+
output: `import PropTypes, {B} from 'prop-types';`,
55+
errors: [{
56+
ruleId: 'rename-default-import',
57+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
58+
}],
59+
}),
60+
test({
61+
code: `import {default as propTypes} from 'prop-types';`,
62+
options: [{'prop-types': 'PropTypes'}],
63+
output: `import {default as PropTypes} from 'prop-types';`,
64+
errors: [{
65+
ruleId: 'rename-default-import',
66+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
67+
}],
68+
}),
69+
test({
70+
code: `import propTypes from 'prop-types';import foo from 'foo';`,
71+
options: [{'prop-types': 'PropTypes', 'foo': 'Foo'}],
72+
output: `import PropTypes from 'prop-types';import Foo from 'foo';`,
73+
errors: [{
74+
ruleId: 'rename-default-import',
75+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
76+
}, {
77+
ruleId: 'rename-default-import',
78+
message: `Default import from 'foo' should be aliased to Foo, not foo`,
79+
}],
80+
}),
81+
test({
82+
code: `
83+
import propTypes from 'prop-types';
84+
85+
const obj = {
86+
foo: propTypes.string
87+
}
88+
`,
89+
options: [{'prop-types': 'PropTypes'}],
90+
output: `
91+
import PropTypes from 'prop-types';
92+
93+
const obj = {
94+
foo: PropTypes.string
95+
}
96+
`,
97+
errors: [{
98+
ruleId: 'rename-default-import',
99+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
100+
}, {
101+
ruleId: 'rename-default-import',
102+
message: `Using incorrect alias 'propTypes' instead of PropTypes for default import from package prop-types`
103+
}],
104+
}),
105+
test({
106+
code: `
107+
import foo from 'bar';
108+
const a = foo.foo();
109+
const b = bar(foo);
110+
const c = (foo) => {
111+
foo();
112+
};
113+
c(foo)
114+
const d = (bar) => {
115+
bar();
116+
};
117+
d(foo);
118+
const e = () => {
119+
foo();
120+
};
121+
`,
122+
options: [{'bar': 'Foo'}],
123+
output: `
124+
import Foo from 'bar';
125+
const a = Foo.foo();
126+
const b = bar(Foo);
127+
const c = (foo) => {
128+
foo();
129+
};
130+
c(Foo)
131+
const d = (bar) => {
132+
bar();
133+
};
134+
d(Foo);
135+
const e = () => {
136+
Foo();
137+
};
138+
`,
139+
errors: [{
140+
ruleId: 'rename-default-import',
141+
message: `Default import from 'bar' should be aliased to Foo, not foo`,
142+
}, {
143+
ruleId: 'rename-default-import',
144+
message: `Using incorrect alias 'foo' instead of Foo for default import from package bar`,
145+
}, {
146+
ruleId: 'rename-default-import',
147+
message: `Using incorrect alias 'foo' instead of Foo for default import from package bar`,
148+
}, {
149+
ruleId: 'rename-default-import',
150+
message: `Using incorrect alias 'foo' instead of Foo for default import from package bar`,
151+
}, {
152+
ruleId: 'rename-default-import',
153+
message: `Using incorrect alias 'foo' instead of Foo for default import from package bar`,
154+
}, {
155+
ruleId: 'rename-default-import',
156+
message: `Using incorrect alias 'foo' instead of Foo for default import from package bar`,
157+
}]
158+
})
159+
]
160+
})

0 commit comments

Comments
 (0)