Skip to content

Commit

Permalink
Merge remote-tracking branch 'ljharb/extensions_options' (#555)
Browse files Browse the repository at this point in the history
  • Loading branch information
benmosher committed Sep 20, 2016
2 parents 12bf6da + f507ff3 commit c45fdc3
Show file tree
Hide file tree
Showing 6 changed files with 105 additions and 24 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ This change log adheres to standards from [Keep a CHANGELOG](http://keepachangel
## [Unreleased]
### Added
- New rule [`no-internal-modules`]: restrict deep package imports to specific folders. ([#485], thanks [@spalger]!)
- [`extensions`]: allow override of a chosen default with options object ([#555], thanks [@ljharb]!)

## [1.15.0] - 2016-09-12
### Added
Expand Down Expand Up @@ -304,6 +305,7 @@ for info on changes for earlier releases.
[`max-dependencies`]: ./docs/rules/max-dependencies.md
[`no-internal-modules`]: ./docs/rules/no-internal-modules.md

[#555]: https://github.com/benmosher/eslint-plugin-import/pull/555
[#538]: https://github.com/benmosher/eslint-plugin-import/pull/538
[#527]: https://github.com/benmosher/eslint-plugin-import/pull/527
[#509]: https://github.com/benmosher/eslint-plugin-import/pull/509
Expand Down
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), then the rule forbids the use for any extension. If it is 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
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@
"es6-map": "^0.1.3",
"es6-set": "^0.1.4",
"eslint-import-resolver-node": "^0.2.0",
"has": "^1.0.1",
"lodash.cond": "^4.3.0",
"lodash.endswith": "^4.0.1",
"lodash.find": "^4.3.0",
Expand Down
3 changes: 2 additions & 1 deletion resolvers/webpack/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ var findRoot = require('find-root')
, assign = require('object-assign')
, resolve = require('resolve')
, semver = require('semver')
, has = require('has')

var log = require('debug')('eslint-plugin-import:resolver:webpack')

Expand Down Expand Up @@ -265,7 +266,7 @@ function findExternal(source, externals, context) {

// else, vanilla object
for (var key in externals) {
if (!externals.hasOwnProperty(key)) continue
if (!has(externals, key)) continue
if (source === key) return true
}
return false
Expand Down
66 changes: 44 additions & 22 deletions src/rules/extensions.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,27 @@
import path from 'path'
import endsWith from 'lodash.endswith'
import has from 'has'
import assign from 'object-assign'

import resolve from '../core/resolve'
import { isBuiltIn } from '../core/importType'

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

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

return configuration === 'always'
function isUseOfExtensionForbidden(extension) {
if (!has(modifiers, extension)) { modifiers[extension] = defaultConfig }
return modifiers[extension] === 'never'
}

function isResolvableWithoutExtension(file) {
Expand All @@ -37,15 +46,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 +68,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 c45fdc3

Please sign in to comment.