From 55c2dcf298382071c0928eff84dc4cfafb3bf4c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Tue, 23 Jun 2026 15:04:13 +0200 Subject: [PATCH 01/19] feat: add documentation link to result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../validator/src/cli-validator/utils/print-results.js | 1 + .../src/markdown-report/tables/rule-violation-details.js | 9 +++++---- packages/validator/src/schemas/results-object.yaml | 3 +++ packages/validator/src/spectral/index.js | 6 ++++++ 4 files changed, 15 insertions(+), 4 deletions(-) diff --git a/packages/validator/src/cli-validator/utils/print-results.js b/packages/validator/src/cli-validator/utils/print-results.js index daf5ec3e9..1703075c8 100644 --- a/packages/validator/src/cli-validator/utils/print-results.js +++ b/packages/validator/src/cli-validator/utils/print-results.js @@ -38,6 +38,7 @@ module.exports = function print(context, results) { console.log(chalk[color](` Message : ${result.message}`)); console.log(chalk[color](` Rule : ${result.rule}`)); console.log(chalk[color](` Path : ${result.path.join('.')}`)); + console.log(chalk[color](` DocLink : ${result.docLink}`)); console.log(chalk[color](` Line : ${result.line}`)); console.log(''); }); diff --git a/packages/validator/src/markdown-report/tables/rule-violation-details.js b/packages/validator/src/markdown-report/tables/rule-violation-details.js index f73051dfa..1be0ebee4 100644 --- a/packages/validator/src/markdown-report/tables/rule-violation-details.js +++ b/packages/validator/src/markdown-report/tables/rule-violation-details.js @@ -10,16 +10,17 @@ function getTable({ error, warning }) { 'Rule', 'Message', 'Path', + 'docLink', 'Line', 'Severity' ); - error.results.forEach(({ message, path, rule, line }) => { - table.addRow(rule, message, path.join('.'), line, 'error'); + error.results.forEach(({ message, path, rule, line, docLink }) => { + table.addRow(rule, message, path.join('.'), docLink, line, 'error'); }); - warning.results.forEach(({ message, path, rule, line }) => { - table.addRow(rule, message, path.join('.'), line, 'warning'); + warning.results.forEach(({ message, path, rule, line, docLink }) => { + table.addRow(rule, message, path.join('.'), docLink, line, 'warning'); }); return table.render(); diff --git a/packages/validator/src/schemas/results-object.yaml b/packages/validator/src/schemas/results-object.yaml index d398b0094..0f2065fe2 100644 --- a/packages/validator/src/schemas/results-object.yaml +++ b/packages/validator/src/schemas/results-object.yaml @@ -139,6 +139,9 @@ $defs: rule: type: string description: The rule identifier in the Spectral ruleset + docLink: + type: string + description: The documentation link to the problem discovered in the API line: type: integer description: The line number in the original file that the problem diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index 8b91af070..6ddf4e59e 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -89,6 +89,7 @@ function convertResults(spectralResults, { config, logger }) { message: r.message, path: r.path, rule: r.code, + docLink: getDocumentationURL(r.code), line: r.range.start.line + 1, }); @@ -233,3 +234,8 @@ function convertSpectralSeverity(s) { const mapping = { 0: 'error', 1: 'warning', 2: 'info', 3: 'hint' }; return mapping[s]; } + +function getDocumentationURL(ruleCode) { + const baseUrl = 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md'; + return `${baseUrl}#${ruleCode}`; +} From 4570692ec9ade9955ed4589ad5286a6171990bf3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Wed, 24 Jun 2026 13:36:40 +0200 Subject: [PATCH 02/19] test: extend tests with url in result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../tests/expected-output.test.js | 46 ++++++++++--------- .../tables/rule-violation-details.test.js | 10 ++-- .../test/test-utils/mock-json-output.json | 3 ++ 3 files changed, 33 insertions(+), 26 deletions(-) diff --git a/packages/validator/test/cli-validator/tests/expected-output.test.js b/packages/validator/test/cli-validator/tests/expected-output.test.js index 9d27a4e65..14c201488 100644 --- a/packages/validator/test/cli-validator/tests/expected-output.test.js +++ b/packages/validator/test/cli-validator/tests/expected-output.test.js @@ -87,39 +87,39 @@ describe('Expected output tests', function () { // errors const errorStart = 3; - expect(capturedText[errorStart + 5].match(/\S+/g)[2]).toEqual('52'); - expect(capturedText[errorStart + 10].match(/\S+/g)[2]).toEqual('96'); - expect(capturedText[errorStart + 15].match(/\S+/g)[2]).toEqual('103'); + expect(capturedText[errorStart + 6].match(/\S+/g)[2]).toEqual('52'); + expect(capturedText[errorStart + 12].match(/\S+/g)[2]).toEqual('96'); + expect(capturedText[errorStart + 18].match(/\S+/g)[2]).toEqual('103'); // Specifically verify that the no-$ref-siblings error occurred. // We do this because this rule is inherited from Spectral's oas ruleset, // but we modify the rule definition in ibmoas.js so that it is run // against both OpenAPI 3.0 and 3.1 documents. - expect(capturedText[errorStart + 17].split(':')[1].trim()).toEqual( + expect(capturedText[errorStart + 20].split(':')[1].trim()).toEqual( '$ref must not be placed next to any other properties' ); - expect(capturedText[errorStart + 18].split(':')[1].trim()).toEqual( + expect(capturedText[errorStart + 21].split(':')[1].trim()).toEqual( 'no-$ref-siblings' ); - expect(capturedText[errorStart + 19].split(':')[1].trim()).toEqual( + expect(capturedText[errorStart + 22].split(':')[1].trim()).toEqual( 'components.schemas.Pet.properties.category.description' ); - expect(capturedText[errorStart + 20].match(/\S+/g)[2]).toEqual('184'); + expect(capturedText[errorStart + 24].match(/\S+/g)[2]).toEqual('184'); - // warnings - const warningStart = 25; + // warnings - each warning block is now 6 lines (5 content + 1 blank) + const warningStart = 30; expect(capturedText[warningStart + 5].match(/\S+/g)[2]).toEqual('22'); - expect(capturedText[warningStart + 10].match(/\S+/g)[2]).toEqual('24'); - expect(capturedText[warningStart + 15].match(/\S+/g)[2]).toEqual('40'); - expect(capturedText[warningStart + 20].match(/\S+/g)[2]).toEqual('41'); - expect(capturedText[warningStart + 25].match(/\S+/g)[2]).toEqual('52'); - expect(capturedText[warningStart + 30].match(/\S+/g)[2]).toEqual('56'); - expect(capturedText[warningStart + 35].match(/\S+/g)[2]).toEqual('57'); - expect(capturedText[warningStart + 40].match(/\S+/g)[2]).toEqual('59'); - expect(capturedText[warningStart + 45].match(/\S+/g)[2]).toEqual('61'); - expect(capturedText[warningStart + 50].match(/\S+/g)[2]).toEqual('96'); - // Skip a few, then verify the last one. - expect(capturedText[warningStart + 145].match(/\S+/g)[2]).toEqual( + expect(capturedText[warningStart + 11].match(/\S+/g)[2]).toEqual('24'); + expect(capturedText[warningStart + 17].match(/\S+/g)[2]).toEqual('40'); + expect(capturedText[warningStart + 23].match(/\S+/g)[2]).toEqual('41'); + expect(capturedText[warningStart + 29].match(/\S+/g)[2]).toEqual('52'); + expect(capturedText[warningStart + 35].match(/\S+/g)[2]).toEqual('56'); + expect(capturedText[warningStart + 41].match(/\S+/g)[2]).toEqual('57'); + expect(capturedText[warningStart + 47].match(/\S+/g)[2]).toEqual('59'); + expect(capturedText[warningStart + 53].match(/\S+/g)[2]).toEqual('61'); + expect(capturedText[warningStart + 59].match(/\S+/g)[2]).toEqual('96'); + // Skip a few, then verify the last one (29 warnings × 6 lines = 174 lines) + expect(capturedText[warningStart + 173].match(/\S+/g)[2]).toEqual( '210' ); } @@ -214,7 +214,10 @@ describe('Expected output tests', function () { [] ); - allMessages.forEach(msg => expect(msg).toHaveProperty('rule')); + allMessages.forEach(msg => { + expect(msg).toHaveProperty('rule'); + expect(msg).toHaveProperty('docLink'); + }); } ); @@ -244,6 +247,7 @@ describe('Expected output tests', function () { const warningToCheck = jsonOutput.warning.results[0]; expect(warningToCheck.rule).toEqual('ibm-prefer-token-pagination'); + expect(warningToCheck.docLink).toContain('ibm-prefer-token-pagination'); expect(warningToCheck.path.join('.')).toBe('paths./letters.get'); expect(warningToCheck.line).toEqual(20); } diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index f140dc1ca..108211a27 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -11,16 +11,16 @@ describe('ruleViolationDetails table tests', function () { const tableRows = ruleViolationDetails(validatorResults).split('\n'); expect(tableRows).toHaveLength(5); - expect(tableRows[0]).toBe('| Rule | Message | Path | Line | Severity |'); - expect(tableRows[1]).toBe('| --- | --- | --- | --- | --- |'); + expect(tableRows[0]).toBe('| Rule | Message | Path | docLink | Line | Severity |'); + expect(tableRows[1]).toBe('| --- | --- | --- | --- | --- | --- |'); expect(tableRows[2]).toBe( - '| ibm-no-consecutive-path-parameter-segments | Path contains two or more consecutive path parameter references: /pets/{pet_id}/{id} | paths./pets/{pet_id}/{id} | 84 | error |' + '| ibm-no-consecutive-path-parameter-segments | Path contains two or more consecutive path parameter references: /pets/{pet_id}/{id} | paths./pets/{pet_id}/{id} | https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-no-consecutive-path-parameter-segments | 84 | error |' ); expect(tableRows[3]).toBe( - "| ibm-integer-attributes | Integer schemas should define property 'minimum' | components.schemas.Pet.properties.id | 133 | error |" + "| ibm-integer-attributes | Integer schemas should define property 'minimum' | components.schemas.Pet.properties.id | https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-integer-attributes | 133 | error |" ); expect(tableRows[4]).toBe( - "| ibm-anchored-patterns | A regular expression used in a 'pattern' attribute should be anchored with ^ and $ | components.schemas.Error.properties.message.pattern | 233 | warning |" + "| ibm-anchored-patterns | A regular expression used in a 'pattern' attribute should be anchored with ^ and $ | components.schemas.Error.properties.message.pattern | https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-anchored-patterns | 233 | warning |" ); }); }); diff --git a/packages/validator/test/test-utils/mock-json-output.json b/packages/validator/test/test-utils/mock-json-output.json index 6e264c798..7c7ef1203 100644 --- a/packages/validator/test/test-utils/mock-json-output.json +++ b/packages/validator/test/test-utils/mock-json-output.json @@ -8,6 +8,7 @@ "/pets/{pet_id}/{id}" ], "rule": "ibm-no-consecutive-path-parameter-segments", + "docLink": "https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-no-consecutive-path-parameter-segments", "line": 84 }, { @@ -20,6 +21,7 @@ "id" ], "rule": "ibm-integer-attributes", + "docLink": "https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-integer-attributes", "line": 133 } ], @@ -52,6 +54,7 @@ "pattern" ], "rule": "ibm-anchored-patterns", + "docLink": "https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-anchored-patterns", "line": 233 } ], From e81b3439d9776a84e559d954d902b5a938c938d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Thu, 25 Jun 2026 14:58:21 +0200 Subject: [PATCH 03/19] fix: include spectral links correctly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/validator/src/spectral/index.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index 6ddf4e59e..11a617fea 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -236,6 +236,11 @@ function convertSpectralSeverity(s) { } function getDocumentationURL(ruleCode) { - const baseUrl = 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md'; - return `${baseUrl}#${ruleCode}`; + if(ruleCode.includes('ibm')) { + const baseUrl = 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md'; + return `${baseUrl}#${ruleCode}`; + } else { + const baseUrl = 'https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules' + return `${baseUrl}#${ruleCode}` + } } From 9973ce3d8fdabb0be7b876aefb1d3ea7ecc30330 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Thu, 25 Jun 2026 15:31:58 +0200 Subject: [PATCH 04/19] fix: make docLink required in validation result MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/validator/src/schemas/results-object.yaml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/validator/src/schemas/results-object.yaml b/packages/validator/src/schemas/results-object.yaml index 0f2065fe2..bcd6f842c 100644 --- a/packages/validator/src/schemas/results-object.yaml +++ b/packages/validator/src/schemas/results-object.yaml @@ -125,6 +125,7 @@ $defs: - message - path - rule + - docLink - line properties: message: @@ -172,3 +173,4 @@ $defs: type: number description: A number describing the demerit quality a rule has on an API minimum: 0.01 + From e0f4fd214e6af87da9fa010f2b3c1385cc64c547 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Thu, 25 Jun 2026 15:36:06 +0200 Subject: [PATCH 05/19] feat: make scoring information collapsible MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/validator/src/markdown-report/report.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/validator/src/markdown-report/report.js b/packages/validator/src/markdown-report/report.js index 4a02cf7d8..1b5f88330 100644 --- a/packages/validator/src/markdown-report/report.js +++ b/packages/validator/src/markdown-report/report.js @@ -34,8 +34,14 @@ inherently weighted by the scoring algorithm, so that security violations are 5 as usability violations, evolution 3 times, and robustness 2 times. ## Scoring information +
+ +Information about how the scoring gets calculated + ${scoringData(results)} +
+ ## Error summary ${errorSummary(results)} From 7758b9e6543247cba3b25e485a675e480d670e79 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Fri, 26 Jun 2026 11:42:07 +0200 Subject: [PATCH 06/19] feat: group result by rule violation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../tables/rule-violation-details.js | 105 +++++++++++++++--- 1 file changed, 90 insertions(+), 15 deletions(-) diff --git a/packages/validator/src/markdown-report/tables/rule-violation-details.js b/packages/validator/src/markdown-report/tables/rule-violation-details.js index 1be0ebee4..9a8950ec6 100644 --- a/packages/validator/src/markdown-report/tables/rule-violation-details.js +++ b/packages/validator/src/markdown-report/tables/rule-violation-details.js @@ -5,25 +5,100 @@ const MarkdownTable = require('../markdown-table'); -function getTable({ error, warning }) { - const table = new MarkdownTable( - 'Rule', - 'Message', - 'Path', - 'docLink', - 'Line', - 'Severity' - ); - - error.results.forEach(({ message, path, rule, line, docLink }) => { - table.addRow(rule, message, path.join('.'), docLink, line, 'error'); +function splitMessage(message) { + const colonIdx = message.indexOf(':'); + if (colonIdx === -1) { + return { base: message, detail: null }; + } + return { + base: message.slice(0, colonIdx).trim(), + detail: message.slice(colonIdx + 1).trim() || null, + }; +} + +function groupResultsByRule(results) { + const groupedByRule = {}; + + results.forEach(result => { + const { base, detail } = splitMessage(result.message); + + if (!groupedByRule[result.rule]) { + groupedByRule[result.rule] = { + base, + docLink: result.docLink, + violations: [], + }; + } + + const pathStr = result.path.join('.'); + + if (!groupedByRule[result.rule].violations.some( + v => v.line === result.line && v.path === pathStr + )) { + groupedByRule[result.rule].violations.push({ + line: result.line, + path: pathStr, + detail, + }); + } }); - warning.results.forEach(({ message, path, rule, line, docLink }) => { - table.addRow(rule, message, path.join('.'), docLink, line, 'warning'); + return groupedByRule; +} + +function renderRuleGroups(groupedByRule) { + const sections = []; + + Object.entries(groupedByRule).forEach(([rule, data]) => { + // Create heading with link + sections.push(`### [${rule}](${data.docLink})`); + sections.push(''); + + // Add the base message as italic description + sections.push(`_${data.base}_`); + sections.push(''); + + // Use a 3-col table when any violation carries extra detail + const hasDetail = data.violations.some(v => v.detail); + const table = hasDetail + ? new MarkdownTable('Line', 'Path', 'Detail') + : new MarkdownTable('Line', 'Path'); + + data.violations.forEach(({ line, path, detail }) => { + if (hasDetail) { + table.addRow(line, path, detail); + } else { + table.addRow(line, path); + } + }); + + sections.push(table.render()); + sections.push(''); }); - return table.render(); + return sections.join('\n'); +} + +function getTable({ error, warning }) { + const sections = []; + + // Process errors + if (error.results.length > 0) { + sections.push('## Errors'); + sections.push(''); + const errorGroups = groupResultsByRule(error.results); + sections.push(renderRuleGroups(errorGroups)); + } + + // Process warnings + if (warning.results.length > 0) { + sections.push('## Warnings'); + sections.push(''); + const warningGroups = groupResultsByRule(warning.results); + sections.push(renderRuleGroups(warningGroups)); + } + + return sections.join('\n').trim(); } module.exports = getTable; From 69e866c6fc8a6ddeb0c1ce1b1cce7e078246f697 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Fri, 26 Jun 2026 11:42:47 +0200 Subject: [PATCH 07/19] test: update markdown test with new output MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../tables/rule-violation-details.test.js | 142 ++++++++++++++++-- 1 file changed, 131 insertions(+), 11 deletions(-) diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index 108211a27..d38088c05 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -7,20 +7,140 @@ const { ruleViolationDetails } = require('../../../src/markdown-report/tables'); const validatorResults = require('../../test-utils/mock-json-output.json'); describe('ruleViolationDetails table tests', function () { - it('should produce a table with all rule violations from the results', function () { - const tableRows = ruleViolationDetails(validatorResults).split('\n'); + it('should produce grouped sections for errors and warnings', function () { + const output = ruleViolationDetails(validatorResults); + const lines = output.split('\n'); - expect(tableRows).toHaveLength(5); - expect(tableRows[0]).toBe('| Rule | Message | Path | docLink | Line | Severity |'); - expect(tableRows[1]).toBe('| --- | --- | --- | --- | --- | --- |'); - expect(tableRows[2]).toBe( - '| ibm-no-consecutive-path-parameter-segments | Path contains two or more consecutive path parameter references: /pets/{pet_id}/{id} | paths./pets/{pet_id}/{id} | https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-no-consecutive-path-parameter-segments | 84 | error |' + // Errors heading + expect(lines[0]).toBe('## Errors'); + expect(lines[1]).toBe(''); + + // 3-col table + expect(lines[2]).toBe( + '### [ibm-no-consecutive-path-parameter-segments](https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-no-consecutive-path-parameter-segments)' + ); + expect(lines[3]).toBe(''); + expect(lines[4]).toBe( + '_Path contains two or more consecutive path parameter references_' + ); + expect(lines[5]).toBe(''); + expect(lines[6]).toBe('| Line | Path | Detail |'); + expect(lines[7]).toBe('| --- | --- | --- |'); + expect(lines[8]).toBe( + '| 84 | paths./pets/{pet_id}/{id} | /pets/{pet_id}/{id} |' + ); + expect(lines[9]).toBe(''); + + // 2-col table + expect(lines[10]).toBe( + '### [ibm-integer-attributes](https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-integer-attributes)' ); - expect(tableRows[3]).toBe( - "| ibm-integer-attributes | Integer schemas should define property 'minimum' | components.schemas.Pet.properties.id | https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-integer-attributes | 133 | error |" + expect(lines[11]).toBe(''); + expect(lines[12]).toBe( + "_Integer schemas should define property 'minimum'_" ); - expect(tableRows[4]).toBe( - "| ibm-anchored-patterns | A regular expression used in a 'pattern' attribute should be anchored with ^ and $ | components.schemas.Error.properties.message.pattern | https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-anchored-patterns | 233 | warning |" + expect(lines[13]).toBe(''); + expect(lines[14]).toBe('| Line | Path |'); + expect(lines[15]).toBe('| --- | --- |'); + expect(lines[16]).toBe('| 133 | components.schemas.Pet.properties.id |'); + expect(lines[17]).toBe(''); + + // Warnings heading + expect(lines[18]).toBe('## Warnings'); + expect(lines[19]).toBe(''); + + // 2-col table + expect(lines[20]).toBe( + '### [ibm-anchored-patterns](https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-anchored-patterns)' + ); + expect(lines[21]).toBe(''); + expect(lines[22]).toBe( + "_A regular expression used in a 'pattern' attribute should be anchored with ^ and $_" ); + expect(lines[23]).toBe(''); + expect(lines[24]).toBe('| Line | Path |'); + expect(lines[25]).toBe('| --- | --- |'); + expect(lines[26]).toBe( + '| 233 | components.schemas.Error.properties.message.pattern |' + ); + + expect(lines).toHaveLength(27); + }); + + it('should return an empty string when there are no results', function () { + const empty = { + error: { results: [] }, + warning: { results: [] }, + }; + expect(ruleViolationDetails(empty)).toBe(''); + }); + + it('should only render the Errors section when there are no warnings', function () { + const errorsOnly = { + error: { results: validatorResults.error.results }, + warning: { results: [] }, + }; + const output = ruleViolationDetails(errorsOnly); + expect(output).toContain('## Errors'); + expect(output).not.toContain('## Warnings'); + }); + + it('should only render the Warnings section when there are no errors', function () { + const warningsOnly = { + error: { results: [] }, + warning: { results: validatorResults.warning.results }, + }; + const output = ruleViolationDetails(warningsOnly); + expect(output).not.toContain('## Errors'); + expect(output).toContain('## Warnings'); + }); + + it('should deduplicate violations with the same line and path', function () { + const duplicate = { + error: { + results: [ + { + message: 'Some error message', + path: ['paths', '/foo'], + rule: 'some-rule', + docLink: 'https://example.com', + line: 10, + }, + { + message: 'Some error message', + path: ['paths', '/foo'], + rule: 'some-rule', + docLink: 'https://example.com', + line: 10, + }, + ], + }, + warning: { results: [] }, + }; + const output = ruleViolationDetails(duplicate); + const dataRows = output + .split('\n') + .filter(l => l.startsWith('| 10 |')); + expect(dataRows).toHaveLength(1); + }); + + it('should use a 3-column table when at least one violation has a detail', function () { + const withDetail = { + error: { + results: [ + { + message: 'Base message: detail value', + path: ['paths', '/foo'], + rule: 'rule-with-detail', + docLink: 'https://example.com', + line: 5, + }, + ], + }, + warning: { results: [] }, + }; + const output = ruleViolationDetails(withDetail); + expect(output).toContain('| Line | Path | Detail |'); + expect(output).toContain('| 5 | paths./foo | detail value |'); }); }); From a6fb05556844785697647276a3127e62d919930c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Fri, 26 Jun 2026 13:55:54 +0200 Subject: [PATCH 08/19] test: fix failing test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../markdown-report/tables/rule-violation-details.js | 4 ++-- .../validator/test/markdown-report/report.test.js | 2 +- .../tables/rule-violation-details.test.js | 12 ++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/validator/src/markdown-report/tables/rule-violation-details.js b/packages/validator/src/markdown-report/tables/rule-violation-details.js index 9a8950ec6..062f7326f 100644 --- a/packages/validator/src/markdown-report/tables/rule-violation-details.js +++ b/packages/validator/src/markdown-report/tables/rule-violation-details.js @@ -84,7 +84,7 @@ function getTable({ error, warning }) { // Process errors if (error.results.length > 0) { - sections.push('## Errors'); + sections.push('### Errors'); sections.push(''); const errorGroups = groupResultsByRule(error.results); sections.push(renderRuleGroups(errorGroups)); @@ -92,7 +92,7 @@ function getTable({ error, warning }) { // Process warnings if (warning.results.length > 0) { - sections.push('## Warnings'); + sections.push('### Warnings'); sections.push(''); const warningGroups = groupResultsByRule(warning.results); sections.push(renderRuleGroups(warningGroups)); diff --git a/packages/validator/test/markdown-report/report.test.js b/packages/validator/test/markdown-report/report.test.js index 5877f713d..debfe4a8e 100644 --- a/packages/validator/test/markdown-report/report.test.js +++ b/packages/validator/test/markdown-report/report.test.js @@ -18,7 +18,7 @@ describe('getReport tests', function () { // Check all subtitle-level headers. const headers = report .split('\n') - .filter(l => l.startsWith('##')) + .filter(l => /^## /.test(l)) .map(l => l.slice(3)); expect(headers).toEqual([ 'Quick view', diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index d38088c05..85152137b 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -12,7 +12,7 @@ describe('ruleViolationDetails table tests', function () { const lines = output.split('\n'); // Errors heading - expect(lines[0]).toBe('## Errors'); + expect(lines[0]).toBe('### Errors'); expect(lines[1]).toBe(''); // 3-col table @@ -46,7 +46,7 @@ describe('ruleViolationDetails table tests', function () { expect(lines[17]).toBe(''); // Warnings heading - expect(lines[18]).toBe('## Warnings'); + expect(lines[18]).toBe('### Warnings'); expect(lines[19]).toBe(''); // 2-col table @@ -81,8 +81,8 @@ describe('ruleViolationDetails table tests', function () { warning: { results: [] }, }; const output = ruleViolationDetails(errorsOnly); - expect(output).toContain('## Errors'); - expect(output).not.toContain('## Warnings'); + expect(output).toContain('### Errors'); + expect(output).not.toContain('### Warnings'); }); it('should only render the Warnings section when there are no errors', function () { @@ -91,8 +91,8 @@ describe('ruleViolationDetails table tests', function () { warning: { results: validatorResults.warning.results }, }; const output = ruleViolationDetails(warningsOnly); - expect(output).not.toContain('## Errors'); - expect(output).toContain('## Warnings'); + expect(output).not.toContain('### Errors'); + expect(output).toContain('### Warnings'); }); it('should deduplicate violations with the same line and path', function () { From 82e14f9fd9704c5165f1cf9e890bf5efaf553bf0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Fri, 26 Jun 2026 14:02:42 +0200 Subject: [PATCH 09/19] chore: fix linter issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../markdown-report/tables/rule-violation-details.js | 6 ++++-- packages/validator/src/spectral/index.js | 10 ++++++---- .../tables/rule-violation-details.test.js | 4 +--- 3 files changed, 11 insertions(+), 9 deletions(-) diff --git a/packages/validator/src/markdown-report/tables/rule-violation-details.js b/packages/validator/src/markdown-report/tables/rule-violation-details.js index 062f7326f..ecb13f80c 100644 --- a/packages/validator/src/markdown-report/tables/rule-violation-details.js +++ b/packages/validator/src/markdown-report/tables/rule-violation-details.js @@ -32,9 +32,11 @@ function groupResultsByRule(results) { const pathStr = result.path.join('.'); - if (!groupedByRule[result.rule].violations.some( + if ( + !groupedByRule[result.rule].violations.some( v => v.line === result.line && v.path === pathStr - )) { + ) + ) { groupedByRule[result.rule].violations.push({ line: result.line, path: pathStr, diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index 11a617fea..9c0f52062 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -236,11 +236,13 @@ function convertSpectralSeverity(s) { } function getDocumentationURL(ruleCode) { - if(ruleCode.includes('ibm')) { - const baseUrl = 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md'; + if (ruleCode.includes('ibm')) { + const baseUrl = + 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md'; return `${baseUrl}#${ruleCode}`; } else { - const baseUrl = 'https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules' - return `${baseUrl}#${ruleCode}` + const baseUrl = + 'https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules'; + return `${baseUrl}#${ruleCode}`; } } diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index 85152137b..f61e15e6b 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -118,9 +118,7 @@ describe('ruleViolationDetails table tests', function () { warning: { results: [] }, }; const output = ruleViolationDetails(duplicate); - const dataRows = output - .split('\n') - .filter(l => l.startsWith('| 10 |')); + const dataRows = output.split('\n').filter(l => l.startsWith('| 10 |')); expect(dataRows).toHaveLength(1); }); From e22b8e4e4497ad8d81727c63bc596d8cdecb5414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Fri, 24 Jul 2026 12:11:58 +0200 Subject: [PATCH 10/19] fix: update docs link to GH pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/validator/src/spectral/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index 9c0f52062..f7043614b 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -238,7 +238,7 @@ function convertSpectralSeverity(s) { function getDocumentationURL(ruleCode) { if (ruleCode.includes('ibm')) { const baseUrl = - 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md'; + 'https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html'; return `${baseUrl}#${ruleCode}`; } else { const baseUrl = From aa75bef20a08ba535130b7832ddbe4d64d68e204 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Mon, 21 Sep 2026 16:39:24 +0200 Subject: [PATCH 11/19] fix: apply changes requested in PR MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/ruleset/src/ibm-oas.js | 4 +++- packages/validator/src/spectral/index.js | 14 +------------- .../tables/rule-violation-details.test.js | 6 +++--- .../test/test-utils/mock-json-output.json | 6 +++--- 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/ruleset/src/ibm-oas.js b/packages/ruleset/src/ibm-oas.js index 6a8895343..7b50fe243 100644 --- a/packages/ruleset/src/ibm-oas.js +++ b/packages/ruleset/src/ibm-oas.js @@ -12,11 +12,13 @@ const ibmRules = require('./rules'); // However, we want to enable this rule also for OpenAPI 3.1.x documents, // so we'll just tweak Spectral's rule definition here. oas.rules['no-$ref-siblings'].formats = [oas3]; +oas.documentationUrl = + 'https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules'; module.exports = { extends: oas, documentationUrl: - 'https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md', + 'https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html', formats: [oas3], rules: { // Original list created from Spectral with: diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index f7043614b..18456b1bf 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -89,7 +89,7 @@ function convertResults(spectralResults, { config, logger }) { message: r.message, path: r.path, rule: r.code, - docLink: getDocumentationURL(r.code), + docLink: r.documentationUrl, line: r.range.start.line + 1, }); @@ -234,15 +234,3 @@ function convertSpectralSeverity(s) { const mapping = { 0: 'error', 1: 'warning', 2: 'info', 3: 'hint' }; return mapping[s]; } - -function getDocumentationURL(ruleCode) { - if (ruleCode.includes('ibm')) { - const baseUrl = - 'https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html'; - return `${baseUrl}#${ruleCode}`; - } else { - const baseUrl = - 'https://meta.stoplight.io/docs/spectral/4dec24461f3af-open-api-rules'; - return `${baseUrl}#${ruleCode}`; - } -} diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index f61e15e6b..d4fcf4cfc 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -17,7 +17,7 @@ describe('ruleViolationDetails table tests', function () { // 3-col table expect(lines[2]).toBe( - '### [ibm-no-consecutive-path-parameter-segments](https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-no-consecutive-path-parameter-segments)' + '### [ibm-no-consecutive-path-parameter-segments](https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html#ibm-no-consecutive-path-parameter-segments)' ); expect(lines[3]).toBe(''); expect(lines[4]).toBe( @@ -33,7 +33,7 @@ describe('ruleViolationDetails table tests', function () { // 2-col table expect(lines[10]).toBe( - '### [ibm-integer-attributes](https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-integer-attributes)' + '### [ibm-integer-attributes](https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html#ibm-integer-attributes)' ); expect(lines[11]).toBe(''); expect(lines[12]).toBe( @@ -51,7 +51,7 @@ describe('ruleViolationDetails table tests', function () { // 2-col table expect(lines[20]).toBe( - '### [ibm-anchored-patterns](https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-anchored-patterns)' + '### [ibm-anchored-patterns](https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html#ibm-anchored-patterns)' ); expect(lines[21]).toBe(''); expect(lines[22]).toBe( diff --git a/packages/validator/test/test-utils/mock-json-output.json b/packages/validator/test/test-utils/mock-json-output.json index 7c7ef1203..d4ddf2664 100644 --- a/packages/validator/test/test-utils/mock-json-output.json +++ b/packages/validator/test/test-utils/mock-json-output.json @@ -8,7 +8,7 @@ "/pets/{pet_id}/{id}" ], "rule": "ibm-no-consecutive-path-parameter-segments", - "docLink": "https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-no-consecutive-path-parameter-segments", + "docLink": "https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html#ibm-no-consecutive-path-parameter-segments", "line": 84 }, { @@ -21,7 +21,7 @@ "id" ], "rule": "ibm-integer-attributes", - "docLink": "https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-integer-attributes", + "docLink": "https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html#ibm-integer-attributes", "line": 133 } ], @@ -54,7 +54,7 @@ "pattern" ], "rule": "ibm-anchored-patterns", - "docLink": "https://github.com/IBM/openapi-validator/blob/main/docs/ibm-cloud-rules.md#ibm-anchored-patterns", + "docLink": "https://ibm.github.io/openapi-validator/docs/ibm-cloud-rules.html#ibm-anchored-patterns", "line": 233 } ], From 4593822776e3576b604b52e381413b224879e451 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Tue, 22 Sep 2026 15:29:07 +0200 Subject: [PATCH 12/19] fix: set doclink during spectral setup MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/validator/src/spectral/index.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index 18456b1bf..62d7a9c30 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -47,10 +47,10 @@ const runSpectral = async function ({ originalFile, validFile }, context) { // Save the resolved API definition for use in the scoring tool logic. context.apiDefinition = doc.data; - return convertResults(spectralResults, context); + return convertResults(spectralResults, context, spectral.ruleset.rules); }; -function convertResults(spectralResults, { config, logger }) { +function convertResults(spectralResults, { config, logger }, rules) { const { errorsOnly } = config; // This structure must match the JSON Schema defined for JSON output @@ -89,7 +89,7 @@ function convertResults(spectralResults, { config, logger }) { message: r.message, path: r.path, rule: r.code, - docLink: r.documentationUrl, + docLink: rules[r.code]?.documentationUrl, line: r.range.start.line + 1, }); From d935b368cd435d0917495a0f88611d5996440d77 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Tue, 22 Sep 2026 15:54:02 +0200 Subject: [PATCH 13/19] build: update ruleset package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- package-lock.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 062bcb223..459247d88 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14041,13 +14041,13 @@ }, "packages/ruleset": { "name": "@ibm-cloud/openapi-ruleset", - "version": "1.33.15", + "version": "1.33.9", "license": "Apache-2.0", "dependencies": { - "@ibm-cloud/openapi-ruleset-utilities": "1.9.4", - "@stoplight/spectral-formats": "1.8.5", - "@stoplight/spectral-functions": "1.10.5", - "@stoplight/spectral-rulesets": "1.22.7", + "@ibm-cloud/openapi-ruleset-utilities": "1.9.1", + "@stoplight/spectral-formats": "1.8.2", + "@stoplight/spectral-functions": "1.9.3", + "@stoplight/spectral-rulesets": "1.21.3", "chalk": "4.1.2", "inflected": "2.1.0", "jsonschema": "1.5.0", @@ -14103,7 +14103,7 @@ }, "packages/utilities": { "name": "@ibm-cloud/openapi-ruleset-utilities", - "version": "1.9.4", + "version": "1.9.1", "license": "Apache-2.0", "devDependencies": { "@stoplight/spectral-core": "1.23.1", @@ -14116,13 +14116,13 @@ }, "packages/validator": { "name": "ibm-openapi-validator", - "version": "1.38.4", + "version": "1.37.13", "license": "Apache-2.0", "dependencies": { - "@ibm-cloud/openapi-ruleset": "1.33.15", - "@ibm-cloud/openapi-ruleset-utilities": "1.9.4", - "@stoplight/spectral-cli": "6.16.3", - "@stoplight/spectral-core": "1.23.1", + "@ibm-cloud/openapi-ruleset": "1.33.9", + "@ibm-cloud/openapi-ruleset-utilities": "1.9.1", + "@stoplight/spectral-cli": "6.14.2", + "@stoplight/spectral-core": "1.19.4", "@stoplight/spectral-parsers": "1.0.5", "@stoplight/spectral-ref-resolver": "1.0.5", "ajv": "8.20.0", From 313dfbaa5915bb102c5d374de36977033433f796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Wed, 23 Sep 2026 13:24:48 +0200 Subject: [PATCH 14/19] build: update packages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- package-lock.json | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 459247d88..062bcb223 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14041,13 +14041,13 @@ }, "packages/ruleset": { "name": "@ibm-cloud/openapi-ruleset", - "version": "1.33.9", + "version": "1.33.15", "license": "Apache-2.0", "dependencies": { - "@ibm-cloud/openapi-ruleset-utilities": "1.9.1", - "@stoplight/spectral-formats": "1.8.2", - "@stoplight/spectral-functions": "1.9.3", - "@stoplight/spectral-rulesets": "1.21.3", + "@ibm-cloud/openapi-ruleset-utilities": "1.9.4", + "@stoplight/spectral-formats": "1.8.5", + "@stoplight/spectral-functions": "1.10.5", + "@stoplight/spectral-rulesets": "1.22.7", "chalk": "4.1.2", "inflected": "2.1.0", "jsonschema": "1.5.0", @@ -14103,7 +14103,7 @@ }, "packages/utilities": { "name": "@ibm-cloud/openapi-ruleset-utilities", - "version": "1.9.1", + "version": "1.9.4", "license": "Apache-2.0", "devDependencies": { "@stoplight/spectral-core": "1.23.1", @@ -14116,13 +14116,13 @@ }, "packages/validator": { "name": "ibm-openapi-validator", - "version": "1.37.13", + "version": "1.38.4", "license": "Apache-2.0", "dependencies": { - "@ibm-cloud/openapi-ruleset": "1.33.9", - "@ibm-cloud/openapi-ruleset-utilities": "1.9.1", - "@stoplight/spectral-cli": "6.14.2", - "@stoplight/spectral-core": "1.19.4", + "@ibm-cloud/openapi-ruleset": "1.33.15", + "@ibm-cloud/openapi-ruleset-utilities": "1.9.4", + "@stoplight/spectral-cli": "6.16.3", + "@stoplight/spectral-core": "1.23.1", "@stoplight/spectral-parsers": "1.0.5", "@stoplight/spectral-ref-resolver": "1.0.5", "ajv": "8.20.0", From dc07f2682a34a1d2b23e23cabef447199902a100 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Mon, 5 Oct 2026 15:14:33 +0200 Subject: [PATCH 15/19] build: bump spectral core to latest version MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- package-lock.json | 400 +++++++++++++++++++------------- packages/ruleset/package.json | 2 +- packages/validator/package.json | 2 +- 3 files changed, 242 insertions(+), 162 deletions(-) diff --git a/package-lock.json b/package-lock.json index 062bcb223..255ffa557 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2350,6 +2350,22 @@ "@sinonjs/commons": "^3.0.0" } }, + "node_modules/@stoplight/better-ajv-errors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz", + "integrity": "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==", + "license": "Apache-2.0", + "dependencies": { + "jsonpointer": "^5.0.0", + "leven": "^3.1.0" + }, + "engines": { + "node": "^12.20 || >= 14.13" + }, + "peerDependencies": { + "ajv": ">=8" + } + }, "node_modules/@stoplight/json": { "version": "3.21.7", "resolved": "https://registry.npmjs.org/@stoplight/json/-/json-3.21.7.tgz", @@ -2492,22 +2508,6 @@ "node": "^16.20 || ^18.18 || >= 20.17" } }, - "node_modules/@stoplight/spectral-core/node_modules/@stoplight/better-ajv-errors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz", - "integrity": "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==", - "license": "Apache-2.0", - "dependencies": { - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": "^12.20 || >= 14.13" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, "node_modules/@stoplight/spectral-core/node_modules/@stoplight/types": { "version": "13.6.0", "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.6.0.tgz", @@ -2521,31 +2521,6 @@ "node": "^12.20 || >=14.13" } }, - "node_modules/@stoplight/spectral-core/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, - "node_modules/@stoplight/spectral-core/node_modules/ajv-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", - "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.0.1" - } - }, "node_modules/@stoplight/spectral-core/node_modules/nimma": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.3.tgz", @@ -2628,38 +2603,6 @@ "node": "^16.20 || ^18.18 || >= 20.17" } }, - "node_modules/@stoplight/spectral-functions/node_modules/@stoplight/better-ajv-errors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz", - "integrity": "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==", - "license": "Apache-2.0", - "dependencies": { - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": "^12.20 || >= 14.13" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "node_modules/@stoplight/spectral-functions/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@stoplight/spectral-functions/node_modules/ajv-draft-04": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/ajv-draft-04/-/ajv-draft-04-1.0.0.tgz", @@ -2674,15 +2617,6 @@ } } }, - "node_modules/@stoplight/spectral-functions/node_modules/ajv-errors": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", - "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", - "license": "MIT", - "peerDependencies": { - "ajv": "^8.0.1" - } - }, "node_modules/@stoplight/spectral-parsers": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@stoplight/spectral-parsers/-/spectral-parsers-1.0.5.tgz", @@ -2800,22 +2734,6 @@ "integrity": "sha512-sV+51I7WYnLJnKPn2EMWgS4EUfoP4iWEbrWwbXsj0MZCB/xOK8j6+C9fntIdOM50kpx45ZLC3s6kwKivWuqvyg==", "license": "Apache-2.0" }, - "node_modules/@stoplight/spectral-ruleset-migrator/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@stoplight/spectral-rulesets": { "version": "1.22.7", "resolved": "https://registry.npmjs.org/@stoplight/spectral-rulesets/-/spectral-rulesets-1.22.7.tgz", @@ -2843,38 +2761,6 @@ "node": "^16.20 || ^18.18 || >= 20.17" } }, - "node_modules/@stoplight/spectral-rulesets/node_modules/@stoplight/better-ajv-errors": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@stoplight/better-ajv-errors/-/better-ajv-errors-1.0.3.tgz", - "integrity": "sha512-0p9uXkuB22qGdNfy3VeEhxkU5uwvp/KrBTAbrLBURv6ilxIVwanKwjMc41lQfIVgPGcOkmLbTolfFrSsueu7zA==", - "license": "Apache-2.0", - "dependencies": { - "jsonpointer": "^5.0.0", - "leven": "^3.1.0" - }, - "engines": { - "node": "^12.20 || >= 14.13" - }, - "peerDependencies": { - "ajv": ">=8" - } - }, - "node_modules/@stoplight/spectral-rulesets/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/@stoplight/spectral-runtime": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/@stoplight/spectral-runtime/-/spectral-runtime-1.1.6.tgz", @@ -3182,6 +3068,31 @@ "node": ">=8" } }, + "node_modules/ajv": { + "version": "8.20.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", + "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.3", + "fast-uri": "^3.0.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-errors": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ajv-errors/-/ajv-errors-3.0.0.tgz", + "integrity": "sha512-V3wD15YHfHz6y0KdhYFjyy9vWtEVALT9UrxfN3zqlI6dMioHnJrqOYfyPKol3oqrnCM9uwkcdCwkJ0WUcbLMTQ==", + "license": "MIT", + "peerDependencies": { + "ajv": "^8.0.1" + } + }, "node_modules/ajv-formats": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", @@ -3199,22 +3110,6 @@ } } }, - "node_modules/ajv-formats/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", - "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", @@ -14058,13 +13953,92 @@ "validator": "13.15.35" }, "devDependencies": { - "@stoplight/spectral-core": "1.23.1", + "@stoplight/spectral-core": "1.24.0", "jest": "29.7.0" }, "engines": { "node": ">=24.0.0" } }, + "packages/ruleset/node_modules/@stoplight/spectral-core": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.24.0.tgz", + "integrity": "sha512-abKt++GFFaqwKiGTfPZ5O3nIv8s+1V7J5awCjWPBtCXn7ZKTEPzEF8tBYsM3ne+RMrQ8ORkHzKFu9QZhenmkPw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@scarf/scarf": "^1.4.0", + "@stoplight/better-ajv-errors": "1.0.3", + "@stoplight/json": "~3.21.0", + "@stoplight/path": "1.3.2", + "@stoplight/spectral-parsers": "^1.0.0", + "@stoplight/spectral-ref-resolver": "^1.0.4", + "@stoplight/spectral-runtime": "^1.1.2", + "@stoplight/types": "~13.6.0", + "@types/es-aggregate-error": "^1.0.2", + "@types/json-schema": "^7.0.11", + "ajv": "^8.18.0", + "ajv-errors": "~3.0.0", + "ajv-formats": "~2.1.1", + "es-aggregate-error": "^1.0.7", + "expr-eval-fork": "^3.0.1", + "jsonpath-plus": "^10.3.0", + "lodash": "^4.18.1", + "lodash.topath": "^4.5.2", + "minimatch": "^3.1.4", + "nimma": "0.2.3", + "pony-cause": "^1.1.1", + "tslib": "^2.8.1" + }, + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "packages/ruleset/node_modules/@stoplight/spectral-core/node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "packages/ruleset/node_modules/@stoplight/spectral-core/node_modules/brace-expansion": { + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "packages/ruleset/node_modules/@stoplight/spectral-core/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "packages/ruleset/node_modules/@stoplight/types": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.6.0.tgz", + "integrity": "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + } + }, "packages/ruleset/node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -14086,6 +14060,25 @@ "node": "20 || >=22" } }, + "packages/ruleset/node_modules/jsonpath-plus": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } + }, "packages/ruleset/node_modules/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", @@ -14101,6 +14094,26 @@ "url": "https://github.com/sponsors/isaacs" } }, + "packages/ruleset/node_modules/nimma": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.3.tgz", + "integrity": "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@jsep-plugin/regex": "^1.0.1", + "@jsep-plugin/ternary": "^1.0.2", + "astring": "^1.8.1", + "jsep": "^1.2.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + }, + "optionalDependencies": { + "jsonpath-plus": "^6.0.1 || ^10.1.0", + "lodash.topath": "^4.5.2" + } + }, "packages/utilities": { "name": "@ibm-cloud/openapi-ruleset-utilities", "version": "1.9.4", @@ -14122,7 +14135,7 @@ "@ibm-cloud/openapi-ruleset": "1.33.15", "@ibm-cloud/openapi-ruleset-utilities": "1.9.4", "@stoplight/spectral-cli": "6.16.3", - "@stoplight/spectral-core": "1.23.1", + "@stoplight/spectral-core": "1.24.0", "@stoplight/spectral-parsers": "1.0.5", "@stoplight/spectral-ref-resolver": "1.0.5", "ajv": "8.20.0", @@ -14151,20 +14164,69 @@ "npm": ">=11.0.0" } }, - "packages/validator/node_modules/ajv": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", - "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", - "license": "MIT", + "packages/validator/node_modules/@stoplight/spectral-core": { + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.24.0.tgz", + "integrity": "sha512-abKt++GFFaqwKiGTfPZ5O3nIv8s+1V7J5awCjWPBtCXn7ZKTEPzEF8tBYsM3ne+RMrQ8ORkHzKFu9QZhenmkPw==", + "license": "Apache-2.0", "dependencies": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "@scarf/scarf": "^1.4.0", + "@stoplight/better-ajv-errors": "1.0.3", + "@stoplight/json": "~3.21.0", + "@stoplight/path": "1.3.2", + "@stoplight/spectral-parsers": "^1.0.0", + "@stoplight/spectral-ref-resolver": "^1.0.4", + "@stoplight/spectral-runtime": "^1.1.2", + "@stoplight/types": "~13.6.0", + "@types/es-aggregate-error": "^1.0.2", + "@types/json-schema": "^7.0.11", + "ajv": "^8.18.0", + "ajv-errors": "~3.0.0", + "ajv-formats": "~2.1.1", + "es-aggregate-error": "^1.0.7", + "expr-eval-fork": "^3.0.1", + "jsonpath-plus": "^10.3.0", + "lodash": "^4.18.1", + "lodash.topath": "^4.5.2", + "minimatch": "^3.1.4", + "nimma": "0.2.3", + "pony-cause": "^1.1.1", + "tslib": "^2.8.1" }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/epoberezkin" + "engines": { + "node": "^16.20 || ^18.18 || >= 20.17" + } + }, + "packages/validator/node_modules/@stoplight/spectral-core/node_modules/nimma": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.3.tgz", + "integrity": "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==", + "license": "Apache-2.0", + "dependencies": { + "@jsep-plugin/regex": "^1.0.1", + "@jsep-plugin/ternary": "^1.0.2", + "astring": "^1.8.1", + "jsep": "^1.2.0" + }, + "engines": { + "node": "^12.20 || >=14.13" + }, + "optionalDependencies": { + "jsonpath-plus": "^6.0.1 || ^10.1.0", + "lodash.topath": "^4.5.2" + } + }, + "packages/validator/node_modules/@stoplight/types": { + "version": "13.6.0", + "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.6.0.tgz", + "integrity": "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==", + "license": "Apache-2.0", + "dependencies": { + "@types/json-schema": "^7.0.4", + "utility-types": "^3.10.0" + }, + "engines": { + "node": "^12.20 || >=14.13" } }, "packages/validator/node_modules/js-yaml": { @@ -14188,6 +14250,24 @@ "bin": { "js-yaml": "bin/js-yaml.mjs" } + }, + "packages/validator/node_modules/jsonpath-plus": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", + "license": "MIT", + "dependencies": { + "@jsep-plugin/assignment": "^1.3.0", + "@jsep-plugin/regex": "^1.0.4", + "jsep": "^1.4.0" + }, + "bin": { + "jsonpath": "bin/jsonpath-cli.js", + "jsonpath-plus": "bin/jsonpath-cli.js" + }, + "engines": { + "node": ">=18.0.0" + } } } } diff --git a/packages/ruleset/package.json b/packages/ruleset/package.json index 936f0becb..66babb9b5 100644 --- a/packages/ruleset/package.json +++ b/packages/ruleset/package.json @@ -35,7 +35,7 @@ "validator": "13.15.35" }, "devDependencies": { - "@stoplight/spectral-core": "1.23.1", + "@stoplight/spectral-core": "1.24.0", "jest": "29.7.0" }, "engines": { diff --git a/packages/validator/package.json b/packages/validator/package.json index 8cf3fadc4..a8c2fc857 100644 --- a/packages/validator/package.json +++ b/packages/validator/package.json @@ -26,7 +26,7 @@ "@ibm-cloud/openapi-ruleset": "1.33.15", "@ibm-cloud/openapi-ruleset-utilities": "1.9.4", "@stoplight/spectral-cli": "6.16.3", - "@stoplight/spectral-core": "1.23.1", + "@stoplight/spectral-core": "1.24.0", "@stoplight/spectral-parsers": "1.0.5", "@stoplight/spectral-ref-resolver": "1.0.5", "ajv": "8.20.0", From ba9b35648599bc056cfd02366ace972226927674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Mon, 5 Oct 2026 15:43:54 +0200 Subject: [PATCH 16/19] fix: bump missed spectral core package MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- package-lock.json | 215 ++------------------------------ packages/utilities/package.json | 2 +- 2 files changed, 8 insertions(+), 209 deletions(-) diff --git a/package-lock.json b/package-lock.json index 255ffa557..64ea3db6d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2476,9 +2476,9 @@ } }, "node_modules/@stoplight/spectral-core": { - "version": "1.23.1", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.23.1.tgz", - "integrity": "sha512-VLC8OhpO/pMJKb6IHhurxJjXO1qB56Ng1unIb8b+hNxdw0+SEcASvmR+RpjfHYX/jv/DfSaA1x8QhFBJBmqBOQ==", + "version": "1.24.0", + "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.24.0.tgz", + "integrity": "sha512-abKt++GFFaqwKiGTfPZ5O3nIv8s+1V7J5awCjWPBtCXn7ZKTEPzEF8tBYsM3ne+RMrQ8ORkHzKFu9QZhenmkPw==", "license": "Apache-2.0", "dependencies": { "@scarf/scarf": "^1.4.0", @@ -7623,9 +7623,9 @@ } }, "node_modules/jsonpath-plus": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.4.0.tgz", - "integrity": "sha512-T92WWatJXmhBbKsgH/0hl+jxjdXrifi5IKeMY02DWggRxX0UElcbVzPlmgLTbvsPeW1PasQ6xE2Q75stkhGbsA==", + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", + "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", "license": "MIT", "dependencies": { "@jsep-plugin/assignment": "^1.3.0", @@ -13960,85 +13960,6 @@ "node": ">=24.0.0" } }, - "packages/ruleset/node_modules/@stoplight/spectral-core": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.24.0.tgz", - "integrity": "sha512-abKt++GFFaqwKiGTfPZ5O3nIv8s+1V7J5awCjWPBtCXn7ZKTEPzEF8tBYsM3ne+RMrQ8ORkHzKFu9QZhenmkPw==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@scarf/scarf": "^1.4.0", - "@stoplight/better-ajv-errors": "1.0.3", - "@stoplight/json": "~3.21.0", - "@stoplight/path": "1.3.2", - "@stoplight/spectral-parsers": "^1.0.0", - "@stoplight/spectral-ref-resolver": "^1.0.4", - "@stoplight/spectral-runtime": "^1.1.2", - "@stoplight/types": "~13.6.0", - "@types/es-aggregate-error": "^1.0.2", - "@types/json-schema": "^7.0.11", - "ajv": "^8.18.0", - "ajv-errors": "~3.0.0", - "ajv-formats": "~2.1.1", - "es-aggregate-error": "^1.0.7", - "expr-eval-fork": "^3.0.1", - "jsonpath-plus": "^10.3.0", - "lodash": "^4.18.1", - "lodash.topath": "^4.5.2", - "minimatch": "^3.1.4", - "nimma": "0.2.3", - "pony-cause": "^1.1.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": "^16.20 || ^18.18 || >= 20.17" - } - }, - "packages/ruleset/node_modules/@stoplight/spectral-core/node_modules/balanced-match": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", - "dev": true, - "license": "MIT" - }, - "packages/ruleset/node_modules/@stoplight/spectral-core/node_modules/brace-expansion": { - "version": "1.1.21", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", - "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "packages/ruleset/node_modules/@stoplight/spectral-core/node_modules/minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, - "packages/ruleset/node_modules/@stoplight/types": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.6.0.tgz", - "integrity": "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.4", - "utility-types": "^3.10.0" - }, - "engines": { - "node": "^12.20 || >=14.13" - } - }, "packages/ruleset/node_modules/balanced-match": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", @@ -14060,25 +13981,6 @@ "node": "20 || >=22" } }, - "packages/ruleset/node_modules/jsonpath-plus": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", - "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", - "dev": true, - "license": "MIT", - "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=18.0.0" - } - }, "packages/ruleset/node_modules/minimatch": { "version": "10.2.6", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", @@ -14094,32 +13996,12 @@ "url": "https://github.com/sponsors/isaacs" } }, - "packages/ruleset/node_modules/nimma": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.3.tgz", - "integrity": "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==", - "dev": true, - "license": "Apache-2.0", - "dependencies": { - "@jsep-plugin/regex": "^1.0.1", - "@jsep-plugin/ternary": "^1.0.2", - "astring": "^1.8.1", - "jsep": "^1.2.0" - }, - "engines": { - "node": "^12.20 || >=14.13" - }, - "optionalDependencies": { - "jsonpath-plus": "^6.0.1 || ^10.1.0", - "lodash.topath": "^4.5.2" - } - }, "packages/utilities": { "name": "@ibm-cloud/openapi-ruleset-utilities", "version": "1.9.4", "license": "Apache-2.0", "devDependencies": { - "@stoplight/spectral-core": "1.23.1", + "@stoplight/spectral-core": "1.24.0", "jest": "29.7.0", "typescript": "5.9.3" }, @@ -14164,71 +14046,6 @@ "npm": ">=11.0.0" } }, - "packages/validator/node_modules/@stoplight/spectral-core": { - "version": "1.24.0", - "resolved": "https://registry.npmjs.org/@stoplight/spectral-core/-/spectral-core-1.24.0.tgz", - "integrity": "sha512-abKt++GFFaqwKiGTfPZ5O3nIv8s+1V7J5awCjWPBtCXn7ZKTEPzEF8tBYsM3ne+RMrQ8ORkHzKFu9QZhenmkPw==", - "license": "Apache-2.0", - "dependencies": { - "@scarf/scarf": "^1.4.0", - "@stoplight/better-ajv-errors": "1.0.3", - "@stoplight/json": "~3.21.0", - "@stoplight/path": "1.3.2", - "@stoplight/spectral-parsers": "^1.0.0", - "@stoplight/spectral-ref-resolver": "^1.0.4", - "@stoplight/spectral-runtime": "^1.1.2", - "@stoplight/types": "~13.6.0", - "@types/es-aggregate-error": "^1.0.2", - "@types/json-schema": "^7.0.11", - "ajv": "^8.18.0", - "ajv-errors": "~3.0.0", - "ajv-formats": "~2.1.1", - "es-aggregate-error": "^1.0.7", - "expr-eval-fork": "^3.0.1", - "jsonpath-plus": "^10.3.0", - "lodash": "^4.18.1", - "lodash.topath": "^4.5.2", - "minimatch": "^3.1.4", - "nimma": "0.2.3", - "pony-cause": "^1.1.1", - "tslib": "^2.8.1" - }, - "engines": { - "node": "^16.20 || ^18.18 || >= 20.17" - } - }, - "packages/validator/node_modules/@stoplight/spectral-core/node_modules/nimma": { - "version": "0.2.3", - "resolved": "https://registry.npmjs.org/nimma/-/nimma-0.2.3.tgz", - "integrity": "sha512-1ZOI8J+1PKKGceo/5CT5GfQOG6H8I2BencSK06YarZ2wXwH37BSSUWldqJmMJYA5JfqDqffxDXynt6f11AyKcA==", - "license": "Apache-2.0", - "dependencies": { - "@jsep-plugin/regex": "^1.0.1", - "@jsep-plugin/ternary": "^1.0.2", - "astring": "^1.8.1", - "jsep": "^1.2.0" - }, - "engines": { - "node": "^12.20 || >=14.13" - }, - "optionalDependencies": { - "jsonpath-plus": "^6.0.1 || ^10.1.0", - "lodash.topath": "^4.5.2" - } - }, - "packages/validator/node_modules/@stoplight/types": { - "version": "13.6.0", - "resolved": "https://registry.npmjs.org/@stoplight/types/-/types-13.6.0.tgz", - "integrity": "sha512-dzyuzvUjv3m1wmhPfq82lCVYGcXG0xUYgqnWfCq3PCVR4BKFhjdkHrnJ+jIDoMKvXb05AZP/ObQF6+NpDo29IQ==", - "license": "Apache-2.0", - "dependencies": { - "@types/json-schema": "^7.0.4", - "utility-types": "^3.10.0" - }, - "engines": { - "node": "^12.20 || >=14.13" - } - }, "packages/validator/node_modules/js-yaml": { "version": "5.4.2", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-5.4.2.tgz", @@ -14250,24 +14067,6 @@ "bin": { "js-yaml": "bin/js-yaml.mjs" } - }, - "packages/validator/node_modules/jsonpath-plus": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/jsonpath-plus/-/jsonpath-plus-10.3.0.tgz", - "integrity": "sha512-8TNmfeTCk2Le33A3vRRwtuworG/L5RrgMvdjhKZxvyShO+mBu2fP50OWUjRLNtvw344DdDarFh9buFAZs5ujeA==", - "license": "MIT", - "dependencies": { - "@jsep-plugin/assignment": "^1.3.0", - "@jsep-plugin/regex": "^1.0.4", - "jsep": "^1.4.0" - }, - "bin": { - "jsonpath": "bin/jsonpath-cli.js", - "jsonpath-plus": "bin/jsonpath-cli.js" - }, - "engines": { - "node": ">=18.0.0" - } } } } diff --git a/packages/utilities/package.json b/packages/utilities/package.json index 080abad53..0720de82f 100644 --- a/packages/utilities/package.json +++ b/packages/utilities/package.json @@ -22,7 +22,7 @@ "pkg": "echo no executables to build in this package" }, "devDependencies": { - "@stoplight/spectral-core": "1.23.1", + "@stoplight/spectral-core": "1.24.0", "jest": "29.7.0", "typescript": "5.9.3" }, From ff374f82e71770ddd903cf665e9c448febee12dd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Tue, 13 Oct 2026 10:30:02 +0200 Subject: [PATCH 17/19] chore: update copyright year in edited files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/ruleset/src/ibm-oas.js | 2 +- packages/validator/src/cli-validator/utils/print-results.js | 2 +- packages/validator/src/markdown-report/report.js | 2 +- .../src/markdown-report/tables/rule-violation-details.js | 2 +- packages/validator/src/spectral/index.js | 4 ++-- .../test/cli-validator/tests/expected-output.test.js | 2 +- packages/validator/test/markdown-report/report.test.js | 2 +- .../markdown-report/tables/rule-violation-details.test.js | 2 +- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/ruleset/src/ibm-oas.js b/packages/ruleset/src/ibm-oas.js index 7b50fe243..f2cd0001b 100644 --- a/packages/ruleset/src/ibm-oas.js +++ b/packages/ruleset/src/ibm-oas.js @@ -1,5 +1,5 @@ /** - * Copyright 2017 - 2025 IBM Corporation. + * Copyright 2017 - 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ diff --git a/packages/validator/src/cli-validator/utils/print-results.js b/packages/validator/src/cli-validator/utils/print-results.js index 1703075c8..57e6c0b41 100644 --- a/packages/validator/src/cli-validator/utils/print-results.js +++ b/packages/validator/src/cli-validator/utils/print-results.js @@ -1,5 +1,5 @@ /** - * Copyright 2017 - 2023 IBM Corporation. + * Copyright 2017 - 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ diff --git a/packages/validator/src/markdown-report/report.js b/packages/validator/src/markdown-report/report.js index 1b5f88330..1872c79a5 100644 --- a/packages/validator/src/markdown-report/report.js +++ b/packages/validator/src/markdown-report/report.js @@ -1,5 +1,5 @@ /** - * Copyright 2024 IBM Corporation. + * Copyright 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ diff --git a/packages/validator/src/markdown-report/tables/rule-violation-details.js b/packages/validator/src/markdown-report/tables/rule-violation-details.js index ecb13f80c..459baecef 100644 --- a/packages/validator/src/markdown-report/tables/rule-violation-details.js +++ b/packages/validator/src/markdown-report/tables/rule-violation-details.js @@ -1,5 +1,5 @@ /** - * Copyright 2024 IBM Corporation. + * Copyright 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ diff --git a/packages/validator/src/spectral/index.js b/packages/validator/src/spectral/index.js index 62d7a9c30..a473a5645 100644 --- a/packages/validator/src/spectral/index.js +++ b/packages/validator/src/spectral/index.js @@ -1,5 +1,5 @@ /** - * Copyright 2017 - 2024 IBM Corporation. + * Copyright 2017 - 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ @@ -89,7 +89,7 @@ function convertResults(spectralResults, { config, logger }, rules) { message: r.message, path: r.path, rule: r.code, - docLink: rules[r.code]?.documentationUrl, + docLink: rules[r.code]?.documentationUrl ?? '', line: r.range.start.line + 1, }); diff --git a/packages/validator/test/cli-validator/tests/expected-output.test.js b/packages/validator/test/cli-validator/tests/expected-output.test.js index 14c201488..50dc00a49 100644 --- a/packages/validator/test/cli-validator/tests/expected-output.test.js +++ b/packages/validator/test/cli-validator/tests/expected-output.test.js @@ -1,5 +1,5 @@ /** - * Copyright 2017 - 2023 IBM Corporation. + * Copyright 2017 - 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ diff --git a/packages/validator/test/markdown-report/report.test.js b/packages/validator/test/markdown-report/report.test.js index debfe4a8e..47210ba6a 100644 --- a/packages/validator/test/markdown-report/report.test.js +++ b/packages/validator/test/markdown-report/report.test.js @@ -1,5 +1,5 @@ /** - * Copyright 2024 IBM Corporation. + * Copyright 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index d4fcf4cfc..f131e0811 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -1,5 +1,5 @@ /** - * Copyright 2024 IBM Corporation. + * Copyright 2026 IBM Corporation. * SPDX-License-Identifier: Apache2.0 */ From 46acada1cc14fe7cbdaab0fa2d0add4da008212b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Tue, 13 Oct 2026 10:40:56 +0200 Subject: [PATCH 18/19] chore: remove extra whitespace char MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- packages/validator/src/schemas/results-object.yaml | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/validator/src/schemas/results-object.yaml b/packages/validator/src/schemas/results-object.yaml index bcd6f842c..fb8d24ffa 100644 --- a/packages/validator/src/schemas/results-object.yaml +++ b/packages/validator/src/schemas/results-object.yaml @@ -173,4 +173,3 @@ $defs: type: number description: A number describing the demerit quality a rule has on an API minimum: 0.01 - From 89079bf39442ba9969c8a7dc8cbe7fc69301bad1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A1s=20Felleg?= Date: Tue, 13 Oct 2026 10:45:27 +0200 Subject: [PATCH 19/19] fix: render all rule violation on same object if present MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: András Felleg --- .../tables/rule-violation-details.js | 60 +++++++++------ .../tables/rule-violation-details.test.js | 76 +++++++++++++++++++ 2 files changed, 111 insertions(+), 25 deletions(-) diff --git a/packages/validator/src/markdown-report/tables/rule-violation-details.js b/packages/validator/src/markdown-report/tables/rule-violation-details.js index 459baecef..c4cdbbeca 100644 --- a/packages/validator/src/markdown-report/tables/rule-violation-details.js +++ b/packages/validator/src/markdown-report/tables/rule-violation-details.js @@ -24,20 +24,26 @@ function groupResultsByRule(results) { if (!groupedByRule[result.rule]) { groupedByRule[result.rule] = { - base, docLink: result.docLink, - violations: [], + groups: [], }; } + const ruleEntry = groupedByRule[result.rule]; + let group = ruleEntry.groups.find(g => g.base === base); + if (!group) { + group = { base, violations: [] }; + ruleEntry.groups.push(group); + } + const pathStr = result.path.join('.'); if ( - !groupedByRule[result.rule].violations.some( - v => v.line === result.line && v.path === pathStr + !group.violations.some( + v => v.line === result.line && v.path === pathStr && v.detail === detail ) ) { - groupedByRule[result.rule].violations.push({ + group.violations.push({ line: result.line, path: pathStr, detail, @@ -52,30 +58,34 @@ function renderRuleGroups(groupedByRule) { const sections = []; Object.entries(groupedByRule).forEach(([rule, data]) => { - // Create heading with link - sections.push(`### [${rule}](${data.docLink})`); + // Create heading with link or plain text when missing + sections.push( + data.docLink ? `### [${rule}](${data.docLink})` : `### ${rule}` + ); sections.push(''); - // Add the base message as italic description - sections.push(`_${data.base}_`); - sections.push(''); + data.groups.forEach(({ base, violations }) => { + // Add the base message as italic description + sections.push(`_${base}_`); + sections.push(''); + + // Use a 3-col table when any violation carries extra detail + const hasDetail = violations.some(v => v.detail); + const table = hasDetail + ? new MarkdownTable('Line', 'Path', 'Detail') + : new MarkdownTable('Line', 'Path'); + + violations.forEach(({ line, path, detail }) => { + if (hasDetail) { + table.addRow(line, path, detail); + } else { + table.addRow(line, path); + } + }); - // Use a 3-col table when any violation carries extra detail - const hasDetail = data.violations.some(v => v.detail); - const table = hasDetail - ? new MarkdownTable('Line', 'Path', 'Detail') - : new MarkdownTable('Line', 'Path'); - - data.violations.forEach(({ line, path, detail }) => { - if (hasDetail) { - table.addRow(line, path, detail); - } else { - table.addRow(line, path); - } + sections.push(table.render()); + sections.push(''); }); - - sections.push(table.render()); - sections.push(''); }); return sections.join('\n'); diff --git a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js index f131e0811..d1a8ff227 100644 --- a/packages/validator/test/markdown-report/tables/rule-violation-details.test.js +++ b/packages/validator/test/markdown-report/tables/rule-violation-details.test.js @@ -122,6 +122,82 @@ describe('ruleViolationDetails table tests', function () { expect(dataRows).toHaveLength(1); }); + it('should keep distinct details reported on the same line and path', function () { + const sameLine = { + error: { + results: [ + { + message: 'Base message: first', + path: ['paths', '/foo'], + rule: 'some-rule', + docLink: 'https://example.com', + line: 10, + }, + { + message: 'Base message: second', + path: ['paths', '/foo'], + rule: 'some-rule', + docLink: 'https://example.com', + line: 10, + }, + ], + }, + warning: { results: [] }, + }; + const output = ruleViolationDetails(sameLine); + expect(output).toContain('| 10 | paths./foo | first |'); + expect(output).toContain('| 10 | paths./foo | second |'); + }); + + it('should render a separate section per distinct base message of a rule', function () { + const multiBase = { + error: { + results: [ + { + message: 'Base A', + path: ['a'], + rule: 'some-rule', + docLink: 'https://example.com', + line: 1, + }, + { + message: 'Base B', + path: ['b'], + rule: 'some-rule', + docLink: 'https://example.com', + line: 2, + }, + ], + }, + warning: { results: [] }, + }; + const output = ruleViolationDetails(multiBase); + expect(output.match(/### \[some-rule\]/g)).toHaveLength(1); + expect(output).toContain('_Base A_'); + expect(output).toContain('_Base B_'); + expect(output).toContain('| 1 | a |'); + expect(output).toContain('| 2 | b |'); + }); + + it('should render a plain heading when docLink is empty', function () { + const noLink = { + error: { + results: [ + { + message: 'Base', + path: ['a'], + rule: 'some-rule', + docLink: '', + line: 1, + }, + ], + }, + warning: { results: [] }, + }; + const output = ruleViolationDetails(noLink); + expect(output.split('\n')[2]).toBe('### some-rule'); + }); + it('should use a 3-column table when at least one violation has a detail', function () { const withDetail = { error: {