From 0e58def46d67562b84ebc0987f96214760ada8ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E2=80=9Emarianfoo=E2=80=9C?= <13335743+marianfoo@users.noreply.github.com> Date: Tue, 8 Jul 2025 16:27:08 +0200 Subject: [PATCH] feat: Add new output format html --- src/cli/base.ts | 7 +- src/formatter/html.ts | 275 ++++++ test/lib/formatter/html.ts | 420 +++++++++ test/lib/formatter/snapshots/html.ts.md | 1014 +++++++++++++++++++++ test/lib/formatter/snapshots/html.ts.snap | Bin 0 -> 1959 bytes 5 files changed, 1715 insertions(+), 1 deletion(-) create mode 100644 src/formatter/html.ts create mode 100644 test/lib/formatter/html.ts create mode 100644 test/lib/formatter/snapshots/html.ts.md create mode 100644 test/lib/formatter/snapshots/html.ts.snap diff --git a/src/cli/base.ts b/src/cli/base.ts index 934b3e083..54be78d1f 100644 --- a/src/cli/base.ts +++ b/src/cli/base.ts @@ -2,6 +2,7 @@ import {Argv, ArgumentsCamelCase, CommandModule, MiddlewareFunction} from "yargs import {Text} from "../formatter/text.js"; import {Json} from "../formatter/json.js"; import {Markdown} from "../formatter/markdown.js"; +import {Html} from "../formatter/html.js"; import {Coverage} from "../formatter/coverage.js"; import {writeFile} from "node:fs/promises"; import baseMiddleware from "./middlewares/base.js"; @@ -106,7 +107,7 @@ const lintCommand: FixedCommandModule = { describe: "Set the output format for the linter result", default: "stylish", type: "string", - choices: ["stylish", "json", "markdown"], + choices: ["stylish", "json", "markdown", "html"], }) .option("quiet", { describe: "Report errors only", @@ -207,6 +208,10 @@ async function handleLint(argv: ArgumentsCamelCase) { const markdownFormatter = new Markdown(); process.stdout.write(markdownFormatter.format(res, details, getVersion(), fix, quiet)); process.stdout.write("\n"); + } else if (format === "html") { + const htmlFormatter = new Html(); + process.stdout.write(htmlFormatter.format(res, details, getVersion(), fix, quiet)); + process.stdout.write("\n"); } else if (format === "" || format === "stylish") { const textFormatter = new Text(rootDir); process.stderr.write(textFormatter.format(res, details, fix, quiet)); diff --git a/src/formatter/html.ts b/src/formatter/html.ts new file mode 100644 index 000000000..1a0ec6484 --- /dev/null +++ b/src/formatter/html.ts @@ -0,0 +1,275 @@ +import {LintResult, LintMessage} from "../linter/LinterContext.js"; +import {LintMessageSeverity} from "../linter/messages.js"; + +export class Html { + format(lintResults: LintResult[], showDetails: boolean, version: string, autofix: boolean, quiet = false): string { + let totalErrorCount = 0; + let totalWarningCount = 0; + let totalFatalErrorCount = 0; + + // Build the HTML content + let resultsHtml = ""; + 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 + resultsHtml += `
+

${filePath}

