Skip to content

Commit 29fdfe0

Browse files
Forbid native TypeScript collection types (#5598)
* remove native collection types * fix rule * remove array type --------- Co-authored-by: Quentin Pradet <[email protected]>
1 parent bcc4f7e commit 29fdfe0

File tree

3 files changed

+59
-22
lines changed

3 files changed

+59
-22
lines changed

validator/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ It is configured [in the specification directory](../specification/eslint.config
99
|---------------------------------------| - |
1010
| `single-key-dictionary-key-is-string` | `SingleKeyDictionary` keys must be strings. |
1111
| `dictionary-key-is-string` | `Dictionary` keys must be strings. |
12-
| `no-native-types` | `Typescript native types not allowed, use aliases. |
12+
| `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. |
1313
| `invalid-node-types` | The spec uses a subset of TypeScript, so some types, clauses and expressions are not allowed. |
1414
| `no-generic-number` | Generic `number` type is not allowed outside of `_types/Numeric.ts`. Use concrete numeric types like `integer`, `long`, `float`, `double`, etc. |
1515
| `request-must-have-urls` | All Request interfaces extending `RequestBase` must have a `urls` property defining their endpoint paths and HTTP methods. |

validator/rules/no-native-types.js

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,25 +20,43 @@ import { ESLintUtils } from '@typescript-eslint/utils';
2020

2121
const createRule = ESLintUtils.RuleCreator(name => `https://example.com/rule/${name}`)
2222

23-
const TYPES_TO_AVOID = ['Record', 'Partial', 'Required', 'Pick', 'Omit'];
23+
const TYPE_SUGGESTIONS = {
24+
'Record': 'Use Dictionary instead',
25+
'Partial': 'Use spec-defined aliases instead',
26+
'Required': 'Use spec-defined aliases instead',
27+
'Pick': 'Use spec-defined aliases instead',
28+
'Omit': 'Use spec-defined aliases instead',
29+
'Map': 'Use Dictionary instead',
30+
'Set': 'Use an array type instead (e.g., string[])',
31+
'WeakMap': 'Use Dictionary instead',
32+
'WeakSet': 'Use an array type instead',
33+
};
2434

2535
export default createRule({
2636
name: 'no-native-types',
2737
create(context) {
2838
return {
2939
TSTypeReference(node) {
30-
if (TYPES_TO_AVOID.includes(node.typeName.name)) {
31-
context.report({ node, messageId: 'stringKey' })
40+
const typeName = node.typeName.name;
41+
if (TYPE_SUGGESTIONS[typeName]) {
42+
context.report({
43+
node,
44+
messageId: 'noNativeType',
45+
data: {
46+
type: typeName,
47+
suggestion: TYPE_SUGGESTIONS[typeName]
48+
}
49+
})
3250
}
3351
},
3452
}
3553
},
3654
meta: {
3755
docs: {
38-
description: 'Typescript native types not allowed, use aliases',
56+
description: 'TypeScript native utility and collection types not allowed, use spec-defined aliases',
3957
},
4058
messages: {
41-
stringKey: "Typescript native types not allowed, use aliases"
59+
noNativeType: 'Native TypeScript type "{{type}}" is not allowed. {{suggestion}}.'
4260
},
4361
type: 'suggestion',
4462
},

validator/test/no-native-types.test.js

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* under the License.
1818
*/
1919
import { RuleTester } from '@typescript-eslint/rule-tester'
20-
import rule from '../rules/dictionary-key-is-string.js'
20+
import rule from '../rules/no-native-types.js'
2121

2222
const ruleTester = new RuleTester({
2323
languageOptions: {
@@ -32,32 +32,51 @@ const ruleTester = new RuleTester({
3232

3333
ruleTester.run('no-native-types', rule, {
3434
valid: [
35-
`type MyRecord = Record<string, object>`,
36-
`type MyPart = Partial<Record>`,
37-
`type MyReq = Required<string>`,
38-
`type MyPick Pick<integer,"something">`,
39-
`type MyOmit = Omit<Record, "something">`,
35+
`type MyDict = Dictionary<string, object>`,
36+
`type MyMapping = Dictionary<string, any>`,
37+
`type MyType = { field: string }`,
38+
`class MyClass { prop: integer }`,
4039
],
4140
invalid: [
4241
{
4342
code: `type MyRecord = Record<string, object>`,
44-
errors: [{ messageId: 'stringKey' }]
43+
errors: [{ messageId: 'noNativeType' }]
4544
},
4645
{
47-
code: `type MyPart = Partial<Record>`,
48-
errors: [{ messageId: 'stringKey' }]
46+
code: `type MyPart = Partial<SomeType>`,
47+
errors: [{ messageId: 'noNativeType' }]
4948
},
5049
{
51-
code: `type MyReq = Required<string>`,
52-
errors: [{ messageId: 'stringKey' }]
50+
code: `type MyReq = Required<SomeType>`,
51+
errors: [{ messageId: 'noNativeType' }]
5352
},
5453
{
55-
code: `type MyPick Pick<integer,"something">`,
56-
errors: [{ messageId: 'stringKey' }]
54+
code: `type MyPick = Pick<SomeType, "field">`,
55+
errors: [{ messageId: 'noNativeType' }]
5756
},
5857
{
59-
code: `type MyOmit = Omit<Record, "something">`,
60-
errors: [{ messageId: 'stringKey' }]
61-
}
58+
code: `type MyOmit = Omit<SomeType, "field">`,
59+
errors: [{ messageId: 'noNativeType' }]
60+
},
61+
{
62+
code: `type MyMap = Map<string, object>`,
63+
errors: [{ messageId: 'noNativeType' }]
64+
},
65+
{
66+
code: `type MySet = Set<string>`,
67+
errors: [{ messageId: 'noNativeType' }]
68+
},
69+
{
70+
code: `type MyWeakMap = WeakMap<object, string>`,
71+
errors: [{ messageId: 'noNativeType' }]
72+
},
73+
{
74+
code: `type MyWeakSet = WeakSet<object>`,
75+
errors: [{ messageId: 'noNativeType' }]
76+
},
77+
{
78+
code: `class MyClass { items: Map<string, number> }`,
79+
errors: [{ messageId: 'noNativeType' }]
80+
},
6281
],
6382
})

0 commit comments

Comments
 (0)