From c9d7e792f2f2c5a0af92d1db6cab7b6f1ce64c2c Mon Sep 17 00:00:00 2001 From: Jeremy Fiel <32110157+jeremyfiel@users.noreply.github.com> Date: Thu, 30 Jan 2025 19:41:39 +0000 Subject: [PATCH] feat(rules): update `operation-tag-defined` rule check for `tags` existence in `Operation` node` --- .changeset/tasty-cougars-dress.md | 6 ++ .../__tests__/operation-tag-defined.test.ts | 68 +++++++++++++++++++ .../src/rules/common/operation-tag-defined.ts | 7 ++ 3 files changed, 81 insertions(+) create mode 100644 .changeset/tasty-cougars-dress.md create mode 100644 packages/core/src/rules/common/__tests__/operation-tag-defined.test.ts diff --git a/.changeset/tasty-cougars-dress.md b/.changeset/tasty-cougars-dress.md new file mode 100644 index 0000000000..b260c55635 --- /dev/null +++ b/.changeset/tasty-cougars-dress.md @@ -0,0 +1,6 @@ +--- +"@redocly/openapi-core": patch +"@redocly/cli": patch +--- + +Updated `operation-tag-defined` configurable rule. diff --git a/packages/core/src/rules/common/__tests__/operation-tag-defined.test.ts b/packages/core/src/rules/common/__tests__/operation-tag-defined.test.ts new file mode 100644 index 0000000000..aedb018562 --- /dev/null +++ b/packages/core/src/rules/common/__tests__/operation-tag-defined.test.ts @@ -0,0 +1,68 @@ +import { outdent } from 'outdent'; +import { lintDocument } from '../../../lint'; +import { parseYamlToDocument, replaceSourceWithRef, makeConfig } from '../../../../__tests__/utils'; +import { BaseResolver } from '../../../resolve'; + +describe('Oas3 operation-tag-defined', () => { + it('should not report on operation object if at least one tag is defined', async () => { + const document = parseYamlToDocument( + outdent` + openapi: 3.0.4 + tags: + - name: a + paths: + /some: + get: + tags: + - a + `, + 'foobar.yaml' + ); + + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ rules: { 'operation-tag-defined': 'error' } }), + }); + + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(`[]`); + }); + + it('should report on operation object if no tags are defined', async () => { + const document = parseYamlToDocument( + outdent` + openapi: 3.0.4 + tags: + - name: a + paths: + /some: + get: + `, + 'foobar.yaml' + ); + + const results = await lintDocument({ + externalRefResolver: new BaseResolver(), + document, + config: await makeConfig({ rules: { 'operation-tag-defined': 'error' } }), + }); + + expect(replaceSourceWithRef(results)).toMatchInlineSnapshot(` + [ + { + "location": [ + { + "pointer": "#/paths/~1some/get/tags", + "reportOnKey": true, + "source": "foobar.yaml", + }, + ], + "message": "Operation tags should be defined", + "ruleId": "operation-tag-defined", + "severity": "error", + "suggest": [], + }, + ] + `); + }); +}); diff --git a/packages/core/src/rules/common/operation-tag-defined.ts b/packages/core/src/rules/common/operation-tag-defined.ts index 804381aea0..5fa045fb20 100644 --- a/packages/core/src/rules/common/operation-tag-defined.ts +++ b/packages/core/src/rules/common/operation-tag-defined.ts @@ -11,6 +11,13 @@ export const OperationTagDefined: Oas3Rule | Oas2Rule = () => { definedTags = new Set((root.tags ?? []).map((t) => t.name)); }, Operation(operation: Oas2Operation | Oas3Operation, { report, location }: UserContext) { + if (!operation?.hasOwnProperty('tags')) { + report({ + message: `Operation tags should be defined`, + location: location.child('tags').key(), + }); + return; + } if (operation.tags) { for (let i = 0; i < operation.tags.length; i++) { if (!definedTags.has(operation.tags[i])) {