Skip to content
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

Optionally resolve single-argument require calls. #100

Merged
merged 8 commits into from
Nov 10, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@ extends: eslint:recommended
env:
node: true
rules:
max-len: [1, 80, 2]
max-len: [1, 99, 2]
semi: [2, "never"]
quotes: [2, "single"]
curly: [2, "multi-line"]
comma-dangle: [2, always-multiline]
eqeqeq: [2, "allow-null"]
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ plugins:
- import

rules:
import/no-unresolved: 2
import/no-unresolved: [2, {commonjs: true, amd: true}]
import/named: 2
import/namespace: 2
import/default: 2
Expand All @@ -73,6 +73,10 @@ as defined by standard Node `require.resolve` behavior.
See [settings](#settings) for customization options for the resolution (i.e.
additional filetypes, `NODE_PATH`, etc.)

This rule can also optionally report on unresolved modules in CommonJS `require('./foo')` calls and AMD `require(['./foo'], function (foo){...})` and `define(['./foo'], function (foo){...})`.

To enable this, send `{ commonjs: true/false, amd: true/false }` as a rule option.
Both are disabled by default.

### `named`

Expand Down
6 changes: 5 additions & 1 deletion src/.eslintrc
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
---
extends: eslint:recommended
extends: '../.eslintrc'

env:
es6: true

ecmaFeatures:
modules: true

rules:
comma-dangle: [1, "always-multiline"]
6 changes: 3 additions & 3 deletions src/core/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@ import fs from 'fs'

const defaultParseOptions = { ecmaVersion: 6 // for espree, esprima. not needed
// for babylon
, sourceType: "module"
, sourceType: 'module',
}

export default function parse(path, context) {
const { settings } = context
, parser = settings['import/parser'] || "babylon"
, parser = settings['import/parser'] || 'babylon'

const { parse } = require(parser)
, options = Object.assign( {}
, defaultParseOptions
, settings['import/parse-options'])

// detect and handle "jsx" ecmaFeature
if (context.ecmaFeatures && parser === "babylon") {
if (context.ecmaFeatures && parser === 'babylon') {
const { jsx } = context.ecmaFeatures
if (jsx && (!options.plugins || options.plugins.indexOf('jsx') < 0)) {
if (!options.plugins) options.plugins = ['jsx']
Expand Down
2 changes: 1 addition & 1 deletion src/rules/default.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,6 @@ module.exports = function (context) {

return {
'ImportDeclaration': checkDefault.bind(null, 'ImportDefaultSpecifier'),
'ExportNamedDeclaration': checkDefault.bind(null, 'ExportDefaultSpecifier')
'ExportNamedDeclaration': checkDefault.bind(null, 'ExportDefaultSpecifier'),
}
}
2 changes: 1 addition & 1 deletion src/rules/export.js
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,6 @@ module.exports = function (context) {
context.report(node, `Multiple exports of name '${name}'.`)
}
}
}
},
}
}
16 changes: 11 additions & 5 deletions src/rules/imports-first.js
Original file line number Diff line number Diff line change
@@ -1,24 +1,30 @@
module.exports = function (context) {
return {
"Program": function (n) {
'Program': function (n) {
const body = n.body
, absoluteFirst = context.options[0] === 'absolute-first'
let last = -1
, anyRelative = false
body.forEach(function (node, i){
if (node.type === "ImportDeclaration") {
if (node.type === 'ImportDeclaration') {
if (absoluteFirst) {
if (/^\./.test(node.source.value)) {
anyRelative = true
} else if (anyRelative) {
context.report(node.source, 'Absolute imports should come before relative imports.')
context.report({
node: node.source,
message: 'Absolute imports should come before relative imports.',
})
}
}
if (i !== ++last) {
context.report(node, 'Import in body of module; reorder to top.')
context.report({
node,
message: 'Import in body of module; reorder to top.',
})
}
}
})
}
},
}
}
2 changes: 1 addition & 1 deletion src/rules/named.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ module.exports = function (context) {
'ExportNamedDeclaration': checkSpecifiers.bind( null
, 'local'
, 'ExportSpecifier'
)
),
}

}
4 changes: 2 additions & 2 deletions src/rules/namespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,13 +90,13 @@ module.exports = function (context) {
for (let property of id.properties) {
if (property.key.type !== 'Identifier') {
context.report( property
, "Only destructure top-level names.")
, 'Only destructure top-level names.')
} else if (!namespace.has(property.key.name)) {
context.report( property
, message(property.key, init)
)
}
}
}
},
}
}
6 changes: 3 additions & 3 deletions src/rules/no-duplicates.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import resolve from '../core/resolve'
module.exports = function (context) {
const imported = new Map()
return {
"ImportDeclaration": function (n) {
'ImportDeclaration': function (n) {
// resolved path will cover aliased duplicates
let resolvedPath = resolve(n.source.value, context) || n.source.value

Expand All @@ -14,14 +14,14 @@ module.exports = function (context) {
}
},

"Program:exit": function () {
'Program:exit': function () {
for (let [module, nodes] of imported.entries()) {
if (nodes.size > 1) {
for (let node of nodes) {
context.report(node, `'${module}' imported multiple times.`)
}
}
}
}
},
}
}
2 changes: 1 addition & 1 deletion src/rules/no-errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,6 @@ module.exports = function (context) {
if (imports.errors.length > 0) {
context.report(node.source, message(node, imports.errors))
}
}
},
}
}
2 changes: 1 addition & 1 deletion src/rules/no-named-as-default.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,6 @@ module.exports = function (context) {
}
return {
'ImportDefaultSpecifier': checkDefault.bind(null, 'local'),
'ExportDefaultSpecifier': checkDefault.bind(null, 'exported')
'ExportDefaultSpecifier': checkDefault.bind(null, 'exported'),
}
}
4 changes: 2 additions & 2 deletions src/rules/no-require.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ module.exports = function (context) {
// keeping it simple: all 1-string-arg `require` calls are reported
context.report({
node: call.callee,
message: `CommonJS require of module '${module.value}'.`
message: `CommonJS require of module '${module.value}'.`,
})
}
},
}
}
72 changes: 66 additions & 6 deletions src/rules/no-unresolved.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,78 @@ import resolve from '../core/resolve'

