diff --git a/README.md b/README.md index 8c6d22ee..d5613cea 100644 --- a/README.md +++ b/README.md @@ -23,7 +23,10 @@ $ npm install --save-dev eslint eslint-plugin-node ```json { - "extends": ["eslint:recommended", "plugin:node/recommended"], + "extends": [ + "eslint:recommended", + "plugin:node/recommended" + ], "rules": { "node/exports-style": ["error", "module.exports"], "node/prefer-global/buffer": ["error", "always"], @@ -102,15 +105,19 @@ These rules have been deprecated in accordance with the [deprecation policy](htt ## 🔧 Configs -This plugin provides `plugin:node/recommended` preset config. -This preset config: +This plugin provides three configs: -- enables the environment of ES2015 (ES6) and Node.js. -- enables rules which are given :star: in the above table. -- enables [no-process-exit](http://eslint.org/docs/rules/no-process-exit) rule because [the official document](https://nodejs.org/api/process.html#process_process_exit_code) does not recommend a use of `process.exit()`. -- adds `{ecmaVersion: 2019}` into `parserOptions`. -- adds `Atomics` and `SharedArrayBuffer` into `globals`. -- adds this plugin into `plugins`. +- `plugin:node/recommended` condiders both CommonJS and ES Modules. If [`"type":"module"` field](https://medium.com/@nodejs/announcing-a-new-experimental-modules-1be8d2d6c2ff#b023) existed in package.json then it considers files as ES Modules. Otherwise it considers files as CommonJS. In addition, it considers `*.mjs` files as ES Modules and `*.cjs` files as CommonJS. +- `plugin:node/recommended-module` considers all files as ES Modules. +- `plugin:node/recommended-script` considers all files as CommonJS. + +Those preset config: + +- enable [no-process-exit](http://eslint.org/docs/rules/no-process-exit) rule because [the official document](https://nodejs.org/api/process.html#process_process_exit_code) does not recommend a use of `process.exit()`. +- enable plugin rules which are given :star: in the above table. +- add `{ecmaVersion: 2019}` and etc into `parserOptions`. +- add proper globals into `globals`. +- add this plugin into `plugins`. ## 👫 FAQ diff --git a/lib/configs/_commons.js b/lib/configs/_commons.js new file mode 100644 index 00000000..6f764e1d --- /dev/null +++ b/lib/configs/_commons.js @@ -0,0 +1,72 @@ +"use strict" + +module.exports = { + commonGlobals: { + // ECMAScript + ArrayBuffer: "readonly", + Atomics: "readonly", + DataView: "readonly", + Float32Array: "readonly", + Float64Array: "readonly", + Int16Array: "readonly", + Int32Array: "readonly", + Int8Array: "readonly", + Map: "readonly", + Promise: "readonly", + Proxy: "readonly", + Reflect: "readonly", + Set: "readonly", + SharedArrayBuffer: "readonly", + Symbol: "readonly", + Uint16Array: "readonly", + Uint32Array: "readonly", + Uint8Array: "readonly", + Uint8ClampedArray: "readonly", + WeakMap: "readonly", + WeakSet: "readonly", + + // ECMAScript (experimental) + BigInt: "readonly", + BigInt64Array: "readonly", + BigUint64Array: "readonly", + globalThis: "readonly", + + // ECMA-404 + Intl: "readonly", + + // Web Standard + TextDecoder: "readonly", + TextEncoder: "readonly", + URL: "readonly", + URLSearchParams: "readonly", + WebAssembly: "readonly", + clearInterval: "readonly", + clearTimeout: "readonly", + console: "readonly", + queueMicrotask: "readonly", + setInterval: "readonly", + setTimeout: "readonly", + + // Node.js + Buffer: "readonly", + GLOBAL: "readonly", + clearImmediate: "readonly", + global: "readonly", + process: "readonly", + root: "readonly", + setImmediate: "readonly", + }, + commonRules: { + "no-process-exit": "error", + "node/no-deprecated-api": "error", + "node/no-extraneous-require": "error", + "node/no-missing-require": "error", + "node/no-unpublished-bin": "error", + "node/no-unpublished-require": "error", + "node/no-unsupported-features/es-builtins": "error", + "node/no-unsupported-features/es-syntax": "error", + "node/no-unsupported-features/node-builtins": "error", + "node/process-exit-as-throw": "error", + "node/shebang": "error", + }, +} diff --git a/lib/configs/recommended-module.js b/lib/configs/recommended-module.js new file mode 100644 index 00000000..89270597 --- /dev/null +++ b/lib/configs/recommended-module.js @@ -0,0 +1,30 @@ +"use strict" + +const { commonGlobals, commonRules } = require("./_commons") + +module.exports = { + globals: { + ...commonGlobals, + __dirname: "off", + __filename: "off", + exports: "off", + module: "off", + require: "off", + }, + parserOptions: { + ecmaFeatures: { globalReturn: false }, + ecmaVersion: 2019, + sourceType: "module", + }, + plugins: ["node"], + rules: { + ...commonRules, + "node/no-extraneous-import": "error", + "node/no-missing-import": "error", + "node/no-unpublished-import": "error", + "node/no-unsupported-features/es-syntax": [ + "error", + { ignores: ["modules"] }, + ], + }, +} diff --git a/lib/configs/recommended-script.js b/lib/configs/recommended-script.js new file mode 100644 index 00000000..3a294d18 --- /dev/null +++ b/lib/configs/recommended-script.js @@ -0,0 +1,27 @@ +"use strict" + +const { commonGlobals, commonRules } = require("./_commons") + +module.exports = { + globals: { + ...commonGlobals, + __dirname: "readonly", + __filename: "readonly", + exports: "readonly", + module: "readonly", + require: "readonly", + }, + parserOptions: { + ecmaFeatures: { globalReturn: true }, + ecmaVersion: 2019, + sourceType: "script", + }, + plugins: ["node"], + rules: { + ...commonRules, + "node/no-extraneous-import": "off", + "node/no-missing-import": "off", + "node/no-unpublished-import": "off", + "node/no-unsupported-features/es-syntax": ["error", { ignores: [] }], + }, +} diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js new file mode 100644 index 00000000..830c7662 --- /dev/null +++ b/lib/configs/recommended.js @@ -0,0 +1,18 @@ +"use strict" + +const getPackageJson = require("../util/get-package-json") +const moduleConfig = require("./recommended-module") +const scriptConfig = require("./recommended-script") + +module.exports = () => { + const packageJson = getPackageJson() + const isModule = (packageJson && packageJson.type) === "module" + + return { + ...(isModule ? moduleConfig : scriptConfig), + overrides: [ + { files: ["*.cjs", ".*.cjs"], ...scriptConfig }, + { files: ["*.mjs", ".*.mjs"], ...moduleConfig }, + ], + } +} diff --git a/lib/configs/recommended.json b/lib/configs/recommended.json deleted file mode 100644 index 7c19820d..00000000 --- a/lib/configs/recommended.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "parserOptions": { - "ecmaVersion": 2019 - }, - "env": { - "es6": true, - "node": true - }, - "globals": { - "Atomics": false, - "SharedArrayBuffer": false - }, - "plugins": [ - "node" - ], - "rules": { - "no-process-exit": "error", - "node/exports-style": "off", - "node/no-deprecated-api": "error", - "node/no-extraneous-import": "off", - "node/no-extraneous-require": "error", - "node/no-missing-import": "off", - "node/no-missing-require": "error", - "node/no-unpublished-bin": "error", - "node/no-unpublished-import": "off", - "node/no-unpublished-require": "error", - "node/no-unsupported-features/es-builtins": "error", - "node/no-unsupported-features/es-syntax": "error", - "node/no-unsupported-features/node-builtins": "error", - "node/prefer-global/buffer": "off", - "node/prefer-global/console": "off", - "node/prefer-global/process": "off", - "node/prefer-global/text-decoder": "off", - "node/prefer-global/text-encoder": "off", - "node/prefer-global/url-search-params": "off", - "node/prefer-global/url": "off", - "node/process-exit-as-throw": "error", - "node/shebang": "error" - } -} \ No newline at end of file diff --git a/lib/index.js b/lib/index.js index df76ff9b..8aec5dd8 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,7 +3,11 @@ module.exports = { configs: { - recommended: require("./configs/recommended.json"), + "recommended-module": require("./configs/recommended-module"), + "recommended-script": require("./configs/recommended-script"), + get recommended() { + return require("./configs/recommended")() + }, }, rules: { "exports-style": require("./rules/exports-style"), diff --git a/lib/util/get-package-json.js b/lib/util/get-package-json.js index 545cd667..685c4a02 100644 --- a/lib/util/get-package-json.js +++ b/lib/util/get-package-json.js @@ -39,11 +39,11 @@ function readPackageJson(dir) { * Gets a `package.json` data. * The data is cached if found, then it's used after. * - * @param {string} startPath - A file path to lookup. + * @param {string} [startPath] - A file path to lookup. * @returns {object|null} A found `package.json` data or `null`. * This object have additional property `filePath`. */ -module.exports = function getPackageJson(startPath) { +module.exports = function getPackageJson(startPath = "a.js") { const startDir = path.dirname(path.resolve(startPath)) let dir = startDir let prevDir = "" diff --git a/package.json b/package.json index 91f782e2..5591d133 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "@mysticatea/eslint-plugin": "^10.0.3", "codecov": "^3.3.0", "eslint": "^5.16.0", + "eslint-plugin-node": "file:.", "fast-glob": "^2.2.6", "mocha": "^6.1.4", "nyc": "^14.0.0", diff --git a/scripts/update-lib-configs-recommended.js b/scripts/update-lib-configs-recommended.js deleted file mode 100644 index de352a04..00000000 --- a/scripts/update-lib-configs-recommended.js +++ /dev/null @@ -1,34 +0,0 @@ -/** - * @author Toru Nagashima - * See LICENSE file in root directory for full license. - */ -"use strict" - -const fs = require("fs") -const path = require("path") -const { rules } = require("./rules") - -const filePath = path.resolve(__dirname, "../lib/configs/recommended.json") -const config = { - parserOptions: { - ecmaVersion: 2019, - }, - env: { - es6: true, - node: true, - }, - globals: { - Atomics: false, - SharedArrayBuffer: false, - }, - plugins: ["node"], - rules: rules.filter(rule => !rule.deprecated).reduce( - (obj, rule) => { - obj[rule.id] = rule.recommended ? "error" : "off" - return obj - }, - { "no-process-exit": "error" } - ), -} - -fs.writeFileSync(filePath, JSON.stringify(config, null, 4)) diff --git a/scripts/update-lib-index.js b/scripts/update-lib-index.js index fad69f49..d896ca91 100644 --- a/scripts/update-lib-index.js +++ b/scripts/update-lib-index.js @@ -15,7 +15,11 @@ const rawContent = `/* DON'T EDIT THIS FILE. This is generated by 'scripts/updat module.exports = { configs: { - recommended: require("./configs/recommended.json"), + "recommended-module": require("./configs/recommended-module"), + "recommended-script": require("./configs/recommended-script"), + get recommended() { + return require("./configs/recommended")() + }, }, rules: { ${rules diff --git a/tests/fixtures/configs/cjs/package.json b/tests/fixtures/configs/cjs/package.json new file mode 100644 index 00000000..2c63c085 --- /dev/null +++ b/tests/fixtures/configs/cjs/package.json @@ -0,0 +1,2 @@ +{ +} diff --git a/tests/fixtures/configs/esm/package.json b/tests/fixtures/configs/esm/package.json new file mode 100644 index 00000000..47200257 --- /dev/null +++ b/tests/fixtures/configs/esm/package.json @@ -0,0 +1,3 @@ +{ + "type": "module" +} diff --git a/tests/lib/configs/recommended.js b/tests/lib/configs/recommended.js new file mode 100644 index 00000000..abcaf074 --- /dev/null +++ b/tests/lib/configs/recommended.js @@ -0,0 +1,163 @@ +"use strict" + +const assert = require("assert") +const path = require("path") +const { CLIEngine } = require("eslint") +const originalCwd = process.cwd() + +describe("node/recommended config", () => { + describe("in CJS directory", () => { + const root = path.resolve(__dirname, "../../fixtures/configs/cjs/") + + /** @type {CLIEngine} */ + let engine = null + + beforeEach(() => { + process.chdir(root) + engine = new CLIEngine({ + baseConfig: { extends: "plugin:node/recommended" }, + useEslintrc: false, + }) + }) + + afterEach(() => { + process.chdir(originalCwd) + }) + + it("*.js files should be a script.", () => { + const report = engine.executeOnText( + "import 'foo'", + path.join(root, "test.js") + ) + + assert.deepStrictEqual(report.results[0].messages, [ + { + column: 1, + fatal: true, + line: 1, + message: + "Parsing error: 'import' and 'export' may appear only with 'sourceType: module'", + ruleId: null, + severity: 2, + }, + ]) + }) + + it("*.cjs files should be a script.", () => { + const report = engine.executeOnText( + "import 'foo'", + path.join(root, "test.cjs") + ) + + assert.deepStrictEqual(report.results[0].messages, [ + { + column: 1, + fatal: true, + line: 1, + message: + "Parsing error: 'import' and 'export' may appear only with 'sourceType: module'", + ruleId: null, + severity: 2, + }, + ]) + }) + + it("*.mjs files should be a module.", () => { + const report = engine.executeOnText( + "import 'foo'", + path.join(root, "test.mjs") + ) + + assert.deepStrictEqual(report.results[0].messages, [ + { + column: 8, + endColumn: 13, + endLine: 1, + line: 1, + message: '"foo" is not found.', + nodeType: "Literal", + ruleId: "node/no-missing-import", + severity: 2, + }, + ]) + }) + }) + + describe("in ESM directory", () => { + const root = path.resolve(__dirname, "../../fixtures/configs/esm/") + + /** @type {CLIEngine} */ + let engine = null + + beforeEach(() => { + process.chdir(root) + engine = new CLIEngine({ + baseConfig: { extends: "plugin:node/recommended" }, + useEslintrc: false, + }) + }) + + afterEach(() => { + process.chdir(originalCwd) + }) + + it("*.js files should be a module.", () => { + const report = engine.executeOnText( + "import 'foo'", + path.join(root, "test.js") + ) + + assert.deepStrictEqual(report.results[0].messages, [ + { + column: 8, + endColumn: 13, + endLine: 1, + line: 1, + message: '"foo" is not found.', + nodeType: "Literal", + ruleId: "node/no-missing-import", + severity: 2, + }, + ]) + }) + + it("*.cjs files should be a script.", () => { + const report = engine.executeOnText( + "import 'foo'", + path.join(root, "test.cjs") + ) + + assert.deepStrictEqual(report.results[0].messages, [ + { + column: 1, + fatal: true, + line: 1, + message: + "Parsing error: 'import' and 'export' may appear only with 'sourceType: module'", + ruleId: null, + severity: 2, + }, + ]) + }) + + it("*.mjs files should be a module.", () => { + const report = engine.executeOnText( + "import 'foo'", + path.join(root, "test.mjs") + ) + + assert.deepStrictEqual(report.results[0].messages, [ + { + column: 8, + endColumn: 13, + endLine: 1, + line: 1, + message: '"foo" is not found.', + nodeType: "Literal", + ruleId: "node/no-missing-import", + severity: 2, + }, + ]) + }) + }) +})