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

no-extraneous-dependencies: multiple packageDir(s) #1085

Merged
merged 9 commits into from
Apr 24, 2018
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -361,6 +361,7 @@ settings:
[`eslint_d`]: https://www.npmjs.com/package/eslint_d
[`eslint-loader`]: https://www.npmjs.com/package/eslint-loader


## SublimeLinter-eslint

SublimeLinter-eslint introduced a change to support `.eslintignore` files
Expand Down
7 changes: 7 additions & 0 deletions docs/rules/no-extraneous-dependencies.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ Also there is one more option called `packageDir`, this option is to specify the
"import/no-extraneous-dependencies": ["error", {"packageDir": './some-dir/'}]
```

It may also be an array of multiple paths, to support monorepos or other novel project
folder layouts:

```js
"import/no-extraneous-dependencies": ["error", {"packageDir": ['./some-dir/', './root-pkg']}]
```

## Rule Details

Given the following `package.json`:
Expand Down
72 changes: 57 additions & 15 deletions src/rules/no-extraneous-dependencies.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,73 @@
import path from 'path'
import fs from 'fs'
import { isArray, isEmpty } from 'lodash'
import readPkgUp from 'read-pkg-up'
import minimatch from 'minimatch'
import resolve from 'eslint-module-utils/resolve'
import importType from '../core/importType'
import isStaticRequire from '../core/staticRequire'
import docsUrl from '../docsUrl'

function hasKeys(obj = {}) {
return Object.keys(obj).length > 0
}

function extractDepFields(pkg) {
return {
dependencies: pkg.dependencies || {},
devDependencies: pkg.devDependencies || {},
optionalDependencies: pkg.optionalDependencies || {},
peerDependencies: pkg.peerDependencies || {},
}
}

function getDependencies(context, packageDir) {
let paths = []
try {
const packageContent = packageDir
? JSON.parse(fs.readFileSync(path.join(packageDir, 'package.json'), 'utf8'))
: readPkgUp.sync({cwd: context.getFilename(), normalize: false}).pkg
const packageContent = {
dependencies: {},
devDependencies: {},
optionalDependencies: {},
peerDependencies: {},
}

if (!packageContent) {
return null
if (!isEmpty(packageDir)) {
if (!isArray(packageDir)) {
paths = [path.resolve(packageDir)]
} else {
paths = packageDir.map(dir => path.resolve(dir))
}
}

return {
dependencies: packageContent.dependencies || {},
devDependencies: packageContent.devDependencies || {},
optionalDependencies: packageContent.optionalDependencies || {},
peerDependencies: packageContent.peerDependencies || {},
if (!isEmpty(paths)) {
// use rule config to find package.json
paths.forEach(dir => {
Object.assign(packageContent, extractDepFields(
JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))
))
})
} else {
// use closest package.json
Object.assign(
packageContent,
extractDepFields(
readPkgUp.sync({cwd: context.getFilename(), normalize: false}).pkg
)
)
}

if (![
packageContent.dependencies,
packageContent.devDependencies,
packageContent.optionalDependencies,
packageContent.peerDependencies,
].some(hasKeys)) {
return null
}

return packageContent
} catch (e) {
if (packageDir && e.code === 'ENOENT') {
if (!isEmpty(paths) && e.code === 'ENOENT') {
context.report({
message: 'The package.json file could not be found.',
loc: { line: 0, column: 0 },
Expand Down Expand Up @@ -66,9 +109,8 @@ function reportIfMissing(context, deps, depsOptions, node, name) {
}

const resolved = resolve(name, context)
if (!resolved) {
return
}
if (!resolved) { return }

const splitName = name.split('/')
const packageName = splitName[0][0] === '@'
? splitName.slice(0, 2).join('/')
Expand Down Expand Up @@ -124,7 +166,7 @@ module.exports = {
'devDependencies': { 'type': ['boolean', 'array'] },
'optionalDependencies': { 'type': ['boolean', 'array'] },
'peerDependencies': { 'type': ['boolean', 'array'] },
'packageDir': { 'type': 'string' },
'packageDir': { 'type': ['string', 'array'] },
},
'additionalProperties': false,
},
Expand Down
6 changes: 6 additions & 0 deletions tests/files/monorepo/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"private": true,
"devDependencies": {
"left-pad": "^1.2.0"
}
}
6 changes: 6 additions & 0 deletions tests/files/monorepo/packages/nested-package/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "nested-monorepo-pkg",
"dependencies": {
"react": "^16.0.0"
}
}
1 change: 1 addition & 0 deletions tests/files/node_modules/left-pad

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions tests/files/node_modules/react

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

68 changes: 67 additions & 1 deletion tests/src/rules/no-extraneous-dependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ const packageFileWithSyntaxErrorMessage = (() => {
}
})()
const packageDirWithFlowTyped = path.join(__dirname, '../../files/with-flow-typed')
const packageDirMonoRepoRoot = path.join(__dirname, '../../files/monorepo')
const packageDirMonoRepoWithNested = path.join(__dirname, '../../files/monorepo/packages/nested-package')

ruleTester.run('no-extraneous-dependencies', rule, {
valid: [
Expand Down Expand Up @@ -75,8 +77,46 @@ ruleTester.run('no-extraneous-dependencies', rule, {
options: [{packageDir: packageDirWithFlowTyped}],
parser: 'babel-eslint',
}),
test({
code: 'import react from "react";',
options: [{packageDir: packageDirMonoRepoWithNested}],
}),
test({
code: 'import leftpad from "left-pad";',
options: [{packageDir: [packageDirMonoRepoWithNested, packageDirMonoRepoRoot]}],
}),
test({
code: 'import leftpad from "left-pad";',
options: [{packageDir: packageDirMonoRepoRoot}],
}),
],
invalid: [
test({
code: 'import "not-a-dependency"',
filename: path.join(packageDirMonoRepoRoot, 'foo.js'),
options: [{packageDir: packageDirMonoRepoRoot }],
errors: [{
ruleId: 'no-extraneous-dependencies',
message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
}],
}),
test({
code: 'import "not-a-dependency"',
filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),
options: [{packageDir: packageDirMonoRepoRoot}],
errors: [{
ruleId: 'no-extraneous-dependencies',
message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
}],
}),
test({
code: 'import "not-a-dependency"',
options: [{packageDir: packageDirMonoRepoRoot}],
errors: [{
ruleId: 'no-extraneous-dependencies',
message: '\'not-a-dependency\' should be listed in the project\'s dependencies. Run \'npm i -S not-a-dependency\' to add it',
}],
}),
test({
code: 'import "not-a-dependency"',
errors: [{
Expand Down Expand Up @@ -197,5 +237,31 @@ ruleTester.run('no-extraneous-dependencies', rule, {
message: 'The package.json file could not be parsed: ' + packageFileWithSyntaxErrorMessage,
}],
}),
],
test({
code: 'import leftpad from "left-pad";',
filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),
options: [{packageDir: packageDirMonoRepoWithNested}],
errors: [{
ruleId: 'no-extraneous-dependencies',
message: "'left-pad' should be listed in the project's dependencies. Run 'npm i -S left-pad' to add it",
}],
}),
test({
code: 'import react from "react";',
filename: path.join(packageDirMonoRepoRoot, 'foo.js'),
errors: [{
ruleId: 'no-extraneous-dependencies',
message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",
}],
}),
test({
code: 'import react from "react";',
filename: path.join(packageDirMonoRepoWithNested, 'foo.js'),
options: [{packageDir: packageDirMonoRepoRoot}],
errors: [{
ruleId: 'no-extraneous-dependencies',
message: "'react' should be listed in the project's dependencies. Run 'npm i -S react' to add it",
}],
}),
]
})