Skip to content

Commit 62c11b4

Browse files
committed
Add rule to enforce default import aliases
1 parent b4a2f11 commit 62c11b4

File tree

6 files changed

+449
-2
lines changed

6 files changed

+449
-2
lines changed

CHANGELOG.md

+5-2
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,17 @@ 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
8+
79
### Fixed
810
- [`no-extraneous-dependencies`]: `packageDir` option with array value was clobbering package deps instead of merging them ([#1175]/[#1176], thanks [@aravindet] & [@pzhine])
911

1012

1113
## [2.14.0] - 2018-08-13
1214
* 69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx
13-
|\
15+
|\
1416
| * e30a757 (source/pr/1151, fork/jsx) Add JSX check to namespace rule
15-
|/
17+
|/
1618
* 8252344 (source/pr/1148) Add error to output when module loaded as resolver has invalid API
1719
### Added
1820
- [`no-useless-path-segments`]: add commonJS (CJS) support ([#1128], thanks [@1pete])
@@ -493,6 +495,7 @@ for info on changes for earlier releases.
493495
[`no-default-export`]: ./docs/rules/no-default-export.md
494496
[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md
495497
[`no-cycle`]: ./docs/rules/no-cycle.md
498+
[`rename-default-import`]: ./docs/rules/rename-default-import.md
496499

497500
[`memo-parser`]: ./memo-parser/README.md
498501

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

+142
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
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+
const expectedAlias = mappings[packageName]
29+
30+
if (expectedAlias === undefined || expectedAlias === importAlias) {
31+
return
32+
}
33+
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+
if (variableReference.identifier.parent.type !== 'ExportSpecifier') {
60+
return fixer.replaceText(variableReference.identifier, mappings[packageName])
61+
}
62+
},
63+
})
64+
}
65+
}
66+
67+
function fixImportOrRequire(node, text) {
68+
return function(fixer) {
69+
let newAlias = text
70+
let nodeOrToken
71+
if (node.type === 'VariableDeclarator') {
72+
nodeOrToken = node.id
73+
newAlias = text
74+
} else {
75+
nodeOrToken = node
76+
if (node.imported && node.imported.name === 'default') {
77+
newAlias = `default as ${text}`
78+
} else {
79+
newAlias = text
80+
}
81+
}
82+
83+
return fixer.replaceText(nodeOrToken, newAlias)
84+
}
85+
}
86+
87+
module.exports = {
88+
meta: {
89+
docs: {
90+
url: docsUrl('rename-default-import'),
91+
recommended: false,
92+
},
93+
schema: [
94+
{
95+
type: 'object',
96+
},
97+
],
98+
fixable: 'code',
99+
},
100+
create: function(context) {
101+
return {
102+
'ImportDeclaration': function(node) {
103+
const {source, specifiers} = node
104+
const {options} = context
105+
106+
if (options.length === 0 || Object.keys(options[0]).length === 0) {
107+
return
108+
}
109+
110+
for (const specifier of specifiers) {
111+
if (!isDefaultImport(specifier)) {
112+
continue
113+
}
114+
115+
handleImport(context, source, specifier, source.value, specifier.local.name)
116+
}
117+
},
118+
'VariableDeclaration': function(node) {
119+
const {declarations} = node
120+
const {options} = context
121+
122+
if (options.length === 0 || Object.keys(options[0]).length === 0) {
123+
return
124+
}
125+
126+
for (const declaration of declarations) {
127+
if (!isCommonJSImport(declaration)) {
128+
continue
129+
}
130+
131+
handleImport(
132+
context,
133+
node,
134+
declaration,
135+
declaration.init.arguments[0].value,
136+
declaration.id.name
137+
)
138+
}
139+
},
140+
}
141+
},
142+
}

0 commit comments

Comments
 (0)