module.exports = function (context) {

function checkSourceValue(source) {
if (source == null) return

if (resolve(source.value, context) == null) {
context.report(source,
'Unable to resolve path to module \'' + source.value + '\'.')
}
}

// for import-y declarations
function checkSource(node) {
if (node.source == null) return
checkSourceValue(node.source)
}

// for CommonJS `require` calls
// adapted from https://github.com/mctep/eslint-plugin-import/commit/acd4b4508d551f7f800fdd06e5c64ec01f3d1113
function checkCommon(call) {
if (call.callee.type !== 'Identifier') return
if (call.callee.name !== 'require') return
if (call.arguments.length !== 1) return

const modulePath = call.arguments[0]
if (modulePath.type !== 'Literal') return
if (typeof modulePath.value !== 'string') return

checkSourceValue(modulePath)
}

if (resolve(node.source.value, context) == null) {
context.report(node.source,
'Unable to resolve path to module \'' + node.source.value + '\'.')
function checkAMD(call) {
if (call.callee.type !== 'Identifier') return
if (call.callee.name !== 'require' &&
call.callee.name !== 'define') return
if (call.arguments.length !== 2) return

const modules = call.arguments[0]
if (modules.type !== 'ArrayExpression') return

for (let element of modules.elements) {
if (element.type !== 'Literal') continue
if (typeof element.value !== 'string') continue

checkSourceValue(element)
}
}

return {
const visitors = {
'ImportDeclaration': checkSource,
'ExportNamedDeclaration': checkSource,
'ExportAllDeclaration': checkSource
'ExportAllDeclaration': checkSource,
}

if (context.options[0] != null) {
const { commonjs, amd } = context.options[0]

if (commonjs || amd) {
visitors['CallExpression'] = function (call) {
if (commonjs) checkCommon(call)
if (amd) checkAMD(call)
}
}
}

return visitors
}

module.exports.schema = [
{
'type': 'object',
'properties': {
'commonjs': { 'type': 'boolean' },
'amd': { 'type': 'boolean' },
},
'additionalProperties': false,
},
]
Loading