feat: add more NodeJS-specific rules

This commit is contained in:
Guz013
2023-09-04 18:42:15 -03:00
parent 8749e51c13
commit dcce924286
8 changed files with 212 additions and 7 deletions

View File

@@ -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`).

View File

@@ -3,7 +3,7 @@
This is a list of rules that implements the same features and/or end This is a list of rules that implements the same features and/or end
up fixing the same errors. 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] 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]**: - **[`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]**: - **[`@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()` `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]: <https://typescript-eslint.io/rules/member-ordering> [ts/member-ordering]: <https://typescript-eslint.io/rules/member-ordering>
[ts/prefer-for-of]: <https://typescript-eslint.io/rules/prefer-for-of> [ts/prefer-for-of]: <https://typescript-eslint.io/rules/prefer-for-of>
@@ -22,10 +30,19 @@ up fixing the same errors.
[ts/prefer-regexp-exec]: <https://typescript-eslint.io/rules/prefer-regexp-exec> [ts/prefer-regexp-exec]: <https://typescript-eslint.io/rules/prefer-regexp-exec>
[ts/sort-type-constituents]: <https://typescript-eslint.io/rules/sort-type-constituents> [ts/sort-type-constituents]: <https://typescript-eslint.io/rules/sort-type-constituents>
[un/no-for-loop]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/6d15a02d48de7ecfc38d0683a8487b2f937d83a0/docs/rules/no-for-loop.md> [un/no-for-loop]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/no-for-loop.md>
[un/prefer-includes]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/6d15a02d48de7ecfc38d0683a8487b2f937d83a0/docs/rules/prefer-includes.md> [un/prefer-includes]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-includes.md>
[un/prefer-regexp-test]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/6d15a02d48de7ecfc38d0683a8487b2f937d83a0/docs/rules/prefer-regexp-test.md> [un/prefer-regexp-test]: <https://github.com/sindresorhus/eslint-plugin-unicorn/blob/main/docs/rules/prefer-regexp-test.md>
[in/order]: <https://github.com/import-js/eslint-plugin-import/blob/6b95a021938139726b3f862beb37012d6e2afab2/docs/rules/order.md> [im/order]: <https://github.com/import-js/eslint-plugin-import/blob/maib/docs/rules/order.md>
[im/extensions]: <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/extensions.md>
[im/no-extraneous-dependencies]: <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-extraneous-dependencies.md>
[im/no-unresolved]: <https://github.com/import-js/eslint-plugin-import/blob/main/docs/rules/no-unresolved.md>
[n/file-extension-in-import]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/file-extension-in-import.md>
[n/no-extraneous-import]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-extraneous-import.md>
[n/no-extraneous-require]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-extraneous-require.md>
[n/no-missing-import]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-import.md>
[n/no-missing-require]: <https://github.com/eslint-community/eslint-plugin-n/blob/master/docs/rules/no-missing-require.md>
[plugin-perfectionist]: <https://eslint-plugin-perfectionist.azat.io/> [plugin-perfectionist]: <https://eslint-plugin-perfectionist.azat.io/>

View File

@@ -46,7 +46,9 @@
"eslint-import-resolver-typescript": "^3.6.0", "eslint-import-resolver-typescript": "^3.6.0",
"eslint-plugin-i": "2.28.0-2", "eslint-plugin-i": "2.28.0-2",
"eslint-plugin-jsdoc": "^46.5.0", "eslint-plugin-jsdoc": "^46.5.0",
"eslint-plugin-n": "^16.0.2",
"eslint-plugin-perfectionist": "^1.5.1", "eslint-plugin-perfectionist": "^1.5.1",
"eslint-plugin-security": "^1.7.1",
"eslint-plugin-unicorn": "^48.0.1", "eslint-plugin-unicorn": "^48.0.1",
"globals": "^13.21.0" "globals": "^13.21.0"
}, },

View File

@@ -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 <contact.guz013@gmail.com> (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;
}

View File

@@ -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 <contact.guz013@gmail.com> (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;
}

View File

@@ -6,7 +6,10 @@
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one) * @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
*/ */
import process from 'node:process';
import tsESLint from '@typescript-eslint/eslint-plugin'; import tsESLint from '@typescript-eslint/eslint-plugin';
import securityPlugin from 'eslint-plugin-security';
import unicornPlugin from 'eslint-plugin-unicorn'; import unicornPlugin from 'eslint-plugin-unicorn';
// @ts-expect-error because the package doesn't export correct types // @ts-expect-error because the package doesn't export correct types
import tsParser from '@typescript-eslint/parser'; import tsParser from '@typescript-eslint/parser';
@@ -17,6 +20,7 @@ import globals from 'globals';
// eslint-disable-next-line import/no-relative-parent-imports // eslint-disable-next-line import/no-relative-parent-imports
import { jsFiles, tsFiles } from '../constants.js'; import { jsFiles, tsFiles } from '../constants.js';
/** @type {import('eslint').Linter.FlatConfig} */ /** @type {import('eslint').Linter.FlatConfig} */
const config = { const config = {
files: [...tsFiles, ...jsFiles], files: [...tsFiles, ...jsFiles],
@@ -40,6 +44,8 @@ const config = {
'import': importPlugin, 'import': importPlugin,
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
'jsdoc': jsdocPlugin, '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 // @ts-expect-error because eslint-plugin-unicorn doesn't export correct types
'unicorn': unicornPlugin, 'unicorn': unicornPlugin,
}, },

View File

@@ -1,3 +1,5 @@
/* eslint-disable import/no-relative-parent-imports */
/* eslint-disable unicorn/no-useless-spread */
/** /**
* @file * @file
* Configuration objects for the NodeJS environment. * Configuration objects for the NodeJS environment.
@@ -6,13 +8,24 @@
* @author Guz013 <contact.guz013@gmail.com> (https://guz.one) * @author Guz013 <contact.guz013@gmail.com> (https://guz.one)
*/ */
/* eslint-disable import/no-relative-parent-imports */ import nodePlugin from 'eslint-plugin-n';
/* eslint-disable unicorn/no-useless-spread */ import globals from 'globals';
import { createVariations } from '../../lib/rule-variations.js'; import { createVariations } from '../../lib/rule-variations.js';
import { jsFiles, tsFiles } from '../../constants.js'; import { jsFiles, tsFiles } from '../../constants.js';
const commonjs = createVariations({ const commonjs = createVariations({
files: ['**/*.cts', '**/*.cjs'], 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: { rules: {
...{}, // Plugin: @typescript-eslint/eslint-plugin ...{}, // Plugin: @typescript-eslint/eslint-plugin
'@typescript-eslint/no-require-imports': 'off', '@typescript-eslint/no-require-imports': 'off',
@@ -23,17 +36,52 @@ const commonjs = createVariations({
...{}, // Plugin: eslint-plugin-import ...{}, // Plugin: eslint-plugin-import
'import/no-commonjs': 'off', '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({ const recommended = createVariations({
files: [...tsFiles, ...jsFiles], files: [...tsFiles, ...jsFiles],
languageOptions: {
globals: {
...globals.nodeBuiltin,
},
},
plugins: {
// @ts-expect-error because types are not defined in 'eslint-plugin-n'
n: nodePlugin,
},
rules: { rules: {
...{}, // Plugin: eslint-plugin-unicorn ...{}, // Plugin: eslint-plugin-unicorn
'unicorn/prefer-node-protocol': 'error', 'unicorn/prefer-node-protocol': 'error',
...{}, // Plugin: eslint-plugin-import ...{}, // Plugin: eslint-plugin-import
'import/no-dynamic-require': 'error', '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, ...recommended.error,
rules: { rules: {
...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',
}, },
}); });

52
pnpm-lock.yaml generated
View File

@@ -60,9 +60,15 @@ importers:
eslint-plugin-jsdoc: eslint-plugin-jsdoc:
specifier: ^46.5.0 specifier: ^46.5.0
version: 46.5.0(eslint@8.47.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: eslint-plugin-perfectionist:
specifier: ^1.5.1 specifier: ^1.5.1
version: 1.5.1(eslint@8.47.0)(typescript@5.1.6) 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: eslint-plugin-unicorn:
specifier: ^48.0.1 specifier: ^48.0.1
version: 48.0.1(eslint@8.47.0) version: 48.0.1(eslint@8.47.0)
@@ -1496,6 +1502,12 @@ packages:
engines: {node: '>=6'} engines: {node: '>=6'}
dev: false dev: false
/builtins@5.0.1:
resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==}
dependencies:
semver: 7.5.4
dev: false
/busboy@1.6.0: /busboy@1.6.0:
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==} resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
engines: {node: '>=10.16.0'} engines: {node: '>=10.16.0'}
@@ -2016,6 +2028,17 @@ packages:
- supports-color - supports-color
dev: false 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): /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==} resolution: {integrity: sha512-z48kG4qmE4TmiLcxbmvxMT5ycwvPkXaWW0XpU1L768uZaTbiDbxsHMEdV24JHlOR1xDsPpKW39BfP/pRdYIwFA==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -2094,6 +2117,23 @@ packages:
- supports-color - supports-color
dev: false 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): /eslint-plugin-perfectionist@1.5.1(eslint@8.47.0)(typescript@5.1.6):
resolution: {integrity: sha512-PiUrAfGDc/l6MKKUP8qt5RXueC7FZC6F/0j8ijXYU8o3x8o2qUi6zEEYBkId/IiKloIXM5KTD4jrH9833kDNzA==} resolution: {integrity: sha512-PiUrAfGDc/l6MKKUP8qt5RXueC7FZC6F/0j8ijXYU8o3x8o2qUi6zEEYBkId/IiKloIXM5KTD4jrH9833kDNzA==}
peerDependencies: peerDependencies:
@@ -2111,6 +2151,12 @@ packages:
- typescript - typescript
dev: false 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): /eslint-plugin-svelte@2.30.0(eslint@8.44.0)(svelte@4.0.5):
resolution: {integrity: sha512-2/qj0BJsfM0U2j4EjGb7iC/0nbUvXx1Gn78CdtyuXpi/rSomLPCPwnsZsloXMzlt6Xwe8LBlpRvZObSKEHLP5A==} resolution: {integrity: sha512-2/qj0BJsfM0U2j4EjGb7iC/0nbUvXx1Gn78CdtyuXpi/rSomLPCPwnsZsloXMzlt6Xwe8LBlpRvZObSKEHLP5A==}
engines: {node: ^14.17.0 || >=16.0.0} engines: {node: ^14.17.0 || >=16.0.0}
@@ -3600,6 +3646,12 @@ packages:
get-intrinsic: 1.2.1 get-intrinsic: 1.2.1
is-regex: 1.1.4 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: /safer-buffer@2.1.2:
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
dev: true dev: true