-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
7 changed files
with
223 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
'use strict' | ||
|
||
import cond from 'lodash.cond' | ||
import builtinModules from 'builtin-modules' | ||
|
||
function constant(value) { | ||
return () => value | ||
} | ||
|
||
function isBuiltIn(name) { | ||
return builtinModules.indexOf(name) !== -1 | ||
} | ||
|
||
const externalModuleRegExp = /^\w/ | ||
function isExternalModule(name) { | ||
return externalModuleRegExp.test(name) | ||
} | ||
|
||
function isRelativeToParent(name) { | ||
return name.indexOf('../') === 0 | ||
} | ||
|
||
const indexFiles = ['.', './', './index', './index.js'] | ||
function isIndex(name) { | ||
return indexFiles.indexOf(name) !== -1 | ||
} | ||
|
||
function isRelativeToSibling(name) { | ||
return name.indexOf('./') === 0 | ||
} | ||
|
||
export default cond([ | ||
[isBuiltIn, constant('builtin')], | ||
[isExternalModule, constant('external')], | ||
[isRelativeToParent, constant('parent')], | ||
[isIndex, constant('index')], | ||
[isRelativeToSibling, constant('sibling')], | ||
[constant(true), constant('unknown')], | ||
]) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export default function isStaticRequire(node) { | ||
return node && | ||
node.callee.type === 'Identifier' && | ||
node.callee.name === 'require' && | ||
node.arguments.length === 1 && | ||
node.arguments[0].type === 'Literal' | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
import fs from 'fs' | ||
import pkgUp from 'pkg-up' | ||
import importType from '../core/importType' | ||
import isStaticRequire from '../core/staticRequire' | ||
|
||
function getDependencies() { | ||
const filepath = pkgUp.sync() | ||
if (!filepath) { | ||
return null | ||
} | ||
|
||
try { | ||
const packageContent = JSON.parse(fs.readFileSync(filepath, 'utf8')) | ||
return { | ||
dependencies: packageContent.dependencies || {}, | ||
devDependencies: packageContent.devDependencies || {}, | ||
} | ||
} catch (e) { | ||
return null | ||
} | ||
} | ||
|
||
function missingErrorMessage(packageName) { | ||
return `'${packageName}' is not listed in the project's dependencies. ` + | ||
`Run 'npm i -S ${packageName}' to add it` | ||
} | ||
|
||
function devDepErrorMessage(packageName) { | ||
return `'${packageName}' is not listed in the project's dependencies, not devDependencies.` | ||
} | ||
|
||
function reportIfMissing(context, deps, allowDevDeps, node, name) { | ||
if (importType(name) !== 'external') { | ||
return | ||
} | ||
const packageName = name.split('/')[0] | ||
|
||
if (deps.dependencies[packageName] === undefined) { | ||
if (!allowDevDeps) { | ||
context.report(node, devDepErrorMessage(packageName)) | ||
} else if (deps.devDependencies[packageName] === undefined) { | ||
context.report(node, missingErrorMessage(packageName)) | ||
} | ||
} | ||
} | ||
|
||
module.exports = function (context) { | ||
const options = context.options[0] || {} | ||
const allowDevDeps = options.devDependencies !== false | ||
const deps = getDependencies() | ||
|
||
if (!deps) { | ||
return {} | ||
} | ||
|
||
return { | ||
ImportDeclaration: function (node) { | ||
reportIfMissing(context, deps, allowDevDeps, node, node.source.value) | ||
}, | ||
CallExpression: function handleRequires(node) { | ||
if (isStaticRequire(node)) { | ||
reportIfMissing(context, deps, allowDevDeps, node, node.arguments[0].value) | ||
} | ||
}, | ||
} | ||
} | ||
|
||
module.exports.schema = [ | ||
{ | ||
'type': 'object', | ||
'properties': { | ||
'devDependencies': { 'type': 'boolean' }, | ||
}, | ||
'additionalProperties': false, | ||
}, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
import { expect } from 'chai' | ||
import importType from 'core/importType' | ||
|
||
describe('importType(name)', function () { | ||
it("should return 'builtin' for node.js modules", function() { | ||
expect(importType('fs')).to.equal('builtin') | ||
expect(importType('path')).to.equal('builtin') | ||
}) | ||
|
||
it("should return 'external' for non-builtin modules without a relative path", function() { | ||
expect(importType('lodash')).to.equal('external') | ||
expect(importType('async')).to.equal('external') | ||
expect(importType('chalk')).to.equal('external') | ||
expect(importType('foo')).to.equal('external') | ||
expect(importType('lodash.find')).to.equal('external') | ||
expect(importType('lodash/fp')).to.equal('external') | ||
}) | ||
|
||
it("should return 'parent' for internal modules that go through the parent", function() { | ||
expect(importType('../foo')).to.equal('parent') | ||
expect(importType('../../foo')).to.equal('parent') | ||
expect(importType('../bar/foo')).to.equal('parent') | ||
}) | ||
|
||
it("should return 'sibling' for internal modules that are connected to one of the siblings", function() { | ||
expect(importType('./foo')).to.equal('sibling') | ||
expect(importType('./foo/bar')).to.equal('sibling') | ||
}) | ||
|
||
it("should return 'index' for sibling index file", function() { | ||
expect(importType('.')).to.equal('index') | ||
expect(importType('./')).to.equal('index') | ||
expect(importType('./index')).to.equal('index') | ||
expect(importType('./index.js')).to.equal('index') | ||
}) | ||
|
||
it("should return 'unknown' for any unhandled cases", function() { | ||
expect(importType('/malformed')).to.equal('unknown') | ||
expect(importType(' foo')).to.equal('unknown') | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
import { test } from '../utils' | ||
|
||
import { RuleTester } from 'eslint' | ||
|
||
const ruleTester = new RuleTester() | ||
, rule = require('rules/no-extraneous-dependencies') | ||
|
||
ruleTester.run('no-extraneous-dependencies', rule, { | ||
valid: [ | ||
test({ code: 'import "lodash.cond"'}), | ||
test({ code: 'import "pkg-up"'}), | ||
test({ code: 'import foo, { bar } from "lodash.cond"'}), | ||
test({ code: 'import foo, { bar } from "pkg-up"'}), | ||
test({ code: 'import "eslint"'}), | ||
test({ code: 'import "eslint/lib/api"'}), | ||
test({ code: 'require("lodash.cond")'}), | ||
test({ code: 'require("pkg-up")'}), | ||
test({ code: 'var foo = require("lodash.cond")'}), | ||
test({ code: 'var foo = require("pkg-up")'}), | ||
test({ code: 'import "fs"'}), | ||
test({ code: 'import "./foo"'}), | ||
], | ||
invalid: [ | ||
test({ | ||
code: 'import "not-a-dependency"', | ||
errors: [{ | ||
ruleId: 'no-extraneous-dependencies', | ||
message: '\'not-a-dependency\' is not listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', | ||
}], | ||
}), | ||
test({ | ||
code: 'import "eslint"', | ||
options: [{devDependencies: false}], | ||
errors: [{ | ||
ruleId: 'no-extraneous-dependencies', | ||
message: '\'eslint\' is not listed in the project\'s dependencies, not devDependencies.', | ||
}], | ||
}), | ||
test({ | ||
code: 'var foo = require("not-a-dependency");', | ||
errors: [{ | ||
ruleId: 'no-extraneous-dependencies', | ||
message: '\'not-a-dependency\' is not listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it', | ||
}], | ||
}), | ||
test({ | ||
code: 'var eslint = require("eslint");', | ||
options: [{devDependencies: false}], | ||
errors: [{ | ||
ruleId: 'no-extraneous-dependencies', | ||
message: '\'eslint\' is not listed in the project\'s dependencies, not devDependencies.', | ||
}], | ||
}), | ||
], | ||
}) |