diff --git a/packages/typescript/CHANGELOG.md b/packages/typescript/CHANGELOG.md index 2c1ac42..7f89209 100644 --- a/packages/typescript/CHANGELOG.md +++ b/packages/typescript/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- **BREAKING:** Restrict usage of `with`, `in`, and sequence expressions, which should have been inherited from the base config but were mistakenly overridden ([#436](https://github.com/MetaMask/eslint-config/pull/436)) + ## [15.0.0] ### Changed diff --git a/packages/typescript/rules-snapshot.json b/packages/typescript/rules-snapshot.json index a153a46..39eb028 100644 --- a/packages/typescript/rules-snapshot.json +++ b/packages/typescript/rules-snapshot.json @@ -226,6 +226,18 @@ "no-redeclare": "off", "no-restricted-syntax": [ "error", + { + "selector": "WithStatement", + "message": "With statements are not allowed" + }, + { + "selector": "BinaryExpression[operator='in']", + "message": "The \"in\" operator is not allowed" + }, + { + "selector": "SequenceExpression", + "message": "Sequence expressions are not allowed" + }, { "selector": "PropertyDefinition[accessibility='private'], MethodDefinition[accessibility='private'], TSParameterProperty[accessibility='private']", "message": "Use a hash name instead." diff --git a/packages/typescript/src/index.mjs b/packages/typescript/src/index.mjs index 85297a3..8bc44f9 100644 --- a/packages/typescript/src/index.mjs +++ b/packages/typescript/src/index.mjs @@ -1,4 +1,4 @@ -import { createConfig } from '@metamask/eslint-config'; +import base, { createConfig } from '@metamask/eslint-config'; import * as resolver from 'eslint-import-resolver-typescript'; import importX from 'eslint-plugin-import-x'; import jsdoc from 'eslint-plugin-jsdoc'; @@ -6,6 +6,37 @@ import jsdoc from 'eslint-plugin-jsdoc'; // eslint-disable-next-line import-x/no-unresolved import typescript from 'typescript-eslint'; +/** + * Collects all options for a given array-valued rule across one or more flat + * config arrays, excluding the leading severity element. + * + * ESLint flat config does not merge array-valued rules across config objects — + * a later config silently replaces earlier ones. This helper makes it possible + * to extend an upstream rule configuration rather than copy-pasting its options. + * + * @param {string} ruleName - The rule to collect options for. + * @param {import('eslint').Linter.Config[][]} configs - Flat config arrays to + * collect options from. + * @returns {unknown[]} The options from all matching rule entries, with the + * leading severity element omitted. + */ +function collectExistingRuleOptions(ruleName, configs) { + return configs.flat().flatMap((config) => { + const rule = config.rules?.[ruleName]; + if (!Array.isArray(rule)) { + return []; + } + // Rule entries are ['error' | 'warn' | number, ...options]. + // Skip the first element (severity) and collect the rest. + return rule.slice(1); + }); +} + +const baseNoRestrictedSyntaxOptions = collectExistingRuleOptions( + 'no-restricted-syntax', + base, +); + const config = createConfig({ name: '@metamask/eslint-config-typescript', @@ -234,6 +265,7 @@ const config = createConfig({ // Prefer hash names over TypeScript's `private` modifier. 'no-restricted-syntax': [ 'error', + ...baseNoRestrictedSyntaxOptions, { selector: "PropertyDefinition[accessibility='private'], MethodDefinition[accessibility='private'], TSParameterProperty[accessibility='private']",