114 lines
6.6 KiB
TypeScript
114 lines
6.6 KiB
TypeScript
import * as vscode from 'vscode';
|
|
import { LinterRule } from './LinterRule';
|
|
import { LinterContext } from '../LinterContext';
|
|
|
|
export class SyntaxRule implements LinterRule {
|
|
|
|
public checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, lines: string[]): void {
|
|
this.checkTabs(rawLine, lineIdx, ctx);
|
|
|
|
const todoMatch = rawLine.match(/\bTODO\s*:(.*)/i);
|
|
if (todoMatch) {
|
|
ctx.addDiag(lineIdx, todoMatch.index!, rawLine.length, `TODO: ${todoMatch[1].trim()}`, vscode.DiagnosticSeverity.Information);
|
|
}
|
|
|
|
let cleanLine = line.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, '""');
|
|
const commentIdx = cleanLine.indexOf('//');
|
|
let codePart = commentIdx !== -1 ? cleanLine.substring(0, commentIdx).trim() : cleanLine.trim();
|
|
|
|
if (codePart.length === 0) {return;}
|
|
|
|
let isStructuralKeyword = /^(tpl|pattern|syncmapping|table|definitions?|define|enumeration|configuration|metadata|overview|constants|triggers|mapping|removal|body|end|on|tags|origin|author|products|publishers|tree_path|import|from)\b/.test(codePart);
|
|
|
|
if (codePart.startsWith('"') || codePart.startsWith("'")) {
|
|
isStructuralKeyword = true;
|
|
}
|
|
|
|
const isValidExecutionArea =
|
|
ctx.activePhaseName === 'body' ||
|
|
ctx.activePhaseName === 'triggers' ||
|
|
ctx.activePhaseName === 'constants' ||
|
|
ctx.activePhaseName === 'overview' ||
|
|
ctx.activePhaseName === 'mapping' ||
|
|
ctx.activePhaseName === 'metadata' ||
|
|
ctx.inModuleMetadata ||
|
|
ctx.inBlock === 'table' ||
|
|
ctx.inBlock === 'definitions' ||
|
|
ctx.openDefines.length > 0;
|
|
|
|
// 'then' und 'do' auf alleinstehenden Zeilen sind legitimer Code
|
|
if (!isStructuralKeyword && !isValidExecutionArea && !/^(then|do)\b/.test(codePart)) {
|
|
ctx.addDiag(lineIdx, 0, rawLine.length, "Ausführbarer Code ist hier nicht erlaubt. (Erlaubt in: 'body', 'triggers', 'constants', 'overview', 'mapping', 'metadata', 'definitions' oder 'define').", vscode.DiagnosticSeverity.Error);
|
|
}
|
|
|
|
this.checkBrackets(codePart, rawLine, lineIdx, ctx);
|
|
this.checkAssignments(codePart, rawLine, lineIdx, ctx);
|
|
this.checkSemicolons(codePart, rawLine, lineIdx, ctx, line);
|
|
}
|
|
|
|
public checkEnd(lines: string[], ctx: LinterContext): void {
|
|
for (const unclosedLine of ctx.parenStack) { ctx.addDiag(unclosedLine, 0, lines[unclosedLine].length, "Öffnende Klammer '(' wird nicht geschlossen!", vscode.DiagnosticSeverity.Error); }
|
|
for (const unclosedLine of ctx.bracketStack) { ctx.addDiag(unclosedLine, 0, lines[unclosedLine].length, "Öffnende eckige Klammer '[' wird nicht geschlossen!", vscode.DiagnosticSeverity.Error); }
|
|
}
|
|
|
|
private checkTabs(rawLine: string, lineIdx: number, ctx: LinterContext) {
|
|
const tabIndex = rawLine.indexOf('\t');
|
|
if (tabIndex !== -1) {
|
|
const diag = ctx.addDiag(lineIdx, tabIndex, tabIndex + 1, "Bitte Tabs durch Spaces ersetzen", vscode.DiagnosticSeverity.Warning);
|
|
diag.code = 'invalid_tab';
|
|
}
|
|
}
|
|
|
|
private checkBrackets(codePart: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
|
for (let charIdx = 0; charIdx < codePart.length; charIdx++) {
|
|
const char = codePart[charIdx];
|
|
if (char === '(') { ctx.parenStack.push(lineIdx); }
|
|
else if (char === ')') {
|
|
if (ctx.parenStack.length > 0) { ctx.parenStack.pop(); }
|
|
else { ctx.addDiag(lineIdx, 0, rawLine.length, "Unerwartete schließende Klammer ')'. Es fehlt eine öffnende Klammer.", vscode.DiagnosticSeverity.Error); }
|
|
}
|
|
else if (char === '[') { ctx.bracketStack.push(lineIdx); }
|
|
else if (char === ']') {
|
|
if (ctx.bracketStack.length > 0) { ctx.bracketStack.pop(); }
|
|
else { ctx.addDiag(lineIdx, 0, rawLine.length, "Unerwartete schließende eckige Klammer ']'. Es fehlt eine öffnende Klammer.", vscode.DiagnosticSeverity.Error); }
|
|
}
|
|
}
|
|
}
|
|
|
|
private checkAssignments(codePart: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
|
if (/\b(if|elif|where)\b/.test(codePart)) { ctx.inConditionBlock = true; }
|
|
|
|
if (codePart.includes('=') && !codePart.includes(':=') && !codePart.includes('>=') && !codePart.includes('<=')) {
|
|
if (!ctx.inConditionBlock && ctx.parenStack.length === 0 && ctx.bracketStack.length === 0 && !/\b(matches)\b/.test(codePart)) {
|
|
const idx = rawLine.indexOf('=');
|
|
if (idx !== -1) {
|
|
ctx.addDiag(lineIdx, idx, idx + 1, "Falsche Zuweisung. In TPL wird ':=' für Zuweisungen genutzt.", vscode.DiagnosticSeverity.Error);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (/\b(then|do)\b/.test(codePart) || codePart.endsWith(';')) { ctx.inConditionBlock = false; }
|
|
}
|
|
|
|
private checkSemicolons(codePart: string, rawLine: string, lineIdx: number, ctx: LinterContext, originalTrimmedLine: string) {
|
|
if (ctx.isClosingString) {
|
|
if (ctx.stringNeedsSemicolon && !originalTrimmedLine.endsWith(';')) {
|
|
ctx.addDiag(lineIdx, Math.max(0, rawLine.length - 1), rawLine.length, "Fehlendes Semikolon am Ende der Zuweisung.", vscode.DiagnosticSeverity.Error);
|
|
}
|
|
} else {
|
|
// "then" und "do" dürfen am Anfang einer Zeile stehen, ohne ein Semikolon zu erfordern
|
|
const noSemicolonNeeded = /^(pattern|syncmapping|traverse|table|enumeration|configuration|definitions?|define|metadata|overview|constants|triggers|mapping|removal|body|end\s+|if\s+|else|elif\s+|for\s+|while\s+|then\b|do\b|\/\/|\/\*|\*)/.test(codePart);
|
|
|
|
// "in" (für for-Schleifen) als gültigen Zeilenumbruch hinzugefügt
|
|
const continuesOnNextLine = /(,|and|or|not|\+|-|\*|\/|=|:=|matches|in)$/i.test(codePart);
|
|
|
|
const endsWithBlockOpener = /\b(then|do)$/.test(codePart);
|
|
|
|
if (!noSemicolonNeeded && !ctx.justOpenedString && ctx.parenStack.length === 0 && ctx.bracketStack.length === 0 && ctx.inBlock !== 'enumeration' && !ctx.inConditionBlock && !continuesOnNextLine && !endsWithBlockOpener) {
|
|
if (codePart.length > 0 && !codePart.endsWith(';') && !codePart.endsWith('{') && !codePart.endsWith(',')) {
|
|
ctx.addDiag(lineIdx, Math.max(0, rawLine.length - 1), rawLine.length, "Fehlendes Semikolon am Ende der Anweisung.", vscode.DiagnosticSeverity.Error);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} |