Skip to content

Commit beb0692

Browse files
committed
refactor: adjust rule details format in generated markdown report
Rather than generating one big table, with lots of wide columns, this generates one section, including a single table, per rule, to remove redundant information and efficiently display all the data associated with the violations of a given rule. Signed-off-by: Dustin Popp <[email protected]>
1 parent b5ace72 commit beb0692

File tree

6 files changed

+109
-32
lines changed

6 files changed

+109
-32
lines changed

packages/validator/src/cli-validator/utils/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2023 IBM Corporation.
2+
* Copyright 2023 - 2025 IBM Corporation.
33
* SPDX-License-Identifier: Apache2.0
44
*/
55

@@ -8,6 +8,7 @@ module.exports = {
88
createCLIOptions: require('./cli-options'),
99
getCopyrightString: require('./get-copyright-string'),
1010
getDefaultRulesetVersion: require('./get-default-ruleset-version'),
11+
parseViolationMessage: require('./parse-violation-message'),
1112
getLocalRulesetVersion: require('./get-local-ruleset-version'),
1213
getVersionString: require('./get-version-string'),
1314
preprocessFile: require('./preprocess-file'),
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
/**
2+
* Copyright 2025 IBM Corporation.
3+
* SPDX-License-Identifier: Apache2.0
4+
*/
5+
6+
// Rule violations messages, use a standard format of
7+
// "<general message>: <any specific details>". This function
8+
// provides a quick utility to extract the generalized and
9+
// detail sections from the message.
10+
function parseViolationMessage(message) {
11+
const [general, detail] = message.split(':');
12+
return [general.trim(), detail?.trim()];
13+
}
14+
15+
module.exports = parseViolationMessage;
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,68 @@
11
/**
2-
* Copyright 2024 IBM Corporation.
2+
* Copyright 2024 - 2025 IBM Corporation.
33
* SPDX-License-Identifier: Apache2.0
44
*/
55

6+
const { parseViolationMessage } = require('../../cli-validator/utils');
7+
68
const MarkdownTable = require('../markdown-table');
79

8-
function getTable({ error, warning }) {
9-
const table = new MarkdownTable(
10-
'Rule',
11-
'Message',
12-
'Path',
13-
'Line',
14-
'Severity'
15-
);
10+
function getTables(violations) {
11+
// Stores the header and the table for each rule.
12+
const ruleReports = {};
13+
14+
for (const severity of ['error', 'warning']) {
15+
for (const { message, path, rule, line } of violations[severity].results) {
16+
const [generalizedMessage, details] = parseViolationMessage(message);
17+
18+
// Add a new entry for this rule.
19+
if (!ruleReports[rule]) {
20+
ruleReports[rule] = {
21+
header: createHeader(rule, severity, generalizedMessage),
22+
};
1623

17-
error.results.forEach(({ message, path, rule, line }) => {
18-
table.addRow(rule, message, path.join('.'), line, 'error');
19-
});
24+
// Add an extra column if the rule includes details in the message.
25+
if (details) {
26+
ruleReports[rule].table = new MarkdownTable(
27+
'Line',
28+
'Path',
29+
'Details'
30+
);
31+
} else {
32+
ruleReports[rule].table = new MarkdownTable('Line', 'Path');
33+
}
34+
}
2035

21-
warning.results.forEach(({ message, path, rule, line }) => {
22-
table.addRow(rule, message, path.join('.'), line, 'warning');
23-
});
36+
// Add additional rows to the table for the rule.
37+
if (details) {
38+
ruleReports[rule].table.addRow(line, path.join('.'), details);
39+
} else {
40+
ruleReports[rule].table.addRow(line, path.join('.'));
41+
}
42+
}
43+
}
2444

25-
return table.render();
45+
let tableOutput = '';
46+
for (const { header, table } of Object.values(ruleReports)) {
47+
tableOutput += `${header}${table.render()}\n\n`;
48+
}
49+
50+
// Remove the final newline characters from the string.
51+
return tableOutput.trim();
2652
}
2753

28-
module.exports = getTable;
54+
module.exports = getTables;
55+
56+
function createHeader(ruleName, severity, generalizedMessage) {
57+
const severityColors = {
58+
error: '🔴',
59+
warning: '🟠',
60+
};
61+
62+
// Template string for header.
63+
return `### ${severityColors[severity]} ${ruleName}
64+
65+
_${generalizedMessage}_
66+
67+
`;
68+
}

packages/validator/src/spectral/index.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2017 - 2024 IBM Corporation.
2+
* Copyright 2017 - 2025 IBM Corporation.
33
* SPDX-License-Identifier: Apache2.0
44
*/
55

@@ -14,6 +14,7 @@ const {
1414
checkRulesetVersion,
1515
getFileExtension,
1616
getLocalRulesetVersion,
17+
parseViolationMessage,
1718
} = require('../cli-validator/utils');
1819

1920
const { findSpectralRuleset } = require('./utils');
@@ -91,7 +92,7 @@ function convertResults(spectralResults, { config, logger }) {
9192
});
9293

9394
// compute a generalized message for the summary
94-
const genMessage = r.message.split(':')[0];
95+
const genMessage = parseViolationMessage(r.message)[0];
9596
if (!summaryHelper[severity][genMessage]) {
9697
summaryHelper[severity][genMessage] = 0;
9798
}

packages/validator/test/markdown-report/report.test.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024 IBM Corporation.
2+
* Copyright 2024 - 2025 IBM Corporation.
33
* SPDX-License-Identifier: Apache2.0
44
*/
55

@@ -18,7 +18,7 @@ describe('getReport tests', function () {
1818
// Check all subtitle-level headers.
1919
const headers = report
2020
.split('\n')
21-
.filter(l => l.startsWith('##'))
21+
.filter(l => l.startsWith('## '))
2222
.map(l => l.slice(3));
2323
expect(headers).toEqual([
2424
'Quick view',
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Copyright 2024 IBM Corporation.
2+
* Copyright 2024 - 2025 IBM Corporation.
33
* SPDX-License-Identifier: Apache2.0
44
*/
55

@@ -8,19 +8,39 @@ const validatorResults = require('../../test-utils/mock-json-output.json');
88

99
describe('ruleViolationDetails table tests', function () {
1010
it('should produce a table with all rule violations from the results', function () {
11-
const tableRows = ruleViolationDetails(validatorResults).split('\n');
11+
// Filter out empty lines, no need to check those.
12+
const tableRows = ruleViolationDetails(validatorResults)
13+
.split('\n')
14+
.filter(row => !!row);
1215

13-
expect(tableRows).toHaveLength(5);
14-
expect(tableRows[0]).toBe('| Rule | Message | Path | Line | Severity |');
15-
expect(tableRows[1]).toBe('| --- | --- | --- | --- | --- |');
16-
expect(tableRows[2]).toBe(
17-
'| 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 |'
16+
expect(tableRows).toHaveLength(15);
17+
18+
expect(tableRows[0]).toBe(
19+
'### 🔴 ibm-no-consecutive-path-parameter-segments'
1820
);
19-
expect(tableRows[3]).toBe(
20-
"| ibm-integer-attributes | Integer schemas should define property 'minimum' | components.schemas.Pet.properties.id | 133 | error |"
21+
expect(tableRows[1]).toBe(
22+
'_Path contains two or more consecutive path parameter references_'
2123
);
24+
expect(tableRows[2]).toBe('| Line | Path | Details |');
25+
expect(tableRows[3]).toBe('| --- | --- | --- |');
2226
expect(tableRows[4]).toBe(
23-
"| ibm-anchored-patterns | A regular expression used in a 'pattern' attribute should be anchored with ^ and $ | components.schemas.Error.properties.message.pattern | 233 | warning |"
27+
'| 84 | paths./pets/{pet_id}/{id} | /pets/{pet_id}/{id} |'
28+
);
29+
expect(tableRows[5]).toBe('### 🔴 ibm-integer-attributes');
30+
expect(tableRows[6]).toBe(
31+
"_Integer schemas should define property 'minimum'_"
32+
);
33+
expect(tableRows[7]).toBe('| Line | Path |');
34+
expect(tableRows[8]).toBe('| --- | --- |');
35+
expect(tableRows[9]).toBe('| 133 | components.schemas.Pet.properties.id |');
36+
expect(tableRows[10]).toBe('### 🟠 ibm-anchored-patterns');
37+
expect(tableRows[11]).toBe(
38+
"_A regular expression used in a 'pattern' attribute should be anchored with ^ and $_"
39+
);
40+
expect(tableRows[12]).toBe('| Line | Path |');
41+
expect(tableRows[13]).toBe('| --- | --- |');
42+
expect(tableRows[14]).toBe(
43+
'| 233 | components.schemas.Error.properties.message.pattern |'
2444
);
2545
});
2646
});

0 commit comments

Comments
 (0)