Skip to content

Commit db0623e

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

File tree

6 files changed

+317
-0
lines changed

6 files changed

+317
-0
lines changed

CHANGELOG.md

+2
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

+2
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 a specific binding name 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

+59
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# import/rename-default-import
2+
3+
This rule will enforce a specific binding name 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+
```json
18+
// .eslintrc
19+
{
20+
"rules": {
21+
"import/rename-default-import": [
22+
"warn",
23+
{"prop-types": "PropTypes", "Foo": "Foo"}
24+
]
25+
}
26+
}
27+
```
28+
29+
The following is considered valid:
30+
31+
```js
32+
import Foo from './foo'
33+
34+
import {default as PropTypes} from 'prop-types'
35+
36+
import PropTypes from 'prop-types'
37+
```
38+
39+
...and the following cases are reported:
40+
41+
```js
42+
import propTypes from 'prop-types';
43+
import {default as propTypes} from 'prop-types';
44+
```
45+
46+
47+
## When not to use it
48+
49+
As long as you don't want to enforce specific naming for default imports.
50+
51+
## Options
52+
53+
This rule accepts an object which is a mapping
54+
between package name and the binding name that should be used for default imports.
55+
For example, a configuration like the one below
56+
57+
`{'prop-types': 'PropTypes'}`
58+
59+
specifies that default import for the package `prop-types` should be aliased to `PropTypes`.

src/index.js

+1
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

+87
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).indexOf(packageName) === -1) {
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 binding name '${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+
}
+166
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
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: [{'foo': 'Foo'}],
21+
}),
22+
test({
23+
code: `
24+
import PropTypes from 'prop-types';
25+
`,
26+
options: [{'prop-types': 'PropTypes'}],
27+
}),
28+
test({
29+
code: `
30+
import PropTypes, {Foo} from 'prop-types';
31+
`,
32+
options: [{'prop-types': 'PropTypes'}],
33+
}),
34+
test({
35+
code: `
36+
import {default as PropTypes} from 'prop-types';
37+
`,
38+
options: [{'prop-types': 'PropTypes'}],
39+
}),
40+
test({
41+
code: `
42+
import {Foo} from 'prop-types';
43+
`,
44+
options: [{'prop-types': 'PropTypes'}],
45+
}),
46+
],
47+
invalid: [
48+
test({
49+
code: `import propTypes from 'prop-types';`,
50+
options: [{'prop-types': 'PropTypes'}],
51+
output: `import PropTypes from 'prop-types';`,
52+
errors: [{
53+
ruleId: 'rename-default-import',
54+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
55+
}],
56+
}),
57+
test({
58+
code: `import propTypes, {B} from 'prop-types';`,
59+
options: [{'prop-types': 'PropTypes'}],
60+
output: `import PropTypes, {B} from 'prop-types';`,
61+
errors: [{
62+
ruleId: 'rename-default-import',
63+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
64+
}],
65+
}),
66+
test({
67+
code: `import {default as propTypes} from 'prop-types';`,
68+
options: [{'prop-types': 'PropTypes'}],
69+
output: `import {default as PropTypes} from 'prop-types';`,
70+
errors: [{
71+
ruleId: 'rename-default-import',
72+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
73+
}],
74+
}),
75+
test({
76+
code: `import propTypes from 'prop-types';import foo from 'foo';`,
77+
options: [{'prop-types': 'PropTypes', 'foo': 'Foo'}],
78+
output: `import PropTypes from 'prop-types';import Foo from 'foo';`,
79+
errors: [{
80+
ruleId: 'rename-default-import',
81+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
82+
}, {
83+
ruleId: 'rename-default-import',
84+
message: `Default import from 'foo' should be aliased to Foo, not foo`,
85+
}],
86+
}),
87+
test({
88+
code: `
89+
import propTypes from 'prop-types';
90+
91+
const obj = {
92+
foo: propTypes.string
93+
}
94+
`,
95+
options: [{'prop-types': 'PropTypes'}],
96+
output: `
97+
import PropTypes from 'prop-types';
98+
99+
const obj = {
100+
foo: PropTypes.string
101+
}
102+
`,
103+
errors: [{
104+
ruleId: 'rename-default-import',
105+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
106+
}, {
107+
ruleId: 'rename-default-import',
108+
message: `Using incorrect binding name 'propTypes' instead of PropTypes for default import from package prop-types`
109+
}],
110+
}),
111+
test({
112+
code: `
113+
import foo from 'bar';
114+
const a = foo.foo();
115+
const b = bar(foo);
116+
const c = (foo) => {
117+
foo();
118+
};
119+
c(foo)
120+
const d = (bar) => {
121+
bar();
122+
};
123+
d(foo);
124+
const e = () => {
125+
foo();
126+
};
127+
`,
128+
options: [{'bar': 'Foo'}],
129+
output: `
130+
import Foo from 'bar';
131+
const a = Foo.foo();
132+
const b = bar(Foo);
133+
const c = (foo) => {
134+
foo();
135+
};
136+
c(Foo)
137+
const d = (bar) => {
138+
bar();
139+
};
140+
d(Foo);
141+
const e = () => {
142+
Foo();
143+
};
144+
`,
145+
errors: [{
146+
ruleId: 'rename-default-import',
147+
message: `Default import from 'bar' should be aliased to Foo, not foo`,
148+
}, {
149+
ruleId: 'rename-default-import',
150+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
151+
}, {
152+
ruleId: 'rename-default-import',
153+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
154+
}, {
155+
ruleId: 'rename-default-import',
156+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
157+
}, {
158+
ruleId: 'rename-default-import',
159+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
160+
}, {
161+
ruleId: 'rename-default-import',
162+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
163+
}]
164+
})
165+
]
166+
})

0 commit comments

Comments
 (0)