From 9dcf7bdfd29ad9f1ec01bfa24ee0fe95a3ae5435 Mon Sep 17 00:00:00 2001 From: margaretjgu <136839162+margaretjgu@users.noreply.github.com> Date: Fri, 7 Nov 2025 13:05:06 -0500 Subject: [PATCH] Forbid native TypeScript collection types (#5598) * remove native collection types * fix rule * remove array type --------- Co-authored-by: Quentin Pradet (cherry picked from commit 29fdfe0c2e245430f465ab951f21da84aa68602b) --- validator/README.md | 2 +- validator/rules/no-native-types.js | 28 +++++++++++--- validator/test/no-native-types.test.js | 51 ++++++++++++++++++-------- 3 files changed, 59 insertions(+), 22 deletions(-) diff --git a/validator/README.md b/validator/README.md index 8e48e886fd..b98f9d60be 100644 --- a/validator/README.md +++ b/validator/README.md @@ -9,7 +9,7 @@ It is configured [in the specification directory](../specification/eslint.config |---------------------------------------| - | | `single-key-dictionary-key-is-string` | `SingleKeyDictionary` keys must be strings. | | `dictionary-key-is-string` | `Dictionary` keys must be strings. | -| `no-native-types` | `Typescript native types not allowed, use aliases. | +| `no-native-types` | TypeScript native utility types (`Record`, `Partial`, etc.) and collection types (`Map`, `Set`, etc.) are not allowed. Use spec-defined aliases like `Dictionary` instead. | | `invalid-node-types` | The spec uses a subset of TypeScript, so some types, clauses and expressions are not allowed. | | `no-generic-number` | Generic `number` type is not allowed outside of `_types/Numeric.ts`. Use concrete numeric types like `integer`, `long`, `float`, `double`, etc. | | `request-must-have-urls` | All Request interfaces extending `RequestBase` must have a `urls` property defining their endpoint paths and HTTP methods. | diff --git a/validator/rules/no-native-types.js b/validator/rules/no-native-types.js index b21c2fa96a..f10b3f9091 100644 --- a/validator/rules/no-native-types.js +++ b/validator/rules/no-native-types.js @@ -20,25 +20,43 @@ import { ESLintUtils } from '@typescript-eslint/utils'; const createRule = ESLintUtils.RuleCreator(name => `https://example.com/rule/${name}`) -const TYPES_TO_AVOID = ['Record', 'Partial', 'Required', 'Pick', 'Omit']; +const TYPE_SUGGESTIONS = { + 'Record': 'Use Dictionary instead', + 'Partial': 'Use spec-defined aliases instead', + 'Required': 'Use spec-defined aliases instead', + 'Pick': 'Use spec-defined aliases instead', + 'Omit': 'Use spec-defined aliases instead', + 'Map': 'Use Dictionary instead', + 'Set': 'Use an array type instead (e.g., string[])', + 'WeakMap': 'Use Dictionary instead', + 'WeakSet': 'Use an array type instead', +}; export default createRule({ name: 'no-native-types', create(context) { return { TSTypeReference(node) { - if (TYPES_TO_AVOID.includes(node.typeName.name)) { - context.report({ node, messageId: 'stringKey' }) + const typeName = node.typeName.name; + if (TYPE_SUGGESTIONS[typeName]) { + context.report({ + node, + messageId: 'noNativeType', + data: { + type: typeName, + suggestion: TYPE_SUGGESTIONS[typeName] + } + }) } }, } }, meta: { docs: { - description: 'Typescript native types not allowed, use aliases', + description: 'TypeScript native utility and collection types not allowed, use spec-defined aliases', }, messages: { - stringKey: "Typescript native types not allowed, use aliases" + noNativeType: 'Native TypeScript type "{{type}}" is not allowed. {{suggestion}}.' }, type: 'suggestion', }, diff --git a/validator/test/no-native-types.test.js b/validator/test/no-native-types.test.js index ef30f764ca..e585ab93be 100644 --- a/validator/test/no-native-types.test.js +++ b/validator/test/no-native-types.test.js @@ -17,7 +17,7 @@ * under the License. */ import { RuleTester } from '@typescript-eslint/rule-tester' -import rule from '../rules/dictionary-key-is-string.js' +import rule from '../rules/no-native-types.js' const ruleTester = new RuleTester({ languageOptions: { @@ -32,32 +32,51 @@ const ruleTester = new RuleTester({ ruleTester.run('no-native-types', rule, { valid: [ - `type MyRecord = Record`, - `type MyPart = Partial`, - `type MyReq = Required`, - `type MyPick Pick`, - `type MyOmit = Omit`, + `type MyDict = Dictionary`, + `type MyMapping = Dictionary`, + `type MyType = { field: string }`, + `class MyClass { prop: integer }`, ], invalid: [ { code: `type MyRecord = Record`, - errors: [{ messageId: 'stringKey' }] + errors: [{ messageId: 'noNativeType' }] }, { - code: `type MyPart = Partial`, - errors: [{ messageId: 'stringKey' }] + code: `type MyPart = Partial`, + errors: [{ messageId: 'noNativeType' }] }, { - code: `type MyReq = Required`, - errors: [{ messageId: 'stringKey' }] + code: `type MyReq = Required`, + errors: [{ messageId: 'noNativeType' }] }, { - code: `type MyPick Pick`, - errors: [{ messageId: 'stringKey' }] + code: `type MyPick = Pick`, + errors: [{ messageId: 'noNativeType' }] }, { - code: `type MyOmit = Omit`, - errors: [{ messageId: 'stringKey' }] - } + code: `type MyOmit = Omit`, + errors: [{ messageId: 'noNativeType' }] + }, + { + code: `type MyMap = Map`, + errors: [{ messageId: 'noNativeType' }] + }, + { + code: `type MySet = Set`, + errors: [{ messageId: 'noNativeType' }] + }, + { + code: `type MyWeakMap = WeakMap`, + errors: [{ messageId: 'noNativeType' }] + }, + { + code: `type MyWeakSet = WeakSet`, + errors: [{ messageId: 'noNativeType' }] + }, + { + code: `class MyClass { items: Map }`, + errors: [{ messageId: 'noNativeType' }] + }, ], })