2026-10-11 02:31:12 +00:00
import * as vscode from 'vscode' ;
import { LinterRule } from './LinterRule' ;
import { LinterContext , PatternPhase } from '../LinterContext' ;
export class BlockRule implements LinterRule {
2026-10-11 04:11:11 +00:00
public checkLine ( line : string , rawLine : string , lineIdx : number , ctx : LinterContext , lines : string [ ] ) : void {
2026-10-11 02:31:12 +00:00
if ( ! ctx . hasModuleDef ) {
if ( line . startsWith ( 'tpl ' ) ) { ctx . hasModuleDef = true ; }
2026-10-18 02:38:07 +00:00
else { ctx . addDiag ( lineIdx , 0 , rawLine . length , "Every file has to start with 'tpl 1.x module <name>;'" , vscode . DiagnosticSeverity . Error ) ; ctx . hasModuleDef = true ; }
2026-10-11 02:31:12 +00:00
}
2026-10-18 01:14:13 +00:00
// Wenn wir im Metadata-Block sind, ignorieren wir den Rest. Die MetadataRule übernimmt!
2026-10-18 01:03:33 +00:00
if ( ctx . inModuleMetadata || ( line . startsWith ( 'metadata' ) && ctx . inBlock === 'none' ) ) {
return ;
2026-10-11 02:31:12 +00:00
}
if ( line . match ( /^(pattern|syncmapping|table|definitions?|enumeration|configuration)\b/ ) ) {
if ( ! ctx . hasModuleMetadata && ctx . inBlock === 'none' ) {
2026-10-18 02:38:07 +00:00
ctx . addDiag ( lineIdx , 0 , line . length , "A 'metadata' block on modul-level is needed before any other blocks can be defined." , vscode . DiagnosticSeverity . Error ) ;
2026-10-11 02:31:12 +00:00
}
}
this . handleBlockOpenings ( line , rawLine , lineIdx , ctx ) ;
2026-10-12 19:43:46 +00:00
this . handleControlBlocks ( line , rawLine , lineIdx , ctx ) ;
2026-10-11 02:31:12 +00:00
if ( ctx . inBlock === 'pattern' ) {
2026-10-11 04:11:11 +00:00
this . checkPatternPhases ( line , rawLine , lineIdx , ctx , lines ) ;
2026-10-11 02:31:12 +00:00
}
this . checkEndBlocks ( line , rawLine , lineIdx , ctx ) ;
}
2026-10-18 01:14:13 +00:00
public checkEnd ( lines : string [ ] , ctx : LinterContext ) : void {
for ( const unclosedLine of ctx . openDefines ) {
2026-10-18 02:38:07 +00:00
ctx . addDiag ( unclosedLine , 0 , lines [ unclosedLine ] . length , "Block 'define' was not ended with 'end define;'!" , vscode . DiagnosticSeverity . Error ) ;
2026-10-18 01:14:13 +00:00
}
for ( const block of ctx . openControlBlocks ) {
2026-10-18 02:38:07 +00:00
ctx . addDiag ( block . lineIdx , 0 , lines [ block . lineIdx ] . length , ` Block ' ${ block . type } ' was not ended with 'end ${ block . type } ;'! ` , vscode . DiagnosticSeverity . Error ) ;
2026-10-18 01:14:13 +00:00
}
}
2026-10-12 19:43:46 +00:00
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 ) {
2026-10-18 02:38:07 +00:00
ctx . addDiag ( lineIdx , 0 , line . length , ` 'end ${ expectedType } ' found without open block. ` , vscode . DiagnosticSeverity . Error ) ;
2026-10-12 19:43:46 +00:00
return ;
}
const lastBlock = ctx . openControlBlocks . pop ( ) ! ;
if ( lastBlock . type !== expectedType ) {
2026-10-18 02:38:07 +00:00
ctx . addDiag ( lineIdx , 0 , line . length , ` Wrong end. Expected: 'end ${ lastBlock . type } ;', found: 'end ${ expectedType } ;'. ` , vscode . DiagnosticSeverity . Error ) ;
2026-10-12 19:43:46 +00:00
ctx . openControlBlocks . push ( lastBlock ) ;
}
2026-10-11 02:31:12 +00:00
}
private handleBlockOpenings ( line : string , rawLine : string , lineIdx : number , ctx : LinterContext ) {
if ( line . startsWith ( 'definitions ' ) || line . startsWith ( 'definition ' ) ) {
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'none' ) { ctx . addDiag ( lineIdx , 0 , line . length , "A defintions block has to be declared on the highest level." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
ctx . inBlock = 'definitions' ;
}
else if ( line . startsWith ( 'define ' ) ) { ctx . openDefines . push ( lineIdx ) ; }
else if ( line . startsWith ( 'enumeration ' ) ) {
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'none' ) { ctx . addDiag ( lineIdx , 0 , line . length , "A enumeration has to be declared on the highest level." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
ctx . inBlock = 'enumeration' ;
}
else if ( line . startsWith ( 'configuration ' ) ) {
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'none' ) { ctx . addDiag ( lineIdx , 0 , line . length , "A configuration has to be declared on the highest level." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
ctx . inBlock = 'configuration' ;
}
else if ( line . startsWith ( 'pattern ' ) || line . startsWith ( 'syncmapping ' ) ) {
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'none' ) { ctx . addDiag ( lineIdx , 0 , 7 , "A Pattern/Syncmapping can not be declared inside another block." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
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 ] ; }
2026-10-18 02:38:07 +00:00
else { ctx . addDiag ( lineIdx , 0 , rawLine . length , "Invalid Pattern declaration. Expected: 'pattern <Name> <Version>'" , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
}
else if ( line . startsWith ( 'table ' ) ) {
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'none' ) { ctx . addDiag ( lineIdx , 0 , 5 , "A table can not be declared inside another block." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
ctx . inBlock = 'table' ;
}
}
2026-10-11 04:11:11 +00:00
private checkPatternPhases ( line : string , rawLine : string , lineIdx : number , ctx : LinterContext , lines : string [ ] ) {
2026-10-11 02:31:12 +00:00
const checkPhase = ( phaseName : string , phaseEnum : PatternPhase ) = > {
if ( line . startsWith ( phaseName ) ) {
2026-10-18 01:14:13 +00:00
if ( ctx . activePhaseName !== "" ) { ctx . addDiag ( ctx . activePhaseLine , 0 , ctx . activePhaseName . length , ` Block ' ${ ctx . activePhaseName } ' wurde nicht mit 'end ${ ctx . activePhaseName } ;' geschlossen. ` , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
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' ) ) {
2026-10-18 01:14:13 +00:00
if ( ctx . activePhaseName !== "" ) { ctx . addDiag ( ctx . activePhaseLine , 0 , ctx . activePhaseName . length , ` Block ' ${ ctx . activePhaseName } ' wurde nicht mit 'end ${ ctx . activePhaseName } ;' geschlossen. ` , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
ctx . activePhaseName = "body" ;
ctx . activePhaseLine = lineIdx ;
2026-10-11 04:11:11 +00:00
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 } "); ` ;
2026-10-18 02:38:07 +00:00
const diag = ctx . addDiag ( lineIdx , 0 , rawLine . length , ` Inside the Body the Log-Statement is missing, e.g. Pattern-Namen: ${ expectedLog } ` , vscode . DiagnosticSeverity . Error ) ;
2026-10-11 04:11:11 +00:00
diag . code = 'missing_log_start' ;
diag . source = expectedLog ;
}
2026-10-11 02:31:12 +00:00
}
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' ) ) {
2026-10-18 01:14:13 +00:00
if ( ctx . activePhaseName !== "" ) { ctx . addDiag ( ctx . activePhaseLine , 0 , ctx . activePhaseName . length , ` Block ' ${ ctx . activePhaseName } ' wurde nicht mit 'end ${ ctx . activePhaseName } ;' geschlossen. ` , vscode . DiagnosticSeverity . Error ) ; }
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'pattern' ) { ctx . addDiag ( lineIdx , 0 , 11 , "'end pattern' found without open pattern block." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
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' ) ) {
2026-10-18 02:38:07 +00:00
if ( ctx . inBlock !== 'definitions' ) { ctx . addDiag ( lineIdx , 0 , line . length , "'end definitions' found without open definitions block." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
ctx . inBlock = 'none' ;
}
else if ( line . startsWith ( 'end define' ) ) {
if ( ctx . openDefines . length > 0 ) { ctx . openDefines . pop ( ) ; }
2026-10-18 02:38:07 +00:00
else { ctx . addDiag ( lineIdx , 0 , line . length , "'end define' found without open define." , vscode . DiagnosticSeverity . Error ) ; }
2026-10-11 02:31:12 +00:00
}
}
}