-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[New]
no-rename-default
: Forbid importing a default export by a dif…
…ferent name
- Loading branch information
Showing
11 changed files
with
294 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
# import/no-rename-default | ||
|
||
⚠️ This rule _warns_ in the 🚸 `warnings` config. | ||
|
||
<!-- end auto-generated rule header --> | ||
|
||
Prohibit importing a default export by another name. | ||
|
||
## Rule Details | ||
|
||
Given: | ||
|
||
```js | ||
// api/get-users.js | ||
export default async function getUsers() {} | ||
``` | ||
|
||
...this would be valid: | ||
|
||
```js | ||
import getUsers from './api/get-users.js'; | ||
``` | ||
|
||
...and the following would be reported: | ||
|
||
```js | ||
// Caution: `get-users.js` has a default export `getUsers`. | ||
// This imports `getUsers` as `findUsers`. | ||
// Check if you meant to write `import getUsers from './api/get-users'` instead. | ||
import findUsers from './get-users'; | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,157 @@ | ||
/** | ||
* @fileOverview Rule to warn about importing a default export by different name | ||
* @author James Whitney | ||
*/ | ||
|
||
import docsUrl from '../docsUrl'; | ||
import ExportMapBuilder from '../exportMap/builder'; | ||
import path from 'path'; | ||
|
||
//------------------------------------------------------------------------------ | ||
// Rule Definition | ||
//------------------------------------------------------------------------------ | ||
|
||
/** @type {import('@typescript-eslint/utils').TSESLint.RuleModule} */ | ||
const rule = { | ||
meta: { | ||
type: 'suggestion', | ||
docs: { | ||
category: 'Helpful warnings', | ||
description: 'Forbid importing a default export by a different name.', | ||
recommended: false, | ||
url: docsUrl('no-named-as-default'), | ||
}, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
commonjs: { | ||
type: 'boolean', | ||
}, | ||
}, | ||
additionalProperties: false, | ||
}, | ||
], | ||
}, | ||
|
||
create(context) { | ||
function getDefaultExportName(defaultExportNode) { | ||
return defaultExportNode.declaration.name; | ||
} | ||
|
||
function getDefaultExportNode(exportMap) { | ||
const defaultExportNode = exportMap.exports.get('default'); | ||
if (defaultExportNode == null) { | ||
return; | ||
} | ||
return defaultExportNode; | ||
} | ||
|
||
function getExportMap(source, context) { | ||
const exportMap = ExportMapBuilder.get(source.value, context); | ||
if (exportMap == null) { | ||
return; | ||
} | ||
if (exportMap.errors.length > 0) { | ||
exportMap.reportErrors(context, source.value); | ||
return; | ||
} | ||
return exportMap; | ||
} | ||
|
||
return { | ||
ImportDeclaration(node) { | ||
const exportMap = getExportMap(node.source, context); | ||
if (exportMap == null) { | ||
return; | ||
} | ||
|
||
const defaultExportNode = getDefaultExportNode(exportMap); | ||
if (defaultExportNode == null) { | ||
return; | ||
} | ||
|
||
const defaultExportName = getDefaultExportName(defaultExportNode); | ||
const importTarget = node.source.value; | ||
const importBasename = path.basename(exportMap.path); | ||
|
||
node.specifiers.forEach((importClause) => { | ||
const importName = importClause.local.name; | ||
|
||
// No named default export | ||
if (defaultExportName === undefined) { | ||
return; | ||
} | ||
|
||
// The name of the import matches the name of the default export. | ||
if (importName === defaultExportName) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node: importClause, | ||
message: `Caution: \`${importBasename}\` has a default export \`${defaultExportName}\`. This imports \`${defaultExportName}\` as \`${importName}\`. Check if you meant to write \`import ${defaultExportName} from '${importTarget}'\` instead.`, | ||
}); | ||
}); | ||
}, | ||
VariableDeclarator(node) { | ||
const options = context.options[0] || {}; | ||
|
||
if ( | ||
!options.commonjs | ||
|| node.type !== 'VariableDeclarator' | ||
// return if it's not an object destructure or it's an empty object destructure | ||
|| !node.id || node.id.type !== 'Identifier' | ||
// return if there is no call expression on the right side | ||
|| !node.init || node.init.type !== 'CallExpression' | ||
) { | ||
return; | ||
} | ||
|
||
const call = node.init; | ||
const [source] = call.arguments; | ||
|
||
if ( | ||
// return if it's not a commonjs require statement | ||
call.callee.type !== 'Identifier' || call.callee.name !== 'require' || call.arguments.length !== 1 | ||
// return if it's not a string source | ||
|| source.type !== 'Literal' | ||
) { | ||
return; | ||
} | ||
|
||
const exportMap = getExportMap(source, context); | ||
if (exportMap == null) { | ||
return; | ||
} | ||
|
||
const defaultExportNode = getDefaultExportNode(exportMap); | ||
if (defaultExportNode == null) { | ||
return; | ||
} | ||
|
||
const defaultExportName = getDefaultExportName(defaultExportNode); | ||
const requireTarget = source.value; | ||
const requireBasename = path.basename(exportMap.path); | ||
const requireName = node.id.name; | ||
|
||
// No named default export | ||
if (defaultExportName === undefined) { | ||
return; | ||
} | ||
|
||
// The name of the require matches the name of the default export. | ||
if (requireName === defaultExportName) { | ||
return; | ||
} | ||
|
||
context.report({ | ||
node, | ||
message: `Caution: \`${requireBasename}\` has a default export \`${defaultExportName}\`. This requires \`${defaultExportName}\` as \`${requireName}\`. Check if you meant to write \`const ${defaultExportName} = require('${requireTarget}')\` instead.`, | ||
}); | ||
}, | ||
}; | ||
}, | ||
}; | ||
|
||
module.exports = rule; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default {}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const bar = 'bar'; | ||
|
||
export default bar; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
const foo = 'foo'; | ||
|
||
export default foo; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export default 123; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
import { RuleTester } from 'eslint'; | ||
import { test } from '../utils'; | ||
|
||
const ruleTester = new RuleTester(); | ||
const rule = require('rules/no-rename-default'); | ||
|
||
ruleTester.run('no-rename-default', rule, { | ||
valid: [ | ||
test({ | ||
code: ` | ||
import _ from './no-rename-default/anon.js' | ||
`, | ||
}), | ||
test({ | ||
code: ` | ||
import bar from './no-rename-default/named-bar' | ||
import foo from './no-rename-default/named-foo' | ||
`, | ||
}), | ||
test({ | ||
code: ` | ||
import _ from './no-rename-default/primitive' | ||
`, | ||
}), | ||
test({ | ||
code: ` | ||
const _ = require('./no-rename-default/anon.js') | ||
`, | ||
options: [{ commonjs: true }], | ||
}), | ||
test({ | ||
code: ` | ||
const bar = require('./no-rename-default/named-bar') | ||
const foo = require('./no-rename-default/named-foo') | ||
`, | ||
options: [{ commonjs: true }], | ||
}), | ||
test({ | ||
code: ` | ||
const _ = require('./no-rename-default/primitive') | ||
`, | ||
options: [{ commonjs: true }], | ||
}), | ||
], | ||
|
||
invalid: [ | ||
test({ | ||
code: ` | ||
import bar from './no-rename-default/named-foo' | ||
`, | ||
errors: [{ | ||
message: 'Caution: `named-foo.js` has a default export `foo`. This imports `foo` as `bar`. Check if you meant to write `import foo from \'./no-rename-default/named-foo\'` instead.', | ||
type: 'ImportDefaultSpecifier', | ||
}], | ||
}), | ||
test({ | ||
code: ` | ||
import foo from './no-rename-default/named-bar' | ||
import bar from './no-rename-default/named-foo' | ||
`, | ||
errors: [{ | ||
message: 'Caution: `named-bar.js` has a default export `bar`. This imports `bar` as `foo`. Check if you meant to write `import bar from \'./no-rename-default/named-bar\'` instead.', | ||
type: 'ImportDefaultSpecifier', | ||
}, { | ||
message: 'Caution: `named-foo.js` has a default export `foo`. This imports `foo` as `bar`. Check if you meant to write `import foo from \'./no-rename-default/named-foo\'` instead.', | ||
type: 'ImportDefaultSpecifier', | ||
}], | ||
}), | ||
test({ | ||
code: ` | ||
const bar = require('./no-rename-default/named-foo') | ||
`, | ||
options: [{ commonjs: true }], | ||
errors: [{ | ||
message: 'Caution: `named-foo.js` has a default export `foo`. This requires `foo` as `bar`. Check if you meant to write `const foo = require(\'./no-rename-default/named-foo\')` instead.', | ||
type: 'VariableDeclarator', | ||
}], | ||
}), | ||
test({ | ||
code: ` | ||
const foo = require('./no-rename-default/named-bar') | ||
const bar = require('./no-rename-default/named-foo') | ||
`, | ||
options: [{ commonjs: true }], | ||
errors: [{ | ||
message: 'Caution: `named-bar.js` has a default export `bar`. This requires `bar` as `foo`. Check if you meant to write `const bar = require(\'./no-rename-default/named-bar\')` instead.', | ||
type: 'VariableDeclarator', | ||
}, { | ||
message: 'Caution: `named-foo.js` has a default export `foo`. This requires `foo` as `bar`. Check if you meant to write `const foo = require(\'./no-rename-default/named-foo\')` instead.', | ||
type: 'VariableDeclarator', | ||
}], | ||
}), | ||
], | ||
}); |