Files
node/eslint.config.mjs
T
Honey Tyagi c5680a0dea tools: use 'readonly' for EventSource global
The EventSource entry was the only global in eslint.config.mjs still
using the deprecated 'readable' value. ESLint keeps 'readable' working
as an alias of 'readonly' for historical reasons, so this is a
consistency fix rather than a functional one.

The flat config migration converted the other globals in this block
but missed this single entry.

Fixes: https://github.com/nodejs/node/issues/64757
Signed-off-by: Honey Tyagi <honeyt290@gmail.com>
PR-URL: https://github.com/nodejs/node/pull/64787
Reviewed-By: Aviv Keller <me@aviv.sh>
Reviewed-By: Mike McCready <66998419+MikeMcC399@users.noreply.github.com>
Reviewed-By: René <contact.9a5d6388@renegade334.me.uk>
2026-07-29 16:43:14 +00:00

465 lines
16 KiB
JavaScript

import { readdirSync } from 'node:fs';
import Module from 'node:module';
import { fileURLToPath, URL } from 'node:url';
import benchmarkConfig from './benchmark/eslint.config_partial.mjs';
import docConfig from './doc/eslint.config_partial.mjs';
import libConfig from './lib/eslint.config_partial.mjs';
import testConfig from './test/eslint.config_partial.mjs';
import toolsConfig from './tools/eslint/eslint.config_partial.mjs';
import {
importEslintTool,
noRestrictedSyntaxCommonAll,
noRestrictedSyntaxCommonLib,
resolveEslintTool,
} from './tools/eslint/eslint.config_utils.mjs';
import nodeCore from './tools/eslint/eslint-plugin-node-core.js';
const { globalIgnores } = await importEslintTool('eslint/config');
const { default: js } = await importEslintTool('@eslint/js');
const { default: babelEslintParser } = await importEslintTool('@babel/eslint-parser');
const babelPluginSyntaxImportSource = resolveEslintTool('@babel/plugin-syntax-import-source');
const babelPluginImportDefer = resolveEslintTool('@babel/plugin-syntax-import-defer');
const { default: jsdoc } = await importEslintTool('eslint-plugin-jsdoc');
const { default: regexpPlugin } = await importEslintTool('eslint-plugin-regexp');
const { default: markdown } = await importEslintTool('@eslint/markdown');
const { default: stylisticJs } = await importEslintTool('@stylistic/eslint-plugin');
nodeCore.RULES_DIR = fileURLToPath(new URL('./tools/eslint-rules', import.meta.url));
function filterFilesInDir(dirpath, filterFn) {
return readdirSync(dirpath)
.filter(filterFn)
.map((f) => `${dirpath}/${f}`);
}
// The Module._resolveFilename() monkeypatching is to make it so that ESLint is able to
// dynamically load extra modules that we install with it.
const ModuleResolveFilename = Module._resolveFilename;
const hacks = [
'eslint-formatter-tap',
];
Module._resolveFilename = (request, parent, isMain, options) => {
if (hacks.includes(request) && parent.id.endsWith('__placeholder__.js')) {
return resolveEslintTool(request);
}
return ModuleResolveFilename(request, parent, isMain, options);
};
export default [
// #region ignores
globalIgnores([
'**/node_modules/**',
'benchmark/fixtures/**',
'benchmark/tmp/**',
'doc/changelogs/CHANGELOG_V1*.md',
'!doc/changelogs/CHANGELOG_V18.md',
'lib/punycode.js',
'test/.tmp.*/**',
'test/addons/??_*',
// We want to lint only a few specific fixtures folders
'test/fixtures/*',
'!test/fixtures/console',
'!test/fixtures/errors',
'!test/fixtures/eval',
'!test/fixtures/source-map',
'test/fixtures/source-map/*',
'!test/fixtures/source-map/output',
...filterFilesInDir(
'test/fixtures/source-map/output',
// Filtering tsc output files (i.e. if there a foo.ts, we ignore foo.js):
(f, _, files) => f.endsWith('js') && files.includes(f.replace(/(\.[cm]?)js$/, '$1ts')),
),
'!test/fixtures/test-inspector-dom-storage.mjs',
'!test/fixtures/test-runner',
'test/fixtures/test-runner/*',
'!test/fixtures/test-runner/output',
...filterFilesInDir(
'test/fixtures/test-runner/output',
// Filtering tsc output files (i.e. if there a foo.ts, we ignore foo.js):
(f, _, files) => f.endsWith('js') && files.includes(f.replace(/\.[cm]?js$/, '.ts')),
),
'!test/fixtures/v8',
'!test/fixtures/vm',
]),
// #endregion
// #region general config
js.configs.recommended,
jsdoc.configs['flat/recommended'],
regexpPlugin.configs.recommended,
{
files: ['**/*.js'],
languageOptions: {
sourceType: 'commonjs',
},
},
{
plugins: {
jsdoc,
'@stylistic/js': stylisticJs,
'node-core': nodeCore,
},
languageOptions: {
parser: babelEslintParser,
parserOptions: {
babelOptions: {
plugins: [
babelPluginSyntaxImportSource,
babelPluginImportDefer,
],
},
requireConfigFile: false,
},
},
},
// #endregion
// #region general globals
{
languageOptions: {
globals: {
AsyncDisposableStack: 'readonly',
ByteLengthQueuingStrategy: 'readonly',
CompressionStream: 'readonly',
CountQueuingStrategy: 'readonly',
CustomEvent: 'readonly',
crypto: 'readonly',
Crypto: 'readonly',
CryptoKey: 'readonly',
DecompressionStream: 'readonly',
DisposableStack: 'readonly',
EventSource: 'readonly',
fetch: 'readonly',
Float16Array: 'readonly',
FormData: 'readonly',
navigator: 'readonly',
QuotaExceededError: 'readonly',
ReadableStream: 'readonly',
ReadableStreamDefaultReader: 'readonly',
ReadableStreamBYOBReader: 'readonly',
ReadableStreamBYOBRequest: 'readonly',
ReadableByteStreamController: 'readonly',
ReadableStreamDefaultController: 'readonly',
Response: 'readonly',
TextDecoderStream: 'readonly',
TextEncoderStream: 'readonly',
TransformStream: 'readonly',
TransformStreamDefaultController: 'readonly',
ShadowRealm: 'readonly',
SubtleCrypto: 'readonly',
WritableStream: 'readonly',
WritableStreamDefaultWriter: 'readonly',
WritableStreamDefaultController: 'readonly',
WebSocket: 'readonly',
},
},
},
// #endregion
// #region general rules
{
rules: {
// ESLint built-in rules
// https://eslint.org/docs/latest/rules/
'accessor-pairs': 'error',
'array-callback-return': 'error',
'block-scoped-var': 'error',
'capitalized-comments': ['error', 'always', {
line: {
// Ignore all lines that have less characters than 20 and all lines
// that start with something that looks like a variable name or code.
ignorePattern: '.{0,20}$|[a-z]+ ?[0-9A-Z_.(/=:[#-]|std|http|ssh|ftp',
ignoreInlineComments: true,
ignoreConsecutiveComments: true,
},
block: {
ignorePattern: '.*',
},
}],
'logical-assignment-operators': ['error', 'always', { enforceForIfStatements: true }],
'default-case-last': 'error',
'dot-notation': 'error',
'eqeqeq': ['error', 'smart'],
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-constant-condition': ['error', { checkLoops: false }],
'no-constructor-return': 'error',
'no-duplicate-imports': 'error',
'no-else-return': 'error',
'no-lonely-if': 'error',
'no-mixed-requires': 'error',
'no-new-require': 'error',
'no-path-concat': 'error',
'no-proto': 'error',
'no-redeclare': ['error', { builtinGlobals: false }],
'no-restricted-modules': ['error', 'sys'],
'no-restricted-properties': [
'error',
{
object: 'assert',
property: 'deepEqual',
message: 'Use `assert.deepStrictEqual()`.',
},
{
object: 'assert',
property: 'notDeepEqual',
message: 'Use `assert.notDeepStrictEqual()`.',
},
{
object: 'assert',
property: 'equal',
message: 'Use `assert.strictEqual()` rather than `assert.equal()`.',
},
{
object: 'assert',
property: 'notEqual',
message: 'Use `assert.notStrictEqual()` rather than `assert.notEqual()`.',
},
{
property: '__defineGetter__',
message: '__defineGetter__ is deprecated.',
},
{
property: '__defineSetter__',
message: '__defineSetter__ is deprecated.',
},
{
property: 'webcrypto',
message: 'Use `globalThis.crypto`.',
},
],
'no-restricted-syntax': [
'error',
...noRestrictedSyntaxCommonAll,
...noRestrictedSyntaxCommonLib,
],
'no-self-compare': 'error',
'no-shadow-restricted-names': ['error', { reportGlobalThis: false }],
'no-template-curly-in-string': 'error',
'no-throw-literal': 'error',
'no-undef': ['error', { typeof: true }],
'no-undef-init': 'error',
'no-unused-expressions': ['error', { allowShortCircuit: true }],
'no-unused-vars': ['error', { args: 'none', caughtErrors: 'all' }],
'no-use-before-define': ['error', {
classes: true,
functions: false,
variables: false,
}],
'no-useless-call': 'error',
'no-useless-concat': 'error',
'no-useless-constructor': 'error',
'no-useless-return': 'error',
'no-var': 'error',
'no-void': 'error',
'one-var': ['error', { initialized: 'never' }],
'prefer-const': ['error', { ignoreReadBeforeAssign: true }],
'prefer-object-has-own': 'error',
'strict': ['error', 'global'],
'symbol-description': 'error',
'unicode-bom': 'error',
'valid-typeof': ['error', { requireStringLiterals: true }],
// ESLint recommended rules that we disable.
'no-inner-declarations': 'off',
'no-useless-assignment': 'off',
// JSDoc rules.
'jsdoc/require-jsdoc': 'off',
'jsdoc/require-param-description': 'off',
'jsdoc/require-returns-description': 'off',
'jsdoc/valid-types': 'error',
'jsdoc/no-defaults': 'error',
'jsdoc/no-undefined-types': 'off',
'jsdoc/require-param': 'off',
'jsdoc/check-tag-names': 'error',
'jsdoc/require-returns': 'error',
'jsdoc/check-line-alignment': ['error', 'any', {
tags: ['param', 'property', 'returns', 'file'],
wrapIndent: ' ',
}],
'jsdoc/check-alignment': 'error',
'jsdoc/reject-any-type': 'off',
'jsdoc/reject-function-type': 'off',
// RegExp recommended rules that we disable.
// Todo: Investigate which rules should be enabled.
'prefer-regex-literals': 'off',
'regexp/control-character-escape': 'off',
'regexp/match-any': 'off',
'regexp/negation': 'off',
'regexp/no-contradiction-with-assertion': 'off',
'regexp/no-dupe-characters-character-class': 'off',
'regexp/no-dupe-disjunctions': 'off',
'regexp/no-empty-alternative': 'off',
'regexp/no-legacy-features': 'off',
'regexp/no-misleading-capturing-group': 'off',
'regexp/no-obscure-range': 'off',
'regexp/no-potentially-useless-backreference': 'off',
'regexp/no-super-linear-backtracking': 'off',
'regexp/no-trivially-nested-quantifier': 'off',
'regexp/no-unused-capturing-group': 'off',
'regexp/no-useless-assertions': 'off',
'regexp/no-useless-character-class': 'off',
'regexp/no-useless-escape': 'off',
'regexp/no-useless-flag': 'off',
'regexp/no-useless-lazy': 'off',
'regexp/no-useless-non-capturing-group': 'off',
'regexp/no-useless-quantifier': 'off',
'regexp/no-useless-range': 'off',
'regexp/optimal-lookaround-quantifier': 'off',
'regexp/optimal-quantifier-concatenation': 'off',
'regexp/prefer-character-class': 'off',
'regexp/prefer-d': 'off',
'regexp/prefer-question-quantifier': 'off',
'regexp/prefer-star-quantifier': 'off',
'regexp/prefer-w': 'off',
'regexp/sort-flags': 'off',
'regexp/strict': 'off',
'regexp/use-ignore-case': 'off',
// Stylistic rules.
'@stylistic/js/arrow-parens': 'error',
'@stylistic/js/arrow-spacing': 'error',
'@stylistic/js/block-spacing': 'error',
'@stylistic/js/brace-style': ['error', '1tbs', { allowSingleLine: true }],
'@stylistic/js/comma-dangle': ['error', 'always-multiline'],
'@stylistic/js/comma-spacing': 'error',
'@stylistic/js/comma-style': 'error',
'@stylistic/js/computed-property-spacing': 'error',
'@stylistic/js/dot-location': ['error', 'property'],
'@stylistic/js/eol-last': 'error',
'@stylistic/js/function-call-spacing': 'error',
'@stylistic/js/indent': ['error', 2, {
ArrayExpression: 'first',
CallExpression: { arguments: 'first' },
FunctionDeclaration: { parameters: 'first' },
FunctionExpression: { parameters: 'first' },
MemberExpression: 'off',
ObjectExpression: 'first',
SwitchCase: 1,
assignmentOperator: 'off',
}],
'@stylistic/js/key-spacing': 'error',
'@stylistic/js/keyword-spacing': 'error',
'@stylistic/js/linebreak-style': 'error',
'@stylistic/js/max-len': ['error', {
code: 120,
ignorePattern: '^// Flags:',
ignoreRegExpLiterals: true,
ignoreTemplateLiterals: true,
ignoreUrls: true,
tabWidth: 2,
}],
'@stylistic/js/new-parens': 'error',
'@stylistic/js/no-confusing-arrow': 'error',
'@stylistic/js/no-extra-parens': ['error', 'functions'],
'@stylistic/js/no-multi-spaces': ['error', { ignoreEOLComments: true }],
'@stylistic/js/no-multiple-empty-lines': ['error', { max: 2, maxEOF: 0, maxBOF: 0 }],
'@stylistic/js/no-tabs': 'error',
'@stylistic/js/no-trailing-spaces': 'error',
'@stylistic/js/no-whitespace-before-property': 'error',
'@stylistic/js/object-curly-newline': 'error',
'@stylistic/js/object-curly-spacing': ['error', 'always'],
'@stylistic/js/one-var-declaration-per-line': 'error',
'@stylistic/js/operator-linebreak': ['error', 'after'],
'@stylistic/js/padding-line-between-statements': [
'error',
{ blankLine: 'always', prev: 'function', next: 'function' },
],
'@stylistic/js/quotes': ['error', 'single', { avoidEscape: true, allowTemplateLiterals: 'always' }],
'@stylistic/js/quote-props': ['error', 'consistent'],
'@stylistic/js/rest-spread-spacing': 'error',
'@stylistic/js/semi': 'error',
'@stylistic/js/semi-spacing': 'error',
'@stylistic/js/space-before-blocks': ['error', 'always'],
'@stylistic/js/space-before-function-paren': ['error', {
anonymous: 'never',
named: 'never',
asyncArrow: 'always',
}],
'@stylistic/js/space-in-parens': 'error',
'@stylistic/js/space-infix-ops': 'error',
'@stylistic/js/space-unary-ops': 'error',
'@stylistic/js/spaced-comment': ['error', 'always', {
'block': { 'balanced': true },
'exceptions': ['-'],
}],
'@stylistic/js/template-curly-spacing': 'error',
// Custom rules in tools/eslint-rules.
'node-core/no-unescaped-regexp-dot': 'error',
'node-core/no-duplicate-requires': 'error',
'node-core/prefer-proto': 'error',
'node-core/prefer-optional-chaining': 'error',
'node-core/func-name-matching': ['error', { considerPropertyDescriptor: true }],
},
},
// #endregion
// #region markdown config
{
files: ['**/*.md'],
ignores: ['CLAUDE.md', 'AGENTS.md'],
plugins: {
markdown,
},
processor: 'markdown/markdown',
},
{
files: ['**/*.md/*.{js,cjs}'],
languageOptions: {
parserOptions: {
ecmaFeatures: { impliedStrict: true },
},
},
rules: { strict: 'off' },
},
{
files: [
'**/*.md/*.mjs',
'doc/api/esm.md/*.js',
'doc/api/packages.md/*.js',
],
languageOptions: {
sourceType: 'module',
},
rules: { 'no-restricted-globals': [
'error',
{
name: '__filename',
message: 'Use import.meta.url instead.',
},
{
name: '__dirname',
message: 'Not available in ESM.',
},
{
name: 'exports',
message: 'Not available in ESM.',
},
{
name: 'module',
message: 'Not available in ESM.',
},
{
name: 'require',
message: 'Use import instead.',
},
{
name: 'Buffer',
message: "Import 'Buffer' instead of using the global.",
},
{
name: 'process',
message: "Import 'process' instead of using the global.",
},
] },
},
// #endregion
// #region partials
...benchmarkConfig,
...docConfig,
...libConfig,
...testConfig,
...toolsConfig,
// #endregion
];