From 623f310fc4b405b30459b9be0f543969ba1ab0d6 Mon Sep 17 00:00:00 2001 From: Matt Devy Date: Fri, 14 Nov 2025 15:47:19 +0000 Subject: [PATCH 1/2] chore: add check-variants rule to ESLint plugin --- specification/eslint.config.js | 3 +- validator/README.md | 27 +++--- validator/eslint-plugin-es-spec.js | 4 +- validator/rules/check-variants.js | 122 ++++++++++++++++++++++++++ validator/test/check-variants.test.js | 74 ++++++++++++++++ 5 files changed, 215 insertions(+), 15 deletions(-) create mode 100644 validator/rules/check-variants.js create mode 100644 validator/test/check-variants.test.js diff --git a/specification/eslint.config.js b/specification/eslint.config.js index cf921da0f4..29563ae1ba 100644 --- a/specification/eslint.config.js +++ b/specification/eslint.config.js @@ -95,6 +95,7 @@ export default defineConfig({ } } ], - 'es-spec-validator/no-all-string-literal-unions': 'error' + 'es-spec-validator/no-all-string-literal-unions': 'error', + 'es-spec-validator/check-variants': 'error' } }) diff --git a/validator/README.md b/validator/README.md index db473da611..113dc8426b 100644 --- a/validator/README.md +++ b/validator/README.md @@ -5,19 +5,20 @@ It is configured [in the specification directory](../specification/eslint.config ## Rules -| Name | Description | -|---------------------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| -| `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 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. | -| `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. | -| `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. | -| `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. | -| `no-duplicate-type-names` | All types must be unique across class and enum definitions. | -| `no-all-string-literal-unions | Unions consisting entirely of string literals (e.g., `"green" \| "yellow" \| "red"`) are not allowed, use enums instead. | | +| Name | Description | +|---------------------------------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| `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 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. | +| `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. | +| `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. | +| `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. | +| `no-duplicate-type-names` | All types must be unique across class and enum definitions. | +| `no-all-string-literal-unions | Unions consisting entirely of string literals (e.g., `"green" \| "yellow" \| "red"`) are not allowed, use enums instead. | +| `check-variants` | Checks that `@variants` are valid for specific types. `Request` and `Response`, no variants allowed; Type Aliases, `internal`, `typed_keys_quirk` or `untagged`; Interface classes, `container` | ## Usage diff --git a/validator/eslint-plugin-es-spec.js b/validator/eslint-plugin-es-spec.js index 15028e809d..4e92fb4bb3 100644 --- a/validator/eslint-plugin-es-spec.js +++ b/validator/eslint-plugin-es-spec.js @@ -27,6 +27,7 @@ import noInlineUnions from './rules/no-inline-unions.js' import preferTaggedVariants from './rules/prefer-tagged-variants.js' import noDuplicateTypeNames from './rules/no-duplicate-type-names.js' import noAllStringLiteralUnions from './rules/no-all-string-literal-unions.js' +import checkVariants from './rules/check-variants.js' export default { rules: { @@ -40,6 +41,7 @@ export default { 'no-inline-unions': noInlineUnions, 'prefer-tagged-variants': preferTaggedVariants, 'no-duplicate-type-names': noDuplicateTypeNames, - 'no-all-string-literal-unions': noAllStringLiteralUnions + 'no-all-string-literal-unions': noAllStringLiteralUnions, + 'check-variants': checkVariants } } diff --git a/validator/rules/check-variants.js b/validator/rules/check-variants.js new file mode 100644 index 0000000000..77d26f8226 --- /dev/null +++ b/validator/rules/check-variants.js @@ -0,0 +1,122 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { ESLintUtils } from '@typescript-eslint/utils' + +export const checkVariants = ESLintUtils.RuleCreator.withoutDocs({ + name: 'check-variants', + meta: { + type: 'problem', + docs: { + description: 'Checks that variants are allowed on a given type.', + recommended: 'error' + }, + messages: { + variantsOnRequestOrResponse: + 'Variants are not allowed on {{ className }} classes.', + interfaceWithNonContainerVariants: + "Interface '{{ interfaceName }}' has '@variants {{ variantValue }}' but only 'container' is allowed for interfaces.", + invalidVariantsTag: + "Type alias '{{ typeName }}' has invalid '@variants {{ variantValue }}'. Must start with: {{ allowedValues }}." + } + }, + defaultOptions: [], + create(context) { + const sourceCode = context.sourceCode || context.getSourceCode() + + const getJsDocTags = (node) => { + const targetNode = + node.parent?.type === 'ExportNamedDeclaration' ? node.parent : node + const comments = sourceCode.getCommentsBefore(targetNode) + + const jsDocComment = comments + ?.filter( + (comment) => comment.type === 'Block' && comment.value.startsWith('*') + ) + .pop() + + if (!jsDocComment) return [] + + return jsDocComment.value + .split('\n') + .map((line) => line.trim().match(/^\*?\s*@(\w+)(?:\s+(.*))?$/)) + .filter(Boolean) + .map(([, tag, value]) => ({ tag, value: value?.trim() || '' })) + } + + const isRequestOrResponse = (name) => + name === 'Request' || name === 'Response' + + const hasVariantsTag = (tags) => tags.some(({ tag }) => tag === 'variants') + + return { + 'TSInterfaceDeclaration, ClassDeclaration'(node) { + const jsDocTags = getJsDocTags(node) + if (isRequestOrResponse(node.id.name)) { + if (hasVariantsTag(jsDocTags)) { + context.report({ + node, + messageId: 'variantsOnRequestOrResponse', + data: { className: node.id.name } + }) + } + return + } + + const nonContainerVariant = jsDocTags.find( + ({ tag, value }) => tag === 'variants' && value !== 'container' + ) + if (nonContainerVariant) { + context.report({ + node, + messageId: 'interfaceWithNonContainerVariants', + data: { + interfaceName: node.id.name, + variantValue: nonContainerVariant.value + } + }) + return + } + }, + TSTypeAliasDeclaration(node) { + const jsDocTags = getJsDocTags(node) + const allowedVariants = ['internal', 'typed_keys_quirk', 'untagged'] + + const invalidVariant = jsDocTags.find( + ({ tag, value }) => + tag === 'variants' && + !allowedVariants.some((allowed) => value.startsWith(allowed)) + ) + + if (invalidVariant) { + context.report({ + node, + messageId: 'invalidVariantsTag', + data: { + typeName: node.id.name, + variantValue: invalidVariant.value, + allowedValues: allowedVariants.join(', ') + } + }) + } + } + } + } +}) + +export default checkVariants diff --git a/validator/test/check-variants.test.js b/validator/test/check-variants.test.js new file mode 100644 index 0000000000..8aad7e3f63 --- /dev/null +++ b/validator/test/check-variants.test.js @@ -0,0 +1,74 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { RuleTester } from '@typescript-eslint/rule-tester' +import rule from '../rules/check-variants.js' + +const ruleTester = new RuleTester({ + languageOptions: { + parserOptions: { + projectService: { + allowDefaultProject: ['*.ts*'] + }, + tsconfigRootDir: import.meta.dirname + } + } +}) + +ruleTester.run('check-variants', rule, { + valid: [ + { + name: 'not Request or Response', + code: `/** @variants container */ +export class MyClass { + body: MyContainer +}` + }, + { + name: 'internal tag on type alias', + code: `/** @variants internal tag='type' */ +export type MyType = string | number` + } + ], + invalid: [ + { + name: 'Request has variants tag', + code: `/** @variants container */ +export class Request {}`, + errors: [{ messageId: 'variantsOnRequestOrResponse' }] + }, + { + name: 'Response has variants tag', + code: `/** @variants container */ +export class Response {}`, + errors: [{ messageId: 'variantsOnRequestOrResponse' }] + }, + { + name: 'Interface has non-container variants tag', + code: `/** @variants internal */ +export class RankContainer {}`, + errors: [{ messageId: 'interfaceWithNonContainerVariants' }] + }, + { + name: 'Type alias has invalid variants tag', + code: `/** @variants invalid */ +export type MyType = string | number`, + errors: [{ messageId: 'invalidVariantsTag' }] + } + ] +}) From e4145758a0cc6323ec38c0f487558a2a3e91ba64 Mon Sep 17 00:00:00 2001 From: Matt Devy Date: Mon, 17 Nov 2025 14:10:46 +0000 Subject: [PATCH 2/2] chore: remove rule and extend existing rule --- specification/eslint.config.js | 1 - validator/README.md | 3 +- validator/eslint-plugin-es-spec.js | 2 - validator/rules/check-variants.js | 122 ------------------ validator/rules/no-variants-on-responses.js | 119 +++++++++++++---- validator/test/check-variants.test.js | 74 ----------- .../test/no-variants-on-responses.test.js | 53 ++++++-- 7 files changed, 141 insertions(+), 233 deletions(-) delete mode 100644 validator/rules/check-variants.js delete mode 100644 validator/test/check-variants.test.js diff --git a/specification/eslint.config.js b/specification/eslint.config.js index 0de8fb594b..36aa810de6 100644 --- a/specification/eslint.config.js +++ b/specification/eslint.config.js @@ -96,7 +96,6 @@ export default defineConfig({ } ], 'es-spec-validator/no-all-string-literal-unions': 'error', - 'es-spec-validator/check-variants': 'error', 'es-spec-validator/jsdoc-endpoint-check': [ 'error', { diff --git a/validator/README.md b/validator/README.md index 3738280dff..db6dc8b10e 100644 --- a/validator/README.md +++ b/validator/README.md @@ -13,12 +13,11 @@ It is configured [in the specification directory](../specification/eslint.config | `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. | -| `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. | +| `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. | | `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. | | `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. | | `no-duplicate-type-names` | All types must be unique across class and enum definitions. | | `no-all-string-literal-unions | Unions consisting entirely of string literals (e.g., `"green" \| "yellow" \| "red"`) are not allowed, use enums instead. | -| `check-variants` | Checks that `@variants` are valid for specific types. `Request` and `Response`, no variants allowed; Type Aliases, `internal`, `typed_keys_quirk` or `untagged`; Interface classes, `container` | | `jsdoc-endpoint-check` | Validates JSDoc on endpoints in the specification. Ensuring consistent formatting. Some errors can be fixed with `--fix`. | ## Usage diff --git a/validator/eslint-plugin-es-spec.js b/validator/eslint-plugin-es-spec.js index b9b0ad2721..e1a768051e 100644 --- a/validator/eslint-plugin-es-spec.js +++ b/validator/eslint-plugin-es-spec.js @@ -27,7 +27,6 @@ import noInlineUnions from './rules/no-inline-unions.js' import preferTaggedVariants from './rules/prefer-tagged-variants.js' import noDuplicateTypeNames from './rules/no-duplicate-type-names.js' import noAllStringLiteralUnions from './rules/no-all-string-literal-unions.js' -import checkVariants from './rules/check-variants.js' import jsdocEndpointCheck from './rules/jsdoc-endpoint-check.js' export default { @@ -43,7 +42,6 @@ export default { 'prefer-tagged-variants': preferTaggedVariants, 'no-duplicate-type-names': noDuplicateTypeNames, 'no-all-string-literal-unions': noAllStringLiteralUnions, - 'check-variants': checkVariants, 'jsdoc-endpoint-check': jsdocEndpointCheck } } diff --git a/validator/rules/check-variants.js b/validator/rules/check-variants.js deleted file mode 100644 index 77d26f8226..0000000000 --- a/validator/rules/check-variants.js +++ /dev/null @@ -1,122 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { ESLintUtils } from '@typescript-eslint/utils' - -export const checkVariants = ESLintUtils.RuleCreator.withoutDocs({ - name: 'check-variants', - meta: { - type: 'problem', - docs: { - description: 'Checks that variants are allowed on a given type.', - recommended: 'error' - }, - messages: { - variantsOnRequestOrResponse: - 'Variants are not allowed on {{ className }} classes.', - interfaceWithNonContainerVariants: - "Interface '{{ interfaceName }}' has '@variants {{ variantValue }}' but only 'container' is allowed for interfaces.", - invalidVariantsTag: - "Type alias '{{ typeName }}' has invalid '@variants {{ variantValue }}'. Must start with: {{ allowedValues }}." - } - }, - defaultOptions: [], - create(context) { - const sourceCode = context.sourceCode || context.getSourceCode() - - const getJsDocTags = (node) => { - const targetNode = - node.parent?.type === 'ExportNamedDeclaration' ? node.parent : node - const comments = sourceCode.getCommentsBefore(targetNode) - - const jsDocComment = comments - ?.filter( - (comment) => comment.type === 'Block' && comment.value.startsWith('*') - ) - .pop() - - if (!jsDocComment) return [] - - return jsDocComment.value - .split('\n') - .map((line) => line.trim().match(/^\*?\s*@(\w+)(?:\s+(.*))?$/)) - .filter(Boolean) - .map(([, tag, value]) => ({ tag, value: value?.trim() || '' })) - } - - const isRequestOrResponse = (name) => - name === 'Request' || name === 'Response' - - const hasVariantsTag = (tags) => tags.some(({ tag }) => tag === 'variants') - - return { - 'TSInterfaceDeclaration, ClassDeclaration'(node) { - const jsDocTags = getJsDocTags(node) - if (isRequestOrResponse(node.id.name)) { - if (hasVariantsTag(jsDocTags)) { - context.report({ - node, - messageId: 'variantsOnRequestOrResponse', - data: { className: node.id.name } - }) - } - return - } - - const nonContainerVariant = jsDocTags.find( - ({ tag, value }) => tag === 'variants' && value !== 'container' - ) - if (nonContainerVariant) { - context.report({ - node, - messageId: 'interfaceWithNonContainerVariants', - data: { - interfaceName: node.id.name, - variantValue: nonContainerVariant.value - } - }) - return - } - }, - TSTypeAliasDeclaration(node) { - const jsDocTags = getJsDocTags(node) - const allowedVariants = ['internal', 'typed_keys_quirk', 'untagged'] - - const invalidVariant = jsDocTags.find( - ({ tag, value }) => - tag === 'variants' && - !allowedVariants.some((allowed) => value.startsWith(allowed)) - ) - - if (invalidVariant) { - context.report({ - node, - messageId: 'invalidVariantsTag', - data: { - typeName: node.id.name, - variantValue: invalidVariant.value, - allowedValues: allowedVariants.join(', ') - } - }) - } - } - } - } -}) - -export default checkVariants diff --git a/validator/rules/no-variants-on-responses.js b/validator/rules/no-variants-on-responses.js index ff6207b4e9..e834eda7f9 100644 --- a/validator/rules/no-variants-on-responses.js +++ b/validator/rules/no-variants-on-responses.js @@ -16,51 +16,124 @@ * specific language governing permissions and limitations * under the License. */ -import { ESLintUtils } from '@typescript-eslint/utils'; +import { ESLintUtils } from '@typescript-eslint/utils' -const createRule = ESLintUtils.RuleCreator(name => `https://example.com/rule/${name}`) +const createRule = ESLintUtils.RuleCreator( + (name) => `https://example.com/rule/${name}` +) export default createRule({ name: 'no-variants-on-responses', create(context) { + const sourceCode = context.sourceCode || context.getSourceCode() + + const getJsDocTags = (node) => { + const targetNode = + node.parent?.type === 'ExportNamedDeclaration' ? node.parent : node + const comments = sourceCode.getCommentsBefore(targetNode) + + const jsDocComment = comments + ?.filter( + (comment) => comment.type === 'Block' && comment.value.startsWith('*') + ) + .pop() + + if (!jsDocComment) return [] + + return jsDocComment.value + .split('\n') + .map((line) => line.trim().match(/^\*?\s*@(\w+)(?:\s+(.*))?$/)) + .filter(Boolean) + .map(([, tag, value]) => ({ tag, value: value?.trim() || '' })) + } + return { - ClassDeclaration(node) { - const className = node.id?.name; - if (className !== 'Response' && className !== 'Request') { - return; + 'TSInterfaceDeclaration, ClassDeclaration'(node) { + const className = node.id?.name + if (className === 'Response' || className === 'Request') { + const fullText = sourceCode.text + + const nodeStart = node.range[0] + const textBefore = fullText.substring( + Math.max(0, nodeStart - 200), + nodeStart + ) + + const hasVariantsTag = + /@variants\s+(container|internal|external|untagged)/.test( + textBefore + ) + + if (hasVariantsTag) { + context.report({ + node, + messageId: 'noVariantsOnResponses', + data: { + className, + suggestion: + 'Move @variants to a separate body class and use value_body pattern with @codegen_name. See SearchResponse for an example.' + } + }) + } + return } - - const sourceCode = context.sourceCode || context.getSourceCode(); - const fullText = sourceCode.text; - - const nodeStart = node.range[0]; - const textBefore = fullText.substring(Math.max(0, nodeStart - 200), nodeStart); - - const hasVariantsTag = /@variants\s+(container|internal|external|untagged)/.test(textBefore); - - if (hasVariantsTag) { + + const jsDocTags = getJsDocTags(node) + + const nonContainerVariant = jsDocTags.find( + ({ tag, value }) => tag === 'variants' && value !== 'container' + ) + if (nonContainerVariant) { context.report({ node, - messageId: 'noVariantsOnResponses', + messageId: 'interfaceWithNonContainerVariants', data: { - className, - suggestion: 'Move @variants to a separate body class and use value_body pattern with @codegen_name. See SearchResponse for an example.' + interfaceName: node.id.name, + variantValue: nonContainerVariant.value } - }); + }) + return } }, + TSTypeAliasDeclaration(node) { + const jsDocTags = getJsDocTags(node) + const allowedVariants = ['internal', 'typed_keys_quirk', 'untagged'] + + const invalidVariant = jsDocTags.find( + ({ tag, value }) => + tag === 'variants' && + !allowedVariants.some((allowed) => value.startsWith(allowed)) + ) + + if (invalidVariant) { + context.report({ + node, + messageId: 'invalidVariantsTag', + data: { + typeName: node.id.name, + variantValue: invalidVariant.value, + allowedValues: allowedVariants.join(', ') + } + }) + } + } } }, meta: { docs: { - description: '@variants is only supported on Interface types, not on Request or Response classes. Use value_body pattern instead.', + description: + '@variants is only supported on Interface types, not on Request or Response classes. Use value_body pattern instead.' }, messages: { - noVariantsOnResponses: '@variants on {{className}} is not supported in metamodel. {{suggestion}}' + noVariantsOnResponses: + '@variants on {{className}} is not supported in metamodel. {{suggestion}}', + interfaceWithNonContainerVariants: + "Interface '{{ interfaceName }}' has '@variants {{ variantValue }}' but only 'container' is allowed for interfaces.", + invalidVariantsTag: + "Type alias '{{ typeName }}' has invalid '@variants {{ variantValue }}'. Must start with: {{ allowedValues }}." }, type: 'problem', schema: [] }, defaultOptions: [] }) - diff --git a/validator/test/check-variants.test.js b/validator/test/check-variants.test.js deleted file mode 100644 index 8aad7e3f63..0000000000 --- a/validator/test/check-variants.test.js +++ /dev/null @@ -1,74 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { RuleTester } from '@typescript-eslint/rule-tester' -import rule from '../rules/check-variants.js' - -const ruleTester = new RuleTester({ - languageOptions: { - parserOptions: { - projectService: { - allowDefaultProject: ['*.ts*'] - }, - tsconfigRootDir: import.meta.dirname - } - } -}) - -ruleTester.run('check-variants', rule, { - valid: [ - { - name: 'not Request or Response', - code: `/** @variants container */ -export class MyClass { - body: MyContainer -}` - }, - { - name: 'internal tag on type alias', - code: `/** @variants internal tag='type' */ -export type MyType = string | number` - } - ], - invalid: [ - { - name: 'Request has variants tag', - code: `/** @variants container */ -export class Request {}`, - errors: [{ messageId: 'variantsOnRequestOrResponse' }] - }, - { - name: 'Response has variants tag', - code: `/** @variants container */ -export class Response {}`, - errors: [{ messageId: 'variantsOnRequestOrResponse' }] - }, - { - name: 'Interface has non-container variants tag', - code: `/** @variants internal */ -export class RankContainer {}`, - errors: [{ messageId: 'interfaceWithNonContainerVariants' }] - }, - { - name: 'Type alias has invalid variants tag', - code: `/** @variants invalid */ -export type MyType = string | number`, - errors: [{ messageId: 'invalidVariantsTag' }] - } - ] -}) diff --git a/validator/test/no-variants-on-responses.test.js b/validator/test/no-variants-on-responses.test.js index a1bbc0cae2..ddd99c5123 100644 --- a/validator/test/no-variants-on-responses.test.js +++ b/validator/test/no-variants-on-responses.test.js @@ -23,11 +23,11 @@ const ruleTester = new RuleTester({ languageOptions: { parserOptions: { projectService: { - allowDefaultProject: ['*.ts*'], + allowDefaultProject: ['*.ts*'] }, - tsconfigRootDir: import.meta.dirname, - }, - }, + tsconfigRootDir: import.meta.dirname + } + } }) ruleTester.run('no-variants-on-responses', rule, { @@ -42,7 +42,7 @@ ruleTester.run('no-variants-on-responses', rule, { classification?: ClassificationSummary regression?: RegressionSummary }`, - + `export class Request { path_parts: {} query_parameters: {} @@ -51,19 +51,31 @@ ruleTester.run('no-variants-on-responses', rule, { /** @variants internal tag='type' */ export type RequestBody = TypeA | TypeB`, - + `/** @variants container */ export interface MyContainer { option_a?: OptionA option_b?: OptionB }`, - + `export class Response { body: { count: integer results: string[] } }`, + { + name: 'not Request or Response', + code: `/** @variants container */ +export class MyClass { + body: MyContainer +}` + }, + { + name: 'internal tag on type alias', + code: `/** @variants internal tag='type' */ +export type MyType = string | number` + } ], invalid: [ { @@ -92,6 +104,29 @@ ruleTester.run('no-variants-on-responses', rule, { }`, errors: [{ messageId: 'noVariantsOnResponses' }] }, - ], + { + name: 'Request has variants tag', + code: `/** @variants container */ +export class Request {}`, + errors: [{ messageId: 'noVariantsOnResponses' }] + }, + { + name: 'Response has variants tag', + code: `/** @variants container */ +export class Response {}`, + errors: [{ messageId: 'noVariantsOnResponses' }] + }, + { + name: 'Interface has non-container variants tag', + code: `/** @variants internal */ +export class RankContainer {}`, + errors: [{ messageId: 'interfaceWithNonContainerVariants' }] + }, + { + name: 'Type alias has invalid variants tag', + code: `/** @variants invalid */ +export type MyType = string | number`, + errors: [{ messageId: 'invalidVariantsTag' }] + } + ] }) -