Skip to content

Commit

Permalink
[New] add [rename-default-import] rule: Enforce default import naming
Browse files Browse the repository at this point in the history
  • Loading branch information
mic4ael authored and ljharb committed Jul 23, 2018
1 parent 4ff9b92 commit f579ea7
Show file tree
Hide file tree
Showing 6 changed files with 556 additions and 0 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
- [`order`]: Add support for TypeScript's "import equals"-expressions ([#1785], thanks [@manuth])
- [`import/default`]: support default export in TSExportAssignment ([#1689], thanks [@Maxim-Mazurok])
- [`no-restricted-paths`]: add custom message support ([#1802], thanks [@malykhinvi])
- add [`rename-default-import`] rule: Enforce default import naming ([#1143], thanks [@mic4ael])

### Fixed
- [`group-exports`]: Flow type export awareness ([#1702], thanks [@ernestostifano])
Expand Down Expand Up @@ -687,6 +688,7 @@ for info on changes for earlier releases.
[`order`]: ./docs/rules/order.md
[`prefer-default-export`]: ./docs/rules/prefer-default-export.md
[`unambiguous`]: ./docs/rules/unambiguous.md
[`rename-default-import`]: ./docs/rules/rename-default-import.md

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

Expand Down Expand Up @@ -800,6 +802,7 @@ for info on changes for earlier releases.
[#1163]: https://github.com/benmosher/eslint-plugin-import/pull/1163
[#1157]: https://github.com/benmosher/eslint-plugin-import/pull/1157
[#1151]: https://github.com/benmosher/eslint-plugin-import/pull/1151
[#1143]: https://github.com/benmosher/eslint-plugin-import/pull/1143
[#1142]: https://github.com/benmosher/eslint-plugin-import/pull/1142
[#1139]: https://github.com/benmosher/eslint-plugin-import/pull/1139
[#1137]: https://github.com/benmosher/eslint-plugin-import/pull/1137
Expand Down Expand Up @@ -1197,3 +1200,4 @@ for info on changes for earlier releases.
[@adjerbetian]: https://github.com/adjerbetian
[@Maxim-Mazurok]: https://github.com/Maxim-Mazurok
[@malykhinvi]: https://github.com/malykhinvi
[@mic4ael]: https://github.com/mic4ael
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
* Forbid anonymous values as default exports ([`no-anonymous-default-export`])
* Prefer named exports to be grouped together in a single export declaration ([`group-exports`])
* Enforce a leading comment with the webpackChunkName for dynamic imports ([`dynamic-import-chunkname`])
* Enforce a specific binding name for the default package import ([`rename-default-import`])

[`first`]: ./docs/rules/first.md
[`exports-last`]: ./docs/rules/exports-last.md
Expand All @@ -109,6 +110,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
[`no-default-export`]: ./docs/rules/no-default-export.md
[`no-named-export`]: ./docs/rules/no-named-export.md
[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md
[`rename-default-import`]: ./docs/rules/rename-default-import.md

## `eslint-plugin-import` for enterprise

Expand Down
63 changes: 63 additions & 0 deletions docs/rules/rename-default-import.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# import/rename-default-import

This rule will enforce a specific binding name for a default package import.
Works for ES6 imports and CJS require.


## Rule Details

Given:

There is a package `prop-types` with a default export

and

```json
// .eslintrc
{
"rules": {
"import/rename-default-import": [
"warn", {
"prop-types": "PropTypes", // key: name of the module, value: desired binding for default import
}
]
}
}
```

The following is considered valid:

```js
import {default as PropTypes} from 'prop-types'

import PropTypes from 'prop-types'
```

```js
const PropTypes = require('prop-types');
```

...and the following cases are reported:

```js
import propTypes from 'prop-types';
import {default as propTypes} from 'prop-types';
```

```js
const propTypes = require('prop-types');
```

## When not to use it

As long as you don't want to enforce specific naming for default imports.

## Options

This rule accepts an object which is a mapping
between package name and the binding name that should be used for default imports.
For example, a configuration like the one below

`{'prop-types': 'PropTypes'}`

specifies that default import for the package `prop-types` should be aliased to `PropTypes`.
1 change: 1 addition & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export const rules = {
'no-unassigned-import': require('./rules/no-unassigned-import'),
'no-useless-path-segments': require('./rules/no-useless-path-segments'),
'dynamic-import-chunkname': require('./rules/dynamic-import-chunkname'),
'rename-default-import': require('./rules/rename-default-import'),

// export
'exports-last': require('./rules/exports-last'),
Expand Down
180 changes: 180 additions & 0 deletions src/rules/rename-default-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,180 @@
/**
* @fileoverview Rule to enforce aliases for default imports
* @author Michał Kołodziejski
*/

import docsUrl from '../docsUrl'
import has from 'has'
import includes from 'array-includes'

function isDefaultImport(specifier) {
if (specifier.type === 'ImportDefaultSpecifier') {
return true
}
if (specifier.type === 'ImportSpecifier' && specifier.imported.name === 'default') {
return true
}
return false
}

function isCommonJSImport(declaration) {
const variableInit = declaration.init
if (variableInit.type === 'CallExpression') {
return variableInit.callee.name === 'require'
}
return false
}

function handleImport(
context,
node,
specifierOrDeclaration,
packageName,
importAlias,
exportedIdentifiers
) {
const mappings = context.options[0] || {}

if (!has(mappings, packageName) || mappings[packageName] === importAlias) {
return
}

let declaredVariables
if (specifierOrDeclaration.type === 'VariableDeclarator') {
declaredVariables = context.getDeclaredVariables(specifierOrDeclaration.parent)[0]
} else {
declaredVariables = context.getDeclaredVariables(specifierOrDeclaration)[0]
}

const references = declaredVariables ? declaredVariables.references : []
const skipFixing = includes(exportedIdentifiers, importAlias)

context.report({
node: node,
message: `Default import from \`${packageName}\` should be bound to \`${mappings[packageName]}\`, not \`${importAlias}\``,
fix: skipFixing ? null : fixImportOrRequire(specifierOrDeclaration, mappings[packageName]),
})

for (const variableReference of references) {
if (specifierOrDeclaration.type === 'VariableDeclarator' && variableReference.init) {
continue
}

context.report({
node: variableReference.identifier,
message: `Using incorrect binding name \`${variableReference.identifier.name}\` instead of \`${mappings[packageName]}\` for default import from package \`${packageName}\``,
fix: fixer => {
if (skipFixing) {
return
}

return fixer.replaceText(variableReference.identifier, mappings[packageName])
},
})
}
}

function fixImportOrRequire(node, text) {
return function(fixer) {
let newAlias = text
let nodeOrToken
if (node.type === 'VariableDeclarator') {
nodeOrToken = node.id
newAlias = text
} else {
nodeOrToken = node
if (node.imported && node.imported.name === 'default') {
newAlias = `default as ${text}`
} else {
newAlias = text
}
}

return fixer.replaceText(nodeOrToken, newAlias)
}
}

module.exports = {
meta: {
type: 'suggestion',
docs: {
url: docsUrl('rename-default-import'),
recommended: false,
},
fixable: 'code',
schema: [
{
type: 'object',
minProperties: 1,
additionalProperties: {
type: 'string',
},
},
],
},
create: function(context) {
const exportedIdentifiers = []
return {
'Program': function(programNode) {
const {body} = programNode

body.forEach((node) => {
if (node.type === 'ExportNamedDeclaration') {
node.specifiers.forEach((specifier) => {
const {exported: {name}} = specifier
if (!includes(exportedIdentifiers, name)) {
exportedIdentifiers.push(name)
}
})
}
})
},
'ImportDeclaration:exit': function(node) {
const {source, specifiers} = node
const {options} = context

if (options.length === 0) {
return
}

for (const specifier of specifiers) {
if (!isDefaultImport(specifier)) {
continue
}

handleImport(
context,
source,
specifier,
source.value,
specifier.local.name,
exportedIdentifiers
)
}
},
'VariableDeclaration:exit': function(node) {
const {declarations} = node
const {options} = context

if (options.length === 0) {
return
}

for (const declaration of declarations) {
if (!isCommonJSImport(declaration) || context.getScope(declaration).type !== 'module') {
continue
}

handleImport(
context,
node,
declaration,
declaration.init.arguments[0].value,
declaration.id.name,
exportedIdentifiers
)
}
},
}
},
}
Loading

0 comments on commit f579ea7

Please sign in to comment.