diff --git a/validator/README.md b/validator/README.md index 13274bc86f..758559eb10 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..b52df99fe3 100644 --- a/validator/rules/no-native-types.js +++ b/validator/rules/no-native-types.js @@ -20,25 +20,45 @@ 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 UTILITY_TYPES = ['Record', 'Partial', 'Required', 'Pick', 'Omit']; + +const COLLECTION_TYPES = ['Map', 'Set', 'WeakMap', 'WeakSet']; + +const TYPES_TO_AVOID = [...UTILITY_TYPES, ...COLLECTION_TYPES]; + +const TYPE_SUGGESTIONS = { + 'Record': 'Use Dictionary 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 (TYPES_TO_AVOID.includes(typeName)) { + context.report({ + node, + messageId: 'noNativeType', + data: { + type: typeName, + suggestion: TYPE_SUGGESTIONS[typeName] || 'Use spec-defined aliases instead' + } + }) } }, } }, 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..6d6007d95e 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,53 @@ 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 MyArray = string[]`, + `type MyList = Array`, + `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' }] + }, ], })