+ + + + + + + + ${showDetails ? "" : ""} + + + `; + + // Sort messages by severity (fatal errors first, then errors, then warnings) + messages.sort((a, b) => { + // Handle fatal errors first to push them to the bottom + if (a.fatal !== b.fatal) { + return a.fatal ? -1 : 1; // Fatal errors go to the top + } + // Then, compare by severity + if (a.severity !== b.severity) { + return b.severity - a.severity; + } + // If severity is the same, compare by line number + if ((a.line ?? 0) !== (b.line ?? 0)) { + return (a.line ?? 0) - (b.line ?? 0); + } + // If both severity and line number are the same, compare by column number + return (a.column ?? 0) - (b.column ?? 0); + }); + + // Format each message + messages.forEach((msg) => { + const severityClass = this.getSeverityClass(msg.severity, msg.fatal); + const severityText = this.formatSeverity(msg.severity, msg.fatal); + const location = `${msg.line ?? 0}:${msg.column ?? 0}`; + const rule = `${msg.ruleId}`; + + resultsHtml += ``; + resultsHtml += ``; + resultsHtml += ``; + resultsHtml += ``; + resultsHtml += ``; + if (showDetails && msg.messageDetails) { + resultsHtml += ``; + } else if (showDetails) { + resultsHtml += ``; + } + resultsHtml += ``; + }); + + resultsHtml += `
SeverityRuleLocationMessageDetails
${severityText}${rule}${location}${msg.message}${this.formatMessageDetails(msg)}
`; + }); + + // Build summary + const totalCount = quiet ? totalErrorCount : totalErrorCount + totalWarningCount; + const errorsText = `${totalErrorCount} ${totalErrorCount === 1 ? "error" : "errors"}`; + const warningsText = quiet ? "" : `, ${totalWarningCount} ${totalWarningCount === 1 ? "warning" : "warnings"}`; + const problemsText = `${totalCount} ${totalCount === 1 ? "problem" : "problems"}`; + + const summary = `
+

Summary

+

+ ${problemsText} (${errorsText}${warningsText}) +

