From 20079c643be9494886a7add1a5c572dddd9e1396 Mon Sep 17 00:00:00 2001 From: Ben Mosher Date: Thu, 15 Sep 2016 06:58:33 -0400 Subject: [PATCH] bump ESLint devDep to 3.x + new rule format - converted all rules using eslint-transforms tool - updated no-unresolved + namespace to match tool output (module.exports vs. exports.{meta,create}) --- package.json | 4 +- src/rules/default.js | 50 ++-- src/rules/export.js | 114 ++++----- src/rules/extensions.js | 120 +++++----- src/rules/first.js | 80 ++++--- src/rules/max-dependencies.js | 68 +++--- src/rules/named.js | 86 +++---- src/rules/namespace.js | 292 ++++++++++++------------ src/rules/newline-after-import.js | 140 ++++++------ src/rules/no-absolute-path.js | 28 ++- src/rules/no-amd.js | 36 +-- src/rules/no-commonjs.js | 73 +++--- src/rules/no-deprecated.js | 198 ++++++++-------- src/rules/no-duplicates.js | 44 ++-- src/rules/no-extraneous-dependencies.js | 74 +++--- src/rules/no-internal-modules.js | 148 ++++++------ src/rules/no-mutable-exports.js | 66 +++--- src/rules/no-named-as-default-member.js | 134 +++++------ src/rules/no-named-as-default.js | 44 ++-- src/rules/no-namespace.js | 18 +- src/rules/no-nodejs-modules.js | 32 +-- src/rules/no-restricted-paths.js | 114 ++++----- src/rules/no-unresolved.js | 44 ++-- src/rules/order.js | 138 +++++------ src/rules/prefer-default-export.js | 120 +++++----- 25 files changed, 1200 insertions(+), 1065 deletions(-) diff --git a/package.json b/package.json index a132997c4..b174c9299 100644 --- a/package.json +++ b/package.json @@ -49,13 +49,13 @@ "homepage": "https://github.com/benmosher/eslint-plugin-import", "devDependencies": { "babel-eslint": "next", - "babel-preset-es2015-argon": "latest", "babel-plugin-istanbul": "^2.0.1", + "babel-preset-es2015-argon": "latest", "babel-register": "6.9.0", "chai": "^3.4.0", "coveralls": "^2.11.4", "cross-env": "^2.0.0", - "eslint": "2.x", + "eslint": "3.x", "eslint-import-resolver-node": "file:./resolvers/node", "eslint-import-resolver-webpack": "file:./resolvers/webpack", "eslint-module-utils": "file:./utils", diff --git a/src/rules/default.js b/src/rules/default.js index 4eaa70ff5..22e5883d8 100644 --- a/src/rules/default.js +++ b/src/rules/default.js @@ -1,31 +1,37 @@ import Exports from '../ExportMap' -module.exports = function (context) { +module.exports = { + meta: { + docs: {}, + }, - function checkDefault(specifierType, node) { + create: function (context) { - // poor man's Array.find - let defaultSpecifier - node.specifiers.some((n) => { - if (n.type === specifierType) { - defaultSpecifier = n - return true - } - }) + function checkDefault(specifierType, node) { + + // poor man's Array.find + let defaultSpecifier + node.specifiers.some((n) => { + if (n.type === specifierType) { + defaultSpecifier = n + return true + } + }) - if (!defaultSpecifier) return - var imports = Exports.get(node.source.value, context) - if (imports == null) return + if (!defaultSpecifier) return + var imports = Exports.get(node.source.value, context) + if (imports == null) return - if (imports.errors.length) { - imports.reportErrors(context, node) - } else if (!imports.get('default')) { - context.report(defaultSpecifier, 'No default export found in module.') + if (imports.errors.length) { + imports.reportErrors(context, node) + } else if (!imports.get('default')) { + context.report(defaultSpecifier, 'No default export found in module.') + } } - } - return { - 'ImportDeclaration': checkDefault.bind(null, 'ImportDefaultSpecifier'), - 'ExportNamedDeclaration': checkDefault.bind(null, 'ExportDefaultSpecifier'), - } + return { + 'ImportDeclaration': checkDefault.bind(null, 'ImportDefaultSpecifier'), + 'ExportNamedDeclaration': checkDefault.bind(null, 'ExportDefaultSpecifier'), + } + }, } diff --git a/src/rules/export.js b/src/rules/export.js index 61d21b86f..f7f37305e 100644 --- a/src/rules/export.js +++ b/src/rules/export.js @@ -1,72 +1,78 @@ import ExportMap, { recursivePatternCapture } from '../ExportMap' -module.exports = function (context) { - const named = new Map() +module.exports = { + meta: { + docs: {}, + }, - function addNamed(name, node) { - let nodes = named.get(name) + create: function (context) { + const named = new Map() - if (nodes == null) { - nodes = new Set() - named.set(name, nodes) - } + function addNamed(name, node) { + let nodes = named.get(name) - nodes.add(node) - } + if (nodes == null) { + nodes = new Set() + named.set(name, nodes) + } - return { - 'ExportDefaultDeclaration': (node) => addNamed('default', node), + nodes.add(node) + } - 'ExportSpecifier': function (node) { - addNamed(node.exported.name, node.exported) - }, + return { + 'ExportDefaultDeclaration': (node) => addNamed('default', node), - 'ExportNamedDeclaration': function (node) { - if (node.declaration == null) return + 'ExportSpecifier': function (node) { + addNamed(node.exported.name, node.exported) + }, - if (node.declaration.id != null) { - addNamed(node.declaration.id.name, node.declaration.id) - } + 'ExportNamedDeclaration': function (node) { + if (node.declaration == null) return - if (node.declaration.declarations != null) { - for (let declaration of node.declaration.declarations) { - recursivePatternCapture(declaration.id, v => addNamed(v.name, v)) + if (node.declaration.id != null) { + addNamed(node.declaration.id.name, node.declaration.id) } - } - }, - 'ExportAllDeclaration': function (node) { - if (node.source == null) return // not sure if this is ever true + if (node.declaration.declarations != null) { + for (let declaration of node.declaration.declarations) { + recursivePatternCapture(declaration.id, v => addNamed(v.name, v)) + } + } + }, - const remoteExports = ExportMap.get(node.source.value, context) - if (remoteExports == null) return + 'ExportAllDeclaration': function (node) { + if (node.source == null) return // not sure if this is ever true - if (remoteExports.errors.length) { - remoteExports.reportErrors(context, node) - return - } - let any = false - remoteExports.forEach((v, name) => - name !== 'default' && - (any = true) && // poor man's filter - addNamed(name, node)) - - if (!any) { - context.report(node.source, - `No named exports found in module '${node.source.value}'.`) - } - }, + const remoteExports = ExportMap.get(node.source.value, context) + if (remoteExports == null) return - 'Program:exit': function () { - for (let [name, nodes] of named) { - if (nodes.size <= 1) continue + if (remoteExports.errors.length) { + remoteExports.reportErrors(context, node) + return + } + let any = false + remoteExports.forEach((v, name) => + name !== 'default' && + (any = true) && // poor man's filter + addNamed(name, node)) - for (let node of nodes) { - if (name === 'default') { - context.report(node, 'Multiple default exports.') - } else context.report(node, `Multiple exports of name '${name}'.`) + if (!any) { + context.report(node.source, + `No named exports found in module '${node.source.value}'.`) } - } - }, - } + }, + + 'Program:exit': function () { + for (let [name, nodes] of named) { + if (nodes.size <= 1) continue + + for (let node of nodes) { + if (name === 'default') { + context.report(node, 'Multiple default exports.') + } else context.report(node, `Multiple exports of name '${name}'.`) + } + } + }, + } + }, } diff --git a/src/rules/extensions.js b/src/rules/extensions.js index a1a3c31a4..eec9530ac 100644 --- a/src/rules/extensions.js +++ b/src/rules/extensions.js @@ -4,73 +4,79 @@ import endsWith from 'lodash.endswith' import resolve from 'eslint-module-utils/resolve' import { isBuiltIn } from '../core/importType' -module.exports = function (context) { - const configuration = context.options[0] || 'never' +module.exports = { + meta: { + docs: {}, - function isUseOfExtensionEnforced(extension) { - if (typeof configuration === 'object') { - return configuration[extension] === 'always' - } + schema: [ + { + oneOf: [ + { + enum: [ 'always', 'never' ], + }, + { + type: 'object', + patternProperties: { + '.*': { enum: [ 'always', 'never' ] }, + }, + }, + ], + }, + ], + }, - return configuration === 'always' - } + create: function (context) { + const configuration = context.options[0] || 'never' - function isResolvableWithoutExtension(file) { - const extension = path.extname(file) - const fileWithoutExtension = file.slice(0, -extension.length) - const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context) + function isUseOfExtensionEnforced(extension) { + if (typeof configuration === 'object') { + return configuration[extension] === 'always' + } - return resolvedFileWithoutExtension === resolve(file, context) - } + return configuration === 'always' + } - function checkFileExtension(node) { - const { source } = node - const importPath = source.value + function isResolvableWithoutExtension(file) { + const extension = path.extname(file) + const fileWithoutExtension = file.slice(0, -extension.length) + const resolvedFileWithoutExtension = resolve(fileWithoutExtension, context) - // don't enforce anything on builtins - if (isBuiltIn(importPath, context.settings)) return + return resolvedFileWithoutExtension === resolve(file, context) + } - const resolvedPath = resolve(importPath, context) + function checkFileExtension(node) { + const { source } = node + const importPath = source.value - // get extension from resolved path, if possible. - // for unresolved, use source value. - const extension = path.extname(resolvedPath || importPath).substring(1) + // don't enforce anything on builtins + if (isBuiltIn(importPath, context.settings)) return - if (!extension || !endsWith(importPath, extension)) { - if (isUseOfExtensionEnforced(extension)) { - context.report({ - node: source, - message: - `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPath}"`, - }) - } - } else if (extension) { - if (!isUseOfExtensionEnforced(extension) && isResolvableWithoutExtension(importPath)) { - context.report({ - node: source, - message: `Unexpected use of file extension "${extension}" for "${importPath}"`, - }) + const resolvedPath = resolve(importPath, context) + + // get extension from resolved path, if possible. + // for unresolved, use source value. + const extension = path.extname(resolvedPath || importPath).substring(1) + + if (!extension || !endsWith(importPath, extension)) { + if (isUseOfExtensionEnforced(extension)) { + context.report({ + node: source, + message: + `Missing file extension ${extension ? `"${extension}" ` : ''}for "${importPath}"`, + }) + } + } else if (extension) { + if (!isUseOfExtensionEnforced(extension) && isResolvableWithoutExtension(importPath)) { + context.report({ + node: source, + message: `Unexpected use of file extension "${extension}" for "${importPath}"`, + }) + } } } - } - - return { - ImportDeclaration: checkFileExtension, - } -} -module.exports.schema = [ - { - oneOf: [ - { - enum: [ 'always', 'never' ], - }, - { - type: 'object', - patternProperties: { - '.*': { enum: [ 'always', 'never' ] }, - }, - }, - ], + return { + ImportDeclaration: checkFileExtension, + } }, -] +} diff --git a/src/rules/first.js b/src/rules/first.js index 8a3f37369..7642cae1d 100644 --- a/src/rules/first.js +++ b/src/rules/first.js @@ -1,45 +1,51 @@ -module.exports = function (context) { - function isPossibleDirective (node) { - return node.type === 'ExpressionStatement' && - node.expression.type === 'Literal' && - typeof node.expression.value === 'string' - } +module.exports = { + meta: { + docs: {}, + }, - return { - 'Program': function (n) { - const body = n.body - , absoluteFirst = context.options[0] === 'absolute-first' - let nonImportCount = 0 - , anyExpressions = false - , anyRelative = false - body.forEach(function (node){ - if (!anyExpressions && isPossibleDirective(node)) { - return - } + create: function (context) { + function isPossibleDirective (node) { + return node.type === 'ExpressionStatement' && + node.expression.type === 'Literal' && + typeof node.expression.value === 'string' + } - anyExpressions = true + return { + 'Program': function (n) { + const body = n.body + , absoluteFirst = context.options[0] === 'absolute-first' + let nonImportCount = 0 + , anyExpressions = false + , anyRelative = false + body.forEach(function (node){ + if (!anyExpressions && isPossibleDirective(node)) { + return + } + + anyExpressions = true - if (node.type === 'ImportDeclaration') { - if (absoluteFirst) { - if (/^\./.test(node.source.value)) { - anyRelative = true - } else if (anyRelative) { + if (node.type === 'ImportDeclaration') { + if (absoluteFirst) { + if (/^\./.test(node.source.value)) { + anyRelative = true + } else if (anyRelative) { + context.report({ + node: node.source, + message: 'Absolute imports should come before relative imports.', + }) + } + } + if (nonImportCount > 0) { context.report({ - node: node.source, - message: 'Absolute imports should come before relative imports.', + node, + message: 'Import in body of module; reorder to top.', }) } + } else { + nonImportCount++ } - if (nonImportCount > 0) { - context.report({ - node, - message: 'Import in body of module; reorder to top.', - }) - } - } else { - nonImportCount++ - } - }) - }, - } + }) + }, + } + }, } diff --git a/src/rules/max-dependencies.js b/src/rules/max-dependencies.js index 396e21e54..c28afd086 100644 --- a/src/rules/max-dependencies.js +++ b/src/rules/max-dependencies.js @@ -14,36 +14,42 @@ const countDependencies = (dependencies, lastNode, context) => { } } -module.exports = context => { - const dependencies = new Set() // keep track of dependencies - let lastNode // keep track of the last node to report on - - return { - ImportDeclaration(node) { - dependencies.add(node.source.value) - lastNode = node.source - }, - - CallExpression(node) { - if (isStaticRequire(node)) { - const [ requirePath ] = node.arguments - dependencies.add(requirePath.value) - lastNode = node - } - }, - - 'Program:exit': function () { - countDependencies(dependencies, lastNode, context) - }, - } -} +module.exports = { + meta: { + docs: {}, + + schema: [ + { + 'type': 'object', + 'properties': { + 'max': { 'type': 'number' }, + }, + 'additionalProperties': false, + }, + ], + }, -module.exports.schema = [ - { - 'type': 'object', - 'properties': { - 'max': { 'type': 'number' }, - }, - 'additionalProperties': false, + create: context => { + const dependencies = new Set() // keep track of dependencies + let lastNode // keep track of the last node to report on + + return { + ImportDeclaration(node) { + dependencies.add(node.source.value) + lastNode = node.source + }, + + CallExpression(node) { + if (isStaticRequire(node)) { + const [ requirePath ] = node.arguments + dependencies.add(requirePath.value) + lastNode = node + } + }, + + 'Program:exit': function () { + countDependencies(dependencies, lastNode, context) + }, + } }, -] +} diff --git a/src/rules/named.js b/src/rules/named.js index 853331752..d7589ebe7 100644 --- a/src/rules/named.js +++ b/src/rules/named.js @@ -1,54 +1,60 @@ import * as path from 'path' import Exports from '../ExportMap' -module.exports = function (context) { - function checkSpecifiers(key, type, node) { - if (node.source == null) return // local export, ignore - - if (!node.specifiers - .some(function (im) { return im.type === type })) { - return // no named imports/exports - } +module.exports = { + meta: { + docs: {}, + }, + + create: function (context) { + function checkSpecifiers(key, type, node) { + if (node.source == null) return // local export, ignore + + if (!node.specifiers + .some(function (im) { return im.type === type })) { + return // no named imports/exports + } - const imports = Exports.get(node.source.value, context) - if (imports == null) return + const imports = Exports.get(node.source.value, context) + if (imports == null) return - if (imports.errors.length) { - imports.reportErrors(context, node) - return - } + if (imports.errors.length) { + imports.reportErrors(context, node) + return + } - node.specifiers.forEach(function (im) { - if (im.type !== type) return + node.specifiers.forEach(function (im) { + if (im.type !== type) return - const deepLookup = imports.hasDeep(im[key].name) + const deepLookup = imports.hasDeep(im[key].name) - if (!deepLookup.found) { - if (deepLookup.path.length > 1) { - const deepPath = deepLookup.path - .map(i => path.relative(path.dirname(context.getFilename()), i.path)) - .join(' -> ') + if (!deepLookup.found) { + if (deepLookup.path.length > 1) { + const deepPath = deepLookup.path + .map(i => path.relative(path.dirname(context.getFilename()), i.path)) + .join(' -> ') - context.report(im[key], - `${im[key].name} not found via ${deepPath}`) - } else { - context.report(im[key], - im[key].name + ' not found in \'' + node.source.value + '\'') + context.report(im[key], + `${im[key].name} not found via ${deepPath}`) + } else { + context.report(im[key], + im[key].name + ' not found in \'' + node.source.value + '\'') + } } - } - }) - } + }) + } - return { - 'ImportDeclaration': checkSpecifiers.bind( null - , 'imported' - , 'ImportSpecifier' - ), + return { + 'ImportDeclaration': checkSpecifiers.bind( null + , 'imported' + , 'ImportSpecifier' + ), - 'ExportNamedDeclaration': checkSpecifiers.bind( null - , 'local' - , 'ExportSpecifier' - ), - } + 'ExportNamedDeclaration': checkSpecifiers.bind( null + , 'local' + , 'ExportSpecifier' + ), + } + }, } diff --git a/src/rules/namespace.js b/src/rules/namespace.js index a313908b1..87824c179 100644 --- a/src/rules/namespace.js +++ b/src/rules/namespace.js @@ -2,49 +2,86 @@ import Exports from '../ExportMap' import importDeclaration from '../importDeclaration' import declaredScope from 'eslint-module-utils/declaredScope' -exports.meta = { - schema: [ - { - 'type': 'object', - 'properties': { - 'allowComputed': { - 'description': - 'If `false`, will report computed (and thus, un-lintable) references ' + - 'to namespace members.', - 'type': 'boolean', - 'default': false, +module.exports = { + meta: { + schema: [ + { + 'type': 'object', + 'properties': { + 'allowComputed': { + 'description': + 'If `false`, will report computed (and thus, un-lintable) references ' + + 'to namespace members.', + 'type': 'boolean', + 'default': false, + }, }, + 'additionalProperties': false, }, - 'additionalProperties': false, - }, - ], -} + ], + }, + + create: function namespaceRule(context) { + + // read options + const { + allowComputed = false, + } = context.options[0] || {} + + const namespaces = new Map() -exports.create = function namespaceRule(context) { + function makeMessage(last, namepath) { + return `'${last.name}' not found in` + + (namepath.length > 1 ? ' deeply ' : ' ') + + `imported namespace '${namepath.join('.')}'.` + } - // read options - const { - allowComputed = false, - } = context.options[0] || {} + return { - const namespaces = new Map() + // pick up all imports at body entry time, to properly respect hoisting + 'Program': function ({ body }) { + function processBodyStatement(declaration) { + if (declaration.type !== 'ImportDeclaration') return - function makeMessage(last, namepath) { - return `'${last.name}' not found in` + - (namepath.length > 1 ? ' deeply ' : ' ') + - `imported namespace '${namepath.join('.')}'.` - } + if (declaration.specifiers.length === 0) return - return { + const imports = Exports.get(declaration.source.value, context) + if (imports == null) return null - // pick up all imports at body entry time, to properly respect hoisting - 'Program': function ({ body }) { - function processBodyStatement(declaration) { - if (declaration.type !== 'ImportDeclaration') return + if (imports.errors.length) { + imports.reportErrors(context, declaration) + return + } + + for (let specifier of declaration.specifiers) { + switch (specifier.type) { + case 'ImportNamespaceSpecifier': + if (!imports.size) { + context.report(specifier, + `No exported names found in module '${declaration.source.value}'.`) + } + namespaces.set(specifier.local.name, imports) + break + case 'ImportDefaultSpecifier': + case 'ImportSpecifier': { + const meta = imports.get( + // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg + specifier.imported ? specifier.imported.name : 'default') + if (!meta || !meta.namespace) break + namespaces.set(specifier.local.name, meta.namespace) + break + } + } + } + } + body.forEach(processBodyStatement) + }, - if (declaration.specifiers.length === 0) return + // same as above, but does not add names to local map + 'ExportNamespaceSpecifier': function (namespace) { + var declaration = importDeclaration(context) - const imports = Exports.get(declaration.source.value, context) + var imports = Exports.get(declaration.source.value, context) if (imports == null) return null if (imports.errors.length) { @@ -52,133 +89,98 @@ exports.create = function namespaceRule(context) { return } - for (let specifier of declaration.specifiers) { - switch (specifier.type) { - case 'ImportNamespaceSpecifier': - if (!imports.size) { - context.report(specifier, - `No exported names found in module '${declaration.source.value}'.`) - } - namespaces.set(specifier.local.name, imports) - break - case 'ImportDefaultSpecifier': - case 'ImportSpecifier': { - const meta = imports.get( - // default to 'default' for default http://i.imgur.com/nj6qAWy.jpg - specifier.imported ? specifier.imported.name : 'default') - if (!meta || !meta.namespace) break - namespaces.set(specifier.local.name, meta.namespace) - break + if (!imports.size) { + context.report(namespace, + `No exported names found in module '${declaration.source.value}'.`) + } + }, + + // todo: check for possible redefinition + + 'MemberExpression': function (dereference) { + if (dereference.object.type !== 'Identifier') return + if (!namespaces.has(dereference.object.name)) return + + if (dereference.parent.type === 'AssignmentExpression' && + dereference.parent.left === dereference) { + context.report(dereference.parent, + `Assignment to member of namespace '${dereference.object.name}'.`) + } + + // go deep + var namespace = namespaces.get(dereference.object.name) + var namepath = [dereference.object.name] + // while property is namespace and parent is member expression, keep validating + while (namespace instanceof Exports && + dereference.type === 'MemberExpression') { + + if (dereference.computed) { + if (!allowComputed) { + context.report(dereference.property, + 'Unable to validate computed reference to imported namespace \'' + + dereference.object.name + '\'.') } + return } - } - } - body.forEach(processBodyStatement) - }, - - // same as above, but does not add names to local map - 'ExportNamespaceSpecifier': function (namespace) { - var declaration = importDeclaration(context) - - var imports = Exports.get(declaration.source.value, context) - if (imports == null) return null - - if (imports.errors.length) { - imports.reportErrors(context, declaration) - return - } - - if (!imports.size) { - context.report(namespace, - `No exported names found in module '${declaration.source.value}'.`) - } - }, - - // todo: check for possible redefinition - - 'MemberExpression': function (dereference) { - if (dereference.object.type !== 'Identifier') return - if (!namespaces.has(dereference.object.name)) return - - if (dereference.parent.type === 'AssignmentExpression' && - dereference.parent.left === dereference) { - context.report(dereference.parent, - `Assignment to member of namespace '${dereference.object.name}'.`) - } - - // go deep - var namespace = namespaces.get(dereference.object.name) - var namepath = [dereference.object.name] - // while property is namespace and parent is member expression, keep validating - while (namespace instanceof Exports && - dereference.type === 'MemberExpression') { - - if (dereference.computed) { - if (!allowComputed) { - context.report(dereference.property, - 'Unable to validate computed reference to imported namespace \'' + - dereference.object.name + '\'.') + + if (!namespace.has(dereference.property.name)) { + context.report( + dereference.property, + makeMessage(dereference.property, namepath)) + break } - return - } - if (!namespace.has(dereference.property.name)) { - context.report( - dereference.property, - makeMessage(dereference.property, namepath)) - break - } + const exported = namespace.get(dereference.property.name) + if (exported == null) return - const exported = namespace.get(dereference.property.name) - if (exported == null) return + // stash and pop + namepath.push(dereference.property.name) + namespace = exported.namespace + dereference = dereference.parent + } - // stash and pop - namepath.push(dereference.property.name) - namespace = exported.namespace - dereference = dereference.parent - } + }, - }, + 'VariableDeclarator': function ({ id, init }) { + if (init == null) return + if (init.type !== 'Identifier') return + if (!namespaces.has(init.name)) return - 'VariableDeclarator': function ({ id, init }) { - if (init == null) return - if (init.type !== 'Identifier') return - if (!namespaces.has(init.name)) return + // check for redefinition in intermediate scopes + if (declaredScope(context, init.name) !== 'module') return - // check for redefinition in intermediate scopes - if (declaredScope(context, init.name) !== 'module') return + // DFS traverse child namespaces + function testKey(pattern, namespace, path = [init.name]) { + if (!(namespace instanceof Exports)) return - // DFS traverse child namespaces - function testKey(pattern, namespace, path = [init.name]) { - if (!(namespace instanceof Exports)) return + if (pattern.type !== 'ObjectPattern') return - if (pattern.type !== 'ObjectPattern') return + for (let property of pattern.properties) { - for (let property of pattern.properties) { + if (property.key.type !== 'Identifier') { + context.report({ + node: property, + message: 'Only destructure top-level names.', + }) + continue + } - if (property.key.type !== 'Identifier') { - context.report({ - node: property, - message: 'Only destructure top-level names.', - }) - continue - } + if (!namespace.has(property.key.name)) { + context.report({ + node: property, + message: makeMessage(property.key, path), + }) + continue + } - if (!namespace.has(property.key.name)) { - context.report({ - node: property, - message: makeMessage(property.key, path), - }) - continue + path.push(property.key.name) + testKey(property.value, namespace.get(property.key.name).namespace, path) + path.pop() } - - path.push(property.key.name) - testKey(property.value, namespace.get(property.key.name).namespace, path) - path.pop() } - } - testKey(id, namespaces.get(init.name)) - }, - } + testKey(id, namespaces.get(init.name)) + }, + } + }, } diff --git a/src/rules/newline-after-import.js b/src/rules/newline-after-import.js index 4c429b6e9..ca0286748 100644 --- a/src/rules/newline-after-import.js +++ b/src/rules/newline-after-import.js @@ -41,86 +41,92 @@ function getLineDifference(node, nextNode) { } -module.exports = function (context) { - const scopes = [] - let scopeIndex = 0 +module.exports = { + meta: { + docs: {}, + }, - function checkForNewLine(node, nextNode, type) { - if (getLineDifference(node, nextNode) < 2) { - let column = node.loc.start.column + create: function (context) { + const scopes = [] + let scopeIndex = 0 - if (node.loc.start.line !== node.loc.end.line) { - column = 0 - } + function checkForNewLine(node, nextNode, type) { + if (getLineDifference(node, nextNode) < 2) { + let column = node.loc.start.column + + if (node.loc.start.line !== node.loc.end.line) { + column = 0 + } - context.report({ - loc: { - line: node.loc.end.line, - column, - }, - message: `Expected empty line after ${type} statement not followed by another ${type}.`, - }) + context.report({ + loc: { + line: node.loc.end.line, + column, + }, + message: `Expected empty line after ${type} statement not followed by another ${type}.`, + }) + } } - } - return { - ImportDeclaration: function (node) { - const { parent } = node - const nodePosition = parent.body.indexOf(node) - const nextNode = parent.body[nodePosition + 1] + return { + ImportDeclaration: function (node) { + const { parent } = node + const nodePosition = parent.body.indexOf(node) + const nextNode = parent.body[nodePosition + 1] - if (nextNode && nextNode.type !== 'ImportDeclaration') { - checkForNewLine(node, nextNode, 'import') - } - }, - Program: function () { - scopes.push({ scope: context.getScope(), requireCalls: [] }) - }, - CallExpression: function(node) { - const scope = context.getScope() - if (isStaticRequire(node)) { - const currentScope = scopes[scopeIndex] - - if (scope === currentScope.scope) { - currentScope.requireCalls.push(node) - } else { - scopes.push({ scope, requireCalls: [ node ] }) - scopeIndex += 1 + if (nextNode && nextNode.type !== 'ImportDeclaration') { + checkForNewLine(node, nextNode, 'import') } - } - }, - 'Program:exit': function () { - log('exit processing for', context.getFilename()) - scopes.forEach(function ({ scope, requireCalls }) { - const scopeBody = getScopeBody(scope) - - // skip non-array scopes (i.e. arrow function expressions) - if (!scopeBody || !(scopeBody instanceof Array)) { - log('invalid scope:', scopeBody) - return + }, + Program: function () { + scopes.push({ scope: context.getScope(), requireCalls: [] }) + }, + CallExpression: function(node) { + const scope = context.getScope() + if (isStaticRequire(node)) { + const currentScope = scopes[scopeIndex] + + if (scope === currentScope.scope) { + currentScope.requireCalls.push(node) + } else { + scopes.push({ scope, requireCalls: [ node ] }) + scopeIndex += 1 + } } + }, + 'Program:exit': function () { + log('exit processing for', context.getFilename()) + scopes.forEach(function ({ scope, requireCalls }) { + const scopeBody = getScopeBody(scope) + + // skip non-array scopes (i.e. arrow function expressions) + if (!scopeBody || !(scopeBody instanceof Array)) { + log('invalid scope:', scopeBody) + return + } - log('got scope:', scopeBody) + log('got scope:', scopeBody) - requireCalls.forEach(function (node, index) { - const nodePosition = findNodeIndexInScopeBody(scopeBody, node) - log('node position in scope:', nodePosition) + requireCalls.forEach(function (node, index) { + const nodePosition = findNodeIndexInScopeBody(scopeBody, node) + log('node position in scope:', nodePosition) - const statementWithRequireCall = scopeBody[nodePosition] - const nextStatement = scopeBody[nodePosition + 1] - const nextRequireCall = requireCalls[index + 1] + const statementWithRequireCall = scopeBody[nodePosition] + const nextStatement = scopeBody[nodePosition + 1] + const nextRequireCall = requireCalls[index + 1] - if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) { - return - } + if (nextRequireCall && containsNodeOrEqual(statementWithRequireCall, nextRequireCall)) { + return + } - if (nextStatement && - (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) { + if (nextStatement && + (!nextRequireCall || !containsNodeOrEqual(nextStatement, nextRequireCall))) { - checkForNewLine(statementWithRequireCall, nextStatement, 'require') - } + checkForNewLine(statementWithRequireCall, nextStatement, 'require') + } + }) }) - }) - }, - } + }, + } + }, } diff --git a/src/rules/no-absolute-path.js b/src/rules/no-absolute-path.js index 08c8b80fe..33da932fd 100644 --- a/src/rules/no-absolute-path.js +++ b/src/rules/no-absolute-path.js @@ -7,15 +7,21 @@ function reportIfMissing(context, node, name) { } } -module.exports = function (context) { - return { - ImportDeclaration: function handleImports(node) { - reportIfMissing(context, node, node.source.value) - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfMissing(context, node, node.arguments[0].value) - } - }, - } +module.exports = { + meta: { + docs: {}, + }, + + create: function (context) { + return { + ImportDeclaration: function handleImports(node) { + reportIfMissing(context, node, node.source.value) + }, + CallExpression: function handleRequires(node) { + if (isStaticRequire(node)) { + reportIfMissing(context, node, node.arguments[0].value) + } + }, + } + }, } diff --git a/src/rules/no-amd.js b/src/rules/no-amd.js index 86f1c9952..6686be935 100644 --- a/src/rules/no-amd.js +++ b/src/rules/no-amd.js @@ -7,27 +7,33 @@ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function (context) { +module.exports = { + meta: { + docs: {}, + }, - return { + create: function (context) { - 'CallExpression': function (node) { - if (context.getScope().type !== 'module') return + return { - if (node.callee.type !== 'Identifier') return - if (node.callee.name !== 'require' && - node.callee.name !== 'define') return + 'CallExpression': function (node) { + if (context.getScope().type !== 'module') return - // todo: capture define((require, module, exports) => {}) form? - if (node.arguments.length !== 2) return + if (node.callee.type !== 'Identifier') return + if (node.callee.name !== 'require' && + node.callee.name !== 'define') return - const modules = node.arguments[0] - if (modules.type !== 'ArrayExpression') return + // todo: capture define((require, module, exports) => {}) form? + if (node.arguments.length !== 2) return - // todo: check second arg type? (identifier or callback) + const modules = node.arguments[0] + if (modules.type !== 'ArrayExpression') return - context.report(node, `Expected imports instead of AMD ${node.callee.name}().`) - }, - } + // todo: check second arg type? (identifier or callback) + context.report(node, `Expected imports instead of AMD ${node.callee.name}().`) + }, + } + + }, } diff --git a/src/rules/no-commonjs.js b/src/rules/no-commonjs.js index eff79660f..62a0804f2 100644 --- a/src/rules/no-commonjs.js +++ b/src/rules/no-commonjs.js @@ -6,54 +6,59 @@ const EXPORT_MESSAGE = 'Expected "export" or "export default"' , IMPORT_MESSAGE = 'Expected "import" instead of "require()"' +function allowPrimitive(node, context) { + if (context.options.indexOf('allow-primitive-modules') < 0) return false + if (node.parent.type !== 'AssignmentExpression') return false + return (node.parent.right.type !== 'ObjectExpression') +} + //------------------------------------------------------------------------------ // Rule Definition //------------------------------------------------------------------------------ -module.exports = function (context) { +module.exports = { + meta: { + docs: {}, + }, - return { + create: function (context) { - 'MemberExpression': function (node) { + return { - // module.exports - if (node.object.name === 'module' && node.property.name === 'exports') { - if (allowPrimitive(node, context)) return - context.report({ node, message: EXPORT_MESSAGE }) - } + 'MemberExpression': function (node) { - // exports. - if (node.object.name === 'exports') { - context.report({ node, message: EXPORT_MESSAGE }) - } + // module.exports + if (node.object.name === 'module' && node.property.name === 'exports') { + if (allowPrimitive(node, context)) return + context.report({ node, message: EXPORT_MESSAGE }) + } - }, - 'CallExpression': function (call) { - if (context.getScope().type !== 'module') return + // exports. + if (node.object.name === 'exports') { + context.report({ node, message: EXPORT_MESSAGE }) + } - if (call.callee.type !== 'Identifier') return - if (call.callee.name !== 'require') return + }, + 'CallExpression': function (call) { + if (context.getScope().type !== 'module') return - if (call.arguments.length !== 1) return - var module = call.arguments[0] + if (call.callee.type !== 'Identifier') return + if (call.callee.name !== 'require') return - if (module.type !== 'Literal') return - if (typeof module.value !== 'string') return + if (call.arguments.length !== 1) return + var module = call.arguments[0] - // keeping it simple: all 1-string-arg `require` calls are reported - context.report({ - node: call.callee, - message: IMPORT_MESSAGE, - }) - }, - } + if (module.type !== 'Literal') return + if (typeof module.value !== 'string') return -} + // keeping it simple: all 1-string-arg `require` calls are reported + context.report({ + node: call.callee, + message: IMPORT_MESSAGE, + }) + }, + } - // allow non-objects as module.exports -function allowPrimitive(node, context) { - if (context.options.indexOf('allow-primitive-modules') < 0) return false - if (node.parent.type !== 'AssignmentExpression') return false - return (node.parent.right.type !== 'ObjectExpression') + }, } diff --git a/src/rules/no-deprecated.js b/src/rules/no-deprecated.js index fc5a959ed..e50c2e516 100644 --- a/src/rules/no-deprecated.js +++ b/src/rules/no-deprecated.js @@ -1,132 +1,138 @@ import Exports from '../ExportMap' import declaredScope from 'eslint-module-utils/declaredScope' -module.exports = function (context) { - const deprecated = new Map() - , namespaces = new Map() - - function checkSpecifiers(node) { - if (node.type !== 'ImportDeclaration') return - if (node.source == null) return // local export, ignore - - const imports = Exports.get(node.source.value, context) - if (imports == null) return +function message(deprecation) { + return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.') +} - let moduleDeprecation - if (imports.doc && - imports.doc.tags.some(t => t.title === 'deprecated' && (moduleDeprecation = t))) { - context.report({ node, message: message(moduleDeprecation) }) - } +function getDeprecation(metadata) { + if (!metadata || !metadata.doc) return - if (imports.errors.length) { - imports.reportErrors(context, node) - return - } + let deprecation + if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) { + return deprecation + } +} - node.specifiers.forEach(function (im) { - let imported, local - switch (im.type) { +module.exports = { + meta: { + docs: {}, + }, + create: function (context) { + const deprecated = new Map() + , namespaces = new Map() - case 'ImportNamespaceSpecifier':{ - if (!imports.size) return - namespaces.set(im.local.name, imports) - return - } + function checkSpecifiers(node) { + if (node.type !== 'ImportDeclaration') return + if (node.source == null) return // local export, ignore - case 'ImportDefaultSpecifier': - imported = 'default' - local = im.local.name - break + const imports = Exports.get(node.source.value, context) + if (imports == null) return - case 'ImportSpecifier': - imported = im.imported.name - local = im.local.name - break + let moduleDeprecation + if (imports.doc && + imports.doc.tags.some(t => t.title === 'deprecated' && (moduleDeprecation = t))) { + context.report({ node, message: message(moduleDeprecation) }) + } - default: return // can't handle this one + if (imports.errors.length) { + imports.reportErrors(context, node) + return } - // unknown thing can't be deprecated - const exported = imports.get(imported) - if (exported == null) return + node.specifiers.forEach(function (im) { + let imported, local + switch (im.type) { - // capture import of deep namespace - if (exported.namespace) namespaces.set(local, exported.namespace) - const deprecation = getDeprecation(imports.get(imported)) - if (!deprecation) return + case 'ImportNamespaceSpecifier':{ + if (!imports.size) return + namespaces.set(im.local.name, imports) + return + } - context.report({ node: im, message: message(deprecation) }) + case 'ImportDefaultSpecifier': + imported = 'default' + local = im.local.name + break - deprecated.set(local, deprecation) + case 'ImportSpecifier': + imported = im.imported.name + local = im.local.name + break - }) - } + default: return // can't handle this one + } - return { - 'Program': ({ body }) => body.forEach(checkSpecifiers), + // unknown thing can't be deprecated + const exported = imports.get(imported) + if (exported == null) return - 'Identifier': function (node) { - if (node.parent.type === 'MemberExpression' && node.parent.property === node) { - return // handled by MemberExpression - } + // capture import of deep namespace + if (exported.namespace) namespaces.set(local, exported.namespace) + + const deprecation = getDeprecation(imports.get(imported)) + if (!deprecation) return - // ignore specifier identifiers - if (node.parent.type.slice(0, 6) === 'Import') return + context.report({ node: im, message: message(deprecation) }) - if (!deprecated.has(node.name)) return + deprecated.set(local, deprecation) - if (declaredScope(context, node.name) !== 'module') return - context.report({ - node, - message: message(deprecated.get(node.name)), }) - }, + } - 'MemberExpression': function (dereference) { - if (dereference.object.type !== 'Identifier') return - if (!namespaces.has(dereference.object.name)) return + return { + 'Program': ({ body }) => body.forEach(checkSpecifiers), - if (declaredScope(context, dereference.object.name) !== 'module') return + 'Identifier': function (node) { + if (node.parent.type === 'MemberExpression' && node.parent.property === node) { + return // handled by MemberExpression + } - // go deep - var namespace = namespaces.get(dereference.object.name) - var namepath = [dereference.object.name] - // while property is namespace and parent is member expression, keep validating - while (namespace instanceof Exports && - dereference.type === 'MemberExpression') { + // ignore specifier identifiers + if (node.parent.type.slice(0, 6) === 'Import') return - // ignore computed parts for now - if (dereference.computed) return + if (!deprecated.has(node.name)) return - const metadata = namespace.get(dereference.property.name) + if (declaredScope(context, node.name) !== 'module') return + context.report({ + node, + message: message(deprecated.get(node.name)), + }) + }, - if (!metadata) break - const deprecation = getDeprecation(metadata) + 'MemberExpression': function (dereference) { + if (dereference.object.type !== 'Identifier') return + if (!namespaces.has(dereference.object.name)) return - if (deprecation) { - context.report({ node: dereference.property, message: message(deprecation) }) - } + if (declaredScope(context, dereference.object.name) !== 'module') return - // stash and pop - namepath.push(dereference.property.name) - namespace = metadata.namespace - dereference = dereference.parent - } - }, - } -} + // go deep + var namespace = namespaces.get(dereference.object.name) + var namepath = [dereference.object.name] + // while property is namespace and parent is member expression, keep validating + while (namespace instanceof Exports && + dereference.type === 'MemberExpression') { -function message(deprecation) { - return 'Deprecated' + (deprecation.description ? ': ' + deprecation.description : '.') -} + // ignore computed parts for now + if (dereference.computed) return -function getDeprecation(metadata) { - if (!metadata || !metadata.doc) return + const metadata = namespace.get(dereference.property.name) - let deprecation - if (metadata.doc.tags.some(t => t.title === 'deprecated' && (deprecation = t))) { - return deprecation - } + if (!metadata) break + const deprecation = getDeprecation(metadata) + + if (deprecation) { + context.report({ node: dereference.property, message: message(deprecation) }) + } + + // stash and pop + namepath.push(dereference.property.name) + namespace = metadata.namespace + dereference = dereference.parent + } + }, + } + }, } diff --git a/src/rules/no-duplicates.js b/src/rules/no-duplicates.js index af5747018..a63fc44d5 100644 --- a/src/rules/no-duplicates.js +++ b/src/rules/no-duplicates.js @@ -10,25 +10,31 @@ function checkImports(imported, context) { } } -module.exports = function (context) { - const imported = new Map() - const typesImported = new Map() - return { - 'ImportDeclaration': function (n) { - // resolved path will cover aliased duplicates - const resolvedPath = resolve(n.source.value, context) || n.source.value - const importMap = n.importKind === 'type' ? typesImported : imported +module.exports = { + meta: { + docs: {}, + }, - if (importMap.has(resolvedPath)) { - importMap.get(resolvedPath).add(n.source) - } else { - importMap.set(resolvedPath, new Set([n.source])) - } - }, + create: function (context) { + const imported = new Map() + const typesImported = new Map() + return { + 'ImportDeclaration': function (n) { + // resolved path will cover aliased duplicates + const resolvedPath = resolve(n.source.value, context) || n.source.value + const importMap = n.importKind === 'type' ? typesImported : imported - 'Program:exit': function () { - checkImports(imported, context) - checkImports(typesImported, context) - }, - } + if (importMap.has(resolvedPath)) { + importMap.get(resolvedPath).add(n.source) + } else { + importMap.set(resolvedPath, new Set([n.source])) + } + }, + + 'Program:exit': function () { + checkImports(imported, context) + checkImports(typesImported, context) + }, + } + }, } diff --git a/src/rules/no-extraneous-dependencies.js b/src/rules/no-extraneous-dependencies.js index 65bdb27e5..a14bb611d 100644 --- a/src/rules/no-extraneous-dependencies.js +++ b/src/rules/no-extraneous-dependencies.js @@ -81,42 +81,48 @@ function testConfig(config, filename) { return config.some(c => minimatch(filename, c)) } -module.exports = function (context) { - const options = context.options[0] || {} - const filename = context.getFilename() - const deps = getDependencies(context) +module.exports = { + meta: { + docs: {}, + + schema: [ + { + 'type': 'object', + 'properties': { + 'devDependencies': { 'type': ['boolean', 'array'] }, + 'optionalDependencies': { 'type': ['boolean', 'array'] }, + 'peerDependencies': { 'type': ['boolean', 'array'] }, + }, + 'additionalProperties': false, + }, + ], + }, - if (!deps) { - return {} - } + create: function (context) { + const options = context.options[0] || {} + const filename = context.getFilename() + const deps = getDependencies(context) - const depsOptions = { - allowDevDeps: testConfig(options.devDependencies, filename) !== false, - allowOptDeps: testConfig(options.optionalDependencies, filename) !== false, - allowPeerDeps: testConfig(options.peerDependencies, filename) !== false, - } + if (!deps) { + return {} + } - // todo: use module visitor from module-utils core - return { - ImportDeclaration: function (node) { - reportIfMissing(context, deps, depsOptions, node, node.source.value) - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfMissing(context, deps, depsOptions, node, node.arguments[0].value) - } - }, - } -} + const depsOptions = { + allowDevDeps: testConfig(options.devDependencies, filename) !== false, + allowOptDeps: testConfig(options.optionalDependencies, filename) !== false, + allowPeerDeps: testConfig(options.peerDependencies, filename) !== false, + } -module.exports.schema = [ - { - 'type': 'object', - 'properties': { - 'devDependencies': { 'type': ['boolean', 'array'] }, - 'optionalDependencies': { 'type': ['boolean', 'array'] }, - 'peerDependencies': { 'type': ['boolean', 'array'] }, - }, - 'additionalProperties': false, + // todo: use module visitor from module-utils core + return { + ImportDeclaration: function (node) { + reportIfMissing(context, deps, depsOptions, node, node.source.value) + }, + CallExpression: function handleRequires(node) { + if (isStaticRequire(node)) { + reportIfMissing(context, deps, depsOptions, node, node.arguments[0].value) + } + }, + } }, -] +} diff --git a/src/rules/no-internal-modules.js b/src/rules/no-internal-modules.js index bc9669a45..0c7ede6df 100644 --- a/src/rules/no-internal-modules.js +++ b/src/rules/no-internal-modules.js @@ -5,88 +5,94 @@ import resolve from 'eslint-module-utils/resolve' import importType from '../core/importType' import isStaticRequire from '../core/staticRequire' -module.exports = function noReachingInside(context) { - const options = context.options[0] || {} - const allowRegexps = (options.allow || []).map(p => minimatch.makeRe(p)) +module.exports = { + meta: { + docs: {}, - // test if reaching to this destination is allowed - function reachingAllowed(importPath) { - return !!find(allowRegexps, re => re.test(importPath)) - } + schema: [ + { + type: 'object', + properties: { + allow: { + type: 'array', + items: { + type: 'string', + }, + }, + }, + additionalProperties: false, + }, + ], + }, - // minimatch patterns are expected to use / path separators, like import - // statements, so normalize paths to use the same - function normalizeSep(somePath) { - return somePath.split('\\').join('/') - } + create: function noReachingInside(context) { + const options = context.options[0] || {} + const allowRegexps = (options.allow || []).map(p => minimatch.makeRe(p)) - // find a directory that is being reached into, but which shouldn't be - function isReachViolation(importPath) { - const steps = normalizeSep(importPath) - .split('/') - .reduce((acc, step) => { - if (!step || step === '.') { - return acc - } else if (step === '..') { - return acc.slice(0, -1) - } else { - return acc.concat(step) - } - }, []) + // test if reaching to this destination is allowed + function reachingAllowed(importPath) { + return !!find(allowRegexps, re => re.test(importPath)) + } + + // minimatch patterns are expected to use / path separators, like import + // statements, so normalize paths to use the same + function normalizeSep(somePath) { + return somePath.split('\\').join('/') + } - if (steps.length <= 1) return false + // find a directory that is being reached into, but which shouldn't be + function isReachViolation(importPath) { + const steps = normalizeSep(importPath) + .split('/') + .reduce((acc, step) => { + if (!step || step === '.') { + return acc + } else if (step === '..') { + return acc.slice(0, -1) + } else { + return acc.concat(step) + } + }, []) - // before trying to resolve, see if the raw import (with relative - // segments resolved) matches an allowed pattern - const justSteps = steps.join('/') - if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) return false + if (steps.length <= 1) return false - // if the import statement doesn't match directly, try to match the - // resolved path if the import is resolvable - const resolved = resolve(importPath, context) - if (!resolved || reachingAllowed(normalizeSep(resolved))) return false + // before trying to resolve, see if the raw import (with relative + // segments resolved) matches an allowed pattern + const justSteps = steps.join('/') + if (reachingAllowed(justSteps) || reachingAllowed(`/${justSteps}`)) return false - // this import was not allowed by the allowed paths, and reaches - // so it is a violation - return true - } + // if the import statement doesn't match directly, try to match the + // resolved path if the import is resolvable + const resolved = resolve(importPath, context) + if (!resolved || reachingAllowed(normalizeSep(resolved))) return false - function checkImportForReaching(importPath, node) { - const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal'] - if (potentialViolationTypes.indexOf(importType(importPath, context)) !== -1 && - isReachViolation(importPath) - ) { - context.report({ - node, - message: `Reaching to "${importPath}" is not allowed.`, - }) + // this import was not allowed by the allowed paths, and reaches + // so it is a violation + return true } - } - return { - ImportDeclaration(node) { - checkImportForReaching(node.source.value, node.source) - }, - CallExpression(node) { - if (isStaticRequire(node)) { - const [ firstArgument ] = node.arguments - checkImportForReaching(firstArgument.value, firstArgument) + function checkImportForReaching(importPath, node) { + const potentialViolationTypes = ['parent', 'index', 'sibling', 'external', 'internal'] + if (potentialViolationTypes.indexOf(importType(importPath, context)) !== -1 && + isReachViolation(importPath) + ) { + context.report({ + node, + message: `Reaching to "${importPath}" is not allowed.`, + }) } - }, - } -} + } -module.exports.schema = [ - { - type: 'object', - properties: { - allow: { - type: 'array', - items: { - type: 'string', - }, + return { + ImportDeclaration(node) { + checkImportForReaching(node.source.value, node.source) }, - }, - additionalProperties: false, + CallExpression(node) { + if (isStaticRequire(node)) { + const [ firstArgument ] = node.arguments + checkImportForReaching(firstArgument.value, firstArgument) + } + }, + } }, -] +} diff --git a/src/rules/no-mutable-exports.js b/src/rules/no-mutable-exports.js index e6173bf11..8d16f8068 100644 --- a/src/rules/no-mutable-exports.js +++ b/src/rules/no-mutable-exports.js @@ -1,45 +1,51 @@ -module.exports = function (context) { - function checkDeclaration(node) { - const {kind} = node - if (kind === 'var' || kind === 'let') { - context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`) +module.exports = { + meta: { + docs: {}, + }, + + create: function (context) { + function checkDeclaration(node) { + const {kind} = node + if (kind === 'var' || kind === 'let') { + context.report(node, `Exporting mutable '${kind}' binding, use 'const' instead.`) + } } - } - function checkDeclarationsInScope({variables}, name) { - for (let variable of variables) { - if (variable.name === name) { - for (let def of variable.defs) { - if (def.type === 'Variable') { - checkDeclaration(def.parent) + function checkDeclarationsInScope({variables}, name) { + for (let variable of variables) { + if (variable.name === name) { + for (let def of variable.defs) { + if (def.type === 'Variable') { + checkDeclaration(def.parent) + } } } } } - } - function handleExportDefault(node) { - const scope = context.getScope() + function handleExportDefault(node) { + const scope = context.getScope() - if (node.declaration.name) { - checkDeclarationsInScope(scope, node.declaration.name) + if (node.declaration.name) { + checkDeclarationsInScope(scope, node.declaration.name) + } } - } - function handleExportNamed(node) { - const scope = context.getScope() + function handleExportNamed(node) { + const scope = context.getScope() - if (node.declaration) { - checkDeclaration(node.declaration) - } else if (!node.source) { - for (let specifier of node.specifiers) { - checkDeclarationsInScope(scope, specifier.local.name) + if (node.declaration) { + checkDeclaration(node.declaration) + } else if (!node.source) { + for (let specifier of node.specifiers) { + checkDeclarationsInScope(scope, specifier.local.name) + } } } - } - return { - 'ExportDefaultDeclaration': handleExportDefault, - 'ExportNamedDeclaration': handleExportNamed, - } + return { + 'ExportDefaultDeclaration': handleExportDefault, + 'ExportNamedDeclaration': handleExportNamed, + } + }, } diff --git a/src/rules/no-named-as-default-member.js b/src/rules/no-named-as-default-member.js index f02bfe4a5..2ffa19cff 100644 --- a/src/rules/no-named-as-default-member.js +++ b/src/rules/no-named-as-default-member.js @@ -11,81 +11,87 @@ import importDeclaration from '../importDeclaration' // Rule Definition //------------------------------------------------------------------------------ -module.exports = function(context) { +module.exports = { + meta: { + docs: {}, + }, - const fileImports = new Map() - const allPropertyLookups = new Map() + create: function(context) { - function handleImportDefault(node) { - const declaration = importDeclaration(context) - const exportMap = Exports.get(declaration.source.value, context) - if (exportMap == null) return + const fileImports = new Map() + const allPropertyLookups = new Map() - if (exportMap.errors.length) { - exportMap.reportErrors(context, declaration) - return - } + function handleImportDefault(node) { + const declaration = importDeclaration(context) + const exportMap = Exports.get(declaration.source.value, context) + if (exportMap == null) return - fileImports.set(node.local.name, { - exportMap, - sourcePath: declaration.source.value, - }) - } + if (exportMap.errors.length) { + exportMap.reportErrors(context, declaration) + return + } - function storePropertyLookup(objectName, propName, node) { - const lookups = allPropertyLookups.get(objectName) || [] - lookups.push({node, propName}) - allPropertyLookups.set(objectName, lookups) - } + fileImports.set(node.local.name, { + exportMap, + sourcePath: declaration.source.value, + }) + } + + function storePropertyLookup(objectName, propName, node) { + const lookups = allPropertyLookups.get(objectName) || [] + lookups.push({node, propName}) + allPropertyLookups.set(objectName, lookups) + } - function handlePropLookup(node) { - const objectName = node.object.name - const propName = node.property.name - storePropertyLookup(objectName, propName, node) - } + function handlePropLookup(node) { + const objectName = node.object.name + const propName = node.property.name + storePropertyLookup(objectName, propName, node) + } - function handleDestructuringAssignment(node) { - const isDestructure = ( - node.id.type === 'ObjectPattern' && - node.init != null && - node.init.type === 'Identifier' - ) - if (!isDestructure) return + function handleDestructuringAssignment(node) { + const isDestructure = ( + node.id.type === 'ObjectPattern' && + node.init != null && + node.init.type === 'Identifier' + ) + if (!isDestructure) return - const objectName = node.init.name - for (const { key } of node.id.properties) { - if (key == null) continue // true for rest properties - storePropertyLookup(objectName, key.name, key) + const objectName = node.init.name + for (const { key } of node.id.properties) { + if (key == null) continue // true for rest properties + storePropertyLookup(objectName, key.name, key) + } } - } - function handleProgramExit() { - allPropertyLookups.forEach((lookups, objectName) => { - const fileImport = fileImports.get(objectName) - if (fileImport == null) return + function handleProgramExit() { + allPropertyLookups.forEach((lookups, objectName) => { + const fileImport = fileImports.get(objectName) + if (fileImport == null) return - for (const {propName, node} of lookups) { - // the default import can have a "default" property - if (propName === 'default') continue - if (!fileImport.exportMap.namespace.has(propName)) continue + for (const {propName, node} of lookups) { + // the default import can have a "default" property + if (propName === 'default') continue + if (!fileImport.exportMap.namespace.has(propName)) continue - context.report({ - node, - message: ( - `Caution: \`${objectName}\` also has a named export ` + - `\`${propName}\`. Check if you meant to write ` + - `\`import {${propName}} from '${fileImport.sourcePath}'\` ` + - 'instead.' - ), - }) - } - }) - } + context.report({ + node, + message: ( + `Caution: \`${objectName}\` also has a named export ` + + `\`${propName}\`. Check if you meant to write ` + + `\`import {${propName}} from '${fileImport.sourcePath}'\` ` + + 'instead.' + ), + }) + } + }) + } - return { - 'ImportDefaultSpecifier': handleImportDefault, - 'MemberExpression': handlePropLookup, - 'VariableDeclarator': handleDestructuringAssignment, - 'Program:exit': handleProgramExit, - } + return { + 'ImportDefaultSpecifier': handleImportDefault, + 'MemberExpression': handlePropLookup, + 'VariableDeclarator': handleDestructuringAssignment, + 'Program:exit': handleProgramExit, + } + }, } diff --git a/src/rules/no-named-as-default.js b/src/rules/no-named-as-default.js index ddee1cc5b..502728cc4 100644 --- a/src/rules/no-named-as-default.js +++ b/src/rules/no-named-as-default.js @@ -1,29 +1,35 @@ import Exports from '../ExportMap' import importDeclaration from '../importDeclaration' -module.exports = function (context) { - function checkDefault(nameKey, defaultSpecifier) { - var declaration = importDeclaration(context) +module.exports = { + meta: { + docs: {}, + }, - var imports = Exports.get(declaration.source.value, context) - if (imports == null) return + create: function (context) { + function checkDefault(nameKey, defaultSpecifier) { + var declaration = importDeclaration(context) - if (imports.errors.length) { - imports.reportErrors(context, declaration) - return - } + var imports = Exports.get(declaration.source.value, context) + if (imports == null) return + + if (imports.errors.length) { + imports.reportErrors(context, declaration) + return + } - if (imports.has('default') && - imports.has(defaultSpecifier[nameKey].name)) { + if (imports.has('default') && + imports.has(defaultSpecifier[nameKey].name)) { - context.report(defaultSpecifier, - 'Using exported name \'' + defaultSpecifier[nameKey].name + - '\' as identifier for default export.') + context.report(defaultSpecifier, + 'Using exported name \'' + defaultSpecifier[nameKey].name + + '\' as identifier for default export.') + } + } + return { + 'ImportDefaultSpecifier': checkDefault.bind(null, 'local'), + 'ExportDefaultSpecifier': checkDefault.bind(null, 'exported'), } - } - return { - 'ImportDefaultSpecifier': checkDefault.bind(null, 'local'), - 'ExportDefaultSpecifier': checkDefault.bind(null, 'exported'), - } + }, } diff --git a/src/rules/no-namespace.js b/src/rules/no-namespace.js index 2cd45d322..673735da0 100644 --- a/src/rules/no-namespace.js +++ b/src/rules/no-namespace.js @@ -8,10 +8,16 @@ //------------------------------------------------------------------------------ -module.exports = function (context) { - return { - 'ImportNamespaceSpecifier': function (node) { - context.report(node, `Unexpected namespace import.`) - }, - } +module.exports = { + meta: { + docs: {}, + }, + + create: function (context) { + return { + 'ImportNamespaceSpecifier': function (node) { + context.report(node, `Unexpected namespace import.`) + }, + } + }, } diff --git a/src/rules/no-nodejs-modules.js b/src/rules/no-nodejs-modules.js index 9160b9e8d..262fec7dd 100644 --- a/src/rules/no-nodejs-modules.js +++ b/src/rules/no-nodejs-modules.js @@ -7,18 +7,24 @@ function reportIfMissing(context, node, allowed, name) { } } -module.exports = function (context) { - const options = context.options[0] || {} - const allowed = options.allow || [] +module.exports = { + meta: { + docs: {}, + }, - return { - ImportDeclaration: function handleImports(node) { - reportIfMissing(context, node, allowed, node.source.value) - }, - CallExpression: function handleRequires(node) { - if (isStaticRequire(node)) { - reportIfMissing(context, node, allowed, node.arguments[0].value) - } - }, - } + create: function (context) { + const options = context.options[0] || {} + const allowed = options.allow || [] + + return { + ImportDeclaration: function handleImports(node) { + reportIfMissing(context, node, allowed, node.source.value) + }, + CallExpression: function handleRequires(node) { + if (isStaticRequire(node)) { + reportIfMissing(context, node, allowed, node.arguments[0].value) + } + }, + } + }, } diff --git a/src/rules/no-restricted-paths.js b/src/rules/no-restricted-paths.js index 9f76b8eb3..0240cd764 100644 --- a/src/rules/no-restricted-paths.js +++ b/src/rules/no-restricted-paths.js @@ -4,68 +4,74 @@ import path from 'path' import resolve from 'eslint-module-utils/resolve' import isStaticRequire from '../core/staticRequire' -module.exports = function noRestrictedPaths(context) { - const options = context.options[0] || {} - const restrictedPaths = options.zones || [] - const basePath = options.basePath || process.cwd() - const currentFilename = context.getFilename() - const matchingZones = restrictedPaths.filter((zone) => { - const targetPath = path.resolve(basePath, zone.target) +module.exports = { + meta: { + docs: {}, - return containsPath(currentFilename, targetPath) - }) + schema: [ + { + type: 'object', + properties: { + zones: { + type: 'array', + minItems: 1, + items: { + type: 'object', + properties: { + target: { type: 'string' }, + from: { type: 'string' }, + }, + additionalProperties: false, + }, + }, + basePath: { type: 'string' }, + }, + additionalProperties: false, + }, + ], + }, - function checkForRestrictedImportPath(importPath, node) { - const absoluteImportPath = resolve(importPath, context) + create: function noRestrictedPaths(context) { + const options = context.options[0] || {} + const restrictedPaths = options.zones || [] + const basePath = options.basePath || process.cwd() + const currentFilename = context.getFilename() + const matchingZones = restrictedPaths.filter((zone) => { + const targetPath = path.resolve(basePath, zone.target) - if (!absoluteImportPath) { - return - } + return containsPath(currentFilename, targetPath) + }) - matchingZones.forEach((zone) => { - const absoluteFrom = path.resolve(basePath, zone.from) + function checkForRestrictedImportPath(importPath, node) { + const absoluteImportPath = resolve(importPath, context) - if (containsPath(absoluteImportPath, absoluteFrom)) { - context.report({ - node, - message: `Unexpected path "${importPath}" imported in restricted zone.`, - }) + if (!absoluteImportPath) { + return } - }) - } - return { - ImportDeclaration(node) { - checkForRestrictedImportPath(node.source.value, node.source) - }, - CallExpression(node) { - if (isStaticRequire(node)) { - const [ firstArgument ] = node.arguments + matchingZones.forEach((zone) => { + const absoluteFrom = path.resolve(basePath, zone.from) - checkForRestrictedImportPath(firstArgument.value, firstArgument) - } - }, - } -} + if (containsPath(absoluteImportPath, absoluteFrom)) { + context.report({ + node, + message: `Unexpected path "${importPath}" imported in restricted zone.`, + }) + } + }) + } -module.exports.schema = [ - { - type: 'object', - properties: { - zones: { - type: 'array', - minItems: 1, - items: { - type: 'object', - properties: { - target: { type: 'string' }, - from: { type: 'string' }, - }, - additionalProperties: false, - }, + return { + ImportDeclaration(node) { + checkForRestrictedImportPath(node.source.value, node.source) }, - basePath: { type: 'string' }, - }, - additionalProperties: false, + CallExpression(node) { + if (isStaticRequire(node)) { + const [ firstArgument ] = node.arguments + + checkForRestrictedImportPath(firstArgument.value, firstArgument) + } + }, + } }, -] +} diff --git a/src/rules/no-unresolved.js b/src/rules/no-unresolved.js index 05e9d484a..e5fe2366c 100644 --- a/src/rules/no-unresolved.js +++ b/src/rules/no-unresolved.js @@ -9,36 +9,38 @@ import moduleVisitor, { makeOptionsSchema } from 'eslint-module-utils/moduleVisi -exports.meta = { - schema: [ makeOptionsSchema({ - caseSensitive: { type: 'boolean', default: true }, - })], -} - -exports.create = function (context) { +module.exports = { + meta: { + schema: [ makeOptionsSchema({ + caseSensitive: { type: 'boolean', default: true }, + })], + }, - function checkSourceValue(source) { - const shouldCheckCase = !CASE_SENSITIVE_FS && - (!context.options[0] || context.options[0].caseSensitive !== false) + create: function (context) { - const resolvedPath = resolve(source.value, context) + function checkSourceValue(source) { + const shouldCheckCase = !CASE_SENSITIVE_FS && + (!context.options[0] || context.options[0].caseSensitive !== false) - if (resolvedPath === undefined) { - context.report(source, - `Unable to resolve path to module '${source.value}'.`) - } + const resolvedPath = resolve(source.value, context) - else if (shouldCheckCase) { - const cacheSettings = ModuleCache.getSettings(context.settings) - if (!fileExistsWithCaseSync(resolvedPath, cacheSettings)) { + if (resolvedPath === undefined) { context.report(source, - `Casing of ${source.value} does not match the underlying filesystem.`) + `Unable to resolve path to module '${source.value}'.`) } + else if (shouldCheckCase) { + const cacheSettings = ModuleCache.getSettings(context.settings) + if (!fileExistsWithCaseSync(resolvedPath, cacheSettings)) { + context.report(source, + `Casing of ${source.value} does not match the underlying filesystem.`) + } + + } } - } - return moduleVisitor(checkSourceValue, context.options[0]) + return moduleVisitor(checkSourceValue, context.options[0]) + }, } diff --git a/src/rules/order.js b/src/rules/order.js index ed5730ab1..0a227097c 100644 --- a/src/rules/order.js +++ b/src/rules/order.js @@ -145,77 +145,83 @@ function makeNewlinesBetweenReport (context, imported, newlinesBetweenImports) { }) } -module.exports = function importOrderRule (context) { - const options = context.options[0] || {} - let ranks - - try { - ranks = convertGroupsToRanks(options.groups || defaultGroups) - } catch (error) { - // Malformed configuration - return { - Program: function(node) { - context.report(node, error.message) - }, - } - } - let imported = [] - let level = 0 +module.exports = { + meta: { + docs: {}, - function incrementLevel() { - level++ - } - function decrementLevel() { - level-- - } + schema: [ + { + type: 'object', + properties: { + groups: { + type: 'array', + }, + 'newlines-between': { + enum: [ 'always', 'never' ], + }, + }, + additionalProperties: false, + }, + ], + }, - return { - ImportDeclaration: function handleImports(node) { - if (node.specifiers.length) { // Ignoring unassigned imports - const name = node.source.value - registerNode(context, node, name, 'import', ranks, imported) - } - }, - CallExpression: function handleRequires(node) { - if (level !== 0 || !isStaticRequire(node) || !isInVariableDeclarator(node.parent)) { - return - } - const name = node.arguments[0].value - registerNode(context, node, name, 'require', ranks, imported) - }, - 'Program:exit': function reportAndReset() { - makeOutOfOrderReport(context, imported) - - if ('newlines-between' in options) { - makeNewlinesBetweenReport(context, imported, options['newlines-between']) + create: function importOrderRule (context) { + const options = context.options[0] || {} + let ranks + + try { + ranks = convertGroupsToRanks(options.groups || defaultGroups) + } catch (error) { + // Malformed configuration + return { + Program: function(node) { + context.report(node, error.message) + }, } + } + let imported = [] + let level = 0 - imported = [] - }, - FunctionDeclaration: incrementLevel, - FunctionExpression: incrementLevel, - ArrowFunctionExpression: incrementLevel, - BlockStatement: incrementLevel, - ObjectExpression: incrementLevel, - 'FunctionDeclaration:exit': decrementLevel, - 'FunctionExpression:exit': decrementLevel, - 'ArrowFunctionExpression:exit': decrementLevel, - 'BlockStatement:exit': decrementLevel, - 'ObjectExpression:exit': decrementLevel, - } -} + function incrementLevel() { + level++ + } + function decrementLevel() { + level-- + } -module.exports.schema = [ - { - type: 'object', - properties: { - groups: { - type: 'array', + return { + ImportDeclaration: function handleImports(node) { + if (node.specifiers.length) { // Ignoring unassigned imports + const name = node.source.value + registerNode(context, node, name, 'import', ranks, imported) + } }, - 'newlines-between': { - enum: [ 'always', 'never' ], + CallExpression: function handleRequires(node) { + if (level !== 0 || !isStaticRequire(node) || !isInVariableDeclarator(node.parent)) { + return + } + const name = node.arguments[0].value + registerNode(context, node, name, 'require', ranks, imported) }, - }, - additionalProperties: false, + 'Program:exit': function reportAndReset() { + makeOutOfOrderReport(context, imported) + + if ('newlines-between' in options) { + makeNewlinesBetweenReport(context, imported, options['newlines-between']) + } + + imported = [] + }, + FunctionDeclaration: incrementLevel, + FunctionExpression: incrementLevel, + ArrowFunctionExpression: incrementLevel, + BlockStatement: incrementLevel, + ObjectExpression: incrementLevel, + 'FunctionDeclaration:exit': decrementLevel, + 'FunctionExpression:exit': decrementLevel, + 'ArrowFunctionExpression:exit': decrementLevel, + 'BlockStatement:exit': decrementLevel, + 'ObjectExpression:exit': decrementLevel, + } }, -] +} diff --git a/src/rules/prefer-default-export.js b/src/rules/prefer-default-export.js index 2bd4783eb..afff03341 100644 --- a/src/rules/prefer-default-export.js +++ b/src/rules/prefer-default-export.js @@ -1,63 +1,69 @@ 'use strict' -module.exports = function(context) { - let specifierExportCount = 0 - let hasDefaultExport = false - let hasStarExport = false - let namedExportNode = null - - return { - 'ExportSpecifier': function(node) { - if (node.exported.name === 'default') { - hasDefaultExport = true - } else { - specifierExportCount++ - namedExportNode = node - } - }, - - 'ExportNamedDeclaration': function(node) { - // if there are specifiers, node.declaration should be null - if (!node.declaration) return - - function captureDeclaration(identifierOrPattern) { - if (identifierOrPattern.type === 'ObjectPattern') { - // recursively capture - identifierOrPattern.properties - .forEach(function(property) { - captureDeclaration(property.value) - }) +module.exports = { + meta: { + docs: {}, + }, + + create: function(context) { + let specifierExportCount = 0 + let hasDefaultExport = false + let hasStarExport = false + let namedExportNode = null + + return { + 'ExportSpecifier': function(node) { + if (node.exported.name === 'default') { + hasDefaultExport = true } else { - // assume it's a single standard identifier specifierExportCount++ + namedExportNode = node + } + }, + + 'ExportNamedDeclaration': function(node) { + // if there are specifiers, node.declaration should be null + if (!node.declaration) return + + function captureDeclaration(identifierOrPattern) { + if (identifierOrPattern.type === 'ObjectPattern') { + // recursively capture + identifierOrPattern.properties + .forEach(function(property) { + captureDeclaration(property.value) + }) + } else { + // assume it's a single standard identifier + specifierExportCount++ + } + } + + if (node.declaration.declarations) { + node.declaration.declarations.forEach(function(declaration) { + captureDeclaration(declaration.id) + }) + } + else { + // captures 'export function foo() {}' syntax + specifierExportCount++ + } + + namedExportNode = node + }, + + 'ExportDefaultDeclaration': function() { + hasDefaultExport = true + }, + + 'ExportAllDeclaration': function() { + hasStarExport = true + }, + + 'Program:exit': function() { + if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport) { + context.report(namedExportNode, 'Prefer default export.') } - } - - if (node.declaration.declarations) { - node.declaration.declarations.forEach(function(declaration) { - captureDeclaration(declaration.id) - }) - } - else { - // captures 'export function foo() {}' syntax - specifierExportCount++ - } - - namedExportNode = node - }, - - 'ExportDefaultDeclaration': function() { - hasDefaultExport = true - }, - - 'ExportAllDeclaration': function() { - hasStarExport = true - }, - - 'Program:exit': function() { - if (specifierExportCount === 1 && !hasDefaultExport && !hasStarExport) { - context.report(namedExportNode, 'Prefer default export.') - } - }, - } + }, + } + }, }