Skip to content

Commit f0f7cef

Browse files
committed
Add rule to enforce default import aliases
1 parent e8954db commit f0f7cef

File tree

6 files changed

+414
-0
lines changed

6 files changed

+414
-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.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

+2
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
9090
* Forbid anonymous values as default exports ([`no-anonymous-default-export`])
9191
* Prefer named exports to be grouped together in a single export declaration ([`group-exports`])
9292
* Enforce a leading comment with the webpackChunkName for dynamic imports ([`dynamic-import-chunkname`])
93+
* Enforce a specific binding name for the default package import ([`rename-default-import`])
9394

9495
[`first`]: ./docs/rules/first.md
9596
[`exports-last`]: ./docs/rules/exports-last.md
@@ -107,6 +108,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
107108
[`no-default-export`]: ./docs/rules/no-default-export.md
108109
[`no-named-export`]: ./docs/rules/no-named-export.md
109110
[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md
111+
[`rename-default-import`]: ./docs/rules/rename-default-import.md
110112

111113
## Installation
112114

docs/rules/rename-default-import.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# import/rename-default-import
2+
3+
This rule will enforce a specific binding name for a default package import.
4+
Works for ES6 imports and CJS require.
5+
6+
7+
## Rule Details
8+
9+
Given:
10+
11+
There is a package `prop-types` with a default export
12+
13+
and
14+
15+
```json
16+
// .eslintrc
17+
{
18+
"rules": {
19+
"import/rename-default-import": [
20+
"warn", {
21+
"prop-types": "PropTypes", // key: name of the module, value: desired binding for default import
22+
}
23+
]
24+
}
25+
}
26+
```
27+
28+
The following is considered valid:
29+
30+
```js
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 name and the binding name 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

+1
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@ export const rules = {
3838
'no-unassigned-import': require('./rules/no-unassigned-import'),
3939
'no-useless-path-segments': require('./rules/no-useless-path-segments'),
4040
'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'),
41+
'rename-default-import': require('./rules/rename-default-import'),
4142

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

src/rules/rename-default-import.js

+141
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
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+
}
15+
return false
16+
}
17+
18+
function isCommonJSImport(declaration) {
19+
const variableInit = declaration.init
20+
if (variableInit.type === 'CallExpression') {
21+
return variableInit.callee.name === 'require'
22+
}
23+
return false
24+
}
25+
26+
function handleImport(context, node, specifierOrDeclaration, packageName, importAlias) {
27+
const mappings = context.options[0] || {}
28+
29+
if (Object.keys(mappings).indexOf(packageName) === -1) {
30+
return
31+
}
32+
33+
if (mappings[packageName] !== importAlias) {
34+
context.report({
35+
node: node,
36+
message: `Default import from '${packageName}' should be aliased to `
37+
+ `${mappings[packageName]}, not ${importAlias}`,
38+
fix: fixImportOrRequire(specifierOrDeclaration, mappings[packageName]),
39+
})
40+
41+
let declaredVariables
42+
if (specifierOrDeclaration.type === 'VariableDeclarator') {
43+
declaredVariables = context.getDeclaredVariables(specifierOrDeclaration.parent)[0]
44+
} else {
45+
declaredVariables = context.getDeclaredVariables(specifierOrDeclaration)[0]
46+
}
47+
48+
for (const variableReference of declaredVariables.references) {
49+
if (specifierOrDeclaration.type === 'VariableDeclarator' && variableReference.init) {
50+
continue
51+
}
52+
53+
context.report({
54+
node: variableReference.identifier,
55+
message: `Using incorrect binding name '${variableReference.identifier.name}' `
56+
+ `instead of ${mappings[packageName]} for `
57+
+ `default import from package ${packageName}`,
58+
fix: fixer => {
59+
return fixer.replaceText(variableReference.identifier, mappings[packageName])
60+
},
61+
})
62+
}
63+
}
64+
}
65+
66+
function fixImportOrRequire(node, text) {
67+
return function(fixer) {
68+
let newAlias = text,
69+
nodeOrToken
70+
if (node.type === 'VariableDeclarator') {
71+
nodeOrToken = node.id
72+
newAlias = text
73+
} else {
74+
nodeOrToken = node
75+
if (node.imported && node.imported.name === 'default') {
76+
newAlias = `default as ${text}`
77+
} else {
78+
newAlias = text
79+
}
80+
}
81+
82+
return fixer.replaceText(nodeOrToken, newAlias)
83+
}
84+
}
85+
86+
module.exports = {
87+
meta: {
88+
docs: {
89+
url: docsUrl('rename-default-import'),
90+
recommended: false,
91+
},
92+
schema: [
93+
{
94+
type: 'object',
95+
},
96+
],
97+
fixable: 'code',
98+
},
99+
create: function(context) {
100+
return {
101+
'ImportDeclaration': function(node) {
102+
const {source, specifiers} = node
103+
const {options} = context
104+
105+
if (options.length === 0 || Object.keys(options[0]).length === 0) {
106+
return
107+
}
108+
109+
for (const specifier of specifiers) {
110+
if (!isDefaultImport(specifier)) {
111+
continue
112+
}
113+
114+
handleImport(context, source, specifier, source.value, specifier.local.name)
115+
}
116+
},
117+
'VariableDeclaration': function(node) {
118+
const {declarations} = node
119+
const {options} = context
120+
121+
if (options.length === 0 || Object.keys(options[0]).length === 0) {
122+
return
123+
}
124+
125+
for (const declaration of declarations) {
126+
if (!isCommonJSImport(declaration)) {
127+
continue
128+
}
129+
130+
handleImport(
131+
context,
132+
node,
133+
declaration,
134+
declaration.init.arguments[0].value,
135+
declaration.id.name
136+
)
137+
}
138+
},
139+
}
140+
},
141+
}

0 commit comments

Comments
 (0)