Skip to content

feat: Add option quiet to hide warnings in output #629

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

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
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
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
- [`--details`](#--details)
- [`--format`](#--format)
- [`--fix`](#--fix)
- [`--quiet`](#--quiet)
- [`--ignore-pattern`](#--ignore-pattern)
- [`--config`](#--config)
- [`--ui5-config`](#--ui5-config)
Expand Down Expand Up @@ -180,6 +181,15 @@ UI5LINT_FIX_DRY_RUN=true ui5lint --fix

In this mode, the linter will show the messages after the fixes would have been applied but will not actually change the files.

#### `--quiet`

Report errors only and hide warnings. Similar to ESLint's --quiet option.

**Example:**
```sh
ui5lint --quiet
```

#### `--ignore-pattern`

Pattern/files that will be ignored during linting. Can also be defined in `ui5lint.config.js`.
Expand Down
30 changes: 27 additions & 3 deletions src/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {isLogLevelEnabled} from "@ui5/logger";
import ConsoleWriter from "@ui5/logger/writers/Console";
import {getVersion} from "./version.js";
import {ui5lint} from "../index.js";
import {LintMessageSeverity} from "../linter/messages.js";

export interface LinterArg {
coverage: boolean;
Expand All @@ -21,6 +22,7 @@ export interface LinterArg {
format: string;
config?: string;
ui5Config?: string;
quiet: boolean;
}

// yargs type definition is missing the "middlewares" property for the CommandModule type
Expand Down Expand Up @@ -106,6 +108,12 @@ const lintCommand: FixedCommandModule<object, LinterArg> = {
type: "string",
choices: ["stylish", "json", "markdown"],
})
.option("quiet", {
describe: "Report errors only",
type: "boolean",
default: false,
alias: "q",
})
.option("ui5-config", {
describe: "Set a custom path for the UI5 Config (default: './ui5.yaml' if that file exists)",
type: "string",
Expand Down Expand Up @@ -147,6 +155,7 @@ async function handleLint(argv: ArgumentsCamelCase<LinterArg>) {
format,
config,
ui5Config,
quiet,
} = argv;

let profile;
Expand All @@ -170,22 +179,37 @@ async function handleLint(argv: ArgumentsCamelCase<LinterArg>) {
ui5Config,
});

// Apply quiet mode filtering directly to the results if needed
if (quiet) {
// Filter out warnings from all result objects
for (const result of res) {
// Keep only error messages (severity === 2)
result.messages = result.messages.filter((msg) => msg.severity === LintMessageSeverity.Error);
// Reset warning counts
result.warningCount = 0;
// Reset fixableWarningCount if it exists
if ("fixableWarningCount" in result) {
result.fixableWarningCount = 0;
}
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why not overwrite res here once so it affects all the cases below?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you´re right, makes more sense, changed that

if (coverage) {
const coverageFormatter = new Coverage();
await writeFile("ui5lint-report.html", await coverageFormatter.format(res, new Date()));
}

if (format === "json") {
const jsonFormatter = new Json();
process.stdout.write(jsonFormatter.format(res, details));
process.stdout.write(jsonFormatter.format(res, details, quiet));
process.stdout.write("\n");
} else if (format === "markdown") {
const markdownFormatter = new Markdown();
process.stdout.write(markdownFormatter.format(res, details, getVersion(), fix));
process.stdout.write(markdownFormatter.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));
process.stderr.write(textFormatter.format(res, details, fix, quiet));
}
// Stop profiling after CLI finished execution
if (profile) {
Expand Down
2 changes: 1 addition & 1 deletion src/formatter/json.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {LintMessage, LintResult} from "../linter/LinterContext.js";

export class Json {
format(lintResults: LintResult[], showDetails: boolean) {
format(lintResults: LintResult[], showDetails: boolean, quiet = false) {

Check failure on line 4 in src/formatter/json.ts

View workflow job for this annotation

GitHub Actions / General checks, tests and coverage reporting

'quiet' is assigned a value but never used. Allowed unused args must match /^_/u
const jsonFormattedResults: Pick<
LintResult,
"filePath"
Expand Down
16 changes: 12 additions & 4 deletions src/formatter/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {LintResult, LintMessage} from "../linter/LinterContext.js";
import {LintMessageSeverity} from "../linter/messages.js";

export class Markdown {
format(lintResults: LintResult[], showDetails: boolean, version: string, autofix: boolean): string {
format(lintResults: LintResult[], showDetails: boolean, version: string, autofix: boolean, quiet = false): string {
let totalErrorCount = 0;
let totalWarningCount = 0;
let totalFatalErrorCount = 0;
Expand Down Expand Up @@ -65,9 +65,17 @@ export class Markdown {
});

let summary = "## Summary\n\n";
summary +=
`> ${totalErrorCount + totalWarningCount} problems ` +
`(${totalErrorCount} errors, ${totalWarningCount} warnings) \n`;
const errorsText = `${totalErrorCount} ${totalErrorCount === 1 ? "error" : "errors"}`;
let warningsText = "";
if (!quiet) {
warningsText = `, ${totalWarningCount} ${totalWarningCount === 1 ? "warning" : "warnings"}`;
}

const totalCount = quiet ? totalErrorCount : totalErrorCount + totalWarningCount;
const problemsText = `${totalCount} ${totalCount === 1 ? "problem" : "problems"}`;

summary += `> ${problemsText} (${errorsText}${warningsText}) \n`;

if (totalFatalErrorCount) {
summary += `> **${totalFatalErrorCount} fatal errors**\n`;
}
Expand Down
17 changes: 12 additions & 5 deletions src/formatter/text.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export class Text {
constructor(private readonly cwd: string) {
}

format(lintResults: LintResult[], showDetails: boolean, autofix: boolean) {
format(lintResults: LintResult[], showDetails: boolean, autofix: boolean, quiet = false) {
this.#writeln(`UI5 linter report:`);
this.#writeln("");
let totalErrorCount = 0;
Expand Down Expand Up @@ -101,12 +101,19 @@ export class Text {
summaryColor = chalk.yellow;
}

const errorsText = `${totalErrorCount} ${totalErrorCount === 1 ? "error" : "errors"}`;
let warningsText = "";
if (!quiet) {
warningsText = `, ${totalWarningCount} ${totalWarningCount === 1 ? "warning" : "warnings"}`;
}

const totalCount = quiet ? totalErrorCount : totalErrorCount + totalWarningCount;
const problemsText = `${totalCount} ${totalCount === 1 ? "problem" : "problems"}`;

this.#writeln(
summaryColor(
`${totalErrorCount + totalWarningCount} problems ` +
`(${totalErrorCount} errors, ${totalWarningCount} warnings)`
)
summaryColor(`${problemsText} (${errorsText}${warningsText})`)
);

if (!autofix && (totalErrorCount + totalWarningCount > 0)) {
this.#writeln(" Run \"ui5lint --fix\" to resolve all auto-fixable problems\n");
}
Expand Down
113 changes: 113 additions & 0 deletions test/lib/cli/base.integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,3 +77,116 @@ test.serial("ui5lint --format markdown", async (t) => {
const resultProcessStdoutNL = processStdoutWriteStub.secondCall.firstArg;
t.is(resultProcessStdoutNL, "\n", "second write only adds a single newline");
});

// Test for --quiet option with default formatter
test.serial("ui5lint --quiet", async (t) => {
const {cli, consoleLogStub, processCwdStub, processExitStub} = t.context;

// We need to manually create a stderr stub since it's not in the context
const stderrWriteStub = sinon.stub(process.stderr, "write").returns(true);

try {
// First run without quiet
await cli.parseAsync([]);
const normalOutput = stderrWriteStub.firstCall.firstArg;
t.true(normalOutput.length > 0, "Normal output is not empty");

// Reset the stub's history before the second run
stderrWriteStub.resetHistory();

// Then run with quiet
await cli.parseAsync(["--quiet"]);
const quietOutput = stderrWriteStub.firstCall.firstArg;
t.true(quietOutput.length > 0, "Quiet output is not empty");

t.is(consoleLogStub.callCount, 0, "console.log should not be used");
t.true(processCwdStub.callCount > 0, "process.cwd was called");
t.is(processExitStub.callCount, 0, "process.exit got never called");

// Reset immediately
process.exitCode = 0;

// Check that quiet output is different from normal output
t.notDeepEqual(quietOutput, normalOutput, "Quiet output differs from normal output");

// Quiet output should not contain the word "warnings" in the summary
t.false(quietOutput.includes(" warnings)"), "Quiet output should not mention warnings count");
} finally {
// Always restore the stub
stderrWriteStub.restore();
// Ensure process.exitCode is reset
process.exitCode = 0;
}
});

// Test for --quiet option with JSON format
test.serial("ui5lint --quiet --format json", async (t) => {
const {cli, processExitStub, processStdoutWriteStub} = t.context;

// Reset the stub's history
processStdoutWriteStub.resetHistory();

// First run without quiet
await cli.parseAsync(["--format", "json"]);
const normalJsonOutput = processStdoutWriteStub.firstCall.firstArg;
t.true(normalJsonOutput.length > 0, "Normal JSON output is not empty");

// Reset history for second run
processStdoutWriteStub.resetHistory();

// Run with quiet
await cli.parseAsync(["--quiet", "--format", "json"]);
const quietJsonOutput = processStdoutWriteStub.firstCall.firstArg;
t.true(quietJsonOutput.length > 0, "Quiet JSON output is not empty");

t.is(processExitStub.callCount, 0, "process.exit got never called");
process.exitCode = 0; // Reset immediately

// Parse and compare results
const normalJson = JSON.parse(normalJsonOutput) as LintResult[];
const quietJson = JSON.parse(quietJsonOutput) as LintResult[];

// Verify quiet output has warningCount set to 0
t.true(quietJson.some((file) => file.warningCount === 0),
"Quiet JSON output has warningCount set to 0");

// Compare with normalJson if it has any warnings
if (normalJson.some((file) => file.warningCount > 0)) {
t.notDeepEqual(normalJson, quietJson, "Quiet JSON output differs from normal JSON output");
}
});

// Test for --quiet option with Markdown format
test.serial("ui5lint --quiet --format markdown", async (t) => {
const {cli, processExitStub, processStdoutWriteStub} = t.context;

// Reset the stub's history
processStdoutWriteStub.resetHistory();

// First run without quiet
await cli.parseAsync(["--format", "markdown"]);
const normalMarkdownOutput = processStdoutWriteStub.firstCall.firstArg;
t.true(normalMarkdownOutput.length > 0, "Normal Markdown output is not empty");

// Reset history for second run
processStdoutWriteStub.resetHistory();

// Run with quiet
await cli.parseAsync(["--quiet", "--format", "markdown"]);
const quietMarkdownOutput = processStdoutWriteStub.firstCall.firstArg;
t.true(quietMarkdownOutput.length > 0, "Quiet Markdown output is not empty");

t.is(processExitStub.callCount, 0, "process.exit got never called");
process.exitCode = 0; // Reset immediately

// Check outputs
const errorMsg = "Quiet Markdown output differs from normal output";
t.notDeepEqual(quietMarkdownOutput, normalMarkdownOutput, errorMsg);

// Quiet output should not contain the word "warnings" in the summary
const warnMsg = "Quiet Markdown output should not mention warnings";
t.false(quietMarkdownOutput.includes(" warnings"), warnMsg);
});

// Always reset exit code at the end
process.exitCode = 0;
35 changes: 35 additions & 0 deletions test/lib/cli/base.ts
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,41 @@ test.serial("ui5lint --ui5-config", async (t) => {
});
});

test.serial("ui5lint --quiet", async (t) => {
const {cli, ui5lint, formatText} = t.context;

// Create a mock result with both errors and warnings
const lintResultWithErrorsAndWarnings: LintResult = {
filePath: "test.js",
messages: [
{ruleId: "rule1", severity: 1, message: "Warning message"}, // Warning
{ruleId: "rule2", severity: 2, message: "Error message"}, // Error
],
coverageInfo: [],
errorCount: 1,
fatalErrorCount: 0,
warningCount: 1,
};

// Override the default result with our custom one
ui5lint.resolves([lintResultWithErrorsAndWarnings]);

await cli.parseAsync(["--quiet"]);

t.true(ui5lint.calledOnce, "Linter is called");

// Verify that formatText is called with filtered results containing only errors
t.true(formatText.calledOnce, "Text formatter has been called");

const formatterResults = formatText.getCall(0).args[0];
t.is(formatterResults[0].messages.length, 1, "Only error messages are included");
t.is(formatterResults[0].messages[0].severity, 2, "Only messages with severity 2 (error) are kept");
t.is(formatterResults[0].warningCount, 0, "Warning count is reset to 0");
t.is(process.exitCode, 1, "Exit code is reset to 1");
// reset process.exitCode
process.exitCode = 0;
});

test.serial("ui5lint path/to/file.js glob/**/*", async (t) => {
const {cli, ui5lint} = t.context;

Expand Down
Loading