diff --git a/docs/rules/no-extraneous-import.md b/docs/rules/no-extraneous-import.md index edf445d5..ce1f8e1f 100644 --- a/docs/rules/no-extraneous-import.md +++ b/docs/rules/no-extraneous-import.md @@ -17,7 +17,8 @@ This rule warns `import` declarations of extraneous modules. "node/no-extraneous-import": ["error", { "allowModules": [], "resolvePaths": [], - "tryExtensions": [] + "tryExtensions": [], + "yarnWorkspaces": false }] } } @@ -55,6 +56,13 @@ When an import path does not exist, this rule checks whether or not any of `path Default is `[".js", ".json", ".node"]`. +#### yarnWorkspaces + +When using [yarn workspaces](https://classic.yarnpkg.com/en/docs/workspaces), the dependencies are +defined in the top level folder. + +Default is `false` + ### Shared Settings The following options can be set by [shared settings](http://eslint.org/docs/user-guide/configuring.html#adding-shared-settings). @@ -63,6 +71,7 @@ Several rules have the same option, but we can set this option at once. - `allowModules` - `resolvePaths` - `tryExtensions` +- `yarnWorkspaces` ```js // .eslintrc.js @@ -71,7 +80,8 @@ module.exports = { "node": { "allowModules": ["electron"], "resolvePaths": [__dirname], - "tryExtensions": [".js", ".json", ".node"] + "tryExtensions": [".js", ".json", ".node"], + "yarnWorkspaces": true } }, "rules": { diff --git a/lib/rules/no-extraneous-import.js b/lib/rules/no-extraneous-import.js index b04f3ec0..1d94961b 100644 --- a/lib/rules/no-extraneous-import.js +++ b/lib/rules/no-extraneous-import.js @@ -9,6 +9,7 @@ const getAllowModules = require("../util/get-allow-modules") const getConvertPath = require("../util/get-convert-path") const getResolvePaths = require("../util/get-resolve-paths") const getTryExtensions = require("../util/get-try-extensions") +const getYarnWorkspaces = require("../util/get-yarn-workspaces") const visitImport = require("../util/visit-import") module.exports = { @@ -31,6 +32,7 @@ module.exports = { convertPath: getConvertPath.schema, resolvePaths: getResolvePaths.schema, tryExtensions: getTryExtensions.schema, + yarnWorkspaces: getYarnWorkspaces.schema, }, additionalProperties: false, }, diff --git a/lib/util/check-extraneous.js b/lib/util/check-extraneous.js index d0ff8ddd..828081bc 100644 --- a/lib/util/check-extraneous.js +++ b/lib/util/check-extraneous.js @@ -4,6 +4,7 @@ */ "use strict" +const path = require("path") const getAllowModules = require("./get-allow-modules") const getPackageJson = require("./get-package-json") @@ -18,7 +19,15 @@ const getPackageJson = require("./get-package-json") * @returns {void} */ module.exports = function checkForExtraneous(context, filePath, targets) { - const packageInfo = getPackageJson(filePath) + const options = context.options[0] || {} + + let packageInfo = getPackageJson(filePath) + + // When using yarn workspaces, we should look in the parent folder + if (options.yarnWorkspaces && packageInfo && !packageInfo.workspaces) { + packageInfo = getPackageJson(path.dirname(packageInfo.filePath)) + } + if (!packageInfo) { return } diff --git a/lib/util/get-yarn-workspaces.js b/lib/util/get-yarn-workspaces.js new file mode 100644 index 00000000..2ba9555f --- /dev/null +++ b/lib/util/get-yarn-workspaces.js @@ -0,0 +1,42 @@ +/** + * @author Toru Nagashima + * See LICENSE file in root directory for full license. + */ +"use strict" + +const DEFAULT_VALUE = false + +/** + * Gets `yarnWorkspaces` property from a given option object. + * + * @param {object|undefined} option - An option object to get. + * @returns {boolean|null} The `yarnWorkspaces` value, or `null`. + */ +function get(option) { + if (option && "yarnWorkspaces" in option) { + return option.yarnWorkspaces + } + return null +} + +/** + * Gets "yarnWorkspaces" setting. + * + * 1. This checks `options` property, then returns it if exists. + * 2. This checks `settings.node` property, then returns it if exists. + * 3. This returns `false`. + * + * @param {RuleContext} context - The rule context. + * @returns {boolean} + */ +module.exports = function getTryExtensions(context, optionIndex = 0) { + return ( + get(context.options && context.options[optionIndex]) || + get(context.settings && context.settings.node) || + DEFAULT_VALUE + ) +} + +module.exports.schema = { + type: "boolean", +} diff --git a/tests/fixtures/no-extraneous/yarnWorkspaces/aaa/package.json b/tests/fixtures/no-extraneous/yarnWorkspaces/aaa/package.json new file mode 100644 index 00000000..8515cb66 --- /dev/null +++ b/tests/fixtures/no-extraneous/yarnWorkspaces/aaa/package.json @@ -0,0 +1,4 @@ +{ + "name": "aaa", + "version": "0.0.0" +} diff --git a/tests/fixtures/no-extraneous/yarnWorkspaces/bbb/package.json b/tests/fixtures/no-extraneous/yarnWorkspaces/bbb/package.json new file mode 100644 index 00000000..7769a21f --- /dev/null +++ b/tests/fixtures/no-extraneous/yarnWorkspaces/bbb/package.json @@ -0,0 +1,4 @@ +{ + "name": "bbb", + "version": "0.0.0" +} diff --git a/tests/fixtures/no-extraneous/yarnWorkspaces/node_modules/.gitignore b/tests/fixtures/no-extraneous/yarnWorkspaces/node_modules/.gitignore new file mode 100644 index 00000000..dbee0265 --- /dev/null +++ b/tests/fixtures/no-extraneous/yarnWorkspaces/node_modules/.gitignore @@ -0,0 +1,2 @@ +aaa +bbb diff --git a/tests/fixtures/no-extraneous/yarnWorkspaces/node_modules/ccc.js b/tests/fixtures/no-extraneous/yarnWorkspaces/node_modules/ccc.js new file mode 100644 index 00000000..e69de29b diff --git a/tests/fixtures/no-extraneous/yarnWorkspaces/package.json b/tests/fixtures/no-extraneous/yarnWorkspaces/package.json new file mode 100644 index 00000000..e77ed8c0 --- /dev/null +++ b/tests/fixtures/no-extraneous/yarnWorkspaces/package.json @@ -0,0 +1,12 @@ +{ + "private": true, + "name": "test", + "version": "0.0.0", + "dependencies": { + "ccc": "*" + }, + "workspaces": [ + "aaa", + "bbb" + ] +} diff --git a/tests/lib/rules/no-extraneous-import.js b/tests/lib/rules/no-extraneous-import.js index 03ee9706..534c79c7 100644 --- a/tests/lib/rules/no-extraneous-import.js +++ b/tests/lib/rules/no-extraneous-import.js @@ -4,6 +4,7 @@ */ "use strict" +const fs = require("fs") const path = require("path") const { Linter, RuleTester } = require("eslint") const rule = require("../../../lib/rules/no-extraneous-import") @@ -31,6 +32,16 @@ function fixture(name) { return path.resolve(__dirname, "../../fixtures/no-extraneous", name) } +// We need to simulate `yarn workspaces` by creating symlinks inside `node_modules` +fs.symlinkSync( + fixture("yarnWorkspaces/aaa"), + fixture("yarnWorkspaces/node_modules/aaa") +) +fs.symlinkSync( + fixture("yarnWorkspaces/bbb"), + fixture("yarnWorkspaces/node_modules/bbb") +) + const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, @@ -71,12 +82,51 @@ ruleTester.run("no-extraneous-import", rule, { filename: fixture("optionalDependencies/a.js"), code: "import aaa from 'aaa'", }, + { + filename: fixture("yarnWorkspaces/a.js"), + code: "import aaa from 'aaa'", + }, + { + filename: fixture("yarnWorkspaces/b.js"), + code: "import bbb from 'bbb'", + }, + { + filename: fixture("yarnWorkspaces/c.js"), + code: "import ccc from 'ccc'", + }, // missing packages are warned by no-missing-import { filename: fixture("dependencies/a.js"), code: "import ccc from 'ccc'", }, + + // yarnWorkspaces + { + filename: fixture("yarnWorkspaces/a.js"), + code: "import aaa from 'aaa'", + options: [{ yarnWorkspaces: true }], + }, + { + filename: fixture("yarnWorkspaces/b.js"), + code: "import bbb from 'bbb'", + options: [{ yarnWorkspaces: true }], + }, + { + filename: fixture("yarnWorkspaces/c.js"), + code: "import ccc from 'ccc'", + options: [{ yarnWorkspaces: true }], + }, + { + filename: fixture("yarnWorkspaces/aaa/c.js"), + code: "import ccc from 'ccc'", + options: [{ yarnWorkspaces: true }], + }, + { + filename: fixture("yarnWorkspaces/bbb/c.js"), + code: "import ccc from 'ccc'", + options: [{ yarnWorkspaces: true }], + }, ], invalid: [ { @@ -100,6 +150,17 @@ ruleTester.run("no-extraneous-import", rule, { errors: ['"bbb" is extraneous.'], }, + { + filename: fixture("yarnWorkspaces/aaa/c.js"), + code: "import ccc from 'ccc'", + errors: ['"ccc" is extraneous.'], + }, + { + filename: fixture("yarnWorkspaces/bbb/c.js"), + code: "import ccc from 'ccc'", + errors: ['"ccc" is extraneous.'], + }, + // import() ...(DynamicImportSupported ? [