Refactoring, alles in neue ordner verschoben und begonnen neu zu unterteilen. dabei wird an der logik erstmal nichts geändert
This commit is contained in:
parent
eeed4e684b
commit
07eb9f7366
|
|
@ -0,0 +1,50 @@
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
|
||||||
|
export enum PatternPhase { None = 0, Metadata = 1, Overview = 2, Constants = 3, Triggers = 4, Mapping = 5, Removal = 6, Body = 7 }
|
||||||
|
|
||||||
|
export class LinterContext {
|
||||||
|
public diagnostics: vscode.Diagnostic[] = [];
|
||||||
|
public configuredTplVersion: number = 23;
|
||||||
|
public tplVersion: number = 10;
|
||||||
|
|
||||||
|
// Status-Tracking
|
||||||
|
public hasModuleDef = false;
|
||||||
|
public inBlock: 'none' | 'pattern' | 'table' | 'definitions' | 'enumeration' | 'configuration' = 'none';
|
||||||
|
public currentPatternPhase: PatternPhase = PatternPhase.None;
|
||||||
|
public inMultiLineComment = false;
|
||||||
|
public inConditionBlock = false;
|
||||||
|
|
||||||
|
// String-Tracking (Basis-Lexer)
|
||||||
|
public inTripleDouble = false;
|
||||||
|
public inTripleSingle = false;
|
||||||
|
public stringNeedsSemicolon = false;
|
||||||
|
public justOpenedString = false;
|
||||||
|
public isClosingString = false;
|
||||||
|
public isInternalStringLine = false;
|
||||||
|
|
||||||
|
// Block- & Metadaten-Tracking
|
||||||
|
public activePhaseName = "";
|
||||||
|
public activePhaseLine = -1;
|
||||||
|
public currentPatternName = "";
|
||||||
|
public inModuleMetadata = false;
|
||||||
|
public hasModuleMetadata = false;
|
||||||
|
public moduleMetadataBlockLine = -1;
|
||||||
|
public foundModuleMetadata = { origin: false, author: false, products: false, publishers: false, tree_path: false };
|
||||||
|
|
||||||
|
// Klammer-Tracking
|
||||||
|
public parenStack: number[] = [];
|
||||||
|
public bracketStack: number[] = [];
|
||||||
|
public openDefines: number[] = [];
|
||||||
|
|
||||||
|
public addDiag(lineIdx: number, start: number, end: number, msg: string, severity: vscode.DiagnosticSeverity): vscode.Diagnostic {
|
||||||
|
const diag = new vscode.Diagnostic(new vscode.Range(lineIdx, start, lineIdx, end), msg, severity);
|
||||||
|
this.diagnostics.push(diag);
|
||||||
|
return diag;
|
||||||
|
}
|
||||||
|
|
||||||
|
public resetLineStates() {
|
||||||
|
this.isInternalStringLine = false;
|
||||||
|
this.justOpenedString = false;
|
||||||
|
this.isClosingString = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,94 @@
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { LinterContext } from './LinterContext';
|
||||||
|
import { LinterRule } from './rules/LinterRule';
|
||||||
|
import { SyntaxRule } from './rules/SyntaxRule';
|
||||||
|
import { BlockRule } from './rules/BlockRule';
|
||||||
|
|
||||||
|
export function validateTplDocument(document: vscode.TextDocument, collection: vscode.DiagnosticCollection) {
|
||||||
|
if (document.languageId !== 'tpl') { return; }
|
||||||
|
|
||||||
|
const ctx = new LinterContext();
|
||||||
|
const config = vscode.workspace.getConfiguration('tpl');
|
||||||
|
ctx.configuredTplVersion = config.get<number>('version', 23);
|
||||||
|
|
||||||
|
const text = document.getText();
|
||||||
|
const lines = text.split('\n');
|
||||||
|
|
||||||
|
// 1. Version prüfen
|
||||||
|
const versionMatch = text.match(/tpl\s+1\.(\d+)\s+module/);
|
||||||
|
if (versionMatch) {
|
||||||
|
ctx.tplVersion = parseInt(versionMatch[1], 10);
|
||||||
|
if (ctx.tplVersion < ctx.configuredTplVersion) {
|
||||||
|
const linesToSearch = text.substring(0, versionMatch.index! + 30).split('\n');
|
||||||
|
ctx.addDiag(linesToSearch.length - 1, 0, versionMatch[0].length, `Das Modul nutzt eine alte TPL Version (1.${ctx.tplVersion}). Aktuell in den Settings definiert ist 1.${ctx.configuredTplVersion}.`, vscode.DiagnosticSeverity.Warning);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Regeln laden
|
||||||
|
const rules: LinterRule[] = [
|
||||||
|
new SyntaxRule(),
|
||||||
|
new BlockRule()
|
||||||
|
];
|
||||||
|
|
||||||
|
// 3. Zeilenweise iterieren
|
||||||
|
for (let i = 0; i < lines.length; i++) {
|
||||||
|
const rawLine = lines[i];
|
||||||
|
const line = rawLine.trim();
|
||||||
|
|
||||||
|
ctx.resetLineStates();
|
||||||
|
|
||||||
|
// Basis-Lexer: Strings & Kommentare tracken (läuft vor den Regeln!)
|
||||||
|
trackStrings(rawLine, line, ctx);
|
||||||
|
|
||||||
|
if (ctx.isInternalStringLine && (ctx.inTripleDouble || ctx.inTripleSingle)) { continue; }
|
||||||
|
|
||||||
|
if (line.startsWith('//') || line.startsWith('/*')) {
|
||||||
|
if (line.startsWith('/*')) { ctx.inMultiLineComment = true; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (ctx.inMultiLineComment) {
|
||||||
|
if (line.includes('*/')) { ctx.inMultiLineComment = false; }
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (line.length === 0) { continue; }
|
||||||
|
|
||||||
|
// Regeln anwenden
|
||||||
|
for (const rule of rules) {
|
||||||
|
rule.checkLine(line, rawLine, i, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Abschluss-Prüfungen durchführen
|
||||||
|
for (const rule of rules) {
|
||||||
|
if (rule.checkEnd) {
|
||||||
|
rule.checkEnd(lines, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Diagnostics an VS Code übergeben
|
||||||
|
collection.set(document.uri, ctx.diagnostics);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Interne Hilfsfunktion für den Parser
|
||||||
|
function trackStrings(rawLine: string, line: string, ctx: LinterContext) {
|
||||||
|
const tripleDoubleCount = (rawLine.match(/"""/g) || []).length;
|
||||||
|
const tripleSingleCount = (rawLine.match(/'''/g) || []).length;
|
||||||
|
|
||||||
|
if (ctx.inTripleDouble) {
|
||||||
|
ctx.isInternalStringLine = true;
|
||||||
|
if (tripleDoubleCount % 2 !== 0) { ctx.inTripleDouble = false; ctx.isClosingString = true; }
|
||||||
|
} else if (tripleDoubleCount % 2 !== 0) {
|
||||||
|
ctx.inTripleDouble = true; ctx.justOpenedString = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.inTripleSingle) {
|
||||||
|
ctx.isInternalStringLine = true;
|
||||||
|
if (tripleSingleCount % 2 !== 0) { ctx.inTripleSingle = false; ctx.isClosingString = true; }
|
||||||
|
} else if (tripleSingleCount % 2 !== 0 && !ctx.justOpenedString) {
|
||||||
|
ctx.inTripleSingle = true; ctx.justOpenedString = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.justOpenedString) {
|
||||||
|
ctx.stringNeedsSemicolon = !line.startsWith('"""') && !line.startsWith("'''");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
import * as vscode from 'vscode';
|
||||||
|
import { LinterRule } from './LinterRule';
|
||||||
|
import { LinterContext, PatternPhase } from '../LinterContext';
|
||||||
|
|
||||||
|
export class BlockRule implements LinterRule {
|
||||||
|
|
||||||
|
public checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext): void {
|
||||||
|
if (!ctx.hasModuleDef) {
|
||||||
|
if (line.startsWith('tpl ')) { ctx.hasModuleDef = true; }
|
||||||
|
else { ctx.addDiag(lineIdx, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module <name>;' beginnen.", vscode.DiagnosticSeverity.Error); ctx.hasModuleDef = true; }
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.startsWith('metadata') && ctx.inBlock === 'none') {
|
||||||
|
ctx.inModuleMetadata = true;
|
||||||
|
ctx.hasModuleMetadata = true;
|
||||||
|
ctx.moduleMetadataBlockLine = lineIdx;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctx.inModuleMetadata) {
|
||||||
|
this.trackModuleMetadata(line, lineIdx, ctx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (line.match(/^(pattern|syncmapping|table|definitions?|enumeration|configuration)\b/)) {
|
||||||
|
if (!ctx.hasModuleMetadata && ctx.inBlock === 'none') {
|
||||||
|
ctx.addDiag(lineIdx, 0, line.length, "Ein 'metadata' Block auf Modul-Ebene ist Pflicht, bevor andere Blöcke definiert werden.", vscode.DiagnosticSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.handleBlockOpenings(line, rawLine, lineIdx, ctx);
|
||||||
|
|
||||||
|
if (ctx.inBlock === 'pattern') {
|
||||||
|
this.checkPatternPhases(line, rawLine, lineIdx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.checkEndBlocks(line, rawLine, lineIdx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
public checkEnd(lines: string[], ctx: LinterContext): void {
|
||||||
|
for (const unclosedLine of ctx.openDefines) {
|
||||||
|
ctx.addDiag(unclosedLine, 0, lines[unclosedLine].length, "Block 'define' wurde nicht mit 'end define;' geschlossen!", vscode.DiagnosticSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private trackModuleMetadata(line: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
if (line.match(/^origin\b/)) { ctx.foundModuleMetadata.origin = true; }
|
||||||
|
if (line.match(/^author\b/)) { ctx.foundModuleMetadata.author = true; }
|
||||||
|
if (line.match(/^products\b/)) { ctx.foundModuleMetadata.products = true; }
|
||||||
|
if (line.match(/^publishers\b/)) { ctx.foundModuleMetadata.publishers = true; }
|
||||||
|
if (line.match(/^tree_path\b/)) { ctx.foundModuleMetadata.tree_path = true; }
|
||||||
|
|
||||||
|
if (line.startsWith('end metadata')) {
|
||||||
|
ctx.inModuleMetadata = false;
|
||||||
|
const missing = Object.keys(ctx.foundModuleMetadata).filter(k => !(ctx.foundModuleMetadata as any)[k]);
|
||||||
|
if (missing.length > 0 && ctx.moduleMetadataBlockLine !== -1) {
|
||||||
|
const diag = ctx.addDiag(ctx.moduleMetadataBlockLine, 0, 8, `Fehlende Pflicht-Metadaten (Modul-Ebene): ${missing.join(', ')}`, vscode.DiagnosticSeverity.Error);
|
||||||
|
diag.code = 'missing_module_metadata';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleBlockOpenings(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
if (line.startsWith('definitions ') || line.startsWith('definition ')) {
|
||||||
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "Ein Definitions-Block muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'definitions';
|
||||||
|
}
|
||||||
|
else if (line.startsWith('define ')) { ctx.openDefines.push(lineIdx); }
|
||||||
|
else if (line.startsWith('enumeration ')) {
|
||||||
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "Eine Enumeration muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'enumeration';
|
||||||
|
}
|
||||||
|
else if (line.startsWith('configuration ')) {
|
||||||
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "Eine Konfiguration muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'configuration';
|
||||||
|
}
|
||||||
|
else if (line.startsWith('pattern ') || line.startsWith('syncmapping ')) {
|
||||||
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, 7, "Ein Pattern/Syncmapping kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'pattern';
|
||||||
|
ctx.currentPatternPhase = PatternPhase.None;
|
||||||
|
ctx.activePhaseName = "";
|
||||||
|
ctx.activePhaseLine = -1;
|
||||||
|
|
||||||
|
const nameMatch = line.match(/(pattern|syncmapping)\s+([a-zA-Z0-9_]+)\s+\d+\.\d+/);
|
||||||
|
if (nameMatch) { ctx.currentPatternName = nameMatch[2]; }
|
||||||
|
else { ctx.addDiag(lineIdx, 0, rawLine.length, "Ungültige Pattern-Deklaration. Erwartet: 'pattern <Name> <Version>'", vscode.DiagnosticSeverity.Error); }
|
||||||
|
}
|
||||||
|
else if (line.startsWith('table ')) {
|
||||||
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, 5, "Eine Tabelle kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'table';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkPatternPhases(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
const checkPhase = (phaseName: string, phaseEnum: PatternPhase) => {
|
||||||
|
if (line.startsWith(phaseName)) {
|
||||||
|
if (ctx.activePhaseName !== "") { ctx.addDiag(ctx.activePhaseLine, 0, ctx.activePhaseName.length, `Block '${ctx.activePhaseName}' wurde nicht mit 'end ${ctx.activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); }
|
||||||
|
if (ctx.currentPatternPhase >= phaseEnum) { ctx.addDiag(lineIdx, 0, phaseName.length, `'${phaseName}' Block ist hier nicht erlaubt.`, vscode.DiagnosticSeverity.Error); }
|
||||||
|
|
||||||
|
ctx.currentPatternPhase = phaseEnum;
|
||||||
|
ctx.activePhaseName = phaseName;
|
||||||
|
ctx.activePhaseLine = lineIdx;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
checkPhase('metadata', PatternPhase.Metadata);
|
||||||
|
checkPhase('overview', PatternPhase.Overview);
|
||||||
|
checkPhase('constants', PatternPhase.Constants);
|
||||||
|
checkPhase('triggers', PatternPhase.Triggers);
|
||||||
|
checkPhase('mapping', PatternPhase.Mapping);
|
||||||
|
checkPhase('removal', PatternPhase.Removal);
|
||||||
|
|
||||||
|
if (line.startsWith('body')) {
|
||||||
|
if (ctx.activePhaseName !== "") { ctx.addDiag(ctx.activePhaseLine, 0, ctx.activePhaseName.length, `Block '${ctx.activePhaseName}' wurde nicht mit 'end ${ctx.activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.activePhaseName = "body";
|
||||||
|
ctx.activePhaseLine = lineIdx;
|
||||||
|
}
|
||||||
|
|
||||||
|
const closers = ['end metadata', 'end overview', 'end constants', 'end triggers', 'end mapping', 'end removal', 'end body'];
|
||||||
|
for (const closer of closers) {
|
||||||
|
if (line.startsWith(closer)) {
|
||||||
|
const phase = closer.replace('end ', '').replace(';', '').trim();
|
||||||
|
if (ctx.activePhaseName !== phase) { ctx.addDiag(lineIdx, 0, line.length, `Unerwartetes '${closer}'. Erwartet wurde 'end ${ctx.activePhaseName};'.`, vscode.DiagnosticSeverity.Error); }
|
||||||
|
else { ctx.activePhaseName = ""; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkEndBlocks(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
if (line.startsWith('end pattern') || line.startsWith('end syncmapping')) {
|
||||||
|
if (ctx.activePhaseName !== "") { ctx.addDiag(ctx.activePhaseLine, 0, ctx.activePhaseName.length, `Block '${ctx.activePhaseName}' wurde nicht mit 'end ${ctx.activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); }
|
||||||
|
if (ctx.inBlock !== 'pattern') { ctx.addDiag(lineIdx, 0, 11, "'end pattern' gefunden, ohne offenen Pattern-Block.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'none';
|
||||||
|
}
|
||||||
|
else if (line.startsWith('end table') || line.startsWith('end enumeration') || line.startsWith('end configuration')) {
|
||||||
|
ctx.inBlock = 'none';
|
||||||
|
}
|
||||||
|
else if (line.startsWith('end definitions') || line.startsWith('end definition')) {
|
||||||
|
if (ctx.inBlock !== 'definitions') { ctx.addDiag(lineIdx, 0, line.length, "'end definitions' gefunden, ohne offenen Definitions-Block.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
ctx.inBlock = 'none';
|
||||||
|
}
|
||||||
|
else if (line.startsWith('end define')) {
|
||||||
|
if (ctx.openDefines.length > 0) { ctx.openDefines.pop(); }
|
||||||
|
else { ctx.addDiag(lineIdx, 0, line.length, "'end define' gefunden, ohne offenes 'define'.", vscode.DiagnosticSeverity.Error); }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import { LinterContext } from '../LinterContext';
|
||||||
|
|
||||||
|
export interface LinterRule {
|
||||||
|
// Wird für jede Code-Zeile aufgerufen
|
||||||
|
checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext): void;
|
||||||
|
|
||||||
|
// Wird ganz am Ende aufgerufen (z.B. um ungeschlossene Klammern zu prüfen)
|
||||||
|
checkEnd?(lines: string[], ctx: LinterContext): void;
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,81 @@
|
||||||
|
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): void {
|
||||||
|
this.checkTabs(rawLine, lineIdx, ctx);
|
||||||
|
this.checkBrackets(line, rawLine, lineIdx, ctx);
|
||||||
|
this.checkAssignments(rawLine, lineIdx, ctx);
|
||||||
|
this.checkSemicolons(line, rawLine, lineIdx, ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
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(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
let cleanLine = line.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, '');
|
||||||
|
const commentIdx = cleanLine.indexOf('//');
|
||||||
|
if (commentIdx !== -1) { cleanLine = cleanLine.substring(0, commentIdx); }
|
||||||
|
|
||||||
|
for (let charIdx = 0; charIdx < cleanLine.length; charIdx++) {
|
||||||
|
const char = cleanLine[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(rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
const lineWithoutStrings = rawLine.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, '""');
|
||||||
|
|
||||||
|
if (/\b(if|elif|where)\b/.test(lineWithoutStrings)) { ctx.inConditionBlock = true; }
|
||||||
|
|
||||||
|
if (lineWithoutStrings.includes('=') && !lineWithoutStrings.includes(':=') && !lineWithoutStrings.includes('>=') && !lineWithoutStrings.includes('<=')) {
|
||||||
|
if (!ctx.inConditionBlock && ctx.parenStack.length === 0 && ctx.bracketStack.length === 0 && !/\b(matches)\b/.test(lineWithoutStrings)) {
|
||||||
|
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(lineWithoutStrings) || lineWithoutStrings.endsWith(';')) { ctx.inConditionBlock = false; }
|
||||||
|
}
|
||||||
|
|
||||||
|
private checkSemicolons(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
||||||
|
if (ctx.isClosingString) {
|
||||||
|
if (ctx.stringNeedsSemicolon && !line.endsWith(';')) {
|
||||||
|
ctx.addDiag(lineIdx, Math.max(0, rawLine.length - 1), rawLine.length, "Fehlendes Semikolon am Ende der Zuweisung.", vscode.DiagnosticSeverity.Error);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
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+|\/\/|\/\*|\*)/;
|
||||||
|
const continuesOnNextLine = /(,|and|or|not|\+|-|\*|\/|=|:=|matches)$/i.test(line);
|
||||||
|
|
||||||
|
if (!noSemicolonNeeded.test(line) && !ctx.justOpenedString && ctx.parenStack.length === 0 && ctx.bracketStack.length === 0 && ctx.inBlock !== 'enumeration' && !ctx.inConditionBlock && !continuesOnNextLine) {
|
||||||
|
const lineToCheck = line.split('//')[0].trim();
|
||||||
|
if (!lineToCheck.endsWith(';') && !lineToCheck.endsWith('{') && !lineToCheck.endsWith(',')) {
|
||||||
|
ctx.addDiag(lineIdx, Math.max(0, rawLine.length - 1), rawLine.length, "Fehlendes Semikolon am Ende der Anweisung.", vscode.DiagnosticSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
312
src/extension.ts
312
src/extension.ts
|
|
@ -1,19 +1,19 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { TplCodeActionProvider } from './TplCodeActionProvider';
|
import { validateTplDocument } from './core/TplLinter';
|
||||||
import { TplDocumentFormatter } from './TplDocumentFormatter';
|
import { TplCodeActionProvider } from './providers/TplCodeActionProvider';
|
||||||
import { TplCompletionProvider } from './TplCompletionProvider';
|
import { TplDocumentFormatter } from './providers/TplDocumentFormatter';
|
||||||
import { TplHoverProvider } from './TplHoverProvider';
|
import { TplCompletionProvider } from './providers/TplCompletionProvider';
|
||||||
|
import { TplHoverProvider } from './providers/TplHoverProvider';
|
||||||
enum PatternPhase { None = 0, Metadata = 1, Overview = 2, Constants = 3, Triggers = 4, Mapping = 5, Removal = 6, Body = 7 }
|
|
||||||
|
|
||||||
export function activate(context: vscode.ExtensionContext) {
|
export function activate(context: vscode.ExtensionContext) {
|
||||||
const diagnosticCollection = vscode.languages.createDiagnosticCollection('tpl-scanner');
|
const diagnosticCollection = vscode.languages.createDiagnosticCollection('tpl-scanner');
|
||||||
context.subscriptions.push(diagnosticCollection);
|
context.subscriptions.push(diagnosticCollection);
|
||||||
|
|
||||||
|
// Linter-Events binden
|
||||||
vscode.workspace.onDidOpenTextDocument(doc => validateTplDocument(doc, diagnosticCollection));
|
vscode.workspace.onDidOpenTextDocument(doc => validateTplDocument(doc, diagnosticCollection));
|
||||||
vscode.workspace.onDidChangeTextDocument(event => validateTplDocument(event.document, diagnosticCollection));
|
vscode.workspace.onDidChangeTextDocument(event => validateTplDocument(event.document, diagnosticCollection));
|
||||||
|
|
||||||
// Sofort reagieren, wenn in den VS Code Einstellungen etwas geändert wird
|
// Live-Reload für Einstellungen
|
||||||
vscode.workspace.onDidChangeConfiguration(e => {
|
vscode.workspace.onDidChangeConfiguration(e => {
|
||||||
if (e.affectsConfiguration('tpl')) {
|
if (e.affectsConfiguration('tpl')) {
|
||||||
for (const doc of vscode.workspace.textDocuments) {
|
for (const doc of vscode.workspace.textDocuments) {
|
||||||
|
|
@ -22,308 +22,14 @@ export function activate(context: vscode.ExtensionContext) {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Provider registrieren
|
||||||
context.subscriptions.push(vscode.languages.registerCodeActionsProvider('tpl', new TplCodeActionProvider(), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }));
|
context.subscriptions.push(vscode.languages.registerCodeActionsProvider('tpl', new TplCodeActionProvider(), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }));
|
||||||
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider('tpl', new TplDocumentFormatter()));
|
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider('tpl', new TplDocumentFormatter()));
|
||||||
context.subscriptions.push(vscode.languages.registerCompletionItemProvider('tpl', new TplCompletionProvider(), '.'));
|
context.subscriptions.push(vscode.languages.registerCompletionItemProvider('tpl', new TplCompletionProvider(), '.'));
|
||||||
context.subscriptions.push(vscode.languages.registerHoverProvider('tpl', new TplHoverProvider()));
|
context.subscriptions.push(vscode.languages.registerHoverProvider('tpl', new TplHoverProvider()));
|
||||||
|
|
||||||
|
// Bei Aktivierung offene Dokumente einmal scannen
|
||||||
for (const doc of vscode.workspace.textDocuments) {
|
for (const doc of vscode.workspace.textDocuments) {
|
||||||
validateTplDocument(doc, diagnosticCollection);
|
validateTplDocument(doc, diagnosticCollection);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function validateTplDocument(document: vscode.TextDocument, collection: vscode.DiagnosticCollection) {
|
|
||||||
if (document.languageId !== 'tpl') { return; }
|
|
||||||
|
|
||||||
const config = vscode.workspace.getConfiguration('tpl');
|
|
||||||
const configuredTplVersion = config.get<number>('version', 23);
|
|
||||||
|
|
||||||
const diagnostics: vscode.Diagnostic[] = [];
|
|
||||||
const text = document.getText();
|
|
||||||
const lines = text.split('\n');
|
|
||||||
|
|
||||||
let tplVersion = 10;
|
|
||||||
let hasModuleDef = false;
|
|
||||||
|
|
||||||
let inBlock: 'none' | 'pattern' | 'table' | 'definitions' | 'enumeration' | 'configuration' = 'none';
|
|
||||||
let currentPatternPhase: PatternPhase = PatternPhase.None;
|
|
||||||
let inMultiLineComment = false;
|
|
||||||
|
|
||||||
let inTripleDouble = false;
|
|
||||||
let inTripleSingle = false;
|
|
||||||
let stringNeedsSemicolon = false;
|
|
||||||
|
|
||||||
let activePhaseName = "";
|
|
||||||
let activePhaseLine = -1;
|
|
||||||
let currentPatternName = "";
|
|
||||||
|
|
||||||
// Modul-Ebene Metadaten Tracking
|
|
||||||
let inModuleMetadata = false;
|
|
||||||
let hasModuleMetadata = false;
|
|
||||||
let moduleMetadataBlockLine = -1;
|
|
||||||
let foundModuleMetadata = { origin: false, author: false, products: false, publishers: false, tree_path: false };
|
|
||||||
|
|
||||||
const parenStack: number[] = [];
|
|
||||||
const bracketStack: number[] = [];
|
|
||||||
const openDefines: number[] = [];
|
|
||||||
|
|
||||||
// State-Tracking für mehrzeilige Vergleiche / Zuweisungen
|
|
||||||
let inConditionBlock = false;
|
|
||||||
|
|
||||||
function addDiag(lineIdx: number, start: number, end: number, msg: string, severity: vscode.DiagnosticSeverity): vscode.Diagnostic {
|
|
||||||
const diag = new vscode.Diagnostic(new vscode.Range(lineIdx, start, lineIdx, end), msg, severity);
|
|
||||||
diagnostics.push(diag);
|
|
||||||
return diag;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Version prüfen
|
|
||||||
const versionMatch = text.match(/tpl\s+1\.(\d+)\s+module/);
|
|
||||||
if (versionMatch) {
|
|
||||||
tplVersion = parseInt(versionMatch[1], 10);
|
|
||||||
// Warnung nur, wenn die Datei älter ist als die in den Settings eingestellte Version! Höher ist in Ordnung.
|
|
||||||
if (tplVersion < configuredTplVersion) {
|
|
||||||
const linesToSearch = text.substring(0, versionMatch.index! + 30).split('\n');
|
|
||||||
const lineIdx = linesToSearch.length - 1;
|
|
||||||
addDiag(lineIdx, 0, versionMatch[0].length, `Das Modul nutzt eine alte TPL Version (1.${tplVersion}). Aktuell in den Settings definiert ist 1.${configuredTplVersion}.`, vscode.DiagnosticSeverity.Warning);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (let i = 0; i < lines.length; i++) {
|
|
||||||
const rawLine = lines[i];
|
|
||||||
const line = rawLine.trim();
|
|
||||||
|
|
||||||
const tripleDoubleCount = (rawLine.match(/"""/g) || []).length;
|
|
||||||
const tripleSingleCount = (rawLine.match(/'''/g) || []).length;
|
|
||||||
|
|
||||||
let isInternalStringLine = false;
|
|
||||||
let justOpenedString = false;
|
|
||||||
let isClosingString = false;
|
|
||||||
|
|
||||||
if (inTripleDouble) {
|
|
||||||
isInternalStringLine = true;
|
|
||||||
if (tripleDoubleCount % 2 !== 0) { inTripleDouble = false; isClosingString = true; }
|
|
||||||
} else if (tripleDoubleCount % 2 !== 0) {
|
|
||||||
inTripleDouble = true; justOpenedString = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inTripleSingle) {
|
|
||||||
isInternalStringLine = true;
|
|
||||||
if (tripleSingleCount % 2 !== 0) { inTripleSingle = false; isClosingString = true; }
|
|
||||||
} else if (tripleSingleCount % 2 !== 0 && !justOpenedString) {
|
|
||||||
inTripleSingle = true; justOpenedString = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isInternalStringLine && (inTripleDouble || inTripleSingle)) { continue; }
|
|
||||||
if (justOpenedString) { stringNeedsSemicolon = !line.startsWith('"""') && !line.startsWith("'''"); }
|
|
||||||
|
|
||||||
const tabIndex = rawLine.indexOf('\t');
|
|
||||||
if (tabIndex !== -1) {
|
|
||||||
const diag = addDiag(i, tabIndex, tabIndex + 1, "Bitte Tabs durch Spaces ersetzen", vscode.DiagnosticSeverity.Warning);
|
|
||||||
diag.code = 'invalid_tab';
|
|
||||||
}
|
|
||||||
|
|
||||||
let cleanLineForParens = line.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'/g, '');
|
|
||||||
const commentIdx = cleanLineForParens.indexOf('//');
|
|
||||||
if (commentIdx !== -1) { cleanLineForParens = cleanLineForParens.substring(0, commentIdx); }
|
|
||||||
|
|
||||||
for (let charIdx = 0; charIdx < cleanLineForParens.length; charIdx++) {
|
|
||||||
const char = cleanLineForParens[charIdx];
|
|
||||||
if (char === '(') { parenStack.push(i); }
|
|
||||||
else if (char === ')') {
|
|
||||||
if (parenStack.length > 0) { parenStack.pop(); }
|
|
||||||
else { addDiag(i, 0, rawLine.length, "Unerwartete schließende Klammer ')'. Es fehlt eine öffnende Klammer.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
}
|
|
||||||
else if (char === '[') { bracketStack.push(i); }
|
|
||||||
else if (char === ']') {
|
|
||||||
if (bracketStack.length > 0) { bracketStack.pop(); }
|
|
||||||
else { addDiag(i, 0, rawLine.length, "Unerwartete schließende eckige Klammer ']'. Es fehlt eine öffnende Klammer.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('//') || line.startsWith('/*')) { if (line.startsWith('/*')) {inMultiLineComment = true;} continue; }
|
|
||||||
if (inMultiLineComment) { if (line.includes('*/')) {inMultiLineComment = false;} continue; }
|
|
||||||
if (line.length === 0) { continue; }
|
|
||||||
|
|
||||||
// --- HIER IST DER KORRIGIERTE LINTER FÜR ZUWEISUNGEN ---
|
|
||||||
const lineWithoutStrings = rawLine.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, '""');
|
|
||||||
|
|
||||||
// 1. Schaltet den Condition-Block EIN
|
|
||||||
if (/\b(if|elif|where)\b/.test(lineWithoutStrings)) {
|
|
||||||
inConditionBlock = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. "=" PRÜFUNG (solange der Block noch aktiv ist)
|
|
||||||
if (lineWithoutStrings.includes('=') && !lineWithoutStrings.includes(':=') && !lineWithoutStrings.includes('>=') && !lineWithoutStrings.includes('<=')) {
|
|
||||||
if (!inConditionBlock && parenStack.length === 0 && bracketStack.length === 0 && !/\b(matches)\b/.test(lineWithoutStrings)) {
|
|
||||||
const idx = rawLine.indexOf('=');
|
|
||||||
if (idx !== -1) {
|
|
||||||
addDiag(i, idx, idx + 1, "Falsche Zuweisung. In TPL wird ':=' für Zuweisungen genutzt.", vscode.DiagnosticSeverity.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Schaltet den Condition-Block wieder AUS (greift für die nächste Zeile)
|
|
||||||
if (/\b(then|do)\b/.test(lineWithoutStrings) || lineWithoutStrings.endsWith(';')) {
|
|
||||||
inConditionBlock = false;
|
|
||||||
}
|
|
||||||
// -------------------------------------------------------
|
|
||||||
|
|
||||||
if (isClosingString) {
|
|
||||||
if (stringNeedsSemicolon && !line.endsWith(';')) {
|
|
||||||
addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Zuweisung.", vscode.DiagnosticSeverity.Error);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
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+|\/\/|\/\*|\*)/;
|
|
||||||
const continuesOnNextLine = /(,|and|or|not|\+|-|\*|\/|=|:=|matches)$/i.test(line);
|
|
||||||
|
|
||||||
if (!noSemicolonNeeded.test(line) && !justOpenedString && parenStack.length === 0 && bracketStack.length === 0 && inBlock !== 'enumeration' && !inConditionBlock && !continuesOnNextLine) {
|
|
||||||
const lineToCheck = line.split('//')[0].trim();
|
|
||||||
if (!lineToCheck.endsWith(';') && !lineToCheck.endsWith('{') && !lineToCheck.endsWith(',')) {
|
|
||||||
addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Anweisung.", vscode.DiagnosticSeverity.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!hasModuleDef) {
|
|
||||||
if (line.startsWith('tpl ')) { hasModuleDef = true; }
|
|
||||||
else { addDiag(i, 0, rawLine.length, "Jede Datei muss mit 'tpl 1.x module <name>;' beginnen.", vscode.DiagnosticSeverity.Error); hasModuleDef = true; }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('metadata') && inBlock === 'none') {
|
|
||||||
inModuleMetadata = true;
|
|
||||||
hasModuleMetadata = true;
|
|
||||||
moduleMetadataBlockLine = i;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inModuleMetadata) {
|
|
||||||
if (line.match(/^origin\b/)) { foundModuleMetadata.origin = true; }
|
|
||||||
if (line.match(/^author\b/)) { foundModuleMetadata.author = true; }
|
|
||||||
if (line.match(/^products\b/)) { foundModuleMetadata.products = true; }
|
|
||||||
if (line.match(/^publishers\b/)) { foundModuleMetadata.publishers = true; }
|
|
||||||
if (line.match(/^tree_path\b/)) { foundModuleMetadata.tree_path = true; }
|
|
||||||
|
|
||||||
if (line.startsWith('end metadata')) {
|
|
||||||
inModuleMetadata = false;
|
|
||||||
const missing = Object.keys(foundModuleMetadata).filter(k => !(foundModuleMetadata as any)[k]);
|
|
||||||
if (missing.length > 0 && moduleMetadataBlockLine !== -1) {
|
|
||||||
const diag = addDiag(moduleMetadataBlockLine, 0, 8, `Fehlende Pflicht-Metadaten (Modul-Ebene): ${missing.join(', ')}`, vscode.DiagnosticSeverity.Error);
|
|
||||||
diag.code = 'missing_module_metadata';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.match(/^(pattern|syncmapping|table|definitions?|enumeration|configuration)\b/)) {
|
|
||||||
if (!hasModuleMetadata && inBlock === 'none') {
|
|
||||||
addDiag(i, 0, line.length, "Ein 'metadata' Block auf Modul-Ebene ist Pflicht, bevor andere Blöcke definiert werden.", vscode.DiagnosticSeverity.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('definitions ') || line.startsWith('definition ')) {
|
|
||||||
if (inBlock !== 'none') { addDiag(i, 0, line.length, "Ein Definitions-Block muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'definitions'; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('define ')) { openDefines.push(i); continue; }
|
|
||||||
|
|
||||||
if (line.startsWith('enumeration ')) {
|
|
||||||
if (inBlock !== 'none') { addDiag(i, 0, line.length, "Eine Enumeration muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'enumeration'; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('configuration ')) {
|
|
||||||
if (inBlock !== 'none') { addDiag(i, 0, line.length, "Eine Konfiguration muss auf oberster Ebene deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'configuration'; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('pattern ') || line.startsWith('syncmapping ')) {
|
|
||||||
if (inBlock !== 'none') { addDiag(i, 0, 7, "Ein Pattern/Syncmapping kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'pattern';
|
|
||||||
currentPatternPhase = PatternPhase.None;
|
|
||||||
activePhaseName = "";
|
|
||||||
activePhaseLine = -1;
|
|
||||||
|
|
||||||
const nameMatch = line.match(/(pattern|syncmapping)\s+([a-zA-Z0-9_]+)\s+\d+\.\d+/);
|
|
||||||
if (nameMatch) { currentPatternName = nameMatch[2]; }
|
|
||||||
else { addDiag(i, 0, rawLine.length, "Ungültige Pattern-Deklaration. Erwartet: 'pattern <Name> <Version>'", vscode.DiagnosticSeverity.Error); }
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('table ')) {
|
|
||||||
if (inBlock !== 'none') { addDiag(i, 0, 5, "Eine Tabelle kann nicht innerhalb eines anderen Blocks deklariert werden.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'table'; continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (inBlock === 'pattern') {
|
|
||||||
const checkPhase = (phaseName: string, phaseEnum: PatternPhase) => {
|
|
||||||
if (line.startsWith(phaseName)) {
|
|
||||||
if (activePhaseName !== "") { addDiag(activePhaseLine, 0, activePhaseName.length, `Block '${activePhaseName}' wurde nicht mit 'end ${activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); }
|
|
||||||
if (currentPatternPhase >= phaseEnum) { addDiag(i, 0, phaseName.length, `'${phaseName}' Block ist hier nicht erlaubt.`, vscode.DiagnosticSeverity.Error); }
|
|
||||||
|
|
||||||
currentPatternPhase = phaseEnum;
|
|
||||||
activePhaseName = phaseName;
|
|
||||||
activePhaseLine = i;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
checkPhase('metadata', PatternPhase.Metadata);
|
|
||||||
checkPhase('overview', PatternPhase.Overview);
|
|
||||||
checkPhase('constants', PatternPhase.Constants);
|
|
||||||
checkPhase('triggers', PatternPhase.Triggers);
|
|
||||||
checkPhase('mapping', PatternPhase.Mapping);
|
|
||||||
checkPhase('removal', PatternPhase.Removal);
|
|
||||||
|
|
||||||
if (line.startsWith('body')) {
|
|
||||||
if (activePhaseName !== "") { addDiag(activePhaseLine, 0, activePhaseName.length, `Block '${activePhaseName}' wurde nicht mit 'end ${activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); }
|
|
||||||
activePhaseName = "body";
|
|
||||||
activePhaseLine = i;
|
|
||||||
|
|
||||||
let foundLog = false;
|
|
||||||
for (let j = i + 1; j < lines.length; j++) {
|
|
||||||
const aheadLine = lines[j].trim();
|
|
||||||
if (aheadLine.startsWith('end pattern') || aheadLine.startsWith('end syncmapping')) { break; }
|
|
||||||
if (aheadLine.includes('log.debug') && aheadLine.includes(currentPatternName)) { foundLog = true; break; }
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!foundLog) {
|
|
||||||
const expectedLog = `log.debug("Start pattern ${currentPatternName}");`;
|
|
||||||
const diag = addDiag(i, 0, rawLine.length, `Im Body fehlt das Log-Statement mit dem Pattern-Namen. Bsp: ${expectedLog}`, vscode.DiagnosticSeverity.Error);
|
|
||||||
diag.code = 'missing_log_start'; diag.source = expectedLog;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const closers = ['end metadata', 'end overview', 'end constants', 'end triggers', 'end mapping', 'end removal', 'end body'];
|
|
||||||
for (const closer of closers) {
|
|
||||||
if (line.startsWith(closer)) {
|
|
||||||
const phase = closer.replace('end ', '').replace(';', '').trim();
|
|
||||||
if (activePhaseName !== phase) { addDiag(i, 0, line.length, `Unerwartetes '${closer}'. Erwartet wurde 'end ${activePhaseName};'.`, vscode.DiagnosticSeverity.Error); }
|
|
||||||
else { activePhaseName = ""; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (line.startsWith('end pattern') || line.startsWith('end syncmapping')) {
|
|
||||||
if (activePhaseName !== "") { addDiag(activePhaseLine, 0, activePhaseName.length, `Block '${activePhaseName}' wurde nicht mit 'end ${activePhaseName};' geschlossen.`, vscode.DiagnosticSeverity.Error); }
|
|
||||||
if (inBlock !== 'pattern') { addDiag(i, 0, 11, "'end pattern' gefunden, ohne offenen Pattern-Block.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'none';
|
|
||||||
}
|
|
||||||
else if (line.startsWith('end table') || line.startsWith('end enumeration') || line.startsWith('end configuration')) {
|
|
||||||
inBlock = 'none';
|
|
||||||
}
|
|
||||||
else if (line.startsWith('end definitions') || line.startsWith('end definition')) {
|
|
||||||
if (inBlock !== 'definitions') { addDiag(i, 0, line.length, "'end definitions' gefunden, ohne offenen Definitions-Block.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
inBlock = 'none';
|
|
||||||
}
|
|
||||||
else if (line.startsWith('end define')) {
|
|
||||||
if (openDefines.length > 0) { openDefines.pop(); }
|
|
||||||
else { addDiag(i, 0, line.length, "'end define' gefunden, ohne offenes 'define'.", vscode.DiagnosticSeverity.Error); }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const unclosedLine of parenStack) { addDiag(unclosedLine, 0, lines[unclosedLine].length, "Öffnende Klammer '(' wird nicht geschlossen!", vscode.DiagnosticSeverity.Error); }
|
|
||||||
for (const unclosedLine of bracketStack) { addDiag(unclosedLine, 0, lines[unclosedLine].length, "Öffnende eckige Klammer '[' wird nicht geschlossen!", vscode.DiagnosticSeverity.Error); }
|
|
||||||
for (const unclosedLine of openDefines) { addDiag(unclosedLine, 0, lines[unclosedLine].length, "Block 'define' wurde nicht mit 'end define;' geschlossen!", vscode.DiagnosticSeverity.Error); }
|
|
||||||
|
|
||||||
collection.set(document.uri, diagnostics);
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { tplBuiltIns } from './TplBuiltIns';
|
import { tplBuiltIns } from '../data/TplBuiltIns';
|
||||||
|
|
||||||
export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
||||||
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.CompletionItem[]> {
|
async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise<vscode.CompletionItem[]> {
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import * as vscode from 'vscode';
|
import * as vscode from 'vscode';
|
||||||
import { tplBuiltIns } from './TplBuiltIns';
|
import { tplBuiltIns } from '../data/TplBuiltIns';
|
||||||
|
|
||||||
export class TplHoverProvider implements vscode.HoverProvider {
|
export class TplHoverProvider implements vscode.HoverProvider {
|
||||||
public async provideHover(
|
public async provideHover(
|
||||||
|
|
@ -1,15 +0,0 @@
|
||||||
import * as assert from 'assert';
|
|
||||||
|
|
||||||
// You can import and use all API from the 'vscode' module
|
|
||||||
// as well as import your extension to test it
|
|
||||||
import * as vscode from 'vscode';
|
|
||||||
// import * as myExtension from '../../extension';
|
|
||||||
|
|
||||||
suite('Extension Test Suite', () => {
|
|
||||||
vscode.window.showInformationMessage('Start all tests.');
|
|
||||||
|
|
||||||
test('Sample test', () => {
|
|
||||||
assert.strictEqual(-1, [1, 2, 3].indexOf(5));
|
|
||||||
assert.strictEqual(-1, [1, 2, 3].indexOf(0));
|
|
||||||
});
|
|
||||||
});
|
|
||||||
Loading…
Reference in New Issue