generated from SAP/repository-template
-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add new output format markdown
- Loading branch information
Showing
6 changed files
with
268 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import path from "node:path"; | ||
import {LintMessageSeverity, LintResult, LintMessage} from "../linter/LinterContext.js"; | ||
|
||
export class Markdown { | ||
format(lintResults: LintResult[], showDetails: boolean): string { | ||
let output = "# UI5 Linter Report\n\n"; | ||
let totalErrorCount = 0; | ||
let totalWarningCount = 0; | ||
let totalFatalErrorCount = 0; | ||
|
||
// Sort files alphabetically | ||
lintResults.sort((a, b) => a.filePath.localeCompare(b.filePath)); | ||
|
||
// Process each lint result | ||
lintResults.forEach(({filePath, messages, errorCount, warningCount, fatalErrorCount}) => { | ||
if (!errorCount && !warningCount) { | ||
// Skip files without errors or warnings | ||
return; | ||
} | ||
// Accumulate totals | ||
totalErrorCount += errorCount; | ||
totalWarningCount += warningCount; | ||
totalFatalErrorCount += fatalErrorCount; | ||
|
||
// Add the file path as a section header | ||
output += `## ${path.resolve(process.cwd(), filePath)}\n\n`; | ||
|
||
// Group messages by rule for easier reading | ||
const rules = new Map<string, LintMessage[]>(); | ||
messages.forEach((msg) => { | ||
const entry = rules.get(msg.ruleId); | ||
if (entry) { | ||
entry.push(msg); | ||
} else { | ||
rules.set(msg.ruleId, [msg]); | ||
} | ||
}); | ||
|
||
// Sort messages by line, then by column (falling back to 0 if undefined) | ||
messages.sort((a, b) => (a.line ?? 0) - (b.line ?? 0) || (a.column ?? 0) - (b.column ?? 0)); | ||
|
||
// Format each message | ||
messages.forEach((msg) => { | ||
const severity = this.formatSeverity(msg.severity); | ||
const location = this.formatLocation(msg.line, msg.column); | ||
const details = this.formatMessageDetails(msg, showDetails); | ||
|
||
output += `- ${severity} ${location} ${msg.fatal ? "Fatal error: " : ""}${msg.message}${details}\n`; | ||
}); | ||
|
||
output += "\n"; | ||
}); | ||
|
||
// Summary section | ||
output += "## Summary\n\n"; | ||
output += `- Total problems: ${totalErrorCount + totalWarningCount}\n`; | ||
output += ` - Errors: ${totalErrorCount}\n`; | ||
output += ` - Warnings: ${totalWarningCount}\n`; | ||
|
||
// Include fatal errors count if any | ||
if (totalFatalErrorCount) { | ||
output += ` - Fatal errors: ${totalFatalErrorCount}\n`; | ||
} | ||
|
||
// Suggest using the details option if not all details are shown | ||
if (!showDetails && (totalErrorCount + totalWarningCount + totalFatalErrorCount) > 0) { | ||
output += "\n**Note:** Use `ui5lint --details` to show more information about the findings.\n"; | ||
} | ||
|
||
return output; | ||
} | ||
|
||
// Formats the severity of the lint message using appropriate emoji | ||
private formatSeverity(severity: LintMessageSeverity): string { | ||
if (severity === LintMessageSeverity.Error) { | ||
return "🔴"; | ||
} else if (severity === LintMessageSeverity.Warning) { | ||
return "🟡"; | ||
} else { | ||
throw new Error(`Unknown severity: ${LintMessageSeverity[severity]}`); | ||
} | ||
} | ||
|
||
// Formats the location of the lint message (line and column numbers) | ||
private formatLocation(line?: number, column?: number): string { | ||
// Default to 0 if line or column are not provided | ||
return `[${line ?? 0}:${column ?? 0}]`; | ||
} | ||
|
||
// Formats additional message details if `showDetails` is true | ||
private formatMessageDetails(msg: LintMessage, showDetails: boolean): string { | ||
if (!showDetails || !msg.messageDetails) { | ||
return ""; | ||
} | ||
// Replace multiple spaces or newlines with a single space for clean output | ||
return `\n - **Details:** ${msg.messageDetails.replace(/\s\s+|\n/g, " ")}`; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
import anyTest, {TestFn} from "ava"; | ||
import {Markdown} from "../../../src/formatter/markdown.js"; | ||
import {LintResult, LintMessageSeverity} from "../../../src/linter/LinterContext.js"; | ||
|
||
const test = anyTest as TestFn<{ | ||
lintResults: LintResult[]; | ||
}>; | ||
|
||
test.beforeEach((t) => { | ||
t.context.lintResults = [{ | ||
filePath: "", | ||
messages: [{ | ||
ruleId: "ui5-linter-no-deprecated-api", | ||
severity: LintMessageSeverity.Error, | ||
line: 5, | ||
column: 1, | ||
message: "Call to deprecated function 'attachInit' of class 'Core'", | ||
messageDetails: "(since 1.118) - Please use {@link sap.ui.core.Core.ready Core.ready} instead.", | ||
}], | ||
coverageInfo: [], | ||
errorCount: 1, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
}]; | ||
}); | ||
|
||
test("Test Markdown Formatter (with '--details true')", (t) => { | ||
const {lintResults} = t.context; | ||
const markdownFormatter = new Markdown(); | ||
const markdownResult = markdownFormatter.format(lintResults, true); | ||
|
||
t.true(markdownResult.includes("# UI5 Linter Report"), | ||
"The Markdown output includes the main header"); | ||
t.true(markdownResult.includes("🔴 [5:1] Call to deprecated function 'attachInit' of class 'Core'"), | ||
"The Markdown output includes the error message with correct formatting"); | ||
t.true(markdownResult.includes("## Summary"), | ||
"The Markdown output includes a summary section"); | ||
t.true(markdownResult.includes("- Total problems: 1"), | ||
"The Markdown output includes the total problem count"); | ||
t.true(markdownResult.includes(" - Errors: 1"), | ||
"The Markdown output includes the error count"); | ||
t.true(markdownResult.includes(" - Warnings: 0"), | ||
"The Markdown output includes the warning count"); | ||
}); | ||
|
||
test("Test Markdown Formatter (with '--details false')", (t) => { | ||
const {lintResults} = t.context; | ||
const markdownFormatter = new Markdown(); | ||
const markdownResult = markdownFormatter.format(lintResults, false); | ||
|
||
t.true(markdownResult.includes("🔴 [5:1] Call to deprecated function 'attachInit' of class 'Core'"), | ||
"The Markdown output includes the error message with correct formatting"); | ||
t.false(markdownResult.includes("**Details:**"), | ||
"The Markdown output does not include the message details"); | ||
t.true(markdownResult.includes("**Note:** Use `ui5lint --details` to show more information about the findings."), | ||
"The Markdown output includes a note about using --details"); | ||
}); | ||
|
||
test("Test Markdown Formatter with multiple files and message types", (t) => { | ||
const lintResults: LintResult[] = [ | ||
{ | ||
filePath: "", | ||
messages: [ | ||
{ | ||
ruleId: "rule1", | ||
severity: LintMessageSeverity.Error, | ||
line: 1, | ||
column: 1, | ||
message: "Error message", | ||
}, | ||
{ | ||
ruleId: "rule2", | ||
severity: LintMessageSeverity.Warning, | ||
line: 2, | ||
column: 2, | ||
message: "Warning message", | ||
}, | ||
], | ||
coverageInfo: [], | ||
errorCount: 1, | ||
fatalErrorCount: 0, | ||
warningCount: 1, | ||
}, | ||
{ | ||
filePath: "", | ||
messages: [ | ||
{ | ||
ruleId: "rule3", | ||
severity: LintMessageSeverity.Error, | ||
line: 3, | ||
column: 3, | ||
message: "Another error message", | ||
}, | ||
], | ||
coverageInfo: [], | ||
errorCount: 1, | ||
fatalErrorCount: 0, | ||
warningCount: 0, | ||
}, | ||
]; | ||
|
||
const markdownFormatter = new Markdown(); | ||
const markdownResult = markdownFormatter.format(lintResults, false); | ||
|
||
t.true(markdownResult.includes("🔴 [1:1] Error message"), | ||
"The Markdown output includes the error message for file1"); | ||
t.true(markdownResult.includes("🟡 [2:2] Warning message"), | ||
"The Markdown output includes the warning message for file1"); | ||
t.true(markdownResult.includes("🔴 [3:3] Another error message"), | ||
"The Markdown output includes the error message for file2"); | ||
t.true(markdownResult.includes("- Total problems: 3"), | ||
"The Markdown output includes the correct total problem count"); | ||
t.true(markdownResult.includes(" - Errors: 2"), | ||
"The Markdown output includes the correct error count"); | ||
t.true(markdownResult.includes(" - Warnings: 1"), | ||
"The Markdown output includes the correct warning count"); | ||
}); |