diff --git a/.changeset/chilly-tigers-promise.md b/.changeset/chilly-tigers-promise.md new file mode 100644 index 0000000..83b8c2d --- /dev/null +++ b/.changeset/chilly-tigers-promise.md @@ -0,0 +1,12 @@ +--- +"@eslegant/js": minor +--- + +Added rules for NodeJS environments, using the eslint-plugin-n and eslint-plugin-security. + +The added configs in the `recommended` object helps preventing issues +such as using deprecated or unsupported APIs and warns about security issues. +Building on top of the recommended configs of the plugins. + +In the `strict` object they helps making the code more node-explicit, such as importing +global variables (e.g. `process` needs to be imported from `node:process`). diff --git a/configs/js/docs/notes/duplicated-rules.md b/configs/js/docs/notes/duplicated-rules.md index bb4d167..8c496de 100644 --- a/configs/js/docs/notes/duplicated-rules.md +++ b/configs/js/docs/notes/duplicated-rules.md @@ -3,7 +3,7 @@ This is a list of rules that implements the same features and/or end up fixing the same errors. -- **[`@typescript/member-ordering`][ts/member-ordering], [`@typescript/sort-type-constituents`][ts/sort-type-constituents], [`import/order`][in/order]**: +- **[`@typescript/member-ordering`][ts/member-ordering], [`@typescript/sort-type-constituents`][ts/sort-type-constituents], [`import/order`][im/order]**: implements the same functions from [`eslint-plugin-perfectionist`][plugin-perfectionist] - **[`unicorn/no-for-loop`][un/no-for-loop] and [`@typescript/prefer-for-of`][ts/prefer-for-of]**: @@ -15,6 +15,14 @@ up fixing the same errors. - **[`@typescript/prefer-regexp-exec`][ts/prefer-regexp-exec] and [`unicorn/prefer-regexp-test`][un/prefer-regexp-test]**: `unicorn/prefer-regexp-exec` was used, because it reports on `RegExp#exec()` and `String#match()` +- **[`import/extensions`][im/extensions] and [`n/file-extension-in-import`][n/file-extension-in-import]**: + `import/extensions` was used, as it is not a node-specific issue. + +- **[`import/no-extraneous-dependencies`][im/no-extraneous-dependencies], [`n/no-extraneous-require`][n/no-extraneous-require] and [`n/no-extraneous-import`][n/no-extraneous-import]**: + `import/no-extraneous-dependencies` was used, as it is not a node-specific issue. + +- **[`import/no-unresolved`][im/no-unresolved], [`n/no-missing-require`][n/no-missing-require] and [`n/no-missing-import`][n/no-missing-import]**: + `import/no-unresolved` was used, as it is not a node-specific issue. [ts/member-ordering]: [ts/prefer-for-of]: @@ -22,10 +30,19 @@ up fixing the same errors. [ts/prefer-regexp-exec]: [ts/sort-type-constituents]: -[un/no-for-loop]: -[un/prefer-includes]: -[un/prefer-regexp-test]: +[un/no-for-loop]: +[un/prefer-includes]: +[un/prefer-regexp-test]: -[in/order]: +[im/order]: +[im/extensions]: +[im/no-extraneous-dependencies]: +[im/no-unresolved]: + +[n/file-extension-in-import]: +[n/no-extraneous-import]: +[n/no-extraneous-require]: +[n/no-missing-import]: +[n/no-missing-require]: [plugin-perfectionist]: diff --git a/configs/js/package.json b/configs/js/package.json index 9c54110..6da12f7 100644 --- a/configs/js/package.json +++ b/configs/js/package.json @@ -46,7 +46,9 @@ "eslint-import-resolver-typescript": "^3.6.0", "eslint-plugin-i": "2.28.0-2", "eslint-plugin-jsdoc": "^46.5.0", + "eslint-plugin-n": "^16.0.2", "eslint-plugin-perfectionist": "^1.5.1", + "eslint-plugin-security": "^1.7.1", "eslint-plugin-unicorn": "^48.0.1", "globals": "^13.21.0" }, diff --git a/configs/js/src/@types/eslint-plugin-n.d.ts b/configs/js/src/@types/eslint-plugin-n.d.ts new file mode 100644 index 0000000..1cd5bc7 --- /dev/null +++ b/configs/js/src/@types/eslint-plugin-n.d.ts @@ -0,0 +1,24 @@ +/** + * @file + * Type declaration for the `eslint-plugin-n` package in a attempt to make it + * compatible with the new flat config. + * @license MIT + * @author Guz013 (https://guz.one) + */ + +import type { ESLint } from 'eslint'; + +/** + * @summary Additional ESLint's rules for Node.js. + * + * --- + * **Note:** Types in this project where overridden to be compatible with + * ESLint new flat config types. ESlint already has backwards compatibility + * for plugins not created in the new flat config. + * @see {@link https://www.npmjs.com/package/eslint-plugin-n npm package} + */ +declare module 'eslint-plugin-n' { + declare const plugin: ESLint.Plugin; + export default plugin; +} + diff --git a/configs/js/src/@types/eslint-plugin-security.d.ts b/configs/js/src/@types/eslint-plugin-security.d.ts new file mode 100644 index 0000000..8c47451 --- /dev/null +++ b/configs/js/src/@types/eslint-plugin-security.d.ts @@ -0,0 +1,24 @@ +/** + * @file + * Type declaration for the `eslint-plugin-security` package in a attempt to make it + * compatible with the new flat config. + * @license MIT + * @author Guz013 (https://guz.one) + */ + +import type { ESLint } from 'eslint'; + +/** + * @summary ESLint rules for Node Security. + * + * --- + * **Note:** Types in this project where overridden to be compatible with + * ESLint new flat config types. ESlint already has backwards compatibility + * for plugins not created in the new flat config. + * @see {@link https://www.npmjs.com/package/eslint-plugin-security npm package} + */ +declare module 'eslint-plugin-security' { + declare const plugin: ESLint.Plugin; + export default plugin; +} + diff --git a/configs/js/src/configs/core.js b/configs/js/src/configs/core.js index a7c41ce..1ddd784 100644 --- a/configs/js/src/configs/core.js +++ b/configs/js/src/configs/core.js @@ -6,7 +6,10 @@ * @author Guz013 (https://guz.one) */ +import process from 'node:process'; + import tsESLint from '@typescript-eslint/eslint-plugin'; +import securityPlugin from 'eslint-plugin-security'; import unicornPlugin from 'eslint-plugin-unicorn'; // @ts-expect-error because the package doesn't export correct types import tsParser from '@typescript-eslint/parser'; @@ -17,6 +20,7 @@ import globals from 'globals'; // eslint-disable-next-line import/no-relative-parent-imports import { jsFiles, tsFiles } from '../constants.js'; + /** @type {import('eslint').Linter.FlatConfig} */ const config = { files: [...tsFiles, ...jsFiles], @@ -40,6 +44,8 @@ const config = { 'import': importPlugin, // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment 'jsdoc': jsdocPlugin, + // @ts-expect-error because eslint-plugin-security doesn't export correct types + 'security': securityPlugin, // @ts-expect-error because eslint-plugin-unicorn doesn't export correct types 'unicorn': unicornPlugin, }, diff --git a/configs/js/src/configs/environments/node.js b/configs/js/src/configs/environments/node.js index 3342883..fb527ec 100644 --- a/configs/js/src/configs/environments/node.js +++ b/configs/js/src/configs/environments/node.js @@ -1,3 +1,5 @@ +/* eslint-disable import/no-relative-parent-imports */ +/* eslint-disable unicorn/no-useless-spread */ /** * @file * Configuration objects for the NodeJS environment. @@ -6,13 +8,24 @@ * @author Guz013 (https://guz.one) */ -/* eslint-disable import/no-relative-parent-imports */ -/* eslint-disable unicorn/no-useless-spread */ +import nodePlugin from 'eslint-plugin-n'; +import globals from 'globals'; + import { createVariations } from '../../lib/rule-variations.js'; import { jsFiles, tsFiles } from '../../constants.js'; const commonjs = createVariations({ files: ['**/*.cts', '**/*.cjs'], + languageOptions: { + globals: { + ...globals.nodeBuiltin, + ...globals.commonjs, + }, + }, + plugins: { + // @ts-expect-error because types are not defined in 'eslint-plugin-n' + n: nodePlugin, + }, rules: { ...{}, // Plugin: @typescript-eslint/eslint-plugin '@typescript-eslint/no-require-imports': 'off', @@ -23,17 +36,52 @@ const commonjs = createVariations({ ...{}, // Plugin: eslint-plugin-import 'import/no-commonjs': 'off', + + ...{}, // Plugin: eslint-plugin-n + 'n/global-require': 'error', + 'n/no-exports-assign': 'error', + + ...{}, // Plugin: eslint-plugin-security + 'security/detect-non-literal-require': 'error', }, }); const recommended = createVariations({ files: [...tsFiles, ...jsFiles], + languageOptions: { + globals: { + ...globals.nodeBuiltin, + }, + }, + plugins: { + // @ts-expect-error because types are not defined in 'eslint-plugin-n' + n: nodePlugin, + }, rules: { ...{}, // Plugin: eslint-plugin-unicorn 'unicorn/prefer-node-protocol': 'error', ...{}, // Plugin: eslint-plugin-import 'import/no-dynamic-require': 'error', + + ...{}, // Plugin: eslint-plugin-n + 'n/no-deprecated-api': 'error', + 'n/no-process-exit': 'error', + 'n/no-unpublished-bin': 'error', + 'n/no-unpublished-import': 'error', + 'n/no-unpublished-require': 'error', + 'n/no-unsupported-features/es-builtins': 'error', + 'n/no-unsupported-features/es-syntax': 'error', + 'n/no-unsupported-features/node-builtins': 'error', + 'n/process-exit-as-throw': 'error', + 'n/shebang': 'error', + + ...{}, // Plugin: eslint-plugin-security + 'security/detect-buffer-noassert': 'warn', + 'security/detect-child-process': 'warn', + 'security/detect-new-buffer': 'warn', + 'security/detect-no-csrf-before-method-override': 'warn', + 'security/detect-non-literal-fs-filename': 'warn', }, }); @@ -41,6 +89,26 @@ const strict = createVariations({ ...recommended.error, rules: { ...recommended.error.rules, + + ...{}, // Plugin: eslint-plugin-n + 'n/no-new-require': 'error', + 'n/no-path-concat': 'error', + 'n/prefer-global/buffer': ['error', 'never'], + 'n/prefer-global/console': ['error', 'always'], + 'n/prefer-global/process': ['error', 'never'], + 'n/prefer-global/text-decoder': ['error', 'always'], + 'n/prefer-global/text-encoder': ['error', 'always'], + 'n/prefer-global/url': ['error', 'always'], + 'n/prefer-global/url-search-params': ['error', 'always'], + 'n/prefer-promises/dns': 'error', + 'n/prefer-promises/fs': 'error', + + ...{}, // Plugin: eslint-plugin-security + 'security/detect-buffer-noassert': 'error', + 'security/detect-child-process': 'error', + 'security/detect-new-buffer': 'error', + 'security/detect-no-csrf-before-method-override': 'error', + 'security/detect-non-literal-fs-filename': 'warn', }, }); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index dfaa9ca..21a38d8 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -60,9 +60,15 @@ importers: eslint-plugin-jsdoc: specifier: ^46.5.0 version: 46.5.0(eslint@8.47.0) + eslint-plugin-n: + specifier: ^16.0.2 + version: 16.0.2(eslint@8.47.0) eslint-plugin-perfectionist: specifier: ^1.5.1 version: 1.5.1(eslint@8.47.0)(typescript@5.1.6) + eslint-plugin-security: + specifier: ^1.7.1 + version: 1.7.1 eslint-plugin-unicorn: specifier: ^48.0.1 version: 48.0.1(eslint@8.47.0) @@ -1496,6 +1502,12 @@ packages: engines: {node: '>=6'} dev: false + /builtins@5.0.1: + resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} + dependencies: + semver: 7.5.4 + dev: false + /busboy@1.6.0: resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} engines: {node: '>=10.16.0'} @@ -2016,6 +2028,17 @@ packages: - supports-color dev: false + /eslint-plugin-es-x@7.2.0(eslint@8.47.0): + resolution: {integrity: sha512-9dvv5CcvNjSJPqnS5uZkqb3xmbeqRLnvXKK7iI5+oK/yTusyc46zbBZKENGsOfojm/mKfszyZb+wNqNPAPeGXA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + eslint: '>=8' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + '@eslint-community/regexpp': 4.7.0 + eslint: 8.47.0 + dev: false + /eslint-plugin-i@2.28.0-2(@typescript-eslint/parser@6.4.1)(eslint-import-resolver-typescript@3.6.0)(eslint@8.47.0): resolution: {integrity: sha512-z48kG4qmE4TmiLcxbmvxMT5ycwvPkXaWW0XpU1L768uZaTbiDbxsHMEdV24JHlOR1xDsPpKW39BfP/pRdYIwFA==} engines: {node: '>=12'} @@ -2094,6 +2117,23 @@ packages: - supports-color dev: false + /eslint-plugin-n@16.0.2(eslint@8.47.0): + resolution: {integrity: sha512-Y66uDfUNbBzypsr0kELWrIz+5skicECrLUqlWuXawNSLUq3ltGlCwu6phboYYOTSnoTdHgTLrc+5Ydo6KjzZog==} + engines: {node: '>=16.0.0'} + peerDependencies: + eslint: '>=7.0.0' + dependencies: + '@eslint-community/eslint-utils': 4.4.0(eslint@8.47.0) + builtins: 5.0.1 + eslint: 8.47.0 + eslint-plugin-es-x: 7.2.0(eslint@8.47.0) + ignore: 5.2.4 + is-core-module: 2.13.0 + minimatch: 3.1.2 + resolve: 1.22.4 + semver: 7.5.4 + dev: false + /eslint-plugin-perfectionist@1.5.1(eslint@8.47.0)(typescript@5.1.6): resolution: {integrity: sha512-PiUrAfGDc/l6MKKUP8qt5RXueC7FZC6F/0j8ijXYU8o3x8o2qUi6zEEYBkId/IiKloIXM5KTD4jrH9833kDNzA==} peerDependencies: @@ -2111,6 +2151,12 @@ packages: - typescript dev: false + /eslint-plugin-security@1.7.1: + resolution: {integrity: sha512-sMStceig8AFglhhT2LqlU5r+/fn9OwsA72O5bBuQVTssPCdQAOQzL+oMn/ZcpeUY6KcNfLJArgcrsSULNjYYdQ==} + dependencies: + safe-regex: 2.1.1 + dev: false + /eslint-plugin-svelte@2.30.0(eslint@8.44.0)(svelte@4.0.5): resolution: {integrity: sha512-2/qj0BJsfM0U2j4EjGb7iC/0nbUvXx1Gn78CdtyuXpi/rSomLPCPwnsZsloXMzlt6Xwe8LBlpRvZObSKEHLP5A==} engines: {node: ^14.17.0 || >=16.0.0} @@ -3600,6 +3646,12 @@ packages: get-intrinsic: 1.2.1 is-regex: 1.1.4 + /safe-regex@2.1.1: + resolution: {integrity: sha512-rx+x8AMzKb5Q5lQ95Zoi6ZbJqwCLkqi3XuJXp5P3rT8OEc6sZCJG5AE5dU3lsgRr/F4Bs31jSlVN+j5KrsGu9A==} + dependencies: + regexp-tree: 0.1.27 + dev: false + /safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} dev: true