-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[New] extensions
: accept both a string, and an object to override it.
#555
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
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) { | ||
|
@@ -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)) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You could compute the value for the extension There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you just mean, cache it per-extension? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sure, can-do. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, not cache per-se, though I don't see a reason why not. I was thinking of computing the value of it before line 41, as you will use the result later There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh, that's true. let me do that. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This change may not even be worth it overall tho - the tests are failing in Travis because the object is not extensible. Which do you prefer - remove the last commit; rework it to pass with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What are the reasons that There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. so, the reason bracket access and There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. k, tests should be passing now There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. (deleted and moved comment to line in question as a review) |
||
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}"`, | ||
|
@@ -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, | ||
}, | ||
], | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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' ] } }, | ||
}), | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe add a test case with never, for completeness' sake. |
||
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' ] }), | ||
|
@@ -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"', | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(restarted as an explicit line review, mostly out of curiosity + to gain familiarity with the new feature)
So, I'm not anti-dependencies in general, but I don't understand why
toString
orhasOwnProperty
Am I correct that these are the cases where
has
matters over just a plain ol'hasOwnProperty
call orin
operator? Because if that's true, I'd still like to revert tohasOwnProperty
or really, justextension in modifiers
because these are well-understood JS core idioms, and in this specific case, not practically distinguishable.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks ready to , other than this, though. 😄 Thanks to both of you for working through @jfmengels' feedback, too!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The first one, I also don't know why someone would do it, but given that it's a possibility, that would be a really silly reason for this package to break, don't you think?
As to the second, for a tool like an eslint plugin, that's a stronger case, since it won't be running in browser environments, or environments with unknown third-party code. However, the idea that it's acceptable for one's code to be broken because someone else did something bad, foolish, or simply unexpected mystifies me - as long as my code runs in a pristine environment, nothing that happens afterwards can break it. This seems like a pretty important property to me.
I'll of course remove the dependency if you insist, but I think this is still strictly better because you don't have to think about all these edge cases. I do not think
in
orhasOwnProperty
are well-understood idioms, which is why this problem keeps coming up in libs all over the place, and whyhas
exists.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(@benmosher let me know your final call, and i'll rebase out the last commit if needed)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fair points. It's not a huge deal either way, or it wouldn't be so easy to have this back and forth. 😄
I hear what you're saying, and maybe one day all our key checks turn into
has
calls, but I don't think today is that day.I didn't realize the
has
part was a separate commit -- I'll just go ahead and merge the first one.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agh, nevermind, it's bundled with some other stuff. I'll just keep it for now. 😁