@ui5/linter
Version:
A static code analysis tool for UI5
234 lines • 9.64 kB
JavaScript
import { Text } from "../formatter/text.js";
import { Json } from "../formatter/json.js";
import { Markdown } from "../formatter/markdown.js";
import { Coverage } from "../formatter/coverage.js";
import { writeFile } from "node:fs/promises";
import baseMiddleware from "./middlewares/base.js";
import chalk from "chalk";
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";
const lintCommand = {
command: "$0 [files...]",
describe: "Runs linter",
handler: handleLint,
middlewares: [baseMiddleware],
builder: function (args) {
args.usage("Usage: $0 [files...] [options]")
.positional("files", {
describe: "List of patterns to lint",
type: "string",
array: true,
})
.coerce([
"files",
], (arg) => {
// Yargs will also provide --files option under the hood
// Enforce an array type
if (!Array.isArray(arg)) {
// If the option is specified multiple times, use the value of the last option
return [arg];
}
return arg;
})
.option("config", {
describe: "Load a custom config by file path",
type: "string",
alias: "c",
})
.option("ignore-pattern", {
describe: "Pattern/files that will be ignored during linting. " +
"Can also be defined in ui5linter.config.js",
type: "string",
})
.array("ignore-pattern")
.option("coverage", {
describe: "Whether to provide a coverage report",
type: "boolean",
default: false,
})
.option("details", {
describe: "Print complementary information for each finding, if available",
type: "boolean",
default: false,
})
.option("fix", {
describe: "Automatically fix linter findings",
type: "boolean",
default: false,
})
.option("loglevel", {
alias: "log-level",
describe: "Set the logging level",
default: "info",
type: "string",
choices: ["silent", "error", "warn", "info", "perf", "verbose", "silly"],
})
.option("verbose", {
describe: "Enable verbose logging.",
default: false,
type: "boolean",
})
.option("perf", {
describe: "Enable performance measurements and related logging.",
default: false,
type: "boolean",
})
.option("silent", {
describe: "Disable all log output.",
default: false,
type: "boolean",
})
.option("format", {
alias: "f",
describe: "Set the output format for the linter result",
default: "stylish",
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",
})
.coerce([
"log-level",
], (arg) => {
// If an option is specified multiple times, yargs creates an array for all the values,
// independently of whether the option is of type "array" or "string".
// This is unexpected for options listed above, which should all only have only one
// definitive value. The yargs behavior could be disabled by using the parserConfiguration
// "duplicate-arguments-array": true. However, yargs would then cease to create arrays for
// those options where we *do* expect the automatic creation of arrays in case the option
// is specified multiple times. Like "--include-task".
// Also see https://github.com/yargs/yargs/issues/1318
// Note: This is not necessary for options of type "boolean"
if (Array.isArray(arg)) {
// If the option is specified multiple times, use the value of the last option
return arg[arg.length - 1];
}
return arg;
})
.example("ui5lint ./path/to/file ./path/**/*", "Execute ui5lint with specified files or glob patterns to restrict linting to the selected files only")
.example("ui5lint --coverage", "Execute ui5lint with coverage enabled");
return args;
},
};
async function handleLint(argv) {
const { files: filePatterns, coverage, ignorePattern: ignorePatterns, details, fix, format, config, ui5Config, quiet, } = argv;
let profile;
if (process.env.UI5LINT_PROFILE) {
profile = await import("./utils/profile.js");
await profile.start();
}
const rootDir = process.cwd();
const reportCoverage = !!(process.env.UI5LINT_COVERAGE_REPORT ?? coverage);
const res = await ui5lint({
rootDir,
ignorePatterns,
filePatterns,
coverage: reportCoverage,
details,
fix,
config,
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;
}
}
}
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, quiet));
process.stdout.write("\n");
}
else if (format === "markdown") {
const markdownFormatter = new Markdown();
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, quiet));
}
// Stop profiling after CLI finished execution
if (profile) {
await profile.stop();
}
if (res.some((file) => !!file.errorCount)) {
// At least one error is reported. Exit with non-zero exit code.
process.exitCode = 1;
}
}
export default function base(cli) {
cli
.showHelpOnFail(true)
.strict(true)
.fail(function (msg, err) {
if (err) {
ConsoleWriter.stop();
// Exception
if (isLogLevelEnabled("error")) {
process.stderr.write("\n");
process.stderr.write(chalk.bold.red("⚠️ Process Failed With Error\n"));
process.stderr.write("\n");
process.stderr.write(chalk.underline("Error Message:\n"));
process.stderr.write(err.message + "\n");
// Unexpected errors should always be logged with stack trace
const unexpectedErrors = ["SyntaxError", "ReferenceError", "TypeError"];
if (unexpectedErrors.includes(err.name) || isLogLevelEnabled("verbose")) {
process.stderr.write("\n");
if (err.stack) {
process.stderr.write(chalk.underline("Stack Trace:\n"));
process.stderr.write(err.stack + "\n");
process.stderr.write("\n");
}
if (err.cause instanceof Error && err.cause.stack) {
process.stderr.write(chalk.underline("Error Cause Stack Trace:\n"));
process.stderr.write(err.cause.stack + "\n");
process.stderr.write("\n");
}
process.stderr.write(chalk.dim(`If you think this is an issue of the ui5-linter, you might report it using the ` +
`following URL: `) +
chalk.dim.bold.underline(`https://github.com/UI5/linter/issues/new/choose`) + "\n");
}
else {
process.stderr.write("\n");
process.stderr.write(chalk.dim(`For details, execute the same command again with an` +
` additional '--verbose' parameter`) + "\n");
}
}
}
else {
// Yargs error
process.stderr.write(chalk.bold.yellow("Command Failed:\n"));
process.stderr.write(`${msg}\n`);
process.stderr.write("\n");
process.stderr.write(chalk.dim(`See 'ui5lint --help'`) + "\n");
}
process.exit(2);
});
cli.command(lintCommand);
}
//# sourceMappingURL=base.js.map