Skip to content

Commit

Permalink
[New] extensions: accept both a string, and an object to override it.
Browse files Browse the repository at this point in the history
Fixes #390.
  • Loading branch information
ljharb committed Sep 12, 2016
1 parent e087ec4 commit 69e2534
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 23 deletions.
4 changes: 3 additions & 1 deletion docs/rules/extensions.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ In order to provide a consistent use of file extensions across your code base, t

## Rule Details

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.
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.

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.

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".

### Exception

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.
Expand Down
59 changes: 37 additions & 22 deletions src/rules/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,15 @@ import { isBuiltIn } from '../core/importType'

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

function isUseOfExtensionEnforced(extension) {
if (typeof configuration === 'object') {
return configuration[extension] === 'always'
}
function isUseOfExtensionRequired(extension) {
return (modifiers[extension] || defaultConfig) === 'always'
}

return configuration === 'always'
function isUseOfExtensionForbidden(extension) {
return (modifiers[extension] || defaultConfig) === 'never'
}

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

if (!extension || !endsWith(importPath, extension)) {
if (isUseOfExtensionEnforced(extension)) {
if (isUseOfExtensionRequired(extension) && !isUseOfExtensionForbidden(extension)) {
context.report({
node: source,
message:
`Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPath}"`,
})
}
} else if (extension) {
if (!isUseOfExtensionEnforced(extension) && isResolvableWithoutExtension(importPath)) {
if (isUseOfExtensionForbidden(extension) && isResolvableWithoutExtension(importPath)) {
context.report({
node: source,
message: `Unexpected use of file extension "${extension}" for "${importPath}"`,
Expand All @@ -59,18 +61,31 @@ module.exports = function (context) {
}
}

module.exports.schema = [
{
oneOf: [
{
enum: [ 'always', 'never' ],
},
{
type: 'object',
patternProperties: {
'.*': { enum: [ 'always', 'never' ] },
},
},
],
},
]
const enumValues = { enum: [ 'always', 'never' ] }
const patternProperties = {
type: 'object',
patternProperties: { '.*': enumValues },
}

module.exports.schema = {
anyOf: [
{
type: 'array',
items: [enumValues],
additionalItems: false,
},
{
type: 'array',
items: [patternProperties],
additionalItems: false,
},
{
type: 'array',
items: [
enumValues,
patternProperties,
],
additionalItems: false,
},
],
}
53 changes: 53 additions & 0 deletions tests/src/rules/extensions.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,25 @@ ruleTester.run('extensions', rule, {
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
}),

test({
code: [
'import lib from "./bar"',
'import lib from "./bar.json"',
'import lib from "./bar.hbs"',
].join('\n'),
options: [ 'always', { js: 'never', jsx: 'never' } ],
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json', '.hbs' ] } },
}),

test({
code: [
'import lib from "./bar.js"',
'import lib from "./package"',
].join('\n'),
options: [ 'never', { js: 'always', json: 'never' } ],
settings: { 'import/resolve': { 'extensions': [ '.js', '.json' ] } },
}),

// unresolved (#271/#295)
test({ code: 'import path from "path"' }),
test({ code: 'import path from "path"', options: [ 'never' ] }),
Expand Down Expand Up @@ -124,6 +143,40 @@ ruleTester.run('extensions', rule, {
],
}),

test({
code: [
'import lib from "./bar.js"',
'import lib from "./bar.json"',
'import lib from "./bar"',
].join('\n'),
options: [ 'always', { json: 'always', js: 'never', jsx: 'never' } ],
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
errors: [
{
message: 'Unexpected use of file extension "js" for "./bar.js"',
line: 1,
column: 17,
},
],
}),

test({
code: [
'import lib from "./bar.js"',
'import lib from "./bar.json"',
'import lib from "./bar"',
].join('\n'),
options: [ 'never', { json: 'always', js: 'never', jsx: 'never' } ],
settings: { 'import/resolve': { 'extensions': [ '.js', '.jsx', '.json' ] } },
errors: [
{
message: 'Unexpected use of file extension "js" for "./bar.js"',
line: 1,
column: 17,
},
],
}),

// unresolved (#271/#295)
test({
code: 'import thing from "./fake-file.js"',
Expand Down

0 comments on commit 69e2534

Please sign in to comment.