This repository has been archived by the owner on Sep 30, 2020. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #138 from liferay/wincent/import-extensions
feat!: add import-extensions rule
- Loading branch information
Showing
6 changed files
with
270 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
41 changes: 41 additions & 0 deletions
41
plugins/eslint-plugin-liferay/docs/rules/import-extensions.md
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,41 @@ | ||
# Omit extensions consistently with `import` and `require` (import-extensions) | ||
|
||
This rule enforces that `import` statements and `require` calls — henceforth referred to as just "imports" — use (or omit) file extensions consistently. | ||
|
||
## Rule Details | ||
|
||
This rule complains if it finds an import with an unnecessary ".js" extension. | ||
|
||
In its current form, it follows some simple heuristics and is not configurable (unlike more complex rules, such as the third-party [imports/extensions rule](https://github.com/benmosher/eslint-plugin-import/blob/HEAD/docs/rules/extensions.md)): | ||
|
||
- Imports should omit the unnecessary ".js" extension. | ||
- NPM package names (which may end in ".js" are exempted). | ||
|
||
Based on the [current usages in liferay-portal](https://gist.github.com/wincent/1a6bbd06aec797032b6918153bef5d87) (via `git grep "import.+\\.js';" -- '*.js'`) and [clay](https://gist.github.com/wincent/775fdb7a0bc117c2fa8c66cd97b2d76f) (via `git grep "import.+\\.(js|ts|tsx)';" -- '*.ts' '*.tsx' '*.js'`) we believe this simpler approach should be sufficient, but we can add configurability in the future if that proves not to be the case. | ||
|
||
## Examples | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
import templates from './Something.soy.js'; | ||
|
||
import {Util} from './Util.es.js'; | ||
|
||
import * as Billboard from './billboard.js'; | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
import templates from './Something.soy'; | ||
|
||
import {Util} from './Util.es'; | ||
|
||
// OK because "billboard.js" is the name of an NPM package: | ||
import {Data} from 'billboard.js'; | ||
``` | ||
|
||
## See also | ||
|
||
- https://github.com/liferay/eslint-config-liferay/issues/137 |
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
107 changes: 107 additions & 0 deletions
107
plugins/eslint-plugin-liferay/lib/rules/import-extensions.js
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,107 @@ | ||
/** | ||
* © 2017 Liferay, Inc. <https://liferay.com> | ||
* | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
const path = require('path'); | ||
|
||
const {isLocal} = require('../common/imports'); | ||
|
||
const BAD_EXTENSIONS = new Set(['.js']); | ||
|
||
function stripExtension(value) { | ||
if (isLocal(value)) { | ||
const extension = path.extname(value); | ||
|
||
if (BAD_EXTENSIONS.has(extension)) { | ||
return value.slice(0, -extension.length); | ||
} | ||
} | ||
|
||
return value; | ||
} | ||
|
||
const message = 'unnecessary extension in import'; | ||
|
||
module.exports = { | ||
create(context) { | ||
function fix(node) { | ||
let delimiter; | ||
let original; | ||
let stripped; | ||
|
||
if (node.type === 'TemplateLiteral' && node.quasis.length === 1) { | ||
// Only deal with template literals if they are static (ie. no | ||
// interpolation). | ||
delimiter = '`'; | ||
original = node.quasis[0].value.raw; | ||
stripped = stripExtension(original); | ||
} else if (node.type === 'Literal') { | ||
delimiter = node.raw[0]; | ||
original = node.value; | ||
stripped = stripExtension(original); | ||
} else { | ||
return; | ||
} | ||
|
||
if (stripped !== original) { | ||
context.report({ | ||
fix: fixer => | ||
fixer.replaceText( | ||
node, | ||
`${delimiter}${stripped}${delimiter}` | ||
), | ||
message, | ||
node, | ||
}); | ||
} | ||
} | ||
|
||
function check(node) { | ||
if (!node) { | ||
return; | ||
} | ||
|
||
if ( | ||
node.type === 'ImportDeclaration' && | ||
(node.source.type === 'Literal' || | ||
node.source.type === 'TemplateLiteral') | ||
) { | ||
fix(node.source); | ||
} else if ( | ||
node.type === 'CallExpression' && | ||
node.arguments && | ||
node.arguments.length && | ||
(node.arguments[0].type === 'Literal' || | ||
node.arguments[0].type === 'TemplateLiteral') | ||
) { | ||
fix(node.arguments[0]); | ||
} | ||
} | ||
|
||
return { | ||
CallExpression(node) { | ||
if (node.callee.name === 'require') { | ||
check(node); | ||
} | ||
}, | ||
|
||
ImportDeclaration(node) { | ||
check(node); | ||
}, | ||
}; | ||
}, | ||
|
||
meta: { | ||
docs: { | ||
category: 'Best Practices', | ||
description: 'imports should use/omit extensions consistently', | ||
recommended: false, | ||
url: 'https://github.com/liferay/eslint-config-liferay/issues/137', | ||
}, | ||
fixable: 'code', | ||
schema: [], | ||
type: 'problem', | ||
}, | ||
}; |
118 changes: 118 additions & 0 deletions
118
plugins/eslint-plugin-liferay/tests/lib/rules/import-extensions.js
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,118 @@ | ||
/** | ||
* © 2017 Liferay, Inc. <https://liferay.com> | ||
* | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
const {RuleTester} = require('eslint'); | ||
|
||
const rule = require('../../../lib/rules/import-extensions'); | ||
|
||
const parserOptions = { | ||
parserOptions: { | ||
ecmaVersion: 6, | ||
sourceType: 'module', | ||
}, | ||
}; | ||
|
||
const ruleTester = new RuleTester(parserOptions); | ||
|
||
const message = 'unnecessary extension in import'; | ||
|
||
const type = 'Literal'; | ||
|
||
const errors = [ | ||
{ | ||
message, | ||
type, | ||
}, | ||
]; | ||
|
||
ruleTester.run('import-extensions', rule, { | ||
invalid: [ | ||
{ | ||
code: `import templates from './Something.soy.js';`, | ||
errors, | ||
output: `import templates from './Something.soy';`, | ||
}, | ||
{ | ||
code: `import {Util} from './Util.es.js';`, | ||
errors, | ||
output: `import {Util} from './Util.es';`, | ||
}, | ||
{ | ||
code: `import * as Billboard from './billboard.js';`, | ||
errors, | ||
output: `import * as Billboard from './billboard';`, | ||
}, | ||
{ | ||
code: `const templates = require('./Something.soy.js');`, | ||
errors, | ||
output: `const templates = require('./Something.soy');`, | ||
}, | ||
{ | ||
code: `const {Util} = require('./Util.es.js');`, | ||
errors, | ||
output: `const {Util} = require('./Util.es');`, | ||
}, | ||
{ | ||
code: `const Billboard = require('./billboard.js');`, | ||
errors, | ||
output: `const Billboard = require('./billboard');`, | ||
}, | ||
{ | ||
// Double quote delimiters are preserved. | ||
code: `const Billboard = require("./billboard.js");`, | ||
errors, | ||
output: `const Billboard = require("./billboard");`, | ||
}, | ||
{ | ||
// Backtick delimiters are preserved. | ||
code: `const Billboard = require(\`./billboard.js\`);`, | ||
errors: [ | ||
{ | ||
message, | ||
type: 'TemplateLiteral', | ||
}, | ||
], | ||
output: `const Billboard = require(\`./billboard\`);`, | ||
}, | ||
], | ||
|
||
valid: [ | ||
{ | ||
code: `import templates from './Something.soy';`, | ||
}, | ||
{ | ||
code: `import {Util} from './Util.es';`, | ||
}, | ||
{ | ||
// OK because "billboard.js" is the name of an NPM package: | ||
code: `import {Data} from 'billboard.js';`, | ||
}, | ||
{ | ||
code: `const templates = require('./Something.soy');`, | ||
}, | ||
{ | ||
code: `const {Util} = require('./Util.es');`, | ||
}, | ||
{ | ||
code: `const {Data} = require('billboard.js');`, | ||
}, | ||
{ | ||
// Note double quotes. | ||
code: `const {Data} = require("billboard.js");`, | ||
}, | ||
{ | ||
// Note backticks... | ||
code: `const {Data} = require(\`billboard.js\`);`, | ||
}, | ||
{ | ||
// ...but we don't look at backticks if they contain interpolation. | ||
code: `const {Data} = require(\`./\${something}.js\`);`, | ||
}, | ||
{ | ||
code: `const billboard = not_a_require('billboard.js');`, | ||
}, | ||
], | ||
}); |