@ui5/linter
Version:
A static code analysis tool for UI5
266 lines • 9.73 kB
JavaScript
import { createReader } from "@ui5/fs/resourceFactory";
import { resolveLinks } from "../formatter/lib/resolveLinks.js";
import { LintMessageSeverity, MESSAGE_INFO } from "./messages.js";
export var CoverageCategory;
(function (CoverageCategory) {
CoverageCategory[CoverageCategory["CallExpressionUnknownType"] = 1] = "CallExpressionUnknownType";
CoverageCategory[CoverageCategory["UnknownControlAggregation"] = 2] = "UnknownControlAggregation";
})(CoverageCategory || (CoverageCategory = {}));
export default class LinterContext {
#rootDir;
#namespace;
#rawMessages = new Map();
#coverageInfo = new Map();
#metadata = new Map();
#rootReader;
#reportCoverage;
#includeMessageDetails;
#applyAutofix;
constructor(options) {
this.#rootDir = options.rootDir;
this.#namespace = options.namespace;
this.#reportCoverage = !!options.coverage;
this.#includeMessageDetails = !!options.details;
this.#applyAutofix = !!options.fix;
}
getRootDir() {
return this.#rootDir;
}
setRootReader(rootReader) {
if (this.#rootReader) {
throw new Error("Root reader is already defined");
}
this.#rootReader = rootReader;
}
getRootReader() {
if (this.#rootReader) {
return this.#rootReader;
}
this.#rootReader = createReader({
fsBasePath: this.#rootDir,
virBasePath: "/",
});
return this.#rootReader;
}
getNamespace() {
return this.#namespace;
}
getReportCoverage() {
return this.#reportCoverage;
}
getIncludeMessageDetails() {
return this.#includeMessageDetails;
}
getApplyAutofix() {
return this.#applyAutofix;
}
getMetadata(resourcePath) {
let metadata = this.#metadata.get(resourcePath);
if (!metadata) {
metadata = {};
this.#metadata.set(resourcePath, metadata);
}
return metadata;
}
getRawLintingMessages(resourcePath) {
let rawMessages = this.#rawMessages.get(resourcePath);
if (!rawMessages) {
rawMessages = [];
this.#rawMessages.set(resourcePath, rawMessages);
}
return rawMessages;
}
addLintingMessage(resourcePath, id, args, position) {
this.getRawLintingMessages(resourcePath).push({ id, args, position });
}
addLintingMessages(resourcePath, rawMessages) {
this.getRawLintingMessages(resourcePath).push(...rawMessages);
}
getCoverageInfo(resourcePath) {
let coverageInfo = this.#coverageInfo.get(resourcePath);
if (!coverageInfo) {
coverageInfo = [];
this.#coverageInfo.set(resourcePath, coverageInfo);
}
return coverageInfo;
}
addCoverageInfo(resourcePath, coverageInfo) {
this.getCoverageInfo(resourcePath).push(coverageInfo);
}
addDirective(resourcePath, directive) {
const metadata = this.getMetadata(resourcePath);
if (!metadata.directives) {
metadata.directives = new Set();
}
metadata.directives.add(directive);
}
#getMessageFromRawMessage(rawMessage) {
const messageInfo = MESSAGE_INFO[rawMessage.id];
if (!messageInfo) {
throw new Error(`Invalid message id '${rawMessage.id}'`);
}
const messageFunc = messageInfo.message;
const message = {
ruleId: messageInfo.ruleId,
severity: messageInfo.severity,
line: rawMessage.position ? rawMessage.position.line : undefined,
column: rawMessage.position ? rawMessage.position.column : undefined,
message: messageFunc(rawMessage.args || {}),
ui5TypeInfo: rawMessage.ui5TypeInfo,
};
if (this.#includeMessageDetails) {
const detailsFunc = messageInfo.details;
const messageDetails = detailsFunc(rawMessage.args || {});
if (messageDetails) {
message.messageDetails = resolveLinks(messageDetails);
}
}
if ("fatal" in messageInfo && typeof messageInfo.fatal === "boolean") {
message.fatal = messageInfo.fatal;
}
if (message.fatal && message.severity !== LintMessageSeverity.Error) {
throw new Error(`Reports flagged as "fatal" must be of severity "Error"`);
}
return message;
}
#getFilteredMessages(resourcePath) {
const sortFn = (a, b) => {
const aPos = a.position ?? { line: 0, column: 0 };
const bPos = b.position ?? { line: 0, column: 0 };
return aPos.line === bPos.line ? aPos.column - bPos.column : aPos.line - bPos.line;
};
const rawMessages = this.#rawMessages.get(resourcePath);
if (!rawMessages) {
return [];
}
const metadata = this.#metadata.get(resourcePath);
if (!metadata?.directives?.size) {
return rawMessages.sort(sortFn);
}
const filteredMessages = [];
const directives = new Set(metadata.directives);
// Sort messages by position
const sortedMessages = rawMessages.filter((rawMessage) => {
if (!rawMessage.position) {
filteredMessages.push(rawMessage);
return false;
}
return true;
}).sort(sortFn);
// Filter messages based on directives
let directiveStack = [];
for (const rawMessage of sortedMessages) {
const { position } = rawMessage;
const { line, column } = position; // Undefined positions are already filtered out above
directiveStack = directiveStack.filter((dir) => {
// Filter out line-based directives that are no longer relevant
if (dir.scope === "line" && dir.line !== line) {
return false;
}
if (dir.scope === "next-line" && dir.line !== line - 1) {
return false;
}
return true;
});
for (const dir of directives) {
if (dir.line > line) {
continue;
}
if (dir.scope !== "line" && dir.line === line && dir.column > column) {
continue;
}
directives.delete(dir);
if (dir.scope === "line" && dir.line !== line) {
continue;
}
if (dir.scope === "next-line" && dir.line !== line - 1) {
continue;
}
directiveStack.push(dir);
}
if (!directiveStack.length) {
filteredMessages.push(rawMessage);
continue;
}
const messageInfo = MESSAGE_INFO[rawMessage.id];
if (!messageInfo) {
throw new Error(`Invalid message id '${rawMessage.id}'`);
}
let disabled = false;
for (const dir of directiveStack) {
if (dir.action === "disable" &&
(!dir.ruleNames.length || dir.ruleNames.includes(messageInfo.ruleId))) {
disabled = true;
}
else if (dir.action === "enable" &&
(!dir.ruleNames.length || dir.ruleNames.includes(messageInfo.ruleId))) {
disabled = false;
}
}
if (!disabled) {
filteredMessages.push(rawMessage);
}
}
return filteredMessages;
}
generateLintResult(resourcePath) {
const coverageInfo = this.#coverageInfo.get(resourcePath) ?? [];
let errorCount = 0;
let warningCount = 0;
let fatalErrorCount = 0;
const rawMessages = this.#getFilteredMessages(resourcePath);
const messages = rawMessages.map((rawMessage) => {
const message = this.#getMessageFromRawMessage(rawMessage);
if (message.severity === LintMessageSeverity.Error) {
errorCount++;
if (message.fatal) {
fatalErrorCount++;
}
}
else {
warningCount++;
}
return message;
});
return {
filePath: resourcePath,
messages,
coverageInfo,
errorCount,
warningCount,
fatalErrorCount,
};
}
generateLintResults() {
const lintResults = [];
let resourcePaths;
if (this.#reportCoverage) {
resourcePaths = new Set([...this.#rawMessages.keys(), ...this.#coverageInfo.keys()]).values();
}
else {
resourcePaths = this.#rawMessages.keys();
}
for (const resourcePath of resourcePaths) {
lintResults.push(this.generateLintResult(resourcePath));
}
return lintResults;
}
generateRawLintResults() {
const rawLintResults = [];
let resourcePaths;
if (this.#reportCoverage) {
resourcePaths = new Set([...this.#rawMessages.keys(), ...this.#coverageInfo.keys()]).values();
}
else {
resourcePaths = this.#rawMessages.keys();
}
for (const resourcePath of resourcePaths) {
rawLintResults.push({
filePath: resourcePath,
rawMessages: this.#getFilteredMessages(resourcePath),
});
}
return rawLintResults;
}
}
//# sourceMappingURL=LinterContext.js.map