164 lines
9.6 KiB
TypeScript
164 lines
9.6 KiB
TypeScript
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, lines: string[]): void {
|
|
if (!ctx.hasModuleDef) {
|
|
if (line.startsWith('tpl ')) { ctx.hasModuleDef = true; }
|
|
else { ctx.addDiag(lineIdx, 0, rawLine.length, "Every file has to start with 'tpl 1.x module <name>;'", vscode.DiagnosticSeverity.Error); ctx.hasModuleDef = true; }
|
|
}
|
|
|
|
// Wenn wir im Metadata-Block sind, ignorieren wir den Rest. Die MetadataRule übernimmt!
|
|
if (ctx.inModuleMetadata || (line.startsWith('metadata') && ctx.inBlock === 'none')) {
|
|
return;
|
|
}
|
|
|
|
if (line.match(/^(pattern|syncmapping|table|definitions?|enumeration|configuration)\b/)) {
|
|
if (!ctx.hasModuleMetadata && ctx.inBlock === 'none') {
|
|
ctx.addDiag(lineIdx, 0, line.length, "A 'metadata' block on modul-level is needed before any other blocks can be defined.", vscode.DiagnosticSeverity.Error);
|
|
}
|
|
}
|
|
|
|
this.handleBlockOpenings(line, rawLine, lineIdx, ctx);
|
|
this.handleControlBlocks(line, rawLine, lineIdx, ctx);
|
|
|
|
if (ctx.inBlock === 'pattern') {
|
|
this.checkPatternPhases(line, rawLine, lineIdx, ctx, lines);
|
|
}
|
|
|
|
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' was not ended with 'end define;'!", vscode.DiagnosticSeverity.Error);
|
|
}
|
|
for (const block of ctx.openControlBlocks) {
|
|
ctx.addDiag(block.lineIdx, 0, lines[block.lineIdx].length, `Block '${block.type}' was not ended with 'end${block.type};'!`, vscode.DiagnosticSeverity.Error);
|
|
}
|
|
}
|
|
|
|
private handleControlBlocks(line: string, rawLine: string, lineIdx: number, ctx: LinterContext) {
|
|
if (/^if\b/.test(line)) { ctx.openControlBlocks.push({ type: 'if', lineIdx }); }
|
|
else if (/^for\b/.test(line)) { ctx.openControlBlocks.push({ type: 'for', lineIdx }); }
|
|
else if (/^while\b/.test(line)) { ctx.openControlBlocks.push({ type: 'while', lineIdx }); }
|
|
|
|
if (line.startsWith('end if')) { this.closeControlBlock('if', line, lineIdx, ctx); }
|
|
else if (line.startsWith('end for')) { this.closeControlBlock('for', line, lineIdx, ctx); }
|
|
else if (line.startsWith('end while')) { this.closeControlBlock('while', line, lineIdx, ctx); }
|
|
}
|
|
|
|
private closeControlBlock(expectedType: string, line: string, lineIdx: number, ctx: LinterContext) {
|
|
if (ctx.openControlBlocks.length === 0) {
|
|
ctx.addDiag(lineIdx, 0, line.length, `'end ${expectedType}' found without open block.`, vscode.DiagnosticSeverity.Error);
|
|
return;
|
|
}
|
|
|
|
const lastBlock = ctx.openControlBlocks.pop()!;
|
|
if (lastBlock.type !== expectedType) {
|
|
ctx.addDiag(lineIdx, 0, line.length, `Wrong end. Expected: 'end ${lastBlock.type};', found: 'end${expectedType};'.`, vscode.DiagnosticSeverity.Error);
|
|
ctx.openControlBlocks.push(lastBlock);
|
|
}
|
|
}
|
|
|
|
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, "A defintions block has to be declared on the highest level.", 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, "A enumeration has to be declared on the highest level.", vscode.DiagnosticSeverity.Error); }
|
|
ctx.inBlock = 'enumeration';
|
|
}
|
|
else if (line.startsWith('configuration ')) {
|
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "A configuration has to be declared on the highest level.", vscode.DiagnosticSeverity.Error); }
|
|
ctx.inBlock = 'configuration';
|
|
}
|
|
else if (line.startsWith('pattern ') || line.startsWith('syncmapping ')) {
|
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, 7, "A Pattern/Syncmapping can not be declared inside another block.", 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, "Invalid Pattern declaration. Expected: 'pattern <Name> <Version>'", vscode.DiagnosticSeverity.Error); }
|
|
}
|
|
else if (line.startsWith('table ')) {
|
|
if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, 5, "A table can not be declared inside another block.", vscode.DiagnosticSeverity.Error); }
|
|
ctx.inBlock = 'table';
|
|
}
|
|
}
|
|
|
|
private checkPatternPhases(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, lines: string[]) {
|
|
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;
|
|
|
|
let foundLog = false;
|
|
for (let j = lineIdx + 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(ctx.currentPatternName)) { foundLog = true; break; }
|
|
}
|
|
|
|
if (!foundLog) {
|
|
const expectedLog = `log.debug("Start pattern ${ctx.currentPatternName}");`;
|
|
const diag = ctx.addDiag(lineIdx, 0, rawLine.length, `Inside the Body the Log-Statement is missing, e.g. Pattern-Namen: ${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 (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' found without open 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' found without open 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' found without open define.", vscode.DiagnosticSeverity.Error); }
|
|
}
|
|
}
|
|
} |