Skip to content

Commit

Permalink
Merge pull request #1085 from benmosher/packageDir-array
Browse files Browse the repository at this point in the history
no-extraneous-dependencies: multiple packageDir(s)
  • Loading branch information
benmosher authored Apr 24, 2018
2 parents 412ee2e + 5111c79 commit 8c9c3b8
Show file tree
Hide file tree
Showing 8 changed files with 146 additions and 16 deletions.
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",
}],
}),
]
})

0 comments on commit 8c9c3b8

Please sign in to comment.