current commit
261027.9 · tools: do not lint GYP JS files
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/??_*',
    'tools/gyp/**',

    // 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',
        crypto: 'readonly',
        Crypto: 'readonly',
        CryptoKey: 'readonly',
        CustomEvent: 'readonly',
        DecompressionStream: 'readonly',
        DisposableStack: 'readonly',
        EventSource: 'readonly',
        fetch: 'readonly',
        Float16Array: 'readonly',
        FormData: 'readonly',
        localStorage: 'readonly',
        navigator: 'readonly',
        QuotaExceededError: 'readonly',
        ReadableByteStreamController: 'readonly',
        ReadableStream: 'readonly',
        ReadableStreamBYOBReader: 'readonly',
        ReadableStreamBYOBRequest: 'readonly',
        ReadableStreamDefaultController: 'readonly',
        ReadableStreamDefaultReader: 'readonly',
        Response: 'readonly',
        sessionStorage: 'readonly',
        ShadowRealm: 'readonly',
        Storage: 'readonly',
        SubtleCrypto: 'readonly',
        TextDecoderStream: 'readonly',
        TextEncoderStream: 'readonly',
        TransformStream: 'readonly',
        TransformStreamDefaultController: 'readonly',
        WebSocket: 'readonly',
        Worker: 'readonly',
        WritableStream: 'readonly',
        WritableStreamDefaultController: 'readonly',
        WritableStreamDefaultWriter: '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'],
    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
];