Skip to content

Commit e82512e

Browse files
Enforce concrete numeric types (#5584) (#5621)
* draft rule * tests, run lint * prettier (cherry picked from commit b2d5fcd) Co-authored-by: margaretjgu <[email protected]>
1 parent 9f4d244 commit e82512e

File tree

7 files changed

+155
-5
lines changed

7 files changed

+155
-5
lines changed

specification/_types/Geo.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919

2020
import { UserDefinedValue } from '@spec_utils/UserDefinedValue'
21-
import { double } from './Numeric'
21+
import { double, integer } from './Numeric'
2222

2323
export class DistanceParsed {
2424
precision: double
@@ -81,13 +81,13 @@ export enum GeoShapeRelation {
8181
contains
8282
}
8383

84-
export type GeoTilePrecision = number
84+
export type GeoTilePrecision = integer
8585

8686
/**
8787
* A precision that can be expressed as a geohash length between 1 and 12, or a distance measure like "1km", "10m".
8888
* @codegen_names geohash_length, distance
8989
*/
90-
export type GeoHashPrecision = number | string
90+
export type GeoHashPrecision = integer | string
9191
export type GeoHash = string
9292

9393
/** A map tile reference, represented as `{zoom}/{x}/{y}` */

specification/eslint.config.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ export default defineConfig({
3434
'es-spec-validator/single-key-dictionary-key-is-string': 'error',
3535
'es-spec-validator/dictionary-key-is-string': 'error',
3636
'es-spec-validator/no-native-types': 'error',
37-
'es-spec-validator/invalid-node-types': 'error'
37+
'es-spec-validator/invalid-node-types': 'error',
38+
'es-spec-validator/no-generic-number': 'error'
3839
}
3940
})

specification/indices/get_alias/_types/response.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* under the License.
1818
*/
1919

20+
import { integer } from '@_types/Numeric'
2021
import { AliasDefinition } from '@indices/_types/AliasDefinition'
2122
import { AdditionalProperties } from '@spec_utils/behaviors'
2223
import { Dictionary } from '@spec_utils/Dictionary'
@@ -32,5 +33,5 @@ export class NotFoundAliases
3233
implements AdditionalProperties<string, IndexAliases>
3334
{
3435
error: string
35-
status: number
36+
status: integer
3637
}

validator/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ It is configured [in the specification directory](../specification/eslint.config
1111
| `dictionary-key-is-string` | `Dictionary` keys must be strings. |
1212
| `no-native-types` | `Typescript native types not allowed, use aliases. |
1313
| `invalid-node-types` | The spec uses a subset of TypeScript, so some types, clauses and expressions are not allowed. |
14+
| `no-generic-number` | Generic `number` type is not allowed outside of `_types/Numeric.ts`. Use concrete numeric types like `integer`, `long`, `float`, `double`, etc. |
1415

1516
## Usage
1617

validator/eslint-plugin-es-spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@ import singleKeyDict from './rules/single-key-dictionary-key-is-string.js'
2020
import dict from './rules/dictionary-key-is-string.js'
2121
import noNativeTypes from './rules/no-native-types.js'
2222
import invalidNodeTypes from './rules/invalid-node-types.js'
23+
import noGenericNumber from './rules/no-generic-number.js'
2324

2425
export default {
2526
rules: {
2627
'single-key-dictionary-key-is-string': singleKeyDict,
2728
'dictionary-key-is-string': dict,
2829
'no-native-types': noNativeTypes,
2930
'invalid-node-types': invalidNodeTypes,
31+
'no-generic-number': noGenericNumber,
3032
}
3133
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { ESLintUtils } from '@typescript-eslint/utils';
20+
import * as path from 'path';
21+
22+
const createRule = ESLintUtils.RuleCreator(name => `https://example.com/rule/${name}`)
23+
24+
export default createRule({
25+
name: 'no-generic-number',
26+
create(context) {
27+
return {
28+
TSNumberKeyword(node) {
29+
const filename = context.filename || context.getFilename();
30+
const normalizedPath = path.normalize(filename);
31+
32+
// allow number only in _types/Numeric.ts
33+
if (normalizedPath.includes(path.join('_types', 'Numeric.ts'))) {
34+
return;
35+
}
36+
37+
context.report({
38+
node,
39+
messageId: 'noGenericNumber',
40+
data: {
41+
types: 'short, byte, integer, uint, long, ulong, float, or double'
42+
}
43+
})
44+
},
45+
}
46+
},
47+
meta: {
48+
docs: {
49+
description: 'Force usage of concrete numeric types instead of generic "number" type',
50+
},
51+
messages: {
52+
noGenericNumber: 'Generic "number" type is not allowed. Use concrete numeric types instead: {{types}}. See specification/_types/Numeric.ts for available types.'
53+
},
54+
type: 'problem',
55+
schema: []
56+
},
57+
defaultOptions: []
58+
})
59+
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/*
2+
* Licensed to Elasticsearch B.V. under one or more contributor
3+
* license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright
5+
* ownership. Elasticsearch B.V. licenses this file to you under
6+
* the Apache License, Version 2.0 (the "License"); you may
7+
* not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
import { RuleTester } from '@typescript-eslint/rule-tester'
20+
import rule from '../rules/no-generic-number.js'
21+
22+
const ruleTester = new RuleTester({
23+
languageOptions: {
24+
parserOptions: {
25+
projectService: {
26+
allowDefaultProject: ['*.ts*'],
27+
},
28+
tsconfigRootDir: import.meta.dirname,
29+
},
30+
},
31+
})
32+
33+
ruleTester.run('no-generic-number', rule, {
34+
valid: [
35+
`type MyType = { count: integer }`,
36+
`type MyType = { amount: long }`,
37+
`type MyType = { price: float }`,
38+
`type MyType = { value: double }`,
39+
`type MyType = { small: short }`,
40+
`type MyType = { tiny: byte }`,
41+
`type MyType = { unsigned: uint }`,
42+
`type MyType = { bigUnsigned: ulong }`,
43+
`class MyClass { score: float; count: integer; }`,
44+
`interface MyInterface { id: long; ratio: double; }`,
45+
`type MyType = { value: integer | string }`,
46+
`type MyType = { id: long | float }`,
47+
`type MyType = { numbers: integer[] }`,
48+
`type MyType = { values: Array<long> }`,
49+
`type MyType = Dictionary<string, integer>`,
50+
],
51+
invalid: [
52+
{
53+
code: `type MyType = { count: number }`,
54+
errors: [{ messageId: 'noGenericNumber' }]
55+
},
56+
{
57+
code: `class MyClass { status: number }`,
58+
errors: [{ messageId: 'noGenericNumber' }]
59+
},
60+
{
61+
code: `interface MyInterface { id: number }`,
62+
errors: [{ messageId: 'noGenericNumber' }]
63+
},
64+
{
65+
code: `type MyType = { value: string | number }`,
66+
errors: [{ messageId: 'noGenericNumber' }]
67+
},
68+
{
69+
code: `type MyType = { items: number[] }`,
70+
errors: [{ messageId: 'noGenericNumber' }]
71+
},
72+
{
73+
code: `type MyType = { items: Array<number> }`,
74+
errors: [{ messageId: 'noGenericNumber' }]
75+
},
76+
{
77+
code: `type MyType = Dictionary<string, number>`,
78+
errors: [{ messageId: 'noGenericNumber' }]
79+
},
80+
{
81+
code: `export class Response { body: { count: number } }`,
82+
errors: [{ messageId: 'noGenericNumber' }]
83+
}
84+
],
85+
})
86+

0 commit comments

Comments
 (0)