Skip to content

Commit 803909b

Browse files
l-trottapquentin
andauthored
Backport 5622 to 9.2 (#5812)
* Fix variants on Response class - use value_body pattern (#5622) Co-authored-by: Quentin Pradet <[email protected]> * fix merge --------- Co-authored-by: Quentin Pradet <[email protected]>
1 parent eb74a61 commit 803909b

File tree

6 files changed

+187
-17
lines changed

6 files changed

+187
-17
lines changed

specification/eslint.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ export default defineConfig({
3737
'es-spec-validator/invalid-node-types': 'error',
3838
'es-spec-validator/no-generic-number': 'error',
3939
'es-spec-validator/request-must-have-urls': 'error',
40+
'es-spec-validator/no-variants-on-responses': 'error',
4041
'es-spec-validator/jsdoc-endpoint-check': [
4142
'error',
4243
{

specification/ml/evaluate_data_frame/MlEvaluateDataFrameResponse.ts

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -23,22 +23,25 @@ import {
2323
DataframeRegressionSummary
2424
} from './types'
2525

26-
/** @variants container */
2726
export class Response {
28-
body: {
29-
/**
30-
* Evaluation results for a classification analysis.
31-
* It outputs a prediction that identifies to which of the classes each document belongs.
32-
*/
33-
classification?: DataframeClassificationSummary
34-
/**
35-
* Evaluation results for an outlier detection analysis.
36-
* It outputs the probability that each document is an outlier.
37-
*/
38-
outlier_detection?: DataframeOutlierDetectionSummary
39-
/**
40-
* Evaluation results for a regression analysis which outputs a prediction of values.
41-
*/
42-
regression?: DataframeRegressionSummary
43-
}
27+
/** @codegen_name result */
28+
body: ResponseBody
29+
}
30+
31+
/** @variants container */
32+
export class ResponseBody {
33+
/**
34+
* Evaluation results for a classification analysis.
35+
* It outputs a prediction that identifies to which of the classes each document belongs.
36+
*/
37+
classification?: DataframeClassificationSummary
38+
/**
39+
* Evaluation results for an outlier detection analysis.
40+
* It outputs the probability that each document is an outlier.
41+
*/
42+
outlier_detection?: DataframeOutlierDetectionSummary
43+
/**
44+
* Evaluation results for a regression analysis which outputs a prediction of values.
45+
*/
46+
regression?: DataframeRegressionSummary
4447
}

validator/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ It is configured [in the specification directory](../specification/eslint.config
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. |
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. |
1617
| `jsdoc-endpoint-check` | Validates JSDoc on endpoints in the specification. Ensuring consistent formatting. Some errors can be fixed with `--fix`. |
1718

1819
## Usage

validator/eslint-plugin-es-spec.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import noNativeTypes from './rules/no-native-types.js'
2222
import invalidNodeTypes from './rules/invalid-node-types.js'
2323
import noGenericNumber from './rules/no-generic-number.js'
2424
import requestMustHaveUrls from './rules/request-must-have-urls.js'
25+
import noVariantsOnResponses from './rules/no-variants-on-responses.js'
2526
import jsdocEndpointCheck from './rules/jsdoc-endpoint-check.js'
2627

2728
export default {
@@ -32,6 +33,7 @@ export default {
3233
'invalid-node-types': invalidNodeTypes,
3334
'no-generic-number': noGenericNumber,
3435
'request-must-have-urls': requestMustHaveUrls,
36+
'no-variants-on-responses': noVariantsOnResponses,
3537
'jsdoc-endpoint-check': jsdocEndpointCheck
3638
}
3739
}
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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+
export default createRule({
24+
name: 'no-variants-on-responses',
25+
create(context) {
26+
return {
27+
ClassDeclaration(node) {
28+
const className = node.id?.name;
29+
if (className !== 'Response' && className !== 'Request') {
30+
return;
31+
}
32+
33+
const sourceCode = context.sourceCode || context.getSourceCode();
34+
const fullText = sourceCode.text;
35+
36+
const nodeStart = node.range[0];
37+
const textBefore = fullText.substring(Math.max(0, nodeStart - 200), nodeStart);
38+
39+
const hasVariantsTag = /@variants\s+(container|internal|external|untagged)/.test(textBefore);
40+
41+
if (hasVariantsTag) {
42+
context.report({
43+
node,
44+
messageId: 'noVariantsOnResponses',
45+
data: {
46+
className,
47+
suggestion: 'Move @variants to a separate body class and use value_body pattern with @codegen_name. See SearchResponse for an example.'
48+
}
49+
});
50+
}
51+
},
52+
}
53+
},
54+
meta: {
55+
docs: {
56+
description: '@variants is only supported on Interface types, not on Request or Response classes. Use value_body pattern instead.',
57+
},
58+
messages: {
59+
noVariantsOnResponses: '@variants on {{className}} is not supported in metamodel. {{suggestion}}'
60+
},
61+
type: 'problem',
62+
schema: []
63+
},
64+
defaultOptions: []
65+
})
66+
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
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-variants-on-responses.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-variants-on-responses', rule, {
34+
valid: [
35+
`export class Response {
36+
/** @codegen_name result */
37+
body: ResponseBody
38+
}
39+
40+
/** @variants container */
41+
export class ResponseBody {
42+
classification?: ClassificationSummary
43+
regression?: RegressionSummary
44+
}`,
45+
46+
`export class Request {
47+
path_parts: {}
48+
query_parameters: {}
49+
body: RequestBody
50+
}
51+
52+
/** @variants internal tag='type' */
53+
export type RequestBody = TypeA | TypeB`,
54+
55+
`/** @variants container */
56+
export interface MyContainer {
57+
option_a?: OptionA
58+
option_b?: OptionB
59+
}`,
60+
61+
`export class Response {
62+
body: {
63+
count: integer
64+
results: string[]
65+
}
66+
}`,
67+
],
68+
invalid: [
69+
{
70+
code: `/** @variants container */
71+
export class Response {
72+
body: {
73+
classification?: ClassificationSummary
74+
regression?: RegressionSummary
75+
}
76+
}`,
77+
errors: [{ messageId: 'noVariantsOnResponses' }]
78+
},
79+
{
80+
code: `/** @variants internal tag='type' */
81+
export class Request {
82+
path_parts: {}
83+
query_parameters: {}
84+
body: SomeType
85+
}`,
86+
errors: [{ messageId: 'noVariantsOnResponses' }]
87+
},
88+
{
89+
code: `/** @variants container */
90+
export class Response {
91+
body: ResponseData
92+
}`,
93+
errors: [{ messageId: 'noVariantsOnResponses' }]
94+
},
95+
],
96+
})
97+

0 commit comments

Comments
 (0)