Skip to content

Commit 69e2534

Browse files
committed
[New] extensions: accept both a string, and an object to override it.
Fixes #390.
1 parent e087ec4 commit 69e2534

File tree

3 files changed

+93
-23
lines changed

3 files changed

+93
-23
lines changed

docs/rules/extensions.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ In order to provide a consistent use of file extensions across your code base, t
66

77
## Rule Details
88

9-
This rule has one option which could be either a string or an object. If it is `"never"` (the default value) the rule forbids the use for any extension. If `"always"` then the rule enforces the use of extensions for all import statements.
9+
This rule either takes one string option, one object option, or a string and an object option. If it is the string `"never"` (the default value) the rule forbids the use for any extension. If the string `"always"` then the rule enforces the use of extensions for all import statements.
1010

1111
By providing an object you can configure each extension separately, so for example `{ "js": "always", "json": "never" }` would always enforce the use of the `.js` extension but never allow the use of the `.json` extension.
1212

13+
By providing both a string and an object, the string will set the default setting for all extensions, and the object can be used to set granular overrides for specific extensions. For example, `[<enabled>, "never", { "svg": "always" }]` would require that all extensions are omitted, except for "svg".
14+
1315
### Exception
1416

1517
When disallowing the use of certain extensions this rule makes an exception and allows the use of extension when the file would not be resolvable without extension.

src/rules/extensions.js

Lines changed: 37 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ import { isBuiltIn } from '../core/importType'
66

77
module.exports = function (context) {
88
const configuration = context.options[0] || 'never'
9+
const defaultConfig = typeof configuration === 'string' ? configuration : null
10+
const modifiers = typeof configuration === 'object' ? configuration : context.options[1] || {}
911

10-
function isUseOfExtensionEnforced(extension) {
11-
if (typeof configuration === 'object') {
12-
return configuration[extension] === 'always'
13-
}
12+
function isUseOfExtensionRequired(extension) {
13+
return (modifiers[extension] || defaultConfig) === 'always'
14+
}
1415

15-
return configuration === 'always'
16+
function isUseOfExtensionForbidden(extension) {
17+
return (modifiers[extension] || defaultConfig) === 'never'
1618
}
1719

1820
function isResolvableWithoutExtension(file) {
@@ -37,15 +39,15 @@ module.exports = function (context) {
3739
const extension = path.extname(resolvedPath || importPath).substring(1)
3840

3941
if (!extension || !endsWith(importPath, extension)) {
40-
if (isUseOfExtensionEnforced(extension)) {
42+
if (isUseOfExtensionRequired(extension) && !isUseOfExtensionForbidden(extension)) {
4143
context.report({
4244
node: source,
4345
message:
4446
`Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPath}"`,
4547
})
4648
}
4749
} else if (extension) {
48-
if (!isUseOfExtensionEnforced(extension) && isResolvableWithoutExtension(importPath)) {
50+
if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) {
4951
context.report({
5052
node: source,
5153
message: `Unexpected use of file extension "${extension}" for "${importPath}"`,
@@ -59,18 +61,31 @@ module.exports = function (context) {
5961
}
6062
}
6163

62-
module.exports.schema = [
63-
{
64-
oneOf: [
65-
{
66-
enum: [ 'always', 'never' ],
67-
},
68-
{
69-
type: 'object',
70-
patternProperties: {
71-
'.*': { enum: [ 'always', 'never' ] },
72-
},
73-
},
74-
],
75-
},
76-
]
64+
const enumValues = { enum: [ 'always', 'never' ] }
65+
const patternProperties = {
66+
type: 'object',
67+
patternProperties: { '.*': enumValues },
68+
}
69+
70+
module.exports.schema = {
71+
anyOf: [
72+
{
73+
type: 'array',
74+
items: [enumValues],
75+
additionalItems: false,
76+
},
77+
{
78+
type: 'array',
79+
items: [patternProperties],
80+
additionalItems: false,
81+
},
82+
{
83+
type: 'array',
84+
items: [
85+
enumValues,
86+
patternProperties,
87+
],
88+
additionalItems: false,
89+
},
90+
],
91+
}

tests/src/rules/extensions.js

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,25 @@ ruleTester.run('extensions', rule, {
3333
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
3434
}),
3535

36+
test({
37+
code: [
38+
'import lib from "./bar"',
39+
'import lib from "./bar.json"',
40+
'import lib from "./bar.hbs"',
41+
].join('\n'),
42+
options: [ 'always', { js: 'never', jsx: 'never' } ],
43+
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json', '.hbs' ] } },
44+
}),
45+
46+
test({
47+
code: [
48+
'import lib from "./bar.js"',
49+
'import lib from "./package"',
50+
].join('\n'),
51+
options: [ 'never', { js: 'always', json: 'never' } ],
52+
settings: { 'import/resolve': { 'extensions': [ '.js', '.json' ] } },
53+
}),
54+
3655
// unresolved (#271/#295)
3756
test({ code: 'import path from "path"' }),
3857
test({ code: 'import path from "path"', options: [ 'never' ] }),
@@ -124,6 +143,40 @@ ruleTester.run('extensions', rule, {
124143
],
125144
}),
126145

146+
test({
147+
code: [
148+
'import lib from "./bar.js"',
149+
'import lib from "./bar.json"',
150+
'import lib from "./bar"',
151+
].join('\n'),
152+
options: [ 'always', { json: 'always', js: 'never', jsx: 'never' } ],
153+
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
154+
errors: [
155+
{
156+
message: 'Unexpected use of file extension "js" for "./bar.js"',
157+
line: 1,
158+
column: 17,
159+
},
160+
],
161+
}),
162+
163+
test({
164+
code: [
165+
'import lib from "./bar.js"',
166+
'import lib from "./bar.json"',
167+
'import lib from "./bar"',
168+
].join('\n'),
169+
options: [ 'never', { json: 'always', js: 'never', jsx: 'never' } ],
170+
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
171+
errors: [
172+
{
173+
message: 'Unexpected use of file extension "js" for "./bar.js"',
174+
line: 1,
175+
column: 17,
176+
},
177+
],
178+
}),
179+
127180
// unresolved (#271/#295)
128181
test({
129182
code: 'import thing from "./fake-file.js"',

0 commit comments

Comments
 (0)