Skip to content

Commit 2b5dde9

Browse files
committed
Add rule to enforce default import aliases
1 parent 6815513 commit 2b5dde9

File tree

6 files changed

+317
-0
lines changed

6 files changed

+317
-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.14.0] - 2018-08-13
910
* 69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx
@@ -490,6 +491,7 @@ for info on changes for earlier releases.
490491
[`no-default-export`]: ./docs/rules/no-default-export.md
491492
[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md
492493
[`no-cycle`]: ./docs/rules/no-cycle.md
494+
[`rename-default-import`]: ./docs/rules/rename-default-import.md
493495

494496
[`memo-parser`]: ./memo-parser/README.md
495497

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

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
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", // key: name of the module, value: desired binding for default import
24+
"./foo": "Foo"
25+
}
26+
]
27+
}
28+
}
29+
```
30+
31+
The following is considered valid:
32+
33+
```js
34+
import Foo from './foo'
35+
36+
import {default as PropTypes} from 'prop-types'
37+
38+
import PropTypes from 'prop-types'
39+
```
40+
41+
...and the following cases are reported:
42+
43+
```js
44+
import propTypes from 'prop-types';
45+
import {default as propTypes} from 'prop-types';
46+
```
47+
48+
49+
## When not to use it
50+
51+
As long as you don't want to enforce specific naming for default imports.
52+
53+
## Options
54+
55+
This rule accepts an object which is a mapping
56+
between package name and the binding name that should be used for default imports.
57+
For example, a configuration like the one below
58+
59+
`{'prop-types': 'PropTypes'}`
60+
61+
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: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
if (specifier.type === 'ImportDefaultSpecifier') {
11+
return true
12+
} else if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'default') {
13+
return true
14+
} else {
15+
return false
16+
}
17+
}
18+
19+
20+
function handleImport(context, specifier, source) {
21+
const {value: packageName} = source
22+
const {local: {name: importAlias}} = specifier
23+
const mappings = context.options[0]
24+
25+
if (Object.keys(mappings).indexOf(packageName) === -1) {
26+
return
27+
}
28+
29+
if (mappings[packageName] !== importAlias) {
30+
context.report({
31+
node: source,
32+
message: `Default import from '${packageName}' should be aliased to `
33+
+ `${mappings[packageName]}, not ${importAlias}`,
34+
fix: fixer => {
35+
let newAlias = mappings[packageName]
36+
if (specifier.imported && specifier.imported.name === 'default') {
37+
newAlias = `default as ${mappings[packageName]}`
38+
}
39+
40+
return fixer.replaceText(specifier, newAlias)
41+
},
42+
})
43+
44+
const declaredVariable = context.getDeclaredVariables(specifier)[0]
45+
for (const variableReference of declaredVariable.references) {
46+
context.report({
47+
node: variableReference.identifier,
48+
message: `Using incorrect binding name '${variableReference.identifier.name}' `
49+
+ `instead of ${mappings[packageName]} for `
50+
+ `default import from package ${packageName}`,
51+
fix: fixer => {
52+
return fixer.replaceText(variableReference.identifier, mappings[packageName])
53+
},
54+
})
55+
}
56+
}
57+
}
58+
59+
60+
module.exports = {
61+
meta: {
62+
docs: {
63+
url: docsUrl('rename-default-import'),
64+
recommended: false,
65+
},
66+
schema: [
67+
{
68+
type: 'object',
69+
},
70+
],
71+
fixable: 'code',
72+
},
73+
create: function(context) {
74+
return {
75+
'ImportDeclaration': function(node) {
76+
const {source, specifiers} = node
77+
const options = context.options
78+
79+
if (options.length === 0 || Object.keys(options[0]).length === 0) {
80+
return
81+
}
82+
83+
for (const specifier of specifiers) {
84+
if (!isDefaultImport(specifier)) {
85+
continue
86+
}
87+
88+
handleImport(context, specifier, source)
89+
}
90+
},
91+
}
92+
},
93+
}
Lines changed: 158 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
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: `import PropTypes from 'prop-types';`,
12+
options: [],
13+
}),
14+
test({
15+
code: `import PropTypes from 'prop-types';`,
16+
options: [{'foo': 'Foo'}],
17+
}),
18+
test({
19+
code: `import PropTypes from 'prop-types';`,
20+
options: [{'prop-types': 'PropTypes'}],
21+
}),
22+
test({
23+
code: `import PropTypes, {Foo} from 'prop-types';`,
24+
options: [{'prop-types': 'PropTypes'}],
25+
}),
26+
test({
27+
code: `import {default as PropTypes} from 'prop-types';`,
28+
options: [{'prop-types': 'PropTypes'}],
29+
}),
30+
test({
31+
code: `import {Foo} from 'prop-types';`,
32+
options: [{'prop-types': 'PropTypes'}],
33+
}),
34+
test({
35+
code: `import * as PropTypes from 'prop-types';`,
36+
options: [{'prop-types': 'PropTypes'}]
37+
}),
38+
],
39+
invalid: [
40+
test({
41+
code: `import propTypes from 'prop-types';`,
42+
options: [{'prop-types': 'PropTypes'}],
43+
output: `import PropTypes from 'prop-types';`,
44+
errors: [{
45+
ruleId: 'rename-default-import',
46+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
47+
}],
48+
}),
49+
test({
50+
code: `import propTypes, {B} from 'prop-types';`,
51+
options: [{'prop-types': 'PropTypes'}],
52+
output: `import PropTypes, {B} from 'prop-types';`,
53+
errors: [{
54+
ruleId: 'rename-default-import',
55+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
56+
}],
57+
}),
58+
test({
59+
code: `import {default as propTypes} from 'prop-types';`,
60+
options: [{'prop-types': 'PropTypes'}],
61+
output: `import {default as PropTypes} from 'prop-types';`,
62+
errors: [{
63+
ruleId: 'rename-default-import',
64+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
65+
}],
66+
}),
67+
test({
68+
code: `import propTypes from 'prop-types';import foo from 'foo';`,
69+
options: [{'prop-types': 'PropTypes', 'foo': 'Foo'}],
70+
output: `import PropTypes from 'prop-types';import Foo from 'foo';`,
71+
errors: [{
72+
ruleId: 'rename-default-import',
73+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
74+
}, {
75+
ruleId: 'rename-default-import',
76+
message: `Default import from 'foo' should be aliased to Foo, not foo`,
77+
}],
78+
}),
79+
test({
80+
code: `
81+
import propTypes from 'prop-types';
82+
83+
const obj = {
84+
foo: propTypes.string
85+
}
86+
`,
87+
options: [{'prop-types': 'PropTypes'}],
88+
output: `
89+
import PropTypes from 'prop-types';
90+
91+
const obj = {
92+
foo: PropTypes.string
93+
}
94+
`,
95+
errors: [{
96+
ruleId: 'rename-default-import',
97+
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
98+
}, {
99+
ruleId: 'rename-default-import',
100+
message: `Using incorrect binding name 'propTypes' instead of PropTypes for default import from package prop-types`
101+
}],
102+
}),
103+
test({
104+
code: `
105+
import foo from 'bar';
106+
const a = foo.foo();
107+
const b = bar(foo);
108+
const c = (foo) => {
109+
foo();
110+
};
111+
c(foo)
112+
const d = (bar) => {
113+
bar();
114+
};
115+
d(foo);
116+
const e = () => {
117+
foo();
118+
};
119+
`,
120+
options: [{'bar': 'Foo'}],
121+
output: `
122+
import Foo from 'bar';
123+
const a = Foo.foo();
124+
const b = bar(Foo);
125+
const c = (foo) => {
126+
foo();
127+
};
128+
c(Foo)
129+
const d = (bar) => {
130+
bar();
131+
};
132+
d(Foo);
133+
const e = () => {
134+
Foo();
135+
};
136+
`,
137+
errors: [{
138+
ruleId: 'rename-default-import',
139+
message: `Default import from 'bar' should be aliased to Foo, not foo`,
140+
}, {
141+
ruleId: 'rename-default-import',
142+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
143+
}, {
144+
ruleId: 'rename-default-import',
145+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
146+
}, {
147+
ruleId: 'rename-default-import',
148+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
149+
}, {
150+
ruleId: 'rename-default-import',
151+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
152+
}, {
153+
ruleId: 'rename-default-import',
154+
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
155+
}]
156+
})
157+
]
158+
})

0 commit comments

Comments
 (0)