Skip to content

refactor: adjust rule details format in generated markdown report #740

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: 2.x
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/validator/src/cli-validator/utils/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2023 IBM Corporation.
* Copyright 2023 - 2025 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

Expand All @@ -8,6 +8,7 @@ module.exports = {
createCLIOptions: require('./cli-options'),
getCopyrightString: require('./get-copyright-string'),
getDefaultRulesetVersion: require('./get-default-ruleset-version'),
parseViolationMessage: require('./parse-violation-message'),
getLocalRulesetVersion: require('./get-local-ruleset-version'),
getVersionString: require('./get-version-string'),
preprocessFile: require('./preprocess-file'),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Copyright 2025 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

// Rule violations messages, use a standard format of
// "<general message>: <any specific details>". This function
// provides a quick utility to extract the generalized and
// detail sections from the message.
function parseViolationMessage(message) {
const [general, detail] = message.split(':');
return [general.trim(), detail?.trim()];
}

module.exports = parseViolationMessage;
Original file line number Diff line number Diff line change
@@ -1,28 +1,68 @@
/**
* Copyright 2024 IBM Corporation.
* Copyright 2024 - 2025 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

const { parseViolationMessage } = require('../../cli-validator/utils');

const MarkdownTable = require('../markdown-table');

function getTable({ error, warning }) {
const table = new MarkdownTable(
'Rule',
'Message',
'Path',
'Line',
'Severity'
);
function getTables(violations) {
// Stores the header and the table for each rule.
const ruleReports = {};

for (const severity of ['error', 'warning']) {
for (const { message, path, rule, line } of violations[severity].results) {
const [generalizedMessage, details] = parseViolationMessage(message);

// Add a new entry for this rule.
if (!ruleReports[rule]) {
ruleReports[rule] = {
header: createHeader(rule, severity, generalizedMessage),
};

error.results.forEach(({ message, path, rule, line }) => {
table.addRow(rule, message, path.join('.'), line, 'error');
});
// Add an extra column if the rule includes details in the message.
if (details) {
ruleReports[rule].table = new MarkdownTable(
'Line',
'Path',
'Details'
);
} else {
ruleReports[rule].table = new MarkdownTable('Line', 'Path');
}
}

warning.results.forEach(({ message, path, rule, line }) => {
table.addRow(rule, message, path.join('.'), line, 'warning');
});
// Add additional rows to the table for the rule.
if (details) {
ruleReports[rule].table.addRow(line, path.join('.'), details);
} else {
ruleReports[rule].table.addRow(line, path.join('.'));
}
}
}

return table.render();
let tableOutput = '';
for (const { header, table } of Object.values(ruleReports)) {
tableOutput += `${header}${table.render()}\n\n`;
}

// Remove the final newline characters from the string.
return tableOutput.trim();
}

module.exports = getTable;
module.exports = getTables;

function createHeader(ruleName, severity, generalizedMessage) {
const severityColors = {
error: '🔴',
warning: '🟠',
};

// Template string for header.
return `### ${severityColors[severity]} ${ruleName}

_${generalizedMessage}_

`;
}
5 changes: 3 additions & 2 deletions packages/validator/src/spectral/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2017 - 2024 IBM Corporation.
* Copyright 2017 - 2025 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

Expand All @@ -14,6 +14,7 @@ const {
checkRulesetVersion,
getFileExtension,
getLocalRulesetVersion,
parseViolationMessage,
} = require('../cli-validator/utils');

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

// compute a generalized message for the summary
const genMessage = r.message.split(':')[0];
const genMessage = parseViolationMessage(r.message)[0];
if (!summaryHelper[severity][genMessage]) {
summaryHelper[severity][genMessage] = 0;
}
Expand Down
4 changes: 2 additions & 2 deletions packages/validator/test/markdown-report/report.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2024 IBM Corporation.
* Copyright 2024 - 2025 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

Expand All @@ -18,7 +18,7 @@ describe('getReport tests', function () {
// Check all subtitle-level headers.
const headers = report
.split('\n')
.filter(l => l.startsWith('##'))
.filter(l => l.startsWith('## '))
.map(l => l.slice(3));
expect(headers).toEqual([
'Quick view',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* Copyright 2024 IBM Corporation.
* Copyright 2024 - 2025 IBM Corporation.
* SPDX-License-Identifier: Apache2.0
*/

Expand All @@ -8,19 +8,39 @@ 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');
// Filter out empty lines, no need to check those.
const tableRows = ruleViolationDetails(validatorResults)
.split('\n')
.filter(row => !!row);

expect(tableRows).toHaveLength(5);
expect(tableRows[0]).toBe('| Rule | Message | Path | 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 |'
expect(tableRows).toHaveLength(15);

expect(tableRows[0]).toBe(
'### 🔴 ibm-no-consecutive-path-parameter-segments'
);
expect(tableRows[3]).toBe(
"| ibm-integer-attributes | Integer schemas should define property 'minimum' | components.schemas.Pet.properties.id | 133 | error |"
expect(tableRows[1]).toBe(
'_Path contains two or more consecutive path parameter references_'
);
expect(tableRows[2]).toBe('| Line | Path | Details |');
expect(tableRows[3]).toBe('| --- | --- | --- |');
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 |"
'| 84 | paths./pets/{pet_id}/{id} | /pets/{pet_id}/{id} |'
);
expect(tableRows[5]).toBe('### 🔴 ibm-integer-attributes');
expect(tableRows[6]).toBe(
"_Integer schemas should define property 'minimum'_"
);
expect(tableRows[7]).toBe('| Line | Path |');
expect(tableRows[8]).toBe('| --- | --- |');
expect(tableRows[9]).toBe('| 133 | components.schemas.Pet.properties.id |');
expect(tableRows[10]).toBe('### 🟠 ibm-anchored-patterns');
expect(tableRows[11]).toBe(
"_A regular expression used in a 'pattern' attribute should be anchored with ^ and $_"
);
expect(tableRows[12]).toBe('| Line | Path |');
expect(tableRows[13]).toBe('| --- | --- |');
expect(tableRows[14]).toBe(
'| 233 | components.schemas.Error.properties.message.pattern |'
);
});
});