Skip to content

Commit

Permalink
Add rule to enforce default import aliases
Browse files Browse the repository at this point in the history
  • Loading branch information
mic4ael committed Sep 3, 2018
1 parent 6815513 commit 2b5dde9
Show file tree
Hide file tree
Showing 6 changed files with 317 additions and 0 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ This project adheres to [Semantic Versioning](http://semver.org/).
This change log adheres to standards from [Keep a CHANGELOG](http://keepachangelog.com).

## [Unreleased]
- Add [`rename-default-import`] rule: Enforce default import naming

## [2.14.0] - 2018-08-13
* 69e0187 (HEAD -> master, source/master, origin/master, origin/HEAD) Merge pull request #1151 from jf248/jsx
Expand Down Expand Up @@ -490,6 +491,7 @@ for info on changes for earlier releases.
[`no-default-export`]: ./docs/rules/no-default-export.md
[`no-useless-path-segments`]: ./docs/rules/no-useless-path-segments.md
[`no-cycle`]: ./docs/rules/no-cycle.md
[`rename-default-import`]: ./docs/rules/rename-default-import.md

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

Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,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 @@ -105,6 +106,7 @@ This plugin intends to support linting of ES2015+ (ES6+) import/export syntax, a
[`group-exports`]: ./docs/rules/group-exports.md
[`no-default-export`]: ./docs/rules/no-default-export.md
[`dynamic-import-chunkname`]: ./docs/rules/dynamic-import-chunkname.md
[`rename-default-import`]: ./docs/rules/rename-default-import.md

## Installation

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

This rule will enforce a specific binding name for a default package import. Only ES6 imports are processed.


## Rule Details

Given:

```js
// ./foo.js
export default function () { return 'Foo' }
```

and

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

The following is considered valid:

```js
import Foo from './foo'

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

import PropTypes from 'prop-types'
```

...and the following cases are reported:

```js
import propTypes from 'prop-types';
import {default as propTypes} from '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 @@ -37,6 +37,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
93 changes: 93 additions & 0 deletions src/rules/rename-default-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
/**
* @fileoverview Rule to enforce aliases for default imports
* @author Michał Kołodziejski
*/

import docsUrl from '../docsUrl'


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


function handleImport(context, specifier, source) {
const {value: packageName} = source
const {local: {name: importAlias}} = specifier
const mappings = context.options[0]

if (Object.keys(mappings).indexOf(packageName) === -1) {
return
}

if (mappings[packageName] !== importAlias) {
context.report({
node: source,
message: `Default import from '${packageName}' should be aliased to `
+ `${mappings[packageName]}, not ${importAlias}`,
fix: fixer => {
let newAlias = mappings[packageName]
if (specifier.imported && specifier.imported.name === 'default') {
newAlias = `default as ${mappings[packageName]}`
}

return fixer.replaceText(specifier, newAlias)
},
})

const declaredVariable = context.getDeclaredVariables(specifier)[0]
for (const variableReference of declaredVariable.references) {
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 => {
return fixer.replaceText(variableReference.identifier, mappings[packageName])
},
})
}
}
}


module.exports = {
meta: {
docs: {
url: docsUrl('rename-default-import'),
recommended: false,
},
schema: [
{
type: 'object',
},
],
fixable: 'code',
},
create: function(context) {
return {
'ImportDeclaration': function(node) {
const {source, specifiers} = node
const options = context.options

if (options.length === 0 || Object.keys(options[0]).length === 0) {
return
}

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

handleImport(context, specifier, source)
}
},
}
},
}
158 changes: 158 additions & 0 deletions tests/src/rules/rename-default-import.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { test } from '../utils'

import { RuleTester } from 'eslint'

const ruleTester = new RuleTester(),
rule = require('rules/rename-default-import')

ruleTester.run('rename-default-import', rule, {
valid: [
test({
code: `import PropTypes from 'prop-types';`,
options: [],
}),
test({
code: `import PropTypes from 'prop-types';`,
options: [{'foo': 'Foo'}],
}),
test({
code: `import PropTypes from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
}),
test({
code: `import PropTypes, {Foo} from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
}),
test({
code: `import {default as PropTypes} from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
}),
test({
code: `import {Foo} from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
}),
test({
code: `import * as PropTypes from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}]
}),
],
invalid: [
test({
code: `import propTypes from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
output: `import PropTypes from 'prop-types';`,
errors: [{
ruleId: 'rename-default-import',
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
}],
}),
test({
code: `import propTypes, {B} from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
output: `import PropTypes, {B} from 'prop-types';`,
errors: [{
ruleId: 'rename-default-import',
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
}],
}),
test({
code: `import {default as propTypes} from 'prop-types';`,
options: [{'prop-types': 'PropTypes'}],
output: `import {default as PropTypes} from 'prop-types';`,
errors: [{
ruleId: 'rename-default-import',
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
}],
}),
test({
code: `import propTypes from 'prop-types';import foo from 'foo';`,
options: [{'prop-types': 'PropTypes', 'foo': 'Foo'}],
output: `import PropTypes from 'prop-types';import Foo from 'foo';`,
errors: [{
ruleId: 'rename-default-import',
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
}, {
ruleId: 'rename-default-import',
message: `Default import from 'foo' should be aliased to Foo, not foo`,
}],
}),
test({
code: `
import propTypes from 'prop-types';
const obj = {
foo: propTypes.string
}
`,
options: [{'prop-types': 'PropTypes'}],
output: `
import PropTypes from 'prop-types';
const obj = {
foo: PropTypes.string
}
`,
errors: [{
ruleId: 'rename-default-import',
message: `Default import from 'prop-types' should be aliased to PropTypes, not propTypes`,
}, {
ruleId: 'rename-default-import',
message: `Using incorrect binding name 'propTypes' instead of PropTypes for default import from package prop-types`
}],
}),
test({
code: `
import foo from 'bar';
const a = foo.foo();
const b = bar(foo);
const c = (foo) => {
foo();
};
c(foo)
const d = (bar) => {
bar();
};
d(foo);
const e = () => {
foo();
};
`,
options: [{'bar': 'Foo'}],
output: `
import Foo from 'bar';
const a = Foo.foo();
const b = bar(Foo);
const c = (foo) => {
foo();
};
c(Foo)
const d = (bar) => {
bar();
};
d(Foo);
const e = () => {
Foo();
};
`,
errors: [{
ruleId: 'rename-default-import',
message: `Default import from 'bar' should be aliased to Foo, not foo`,
}, {
ruleId: 'rename-default-import',
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
}, {
ruleId: 'rename-default-import',
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
}, {
ruleId: 'rename-default-import',
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
}, {
ruleId: 'rename-default-import',
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
}, {
ruleId: 'rename-default-import',
message: `Using incorrect binding name 'foo' instead of Foo for default import from package bar`,
}]
})
]
})

0 comments on commit 2b5dde9

Please sign in to comment.