+ ${totalFatalErrorCount ? `

${totalFatalErrorCount} fatal errors

` : ""} + ${!autofix && totalCount > 0 ? + "

Run ui5lint --fix to resolve all auto-fixable problems

" : + ""} +
`; + + // Full HTML document with some basic styling + const html = ` + + + + + UI5 Linter Report + + + +

UI5 Linter Report

+

Generated with UI5 Linter v${version}

+ + ${summary} + + ${resultsHtml && `

Findings

${resultsHtml}`} + + ${!showDetails && totalCount > 0 ? + "
Note: Use ui5lint --details " + + "to show more information about the findings.
" : + ""} + +`; + + return html; + } + + // Formats the severity of the lint message + private formatSeverity(severity: LintMessageSeverity, fatal: LintMessage["fatal"]): string { + if (fatal === true) { + return "Fatal Error"; + } else if (severity === LintMessageSeverity.Warning) { + return "Warning"; + } else if (severity === LintMessageSeverity.Error) { + return "Error"; + } else { + throw new Error(`Unknown severity: ${LintMessageSeverity[severity]}`); + } + } + + // Returns CSS class name based on severity + private getSeverityClass(severity: LintMessageSeverity, fatal: LintMessage["fatal"]): string { + if (fatal === true) { + return "fatal-error"; + } else if (severity === LintMessageSeverity.Warning) { + return "warning"; + } else if (severity === LintMessageSeverity.Error) { + return "error"; + } else { + return ""; + } + } + + // Formats additional message details if available + private formatMessageDetails(msg: LintMessage): string { + if (!msg.messageDetails) { + return ""; + } + // Replace multiple spaces, tabs, or newlines with a single space for clean output + // This more comprehensive regex handles all whitespace characters + const cleanedDetails = msg.messageDetails.replace(/[\s\t\r\n]+/g, " "); + + // Convert URLs to hyperlinks + // This regex matches http/https URLs and also patterns like ui5.sap.com/... with or without protocol + return cleanedDetails.replace( + /(https?:\/\/[^\s)]+)|(\([^(]*?)(https?:\/\/[^\s)]+)([^)]*?\))|(\b(?:www\.|ui5\.sap\.com)[^\s)]+)/g, + (match, directUrl, beforeParen, urlInParen, afterParen, domainUrl) => { + if (directUrl) { + // Direct URL without parentheses + return `${directUrl}`; + } else if (urlInParen) { + // URL inside parentheses - keep the parentheses as text but make the URL a link + return `${beforeParen}${urlInParen}${afterParen}`; + } else if (domainUrl) { + // Domain starting with www. or ui5.sap.com without http(s):// + const fullUrl = typeof domainUrl === "string" && domainUrl.startsWith("www.") ? + `http://${domainUrl}` : + `https://${domainUrl}`; + return `${domainUrl}`; + } + return match; + } + ); + } +} diff --git a/test/lib/formatter/html.ts b/test/lib/formatter/html.ts new file mode 100644 index 000000000..2ba758bd3 --- /dev/null +++ b/test/lib/formatter/html.ts @@ -0,0 +1,420 @@ +import anyTest, {TestFn} from "ava"; +import {Html} from "../../../src/formatter/html.js"; +import {LintResult} from "../../../src/linter/LinterContext.js"; +import {LintMessageSeverity} from "../../../src/linter/messages.js"; + +const test = anyTest as TestFn<{ + lintResults: LintResult[]; +}>; + +test.beforeEach((t) => { + t.context.lintResults = [ + { + filePath: "webapp/Component.js", + messages: [ + { + ruleId: "rule1", + severity: LintMessageSeverity.Error, + line: 1, + column: 1, + message: "Error message", + messageDetails: "Message details", + }, + { + ruleId: "rule2", + severity: LintMessageSeverity.Warning, + line: 2, + column: 2, + message: "Warning message", + messageDetails: "Message details", + }, + ], + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + warningCount: 1, + }, + { + filePath: "webapp/Main.controller.js", + messages: [ + { + ruleId: "rule3", + severity: LintMessageSeverity.Error, + line: 11, + column: 3, + message: "Another error message", + messageDetails: "Message details", + }, + { + ruleId: "rule3", + severity: LintMessageSeverity.Error, + line: 12, + column: 3, + message: "Another error message", + messageDetails: "Message details", + fatal: true, + }, + { + ruleId: "rule3", + severity: LintMessageSeverity.Error, + line: 3, + column: 6, + message: "Another error message", + messageDetails: "Message details", + fatal: true, + }, + { + ruleId: "rule3", + severity: LintMessageSeverity.Warning, + line: 12, + column: 3, + message: "Another error message", + messageDetails: "Message details", + }, + { + ruleId: "rule3", + severity: LintMessageSeverity.Error, + line: 11, + column: 2, + message: "Another error message", + messageDetails: "Message details", + }, + ], + coverageInfo: [], + errorCount: 4, + fatalErrorCount: 2, + warningCount: 1, + }, + // Add a file with empty messages to cover the skip condition + { + filePath: "webapp/Empty.js", + messages: [], + coverageInfo: [], + errorCount: 0, + fatalErrorCount: 0, + warningCount: 0, + }, + ]; +}); + +test("Default", (t) => { + const {lintResults} = t.context; + + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format(lintResults, false, "1.2.3", false, false); + + t.snapshot(htmlResult); +}); + +test("Details", (t) => { + const {lintResults} = t.context; + + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format(lintResults, true, "1.2.3", false, false); + + t.snapshot(htmlResult); +}); + +test("No findings", (t) => { + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format([], true, "1.2.3", false, false); + + t.snapshot(htmlResult); +}); + +// Test for message with no messageDetails +test("Message with no details", (t) => { + const lintResults: LintResult[] = [ + { + filePath: "webapp/NoDetails.js", + messages: [ + { + ruleId: "rule1", + severity: LintMessageSeverity.Error, + line: 1, + column: 1, + message: "Error with no details", + // No messageDetails provided + }, + ], + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + warningCount: 0, + }, + ]; + + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format(lintResults, true, "1.2.3", false, false); + + t.snapshot(htmlResult); + t.true(htmlResult.includes("Error with no details")); +}); + +// Test formatSeverity and getSeverityClass directly to cover all branches +test("Formatter helpers - all branches", (t) => { + const htmlFormatter = new Html(); + + // Test normal cases for formatSeverity + // @ts-expect-error Accessing private method + t.is(htmlFormatter.formatSeverity(LintMessageSeverity.Error, false), "Error"); + // @ts-expect-error Accessing private method + t.is(htmlFormatter.formatSeverity(LintMessageSeverity.Warning, false), "Warning"); + // @ts-expect-error Accessing private method + t.is(htmlFormatter.formatSeverity(LintMessageSeverity.Error, true), "Fatal Error"); + + // Test for error case in formatSeverity (invalid severity) + const error = t.throws(() => { + // @ts-expect-error Testing invalid severity and accessing private method + htmlFormatter.formatSeverity(999, false); + }, {instanceOf: Error}); + t.true(error.message.includes("Unknown severity")); + + // Test normal cases for getSeverityClass + // @ts-expect-error Accessing private method + t.is(htmlFormatter.getSeverityClass(LintMessageSeverity.Error, false), "error"); + // @ts-expect-error Accessing private method + t.is(htmlFormatter.getSeverityClass(LintMessageSeverity.Warning, false), "warning"); + // @ts-expect-error Accessing private method + t.is(htmlFormatter.getSeverityClass(LintMessageSeverity.Error, true), "fatal-error"); + + // Test default case for getSeverityClass (invalid severity) + // @ts-expect-error Testing invalid severity and accessing private method + t.is(htmlFormatter.getSeverityClass(999, false), ""); +}); + +// Test formatMessageDetails directly to cover regex replacement +test("Formatter messageDetails with whitespace", (t) => { + const htmlFormatter = new Html(); + + // Create a message with extra whitespace and newlines in messageDetails + const lintResults: LintResult[] = [ + { + filePath: "webapp/WhitespaceDetails.js", + messages: [ + { + ruleId: "rule1", + severity: LintMessageSeverity.Error, + line: 1, + column: 1, + message: "Error with whitespace in details", + messageDetails: "This has multiple spaces\nand newlines\r\nand\ttabs", + }, + ], + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + warningCount: 0, + }, + ]; + + const htmlResult = htmlFormatter.format(lintResults, true, "1.2.3", false, false); + + // Check the HTML content directly instead of the normalized text + // @ts-expect-error Accessing private method + const formattedDetails = htmlFormatter.formatMessageDetails(lintResults[0].messages[0]); + // Ensure we're testing exactly the expected output after whitespace normalization + t.is(formattedDetails, "This has multiple spaces and newlines and tabs"); + t.true(htmlResult.includes(formattedDetails)); + + // Also directly test the private method for messageDetails + // @ts-expect-error Accessing private method + const testDetails = htmlFormatter.formatMessageDetails({ + messageDetails: "Multiple spaces\nand\nnewlines", + ruleId: "test-rule", + severity: LintMessageSeverity.Error, + message: "Test message", + line: 1, + column: 1, + fatal: false, + }); + + t.is(testDetails, "Multiple spaces and newlines"); +}); + +// Test URL detection in messageDetails +test("URL detection in message details", (t) => { + const htmlFormatter = new Html(); + + // Test various URL formats + const testCases = [ + { + input: "Check https://example.com/api for details", + expected: "Check https://example.com/api for details", + }, + { + input: "Documentation at (https://ui5.sap.com/api/)", + expected: "Documentation at (https://ui5.sap.com/api/)", + }, + { + input: "See www.example.org for more information", + expected: "See www.example.org for more information", + }, + { + input: "UI5 docs ui5.sap.com/topic/documentation", + expected: "UI5 docs ui5.sap.com/topic/documentation", + }, + { + input: "No URLs in this text", + expected: "No URLs in this text", + }, + ]; + + // Test each case with the formatter + testCases.forEach(({input, expected}) => { + // @ts-expect-error Accessing private method + const result = htmlFormatter.formatMessageDetails({ + messageDetails: input, + ruleId: "test-rule", + severity: LintMessageSeverity.Error, + message: "Test message", + line: 1, + column: 1, + }); + + t.is(result, expected, `Failed to format URL in: ${input}`); + }); + + // Also verify the URLs work in the complete HTML output + const lintResults: LintResult[] = [ + { + filePath: "webapp/UrlsInDetails.js", + messages: [ + { + ruleId: "rule1", + severity: LintMessageSeverity.Error, + line: 1, + column: 1, + message: "Error with URL in details", + messageDetails: "See https://ui5.sap.com and www.example.com", + }, + ], + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + warningCount: 0, + }, + ]; + + const htmlResult = htmlFormatter.format(lintResults, true, "1.2.3", false, false); + + // Make sure both URLs were converted to links in the HTML + t.true(htmlResult.includes("https://ui5.sap.com")); + t.true(htmlResult.includes("www.example.com")); +}); + +// Test with undefined messageDetails +test("Formatter messageDetails with undefined", (t) => { + const htmlFormatter = new Html(); + + // @ts-expect-error Accessing private method + const result = htmlFormatter.formatMessageDetails({ + ruleId: "test-rule", + severity: LintMessageSeverity.Error, + message: "Test message", + line: 1, + column: 1, + }); + + t.is(result, "", "Should return empty string for undefined messageDetails"); +}); + +test("Quiet mode - errors only", (t) => { + const {lintResults} = t.context; + + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format(lintResults, false, "1.2.3", false, true); + + t.snapshot(htmlResult); + + // Should only show error count in summary, not warning count + t.true(htmlResult.includes("5 problems (5 errors)")); + // Should not include warning count in summary + t.false(htmlResult.includes(", 2 warnings")); +}); + +test("Quiet mode disabled - shows warnings", (t) => { + const {lintResults} = t.context; + + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format(lintResults, false, "1.2.3", false, false); + + t.snapshot(htmlResult); + + // Should show both errors and warnings in summary + t.true(htmlResult.includes("7 problems (5 errors, 2 warnings)")); +}); + +test("Quiet mode with single error/warning", (t) => { + const singleResultLintResults: LintResult[] = [ + { + filePath: "webapp/Single.js", + messages: [ + { + ruleId: "rule1", + severity: LintMessageSeverity.Error, + line: 1, + column: 1, + message: "Single error message", + messageDetails: "Message details", + }, + { + ruleId: "rule2", + severity: LintMessageSeverity.Warning, + line: 2, + column: 2, + message: "Single warning message", + messageDetails: "Message details", + }, + ], + coverageInfo: [], + errorCount: 1, + fatalErrorCount: 0, + warningCount: 1, + }, + ]; + + const htmlFormatter = new Html(); + + // Test quiet mode - should show "1 problem (1 error)" + const quietResult = htmlFormatter.format(singleResultLintResults, false, "1.2.3", false, true); + t.true(quietResult.includes("1 problem (1 error)")); + // Should not include warning count in summary + t.false(quietResult.includes(", 1 warning")); + + // Test normal mode - should show "2 problems (1 error, 1 warning)" + const normalResult = htmlFormatter.format(singleResultLintResults, false, "1.2.3", false, false); + t.true(normalResult.includes("2 problems (1 error, 1 warning)")); +}); + +test("Quiet mode with no errors", (t) => { + const warningOnlyResults: LintResult[] = [ + { + filePath: "webapp/WarningOnly.js", + messages: [ + { + ruleId: "rule1", + severity: LintMessageSeverity.Warning, + line: 1, + column: 1, + message: "Warning message", + messageDetails: "Message details", + }, + ], + coverageInfo: [], + errorCount: 0, + fatalErrorCount: 0, + warningCount: 1, + }, + ]; + + const htmlFormatter = new Html(); + const htmlResult = htmlFormatter.format(warningOnlyResults, false, "1.2.3", false, true); + + t.snapshot(htmlResult); + + // Should show "0 problems (0 errors)" in quiet mode + t.true(htmlResult.includes("0 problems (0 errors)")); + // Should not include warning count in summary + t.false(htmlResult.includes(", 1 warning")); +}); diff --git a/test/lib/formatter/snapshots/html.ts.md b/test/lib/formatter/snapshots/html.ts.md new file mode 100644 index 000000000..7a4d37f90 --- /dev/null +++ b/test/lib/formatter/snapshots/html.ts.md @@ -0,0 +1,1014 @@ +# Snapshot report for `test/lib/formatter/html.ts` + +The actual snapshot is saved in `html.ts.snap`. + +Generated by [AVA](https://avajs.dev). + +## Default + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 7 problems (5 errors, 2 warnings)␊ +

␊ +

2 fatal errors

␊ +

Run ui5lint --fix to resolve all auto-fixable problems

␊ +
␊ + ␊ +

Findings

␊ +

webapp/Component.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Errorrule11:1Error message
Warningrule22:2Warning message
␊ +

webapp/Main.controller.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Fatal Errorrule33:6Another error message
Fatal Errorrule312:3Another error message
Errorrule311:2Another error message
Errorrule311:3Another error message
Warningrule312:3Another error message
␊ + ␊ +
Note: Use ui5lint --details to show more information about the findings.
␊ + ␊ + ` + +## Details + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 7 problems (5 errors, 2 warnings)␊ +

␊ +

2 fatal errors

␊ +

Run ui5lint --fix to resolve all auto-fixable problems

␊ +
␊ + ␊ +

Findings

␊ +

webapp/Component.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessageDetails
Errorrule11:1Error messageMessage details
Warningrule22:2Warning messageMessage details
␊ +

webapp/Main.controller.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessageDetails
Fatal Errorrule33:6Another error messageMessage details
Fatal Errorrule312:3Another error messageMessage details
Errorrule311:2Another error messageMessage details
Errorrule311:3Another error messageMessage details
Warningrule312:3Another error messageMessage details
␊ + ␊ + ␊ + ␊ + ` + +## No findings + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 0 problems (0 errors, 0 warnings)␊ +

␊ + ␊ + ␊ +
␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ` + +## Message with no details + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 1 problem (1 error, 0 warnings)␊ +

␊ + ␊ +

Run ui5lint --fix to resolve all auto-fixable problems

␊ +
␊ + ␊ +

Findings

␊ +

webapp/NoDetails.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessageDetails
Errorrule11:1Error with no details
␊ + ␊ + ␊ + ␊ + ` + +## Quiet mode - errors only + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 5 problems (5 errors)␊ +

␊ +

2 fatal errors

␊ +

Run ui5lint --fix to resolve all auto-fixable problems

␊ +
␊ + ␊ +

Findings

␊ +

webapp/Component.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Errorrule11:1Error message
Warningrule22:2Warning message
␊ +

webapp/Main.controller.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Fatal Errorrule33:6Another error message
Fatal Errorrule312:3Another error message
Errorrule311:2Another error message
Errorrule311:3Another error message
Warningrule312:3Another error message
␊ + ␊ +
Note: Use ui5lint --details to show more information about the findings.
␊ + ␊ + ` + +## Quiet mode disabled - shows warnings + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 7 problems (5 errors, 2 warnings)␊ +

␊ +

2 fatal errors

␊ +

Run ui5lint --fix to resolve all auto-fixable problems

␊ +
␊ + ␊ +

Findings

␊ +

webapp/Component.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Errorrule11:1Error message
Warningrule22:2Warning message
␊ +

webapp/Main.controller.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Fatal Errorrule33:6Another error message
Fatal Errorrule312:3Another error message
Errorrule311:2Another error message
Errorrule311:3Another error message
Warningrule312:3Another error message
␊ + ␊ +
Note: Use ui5lint --details to show more information about the findings.
␊ + ␊ + ` + +## Quiet mode with no errors + +> Snapshot 1 + + `␊ + ␊ + ␊ + ␊ + ␊ + UI5 Linter Report␊ + ␊ + ␊ + ␊ +

UI5 Linter Report

␊ +

Generated with UI5 Linter v1.2.3

␊ + ␊ +
␊ +

Summary

␊ +

␊ + 0 problems (0 errors)␊ +

␊ + ␊ + ␊ +
␊ + ␊ +

Findings

␊ +

webapp/WarningOnly.js

␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ + ␊ +
SeverityRuleLocationMessage
Warningrule11:1Warning message
␊ + ␊ + ␊ + ␊ + ` diff --git a/test/lib/formatter/snapshots/html.ts.snap b/test/lib/formatter/snapshots/html.ts.snap new file mode 100644 index 0000000000000000000000000000000000000000..1f3e0537431236fb5c192ae670f414f9498efa14 GIT binary patch literal 1959 zcmV;Y2Uz$)RzV4!uSOwctRDC4CgZCO8)p$9AkyQefac~2fuhw{QT_G2M>S# z_+jwt7jK?_^X;F%Mybl^s1p=_5XEeK)CJZp5&*}ejum8}Fp5$vB&ehA`8UV*VYf+V zID@0^l)xLwjd1=77#$?)A_q54qyKq8jVXd@%;rL+9aysYwq#$4p@Xl=jfxn;aNA!%Xh= z(B7YbPC=0fqt`Hjy&jU7Nn3&-$+My+N*LHFka4Pf=(9!62VksbWl+ zj|K=&6xT^Pj$^{cKJw~J5vN@E=+SUEEbiZSEGu<;DD_JJLoQ+vcF2|DnUCCjjwGif zMvnkP1WCKrCfA@pglwgD?}_)72TcRRvhv0ncg(0{^}*nacBzO{igO7*s;^q^ zHFY+Fa4kzNnXe5=dq~BlB?{)s#*~bikBa_S9q+?@j$CiGXSNP%S$!1*TgO39ky#ay z2ZKRVTJ%8(F0QU4N%oWdwd^YP6C$Ltqm zX&4@OgLgKaV1=puF2Calt}3+6gmCD_-r-ggA|Ata2CWH28G9+%hE>R{ ztb=FAC!BHn6vh*ZMGrmaOmd245AD4oAqcDpXQ;@t*Fz`3DDR<+GcI!+HIt|>Cs~F? zwa#9e8NuNqJ1E*Kw$0Stw5z6W@hl(x z5m*rs5ThAUDQe}Mx_z%d44iz_=~zKbrYNFV%A;--8O5$`SwZTJ&dP+qNxd>H*MF?1 zC>K1WkV*7-AL$7sd&oo8e3oD8%uZex3ZxR8jYl5RQ?4pec&3wEIZr1H1;u0((83RI<^syk6%7EeZJFa<%>B5+i?^5AqrVJ-0$ zk96p*Jvad=@p!dB-J;VpSv+3a5-1%XffBWky0^QdK*gi4i_5@KvAzp1N(CfG-BhVu z`i?UuDxHM=h-c3E%YBFHcyU6?L#GTHC+3lK3MXVgiyw&z1-FY77UM!Yeiu^AF1jO~ z95{HiY)KEF>$}xJHV?ExC~N$TN7}KL^;Jxj_W!lK`M4fBQwHR_gYN_r)-QCn;FN;67a`c%Y0H5;*6YA6gyV4YU=BA7=FmU5Jea>{T%{n& z1!`+p?xaVq=MUfCrQ2O6ZsLZ!KF#i?8y_$lx4-V)a$s#-{w^twLOgaGdGMMm@EcWt z&LymAffy7fRJQbhO!*9DTmT|0;UX(VFb?@dA(aA3s;$3Y84x(7ehM57MwVnnSG<9& z{J26^gdr=2tQfLl$ciB=hO8K}V#taiEBA%06zMN&eYs4(zobRQbw8j+SKk)`v(>kI zOJ+Vigm;o_+>F#*Gpu*g(;I%!zNV|YghXyacJ82iw=jBlnFQT@8SZqfD;UkiYu?bD zbx!lyzgIZTzdkdZ#&8HK#^?);}?hw}CANwiwuAV2gn*2DTX3 ta{KSPZGFG(J%C50FW)e_xEG%{Z(lrr81}d!G}v$+`+u|T#af(X008wM(C+{M literal 0 HcmV?d00001