Skip to content

Commit aa97c99

Browse files
l-trottagithub-actions[bot]
authored andcommitted
eslint - field should not have the same name as the enclosing class (#5843)
* base structure * impl * correct rule ignore * include codegen_name comment in logic * restore comment on weight * better check for comment (cherry picked from commit 6463ee1)
1 parent 55018dd commit aa97c99

File tree

9 files changed

+186
-14
lines changed

9 files changed

+186
-14
lines changed

specification/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ export default defineConfig([
3636
'es-spec-validator/single-key-dictionary-key-is-string': 'error',
3737
'es-spec-validator/dictionary-key-is-string': 'error',
3838
'es-spec-validator/no-native-types': 'error',
39+
'es-spec-validator/no-same-name-as-enclosing-type': 'error',
3940
'es-spec-validator/invalid-node-types': 'error',
4041
'es-spec-validator/no-generic-number': 'error',
4142
'es-spec-validator/codegen-exclude-on-request-only': 'error',

specification/logstash/_types/Pipeline.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export class PipelineSettings {
5353
*/
5454
'queue.checkpoint.writes': integer
5555
}
56+
// eslint-disable-next-line es-spec-validator/no-same-name-as-enclosing-type
5657
export class Pipeline {
5758
/**
5859
* A description of the pipeline.

specification/ml/put_trained_model/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,7 @@ export class AggregateOutput {
105105
exponent?: Weights
106106
}
107107

108+
// eslint-disable-next-line es-spec-validator/no-same-name-as-enclosing-type
108109
export class Weights {
109110
weights: double
110111
}

specification/nodes/_types/Stats.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1072,6 +1072,7 @@ export class Scripting {
10721072
contexts?: Context[]
10731073
}
10741074

1075+
// eslint-disable-next-line es-spec-validator/no-same-name-as-enclosing-type
10751076
export class Context {
10761077
context?: string
10771078
compilations?: long

specification/security/put_privileges/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
import { Metadata, Name } from '@_types/common'
2121

22+
// eslint-disable-next-line es-spec-validator/no-same-name-as-enclosing-type
2223
export class Actions {
2324
actions: string[]
2425
application?: string

validator/README.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,22 @@ It is configured [in the specification directory](../specification/eslint.config
55

66
## Rules
77

8-
| Name | Description |
9-
|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
10-
| `single-key-dictionary-key-is-string` | `SingleKeyDictionary` keys must be strings. |
11-
| `dictionary-key-is-string` | `Dictionary` keys must be strings. |
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. |
13-
| `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. |
15-
| `request-must-have-urls` | All Request interfaces extending `RequestBase` must have a `urls` property defining their endpoint paths and HTTP methods. |
16-
| `no-variants-on-responses` | `@variants` is only supported on Interface types, not on Request or Response classes. Use value_body pattern with `@codegen_name` instead. Includes additional checks on variant tag use. |
17-
| `no-inline-unions` | Inline union types (e.g., `field: A \| B`) are not allowed in properties/fields. Define a named type alias instead to improve code generation for statically-typed languages. |
18-
| `prefer-tagged-variants` | Union of class types should use tagged variants (`@variants internal` or `@variants container`) instead of inline unions for better deserialization support in statically-typed languages. |
19-
| `no-duplicate-type-names` | All types must be unique across class and enum definitions. |
20-
| `no-all-string-literal-unions | Unions consisting entirely of string literals (e.g., `"green" \| "yellow" \| "red"`) are not allowed, use enums instead. |
8+
| Name | Description |
9+
|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
10+
| `single-key-dictionary-key-is-string` | `SingleKeyDictionary` keys must be strings. |
11+
| `dictionary-key-is-string` | `Dictionary` keys must be strings. |
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. |
13+
| `no-same-name-as-enclosing-type` | Classes having fields with the same name as the class is breaking for some client libraries. |
14+
| `invalid-node-types` | The spec uses a subset of TypeScript, so some types, clauses and expressions are not allowed. |
15+
| `no-generic-number` | Generic `number` type is not allowed outside of `_types/Numeric.ts`. Use concrete numeric types like `integer`, `long`, `float`, `double`, etc. |
16+
| `request-must-have-urls` | All Request interfaces extending `RequestBase` must have a `urls` property defining their endpoint paths and HTTP methods. |
17+
| `no-variants-on-responses` | `@variants` is only supported on Interface types, not on Request or Response classes. Use value_body pattern with `@codegen_name` instead. Includes additional checks on variant tag use. |
18+
| `no-inline-unions` | Inline union types (e.g., `field: A \| B`) are not allowed in properties/fields. Define a named type alias instead to improve code generation for statically-typed languages. |
19+
| `prefer-tagged-variants` | Union of class types should use tagged variants (`@variants internal` or `@variants container`) instead of inline unions for better deserialization support in statically-typed languages. |
20+
| `no-duplicate-type-names` | All types must be unique across class and enum definitions. |
21+
| `no-all-string-literal-unions | Unions consisting entirely of string literals (e.g., `"green" \| "yellow" \| "red"`) are not allowed, use enums instead. |
2122
| `jsdoc-endpoint-check` | Validates JSDoc on endpoints in the specification. Ensuring consistent formatting. Some errors can be fixed with `--fix`. |
22-
| `codegen-exclude-on-request-only` | Ensures `@codegen_exclude` is only used on request definitions located in namespaced `specification/` files (i.e. files. |
23+
| `codegen-exclude-on-request-only` | Ensures `@codegen_exclude` is only used on request definitions located in namespaced `specification/` files (i.e. files. |
2324

2425
## Usage
2526

validator/eslint-plugin-es-spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
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'
22+
import noSameNameAsEnclosingType from './rules/no-same-name-as-enclosing-type.js'
2223
import invalidNodeTypes from './rules/invalid-node-types.js'
2324
import noGenericNumber from './rules/no-generic-number.js'
2425
import requestMustHaveUrls from './rules/request-must-have-urls.js'
@@ -35,6 +36,7 @@ export default {
3536
'single-key-dictionary-key-is-string': singleKeyDict,
3637
'dictionary-key-is-string': dict,
3738
'no-native-types': noNativeTypes,
39+
'no-same-name-as-enclosing-type': noSameNameAsEnclosingType,
3840
'invalid-node-types': invalidNodeTypes,
3941
'no-generic-number': noGenericNumber,
4042
'request-must-have-urls': requestMustHaveUrls,
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
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+
21+
const createRule = ESLintUtils.RuleCreator(name => `https://example.com/rule/${name}`)
22+
23+
/**
24+
* Finds if selected field has a @codegen_name comment above
25+
* @param comments - Array of comments from the class
26+
* @param field - Definition of the field
27+
* @returns {boolean}
28+
*/
29+
function commentAboveFieldHasCodegenName(comments, field) {
30+
for (const comment of comments) {
31+
if (comment.loc.end.line === field.loc.start.line-1) { // checking the comment is one line above the field
32+
if (comment.value && comment.value.includes("@codegen_name")){
33+
const newName = comment.value.replaceAll("*","").trim().split(" ")[1]
34+
if (newName !== field.name) return true
35+
}
36+
}
37+
}
38+
return false
39+
}
40+
41+
export default createRule({
42+
name: 'no-same-name-as-enclosing-type',
43+
create(context) {
44+
return {
45+
ClassDeclaration(node) {
46+
if (!node.id || !node.id.name) {
47+
return; // anonymous class - nothing to check
48+
}
49+
const services = ESLintUtils.getParserServices(context)
50+
const tsClass = services.esTreeNodeToTSNodeMap.get(node);
51+
if (!tsClass || !tsClass.members) {
52+
return; // no fields
53+
}
54+
const className = node.id.name;
55+
for (const element of node.body.body) {
56+
if (element.type === "PropertyDefinition" && element.key.name && element.key.type === "Identifier") {
57+
const fieldName = element.key.name
58+
const field = element.key;
59+
if (String(fieldName).toUpperCase() === className.toUpperCase()) {
60+
const sourceCode = context.getSourceCode();
61+
const commentsInside = sourceCode.getCommentsInside(node)
62+
if (!commentsInside || commentsInside.length === 0 || !commentAboveFieldHasCodegenName(commentsInside, field)) {
63+
context.report({
64+
node,
65+
messageId: 'shouldNotUseClassNameForFieldNames',
66+
data: {
67+
class: className,
68+
suggestion: 'Either change the class name, or if it\'s not possible, use a different codegen name for the field by commenting it with /** @codegen_name different_name **/.'
69+
}
70+
})
71+
}
72+
}
73+
}
74+
}
75+
}
76+
,
77+
}
78+
},
79+
meta:
80+
{
81+
docs: {
82+
description: 'Classes having fields with the same name as the class is breaking for some client libraries.'
83+
}
84+
,
85+
messages: {
86+
shouldNotUseClassNameForFieldNames: 'Class "{{class}}" has invalid fields. {{suggestion}}.'
87+
}
88+
,
89+
type: 'suggestion',
90+
}
91+
,
92+
defaultOptions: []
93+
})
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
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-same-name-as-enclosing-type.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-same-name-as-enclosing-type', rule, {
34+
valid: [
35+
`class MyClass {
36+
field: integer
37+
anotherfield: string
38+
}`,
39+
`class MyClass {
40+
/** @codegen_name new_name **/
41+
myclass: integer
42+
}`
43+
],
44+
invalid: [
45+
{
46+
code: `class MyClass { MyClass: integer }`,
47+
errors: [{ messageId: 'shouldNotUseClassNameForFieldNames' }]
48+
},
49+
{
50+
code: `class MyClass { myclass: integer }`,
51+
errors: [{ messageId: 'shouldNotUseClassNameForFieldNames' }]
52+
},
53+
{
54+
code: `class MyClass {
55+
field: integer
56+
anotherfield: string
57+
myclass: boolean
58+
}`,
59+
errors: [{ messageId: 'shouldNotUseClassNameForFieldNames' }]
60+
},
61+
{
62+
code: `class MyClass {
63+
field: integer
64+
anotherfield: string
65+
/** @codegen_name myclass **/
66+
myclass: boolean
67+
}`,
68+
errors: [{ messageId: 'shouldNotUseClassNameForFieldNames' }]
69+
}
70+
],
71+
})

0 commit comments

Comments
 (0)