UNPKG

@ui5/linter

Version:

A static code analysis tool for UI5

266 lines 9.73 kB
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