diff --git a/package.json b/package.json index 5da9f23..8610ed9 100644 --- a/package.json +++ b/package.json @@ -50,7 +50,7 @@ "tpl.metadata.publisher": { "type": "string", "default": "IT-Operations", - "description": "Standard-Publisher ffor new generated TPL-Modules." + "description": "Standard-Publisher for new generated TPL-Modules." }, "tpl.metadata.treePathBase": { "type": "string", @@ -90,13 +90,18 @@ "tpl.metadata.treePath.staticNodes": { "type": "array", "items": { "type": "string" }, - "default": ["default"], + "default": [""], "description": "Fixed definitions for the tree_path left to right (e.g. ['Company', 'Software']). The rest of the levels are free to choose." }, "tpl.version": { "type": "number", "default": 23, - "description": "Aktuelle TPL-Nebenversion (z. B. 23 für TPL 1.23). Wird genutzt um veraltete Module zu erkennen und beim Generieren eingefügt." + "description": "Current TPL-Subversion (e. g. 23 for TPL 1.23). Used to find old Modules and for auto generating the sceleton." + }, + "tpl.rules.requireBodyLog": { + "type": "boolean", + "default": true, + "description": "Require a log.debug statement at the start of a pattern's body phase." } }, "commands": [ @@ -148,4 +153,4 @@ "@vscode/test-cli": "^0.0.15", "@vscode/test-electron": "^3.0.0" } -} \ No newline at end of file +} diff --git a/src/core/TplLinter.ts b/src/core/TplLinter.ts index 08addbe..74450da 100644 --- a/src/core/TplLinter.ts +++ b/src/core/TplLinter.ts @@ -4,6 +4,7 @@ import { SyntaxRule } from './rules/SyntaxRule'; import { BlockRule } from './rules/BlockRule'; import { MetadataRule } from './rules/MetadataRule'; import { IndentationRule } from './rules/IndentationRule'; +import { ParameterRule } from './rules/ParameterRule'; export function validateTplDocument(document: vscode.TextDocument, collection: vscode.DiagnosticCollection) { if (document.languageId !== 'tpl') {return;} @@ -11,12 +12,13 @@ export function validateTplDocument(document: vscode.TextDocument, collection: v const text = document.getText(); const lines = text.split('\n'); const ctx = new LinterContext(); - + const rules = [ + new IndentationRule(), new SyntaxRule(), new MetadataRule(), new BlockRule(), - new IndentationRule() + new ParameterRule() ]; for (let i = 0; i < lines.length; i++) { @@ -83,4 +85,4 @@ function trackStrings(rawLine: string, line: string, ctx: LinterContext) { if (ctx.justOpenedString) { ctx.stringNeedsSemicolon = !line.startsWith('"""') && !line.startsWith("'''"); } -} \ No newline at end of file +} diff --git a/src/core/TplLookup.ts b/src/core/TplLookup.ts new file mode 100644 index 0000000..1234178 --- /dev/null +++ b/src/core/TplLookup.ts @@ -0,0 +1,41 @@ +import { tplBuiltIns } from '../data/TplBuiltIns'; +import { generatedTplBuiltIns } from '../data/GeneratedTplBuiltIns'; + +export class TplLookup { + // Holt eine spezifische Methode (Case-Insensitive) + public static getBuiltInFunction(namespace: string, funcName: string): any | null { + const nsLower = namespace.toLowerCase(); + const fnLower = funcName.toLowerCase(); + + // 1. Suche in Core Built-Ins (discovery, model, etc.) + const coreKey = Object.keys(tplBuiltIns).find(k => k.toLowerCase() === nsLower); + if (coreKey) { + const methods = (tplBuiltIns as any)[coreKey]; + const mKey = Object.keys(methods).find(k => k.toLowerCase() === fnLower); + if (mKey) return methods[mKey]; + } + + // 2. Suche in Generierten TKU Built-Ins (rdbms_functions, etc.) + const tkuKey = Object.keys(generatedTplBuiltIns).find(k => k.toLowerCase() === nsLower); + if (tkuKey) { + const methods = (generatedTplBuiltIns as any)[tkuKey]; + const mKey = Object.keys(methods).find(k => k.toLowerCase() === fnLower); + if (mKey) return methods[mKey]; + } + + return null; + } + + // Holt alle Methoden eines Moduls für die Autovervollständigung (Case-Insensitive) + public static getAllMethodsForNamespace(namespace: string): Record | null { + const nsLower = namespace.toLowerCase(); + + const coreKey = Object.keys(tplBuiltIns).find(k => k.toLowerCase() === nsLower); + if (coreKey) return (tplBuiltIns as any)[coreKey]; + + const tkuKey = Object.keys(generatedTplBuiltIns).find(k => k.toLowerCase() === nsLower); + if (tkuKey) return (generatedTplBuiltIns as any)[tkuKey]; + + return null; + } +} diff --git a/src/core/WorkspaceIndex.ts b/src/core/WorkspaceIndex.ts index bf33a86..45ab266 100644 --- a/src/core/WorkspaceIndex.ts +++ b/src/core/WorkspaceIndex.ts @@ -1,20 +1,18 @@ import * as vscode from 'vscode'; export interface TplFunctionSignature { - name: string; // z.B. "modelling.doWork" - moduleName: string; // z.B. "Company.software_modelling" - params: string[]; // z.B. ["host", "ip_address"] - docString?: string; // Extrahierter Kommentar + name: string; + moduleName: string; + params: string[]; + docString?: string; + hasReturnValue?: boolean; + lineIdx: number; // NEU: Für 'Go to Definition' + fsPath: string; // NEU: Für 'Go to Definition' } export class WorkspaceIndex { - // Map: Datei-Pfad -> Map von Funktionen (Funktionsname -> Signatur) private fileIndex = new Map>(); - - // Map: Datei-Pfad -> Modulname private fileModules = new Map(); - - // Map: Modulname -> Set von Definitions-Blöcken (für die Autovervollständigung beim Import) private moduleDefinitions = new Map>(); public updateFile(uri: vscode.Uri, text: string) { @@ -27,22 +25,19 @@ export class WorkspaceIndex { const functions = new Map(); const defBlocks = new Set(); - // 1. Modulnamen parsen const modMatch = text.match(/tpl\s+1\.\d+\s+module\s+([a-zA-Z0-9_.]+)\s*;/); if (modMatch) { currentModule = modMatch[1]; } this.fileModules.set(fsPath, currentModule); - // 2. Blöcke und Funktionen parsen for (let i = 0; i < lines.length; i++) { const line = lines[i]; - // Startet ein Definitions-Block? const defBlockMatch = line.match(/^\s*definitions?\s+([a-zA-Z0-9_]+)/); if (defBlockMatch) { currentDefBlock = defBlockMatch[1]; - defBlocks.add(currentDefBlock); // Block merken! + defBlocks.add(currentDefBlock); continue; } if (line.match(/^\s*end\s+definitions?/)) { @@ -50,38 +45,44 @@ export class WorkspaceIndex { continue; } - // Patterns, Syncmappings und Tabellen für den Import freigeben const structMatch = line.match(/^\s*(?:pattern|syncmapping|table)\s+([a-zA-Z0-9_]+)/); if (structMatch) { defBlocks.add(structMatch[1]); } - // Sucht nach 'define name(param1, param2)' const defineMatch = line.match(/^\s*define\s+([a-zA-Z0-9_]+)\b(?:\s*\((.*?)\))?/); if (defineMatch) { const funcName = defineMatch[1]; const paramString = defineMatch[2] || ""; const fullName = currentDefBlock ? `${currentDefBlock}.${funcName}` : funcName; - - // Parameter säubern und zählen const params = paramString.split(',').map(p => p.trim()).filter(p => p.length > 0); - // DocString extrahieren let docString = undefined; const textAfterDefine = text.substring(text.indexOf(line) + line.length); const docMatch = textAfterDefine.match(/^\s*(?:"""|''')([\s\S]*?)(?:"""|''')/); if (docMatch) { docString = docMatch[1].trim(); } + let hasReturnValue = false; + for (let j = i + 1; j < lines.length; j++) { + if (lines[j].match(/^\s*end\s+define/)) break; + if (lines[j].match(/^\s*return\b/)) { + hasReturnValue = true; + break; + } + } + functions.set(fullName, { name: fullName, moduleName: currentModule, params: params, - docString: docString + docString: docString, + hasReturnValue: hasReturnValue, + lineIdx: i, + fsPath: fsPath }); } } - // In den RAM schreiben this.fileIndex.set(fsPath, functions); if (currentModule) { this.moduleDefinitions.set(currentModule, defBlocks); @@ -97,20 +98,15 @@ export class WorkspaceIndex { this.fileModules.delete(uri.fsPath); } - // --- HILFSFUNKTIONEN FÜR PROVIDER UND LINTER --- - - // Liefert alle Module (z.B. für "from [Autocomplete] import ...") public getAllModules(): string[] { return Array.from(this.moduleDefinitions.keys()); } - // Liefert alle Definitions-Blöcke eines Moduls (z.B. für "from XYZ import [Autocomplete]") public getDefinitionBlocksForModule(moduleName: string): string[] { const blocks = this.moduleDefinitions.get(moduleName); return blocks ? Array.from(blocks) : []; } - // Liefert alle Funktionen eines Moduls (für "modelling.[Autocomplete]") public getFunctionsForModule(moduleName: string): TplFunctionSignature[] { const result: TplFunctionSignature[] = []; for (const [fsPath, map] of this.fileIndex.entries()) { @@ -120,6 +116,18 @@ export class WorkspaceIndex { } return result; } + + public getFunctionSignatures(funcName: string): TplFunctionSignature[] { + const result: TplFunctionSignature[] = []; + for (const map of this.fileIndex.values()) { + for (const sig of map.values()) { + if (sig.name === funcName || sig.name.endsWith(`.${funcName}`)) { + result.push(sig); + } + } + } + return result; + } } export const workspaceIndex = new WorkspaceIndex(); diff --git a/src/core/rules/BlockRule.ts b/src/core/rules/BlockRule.ts index 164c239..8f6e202 100644 --- a/src/core/rules/BlockRule.ts +++ b/src/core/rules/BlockRule.ts @@ -7,17 +7,17 @@ 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 ;'", vscode.DiagnosticSeverity.Error); ctx.hasModuleDef = true; } + else { ctx.addDiag(lineIdx, 0, rawLine.length, "Every file must start with 'tpl 1.x module ;'.", vscode.DiagnosticSeverity.Error); ctx.hasModuleDef = true; } } - // Wenn wir im Metadata-Block sind, ignorieren wir den Rest. Die MetadataRule übernimmt! + // If we are inside the metadata block, we ignore the rest. MetadataRule handles it! 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); + ctx.addDiag(lineIdx, 0, line.length, "A 'metadata' block at the module level is mandatory before defining other blocks.", vscode.DiagnosticSeverity.Error); } } @@ -33,10 +33,10 @@ export class BlockRule implements LinterRule { 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); + ctx.addDiag(unclosedLine, 0, lines[unclosedLine].length, "Block 'define' was not closed 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); + ctx.addDiag(block.lineIdx, 0, lines[block.lineIdx].length, `Block '${block.type}' was not closed with 'end ${block.type};'!`, vscode.DiagnosticSeverity.Error); } } @@ -52,33 +52,33 @@ export class BlockRule implements LinterRule { 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); + ctx.addDiag(lineIdx, 0, line.length, `Found 'end ${expectedType}' without an 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.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); } + if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "A 'definitions' block must be declared at the top 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); } + if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "An 'enumeration' must be declared at the top 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); } + if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, line.length, "A 'configuration' must be declared at the top 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); } + if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, 7, "A pattern or syncmapping cannot be declared inside another block.", vscode.DiagnosticSeverity.Error); } ctx.inBlock = 'pattern'; ctx.currentPatternPhase = PatternPhase.None; ctx.activePhaseName = ""; @@ -86,10 +86,10 @@ export class BlockRule implements LinterRule { 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 '", vscode.DiagnosticSeverity.Error); } + else { ctx.addDiag(lineIdx, 0, rawLine.length, "Invalid pattern declaration. Expected: 'pattern '", 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); } + if (ctx.inBlock !== 'none') { ctx.addDiag(lineIdx, 0, 5, "A table cannot be declared inside another block.", vscode.DiagnosticSeverity.Error); } ctx.inBlock = 'table'; } } @@ -97,8 +97,8 @@ export class BlockRule implements LinterRule { 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); } + if (ctx.activePhaseName !== "") { ctx.addDiag(ctx.activePhaseLine, 0, ctx.activePhaseName.length, `Block '${ctx.activePhaseName}' was not closed with 'end ${ctx.activePhaseName};'.`, vscode.DiagnosticSeverity.Error); } + if (ctx.currentPatternPhase >= phaseEnum) { ctx.addDiag(lineIdx, 0, phaseName.length, `Block '${phaseName}' is not allowed here.`, vscode.DiagnosticSeverity.Error); } ctx.currentPatternPhase = phaseEnum; ctx.activePhaseName = phaseName; @@ -114,22 +114,27 @@ export class BlockRule implements LinterRule { 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); } + if (ctx.activePhaseName !== "") { ctx.addDiag(ctx.activePhaseLine, 0, ctx.activePhaseName.length, `Block '${ctx.activePhaseName}' was not closed with 'end ${ctx.activePhaseName};'.`, 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; } - } + const config = vscode.workspace.getConfiguration('tpl'); + const requireLog = config.get('rules.requireBodyLog', true); - 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; + if (requireLog) { + 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, `Missing log statement with pattern name in body. E.g.: ${expectedLog}`, vscode.DiagnosticSeverity.Error); + diag.code = 'missing_log_start'; + diag.source = expectedLog; + } } } @@ -137,7 +142,7 @@ export class BlockRule implements LinterRule { 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); } + if (ctx.activePhaseName !== phase) { ctx.addDiag(lineIdx, 0, line.length, `Unexpected '${closer}'. Expected 'end ${ctx.activePhaseName};'.`, vscode.DiagnosticSeverity.Error); } else { ctx.activePhaseName = ""; } } } @@ -145,20 +150,20 @@ export class BlockRule implements LinterRule { 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); } + if (ctx.activePhaseName !== "") { ctx.addDiag(ctx.activePhaseLine, 0, ctx.activePhaseName.length, `Block '${ctx.activePhaseName}' was not closed with 'end ${ctx.activePhaseName};'.`, vscode.DiagnosticSeverity.Error); } + if (ctx.inBlock !== 'pattern') { ctx.addDiag(lineIdx, 0, 11, "Found 'end pattern' without an 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); } + if (ctx.inBlock !== 'definitions') { ctx.addDiag(lineIdx, 0, line.length, "Found 'end definitions' without an 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); } + else { ctx.addDiag(lineIdx, 0, line.length, "Found 'end define' without an open 'define'.", vscode.DiagnosticSeverity.Error); } } } -} \ No newline at end of file +} diff --git a/src/core/rules/IndentationRule.ts b/src/core/rules/IndentationRule.ts index e566c5c..3bdda3a 100644 --- a/src/core/rules/IndentationRule.ts +++ b/src/core/rules/IndentationRule.ts @@ -8,9 +8,9 @@ export class IndentationRule implements LinterRule { if (rawLine.trim().length === 0) {return;} if (rawLine.includes('\t')) {return;} - // HIER IST DER FIX: Wenn wir innerhalb von Klammern sind (Visual Alignment), + // Sobald wir in Klammern oder einem Multi-Line Import sind (Visual Alignment), // greift die 4-Leerzeichen-Regel nicht! - if (ctx.parenStack.length > 0 || ctx.bracketStack.length > 0) { + if (ctx.parenStack.length > 0 || ctx.bracketStack.length > 0 || ctx.inImportBlock) { return; } diff --git a/src/core/rules/ParameterRule.ts b/src/core/rules/ParameterRule.ts new file mode 100644 index 0000000..85093be --- /dev/null +++ b/src/core/rules/ParameterRule.ts @@ -0,0 +1,157 @@ +import * as vscode from 'vscode'; +import { LinterRule } from './LinterRule'; +import { LinterContext } from '../LinterContext'; +import { workspaceIndex, TplFunctionSignature } from '../WorkspaceIndex'; +import { TplLookup } from '../TplLookup'; // Nutzt ab sofort das zentrale Lexikon + +export class ParameterRule implements LinterRule { + public checkLine(line: string, rawLine: string, lineIdx: number, ctx: LinterContext, lines: string[]): void { + let cleanLine = ""; + let inString = false; + let stringChar = ''; + let inTripleQuote = false; + + for (let i = 0; i < line.length; i++) { + const char = line[i]; + + // 1. Single-Line Kommentare (//) ignorieren + if (!inString && !inTripleQuote && char === '/' && line[i+1] === '/') { + break; + } + + // 2. TPL Triple Quotes (""" oder ''') erkennen + if (!inString && !inTripleQuote && + (char === '"' || char === "'") && + line[i+1] === char && + line[i+2] === char) { + + inTripleQuote = true; + stringChar = char; + cleanLine += char + char + char; // Die """ behalten, damit wir die Zeile nicht komplett leeren + i += 2; // Überspringe die nächsten zwei Zeichen + continue; + } + + // 3. Triple Quotes beenden + if (inTripleQuote) { + if (char === stringChar && line[i+1] === stringChar && line[i+2] === stringChar) { + inTripleQuote = false; + cleanLine += char + char + char; + i += 2; + } + continue; // Alles innerhalb von """ ignorieren! + } + + // 4. Normale Strings beenden + if (inString) { + if (char === '\\') { + i++; // Escape-Sequenzen ignorieren + } else if (char === stringChar) { + inString = false; + cleanLine += char; + } + continue; // Alles innerhalb von " ignorieren! + } + + // 5. Normale Strings starten + if (!inString && !inTripleQuote && (char === '"' || char === "'")) { + inString = true; + stringChar = char; + cleanLine += char; + continue; + } + + // Wenn wir hier ankommen, sind wir im echten, ausführbaren TPL-Code + cleanLine += char; + } + + if (cleanLine.trim().length === 0) return; + + // Die Definition der Funktion selbst ist kein Aufruf und wird ignoriert. + if (/^\s*define\s+/.test(cleanLine)) return; + + const callRegex = /\b([a-zA-Z0-9_]+(?:\.[a-zA-Z0-9_]+)?)\s*\(/g; + let match; + + while ((match = callRegex.exec(cleanLine)) !== null) { + const funcName = match[1]; + const callStart = match.index; + const parenStart = callStart + funcName.length; + + if (/^(if|elif|where|for|while|in|matches|has|subword|defined)\b/.test(funcName)) continue; + + let depth = 0; + let parenEnd = -1; + for (let i = parenStart; i < cleanLine.length; i++) { + if (cleanLine[i] === '(') depth++; + else if (cleanLine[i] === ')') { + depth--; + if (depth === 0) { + parenEnd = i; + break; + } + } + } + + if (parenEnd !== -1) { + const paramsStr = cleanLine.substring(parenStart + 1, parenEnd).trim(); + let paramCount = 0; + + if (paramsStr.length > 0) { + let paramDepth = 0; + let commas = 0; + for (let i = 0; i < paramsStr.length; i++) { + if (paramsStr[i] === '(' || paramsStr[i] === '[') paramDepth++; + else if (paramsStr[i] === ')' || paramsStr[i] === ']') paramDepth--; + else if (paramsStr[i] === ',' && paramDepth === 0) commas++; + } + paramCount = commas + 1; + } + + this.validateFunctionParams(funcName, paramCount, lineIdx, rawLine, ctx); + this.validateReturnValue(funcName, cleanLine, lineIdx, rawLine, ctx); + } + } + } + + public checkEnd(lines: string[], ctx: LinterContext): void { + // empty + } + + private validateFunctionParams(funcName: string, passedCount: number, lineIdx: number, rawLine: string, ctx: LinterContext) { + const parts = funcName.split('.'); + + if (parts.length > 1) { + // Case-Insensitive Prüfung über den neuen Lookup! + if (TplLookup.getBuiltInFunction(parts[0], parts[1])) { + return; // Funktion existiert, Linter ist zufrieden. + } + } + + const signatures = workspaceIndex.getFunctionSignatures(funcName); + if (signatures.length > 0) { + const matches = signatures.some((sig: TplFunctionSignature) => sig.params.length === passedCount); + if (!matches) { + const expected = signatures[0].params.length; + const idx = rawLine.indexOf(funcName); + ctx.addDiag(lineIdx, Math.max(0, idx), Math.max(0, idx) + funcName.length, `Function '${funcName}' expects ${expected} parameter(s), but ${passedCount} were provided.`, vscode.DiagnosticSeverity.Warning); + } + } else if (parts.length > 1) { + const idx = rawLine.indexOf(funcName); + ctx.addDiag(lineIdx, Math.max(0, idx), Math.max(0, idx) + funcName.length, `Unknown function '${funcName}'. Please check your imports or contact the plugin provider.`, vscode.DiagnosticSeverity.Information); + } + } + + private validateReturnValue(funcName: string, cleanLine: string, lineIdx: number, rawLine: string, ctx: LinterContext) { + const signatures = workspaceIndex.getFunctionSignatures(funcName); + if (signatures.length > 0 && signatures[0].hasReturnValue) { + const isAssigned = cleanLine.includes(':='); + const isUsedInCondition = /\b(if|elif|while)\b/.test(cleanLine); + + if (!isAssigned && !isUsedInCondition) { + const idx = rawLine.indexOf(funcName); + ctx.addDiag(lineIdx, Math.max(0, idx), Math.max(0, idx) + funcName.length, `The return value of '${funcName}' is not used. Did you forget an assignment (:=)?`, vscode.DiagnosticSeverity.Warning); + } + } + } +} diff --git a/src/data/GeneratedTplBuiltIns.ts b/src/data/GeneratedTplBuiltIns.ts index b4179a5..33b1ec5 100644 --- a/src/data/GeneratedTplBuiltIns.ts +++ b/src/data/GeneratedTplBuiltIns.ts @@ -6,6 +6,40 @@ export interface TplBuiltInMethod { export const generatedTplBuiltIns: Record> = { "A10Networks.AX_LoadBalancer": { }, + "AIX.WPAR": { + }, + "AIX_Storage": { + "createLocalDisk": { + snippets: ["${1:host}, ${2:volume}, ${3:disk_entry}, ${4:pvid}, ${5:odm_data}, ${6:pv2lvidlist}, ${7:LOCAL_DRIVES}, ${8:raid_disk}"], + description: `Function creates local Disk Drive and create relationships to the host.\nReturns DiskDrive node.\n\nParameters:\nhost - host to be queried\nvolume - physical volume (physical disk for RAID) name\ndisk_entry - table containing disk parameters (from getDiskParameters)\npvid - physical volume id\nodm_data - ODM data\npv2lvidlist - table with mapping between PV name and list of LV id's\nLOCAL_DRIVES - removal group name\nraid_disk - RAID disk (void if not part of a RAID array)` + }, + "getDiskParameters": { + snippets: ["${1:host}, ${2:cmd_locations}, ${3:odm_data}, ${4:device_type_var}, ${5:disk_parameters_table}, ${6:NAAID_REGEX}"], + description: `Updates the given disk_parameters_table with data on the request device_type_var\ndevice_type_var is expected to be "hdisk" or "pdisk"\n\nReturns false if the command cannot be run\n\nChange History:\n2017-01-23 - Updated in order to support naa_id in IEEE Registered format,\ni.e. starts with 5h, 8 bytes long (DRDC1-7698);\n- Updated in order to use DiscoveryFunctions module instead of Common_Functions;\n2017-05-16 - Updated in order to get full 'lscfg' output (DRDC1-8301);\n2019-03-08 - Use new runFutureActiveCommand;\n2019-06-14 - Updated to better handle local disks detection (DRDC1-13430);\n2020-07-16 - Updated search to consider scope (DRDC1-15362)\n2026-04-08 - Updated escape sequence (DRDC1-29309)` + }, + "getNAAIDFromLsmpio": { + snippets: ["${1:host}, ${2:pv2naaid_table}, ${3:cmd_locations}"], + description: `Using lspv and lsmpio commands function gets the mapping between volume and naa_id.\nReturns table pv2naaid_table e.g. {'hdisk11' : '60000970000295700516533032454535'}\nReturns none if the lsmpio or lspv command isn't available or execution fails\nor no naa_id were obtained;\n\nChange History:\n2017-01-23 - Updated in order to support naa_id in IEEE Registered format,\ni.e. starts with 5h, 8 bytes long (DRDC1-7698);\n2019-03-08 - Use new future runCommand` + }, + "getODMData": { + snippets: ["${1:host}, ${2:cmd_locations}"], + description: `Query the Object Database Manager for Customized Attributes\n\nReturns odm_data table\nodm_data maps the object name (e.g. "hdisk0") to a table of keys and values\nThe table includes all the system objects not just storage things.\n\nExample:\nodm_data = {"hdisk0" : {"unique_id" : "2A1135000C5003A7F131B0BST9300603SS03IBMsas", "pvid" : "00f6ba5a370069b20000000000000000", ...}}\n\nReturns none if the odmget command isn't available or execution fails.\n\nChange History:\n2017-01-23 - Updated in order to use DiscoveryFunctions module instead of Common_Functions;\n2019-03-08 - Use runFutureActiveCommand` + }, + "getPV2LVIDList": { + snippets: ["${1:host}, ${2:odm_data}"], + description: `Build mapping from physical volume (i.e. hdisk0) to a list of LVIDs\ne.g. {"hdisk0" : [00f6ba5a00004c000000014373654326.4, 00f6ba5a00004c000000014373654444.5]}` + }, + "getRaidDisks": { + snippets: ["${1:host}, ${2:volume}, ${3:cmd_locations}"], + description: `Function obtains information about physical disks forming raid array.\nOnly SAS raids are supported.\nReturns list containing physical disk names.\n\nParameters:\nhost - host to be queried;\nvolume - physical volume (physical disk for RAID) name;\ncmd_locations - command locations (from locateCommands());\n\nChange History:\n2017-01-23 - Updated in order to use DiscoveryFunctions module instead of Common_Functions;\n2019-03-08 - Use new runFutureActiveCommand\n2025-06-04 - Updated to use standard path for sissasraidmgr command` + }, + "getVIOBackingDevice": { + snippets: ["${1:host}, ${2:all_vio_disks}, ${3:volume}, ${4:pvid}, ${5:udid}"], + description: `Function obtains information about physical disks or logical volumes on VIO servers,\nwhich backing physical volumes on LPARs.\nReturns list of DiskDrive nodes.\n\nParameters:\nhost - host to be queried;\nvolume - physical volume (physical disk for RAID) name;\npvid - physical volume id;\nudid - unique disk id;` + } + }, + "ASG.TMONPAServer": { + }, "AbInitio.CoOperatingSystem": { }, "AbInitio.EnterpriseMetaEnvironment": { @@ -20,27 +54,29 @@ export const generatedTplBuiltIns: Record'\n\nTakes:\nhost - Host Node which run the function\nconfig_file - path to the include(full or relative)\nExamples: '/data/config/vhosts.d/vh-*.conf', '/data/config/vhosts.d/*.conf'\n\nReturns:\nwildcards - list of found file names by their wildcards\n\nChange History:\n2014-05-29 - Updated parsing method to consider complex wildcards (ISS04305797)\n2016-09-21 - Updated parsing method to consider any wildcard file names (DRDC1-6476)\n2019-02-11 - Updated function to use DiscoveryFunctions.escapePath().\n2019-05-17 - Remove cmd /c from runCommand (DRDC1-12533)\n2026-04-06 - Updated escape sequence (DRDC1-29309)` - }, - "sc_model": { - snippets: ["${1:host}, ${2:apache_si}, ${3:config_file_path}, ${4:website_table}"], - description: `Current function collects information about configured websites and returns a dictionary with websites details and list of included configs\n\nChange History:\n2013-08-23 - Updated regexes for website attributes extraction;\n2014-04-01 - Re-order and reorganize parsing of config files\n2014-05-29 - Updated parsing method to consider complex wildcards (ISS04305797)\n2014-08-06 - Added listening_ports attribute to SC (TKU-2355)\n2015-02-04 - server_root extraction from config file path updated(TKU-3208)\n2015-10-29 - added 'websphere_plugin_path' attribute to the Apache SI(QM001887828)\n2016-07-21 - Updated parsing method to consider any configuration file names (DRDC1-6476)\n2016-09-21 - Added support of IncludeOptional directive (DRDC1-6546)\n2016-11-30 - Updated the 'listen_tcp_sockets' attribute extraction\nAdded 'listen_secure_tcp_sockets' attribute\n2017-01-24 - added filter to avoid setting environment variables into additional attributes(esc 101108)\n2018-06-12 - Updated regex to extract list of includes (DRDC1-10977)\n2020-09-15 - Modeling Default website (DRDC1-15732)\n2021-04-05 - Added support for 'SSLEnable' (DRDC1-16709)\n2021-08-05 - Updated to use clean_apache_config_file function and fast regexes(DRDC1-16920)\n2021-09-08 - Updated to collect the information about Default Website (DRDC1-17328)\n2025-10-17 - Tighten regex to find website name (DRDC1-27788)\n2026-04-06 - Updated escape sequence (DRDC1-29309)\n2026-04-06 - Extract Wordpress path attribute (DRDC1-29389)` - } - }, - "ApacheFoundation.MavenFunctions": { - "pomVersioning": { - snippets: ["${1:host}, ${2:install_dir}"], - description: `Get version from pom.xml or pom.properties. Both are common files in\nmaven apps\n\nTakes:\nHost node\ninstall_dir - Path to pom.xml and pom.properties\n\nReturns: Full_version` - } - }, - "ApacheFoundation.Tomcat": { - "catalina_sh_version": { - snippets: ["${1:host}, ${2:run_cmd}, ${3:run_cmd_path}"], - description: `Gets version from getting run_cmd result.\n\nThis command might be catalina.sh version or might be something else such as\njava org.apache.catalina.util.ServerInfo. The function name is a legacy name,\nkept to ensure backward compatibility\nChange History:\n2016-02-18 - Updated full_version regex and added server type extraction (DRDC1-5597)\n2017-03-17 - Added runActiveCommand() common function\nto run the command with elevated privileges if necessary(DRDC1-8319)\n2018-04-06 - Change versioning order (DRDC1-11084)\n2018-11-19 - Catalina.sh should not be executed for DACS application (DRDC1-12261)\n2019-04-04 - Updated to use modern discovery functions\n2020-03-20 - Catalina.sh may return version of "0.0.0.0", should ignore it (DRDC1-14843)\n2020-11-23 - Catalina.sh may return non-existent version, like '0.0.1',\nshould ignore it (DRDC1-16141)\n2023-05-12 - Check for run command updated (DRDC1-19945)` - }, - "convertPathVersion": { - snippets: ["${1:raw}"], - description: `Take a raw path version and process it into something we can use\n\nConvert a version of\n* 85 to 8.5\n* 10 to 10 (no change)\n* 101 to 10.1\n\nTakes : Raw value (e.g. 85)\nReturns : Processed value (e.g. 8.5)` - } - }, "Apache.Accumulo": { "active_versioning": { snippets: ["${1:host}, ${2:install_root}"], description: `Function for version extraction from active command ./accumulo version\n\nChange History:\n2019-03-01 - Use modern version of runCommand\n2023-11-16 - Fix indentation and spacing` }, - "path_versioning": { - snippets: ["${1:host}, ${2:process}"], - description: `Function for version extraction from process.args\n\nChange History:\n2023-11-16 - Fix indentation and spacing` - }, "get_install_root": { snippets: ["${1:host}, ${2:process}"], description: `Function for install_root extraction from process.args\n\nChange History:\n2023-11-16 - Fix indentation and spacing` + }, + "path_versioning": { + snippets: ["${1:host}, ${2:process}"], + description: `Function for version extraction from process.args\n\nChange History:\n2023-11-16 - Fix indentation and spacing` } }, "Apache.ActiveMQ": { @@ -652,7 +612,13 @@ export const generatedTplBuiltIns: Record/hdfs-site.xml file.\nReturns Cluster ID and sorted list of HA hosts.\nExamples:\n\ndfs.nameservices\nmycluster\n\n\ndfs.ha.namenodes.mycluster\nnn1,nn2\n\n\ndfs.namenode.http-address.mycluster.nn1\nmachine1.example.com:9870\n\n\ndfs.namenode.http-address.mycluster.nn2\nmachine2.example.com:9870\n` }, - "yarn_active_versioning": { - snippets: ["${1:process}, ${2:install_root}"], - description: `Performs active versioning using command:\n[root@clm-pun-006625 bin]# /home/hadoop-2.5.1/bin/yarn version\nHadoop 2.5.1\nThis command was run using /home/hadoop-2.5.1/share/hadoop/common/hadoop-common-2.5.1.jar\n\nChange History:\n2015-04-29 - added support for Hortonworks Data Platform (TKU-3299)\n2019-02-06 - Updated to use Future.runCommand function` - }, - "package_versioning": { - snippets: ["${1:host}, ${2:packages}"], - description: `package_versioning performs package versioning. Function returns version from first package that matches.` + "getYarnResourceManagerHA": { + snippets: ["${1:host}, ${2:yarn_site_xml_content}"], + description: `Get "Apache Hadoop Yarn Resource Manager" HA configuration\nin /yarn-site.xml file.\nReturns Cluster ID and sorted list of HA hosts.\nExamples:\n\nyarn.resourcemanager.ha.enabled\ntrue\n\n\nyarn.resourcemanager.cluster-id\ncluster1\n\n\nyarn.resourcemanager.ha.rm-ids\nrm1,rm2\n\n\nyarn.resourcemanager.hostname.rm1\nmaster1\n\n\nyarn.resourcemanager.hostname.rm2\nmaster2\n` }, "get_hadoop_config": { snippets: ["${1:process}"], @@ -737,18 +699,6 @@ export const generatedTplBuiltIns: Record/core-site.xml file, examples:\nfile path: /home/hadoop-2.5.1/etc/hadoop/core-site.xml\nurl 'hdfs://NAMENODE_HOSTNAME:9000'\n\nChange History:\n2017-02-03 - updated to use new SearchFunctions (DRDC1-8015)\n2022-07-13 - updated to find 'Cloudera Hadoop NameNode' (DRDC1-18489)\n2026-04-06 - fixed regex usage issues. (DRDC1-29309)` }, - "get_yarn_resourcemanager_si": { - snippets: ["${1:host}, ${2:hadoop_config_path}"], - description: `Get "Apache Hadoop Yarn Resource Manager" by its url in /yarn-site.xml file, examples:\nfile path: /home/hadoop-2.5.1/etc/hadoop/yarn-site.xml\nurl 'RESOURCEMANAGER_HOSTNAME'\n\nChange History:\n2017-02-03 - updated to use new SearchFunctions (DRDC1-8015)\n2022-07-13 - updated to find 'Cloudera Hadoop Yarn Resource Manager' (DRDC1-18489)\n2026-04-06 - fixed regex usage issues. (DRDC1-29309)` - }, - "getYarnResourceManagerHA": { - snippets: ["${1:host}, ${2:yarn_site_xml_content}"], - description: `Get "Apache Hadoop Yarn Resource Manager" HA configuration\nin /yarn-site.xml file.\nReturns Cluster ID and sorted list of HA hosts.\nExamples:\n\nyarn.resourcemanager.ha.enabled\ntrue\n\n\nyarn.resourcemanager.cluster-id\ncluster1\n\n\nyarn.resourcemanager.ha.rm-ids\nrm1,rm2\n\n\nyarn.resourcemanager.hostname.rm1\nmaster1\n\n\nyarn.resourcemanager.hostname.rm2\nmaster2\n` - }, - "getHDFSNameNodeHA": { - snippets: ["${1:host}, ${2:hdfs_site_xml_content}"], - description: `Get "Apache Hadoop NameNode" HA configuration\nin /hdfs-site.xml file.\nReturns Cluster ID and sorted list of HA hosts.\nExamples:\n\ndfs.nameservices\nmycluster\n\n\ndfs.ha.namenodes.mycluster\nnn1,nn2\n\n\ndfs.namenode.http-address.mycluster.nn1\nmachine1.example.com:9870\n\n\ndfs.namenode.http-address.mycluster.nn2\nmachine2.example.com:9870\n` - }, "get_jobhistory_si": { snippets: ["${1:host}, ${2:hadoop_config_path}"], description: `Get "Apache Hadoop Yarn Resource Manager" by its url in /mapred-site.xml file, examples:\nfile path: /home/hadoop-2.5.1/etc/hadoop/mapred-site.xml\nurl 'JOBHISTORY_HOSTNAME:10020'\n\nChange History:\n2017-02-03 - updated to use new SearchFunctions (DRDC1-8015)\n2022-07-13 - updated to find 'Cloudera Hadoop Job History Server' (DRDC1-18489)\n2026-04-06 - fixed regex usage issues (DRDC1-29309)` @@ -756,12 +706,22 @@ export const generatedTplBuiltIns: Record/slaves file, examples:\nfile path: /home/hadoop-2.5.1/etc/hadoop/slaves\ndatanode_host\ndatanode_host\nnodemanager_host\nnodemanager_host\n\nChange History:\n2017-02-03 - updated to use new SearchFunctions (DRDC1-8015)` - } - }, - "Apache.HBase": { - "HBase_versioning": { - snippets: ["${1:host}, ${2:install_root}"], - description: `Function for version extraction from hbase .jar files.\nAll .jar files are in %HBASE_INSTALL_ROOT%/lib/ directory\nAlso, performs active versioning. E.g. /usr/lib/hbase/hbase-hadoop/bin/hbase version\n\nChange History:\n2019-02-06 - Updated to use Future.runCommand() and\nDiscoveryFunctions.escapePath() functions;\n2021-12-31 - Add file versioning (DRDC1-18010)` + }, + "get_yarn_resourcemanager_si": { + snippets: ["${1:host}, ${2:hadoop_config_path}"], + description: `Get "Apache Hadoop Yarn Resource Manager" by its url in /yarn-site.xml file, examples:\nfile path: /home/hadoop-2.5.1/etc/hadoop/yarn-site.xml\nurl 'RESOURCEMANAGER_HOSTNAME'\n\nChange History:\n2017-02-03 - updated to use new SearchFunctions (DRDC1-8015)\n2022-07-13 - updated to find 'Cloudera Hadoop Yarn Resource Manager' (DRDC1-18489)\n2026-04-06 - fixed regex usage issues. (DRDC1-29309)` + }, + "hadoop_active_versioning": { + snippets: ["${1:process}, ${2:install_root}"], + description: `Performs active versioning using command:\n[root@clm-pun-006625 bin]# /home/hadoop-2.5.1/bin/hadoop version\nHadoop 2.5.1\nThis command was run using /home/hadoop-2.5.1/share/hadoop/common/hadoop-common-2.5.1.jar\n\nChange History:\n2019-02-06 - Updated to use Future.runCommand function` + }, + "package_versioning": { + snippets: ["${1:host}, ${2:packages}"], + description: `package_versioning performs package versioning. Function returns version from first package that matches.` + }, + "yarn_active_versioning": { + snippets: ["${1:process}, ${2:install_root}"], + description: `Performs active versioning using command:\n[root@clm-pun-006625 bin]# /home/hadoop-2.5.1/bin/yarn version\nHadoop 2.5.1\nThis command was run using /home/hadoop-2.5.1/share/hadoop/common/hadoop-common-2.5.1.jar\n\nChange History:\n2015-04-29 - added support for Hortonworks Data Platform (TKU-3299)\n2019-02-06 - Updated to use Future.runCommand function` } }, "Apache.Hive": { @@ -781,14 +741,6 @@ export const generatedTplBuiltIns: Record ... ',\n' ... ']` + "getTopicGroupMappings": { + snippets: ["${1:input}"], + description: `Get mapping from Topic to Group\n\nChange history:\n2025-03-17 - Blank lines processing updated (DRDC1-26071)` }, - "findIpPort": { - snippets: ["${1:host}, ${2:address_port}, ${3:resolved}"], - description: `Keine Beschreibung verfügbar.` + "modelTopics": { + snippets: ["${1:host}, ${2:node}, ${3:install_root}, ${4:port}, ${5:zk_addr}, ${6:endpoint}"], + description: `Model topic details for SoftwareInstance or SoftwareCluster\n\nhost: Host where Kafka is running or any host from the SoftwareCluster\nnode: SoftwareInstance or SoftwareCluster to link Details to\ninstall_root: Install root for Apache Kafka SI\nport: Port which Apache Kafka is listening on\nzk_addr: Apache Zookeeper address (host:port) to query older Kafka versions\n\nChange history:\n2021-01-15 - Added support for older Kafka versions (DRDC1-16411);\nUpdated to check 'kafka-topics' commands output\n2021-02-10 - Additional check for 'kafka-topics' commands output (DRDC1-16515)\n2021-04-01 - Updated to obtain all details in one command (DRDC1-16721);\nAdded option to disable topics discovery\n2021-11-18 - Added Confluent/Cloudera publishers (DRDC1-17776)\n2023-02-20 - Improve parsing of kafka-topics.sh command (DRDC1-20823)\n2023-08-03 - Add details of consumer groups to each\ntopic (DRDC1-20785)\n2024-01-31 - Improve performance (DRDC1-22779)\n2024-06-26 - Updated commands to use DiscoveryFunctions (DRDC1-23863)\n2025-01-15 - Improve performance for consumer group modelling (DRDC1-25435)\n2025-02-12 - Performance improvements (DRDC1-25435)\n2025-12-30 - Added support for command-config option (DRDC1-26198)\n2026-06-03 - Improve commands to use ip:port or hostname:port, extracted\nfrom conf file (DRDC1-29725)` } }, "Apache.Mesos": { - "get_product_version": { - snippets: ["${1:full_version}"], - description: `Function to extract product version from full version` - }, "active_versioning": { snippets: ["${1:host}, ${2:process_cmd}"], description: `Function which extracts version from command output\n\nChange History:\n2019-02-06 - Updated to use Future.runCommand() function.` }, - "package_versioning": { - snippets: ["${1:host}"], - description: `Function which extracts version from package` - }, "get_cluster_id": { snippets: ["${1:host}, ${2:process_args}"], description: `Function to extract cluster id from /etc/mesos/zk file\nValid for Mesos deployed with ZooKeeper\n\nChange History:\n2017-06-28 - added an alternative method of cluster_id extraction (107357)\n2019-02-13 - added an alternative method of cluster_id extraction (DRDC1-12612)\n- updated regex to extract cluster_id (DRDC1-12601)` }, + "get_member_ip": { + snippets: ["${1:host}, ${2:node_type}, ${3:process_args}"], + description: `Function to extract member node ip from /etc/mesos-master/ip\nor /etc/mesos-slave/ip file.\nIf this approach fails, pattern will try to extract ip attribute value from process arguments\n\nChange History:\n2017-06-28 - process_args value now passed to the function to avoid potential ECA erros (107357)\n- added an alternative method of member IP extraction (107357)` + }, "get_member_status": { snippets: ["${1:host}, ${2:node_type}"], description: `Function to extract status and state of member node` }, - "get_member_ip": { - snippets: ["${1:host}, ${2:node_type}, ${3:process_args}"], - description: `Function to extract member node ip from /etc/mesos-master/ip\nor /etc/mesos-slave/ip file.\nIf this approach fails, pattern will try to extract ip attribute value from process arguments\n\nChange History:\n2017-06-28 - process_args value now passed to the function to avoid potential ECA erros (107357)\n- added an alternative method of member IP extraction (107357)` + "get_product_version": { + snippets: ["${1:full_version}"], + description: `Function to extract product version from full version` + }, + "package_versioning": { + snippets: ["${1:host}"], + description: `Function which extracts version from package` } }, "Apache.NiFi": { @@ -884,6 +830,62 @@ export const generatedTplBuiltIns: Record'\n\nTakes:\nhost - Host Node which run the function\nconfig_file - path to the include(full or relative)\nExamples: '/data/config/vhosts.d/vh-*.conf', '/data/config/vhosts.d/*.conf'\n\nReturns:\nwildcards - list of found file names by their wildcards\n\nChange History:\n2014-05-29 - Updated parsing method to consider complex wildcards (ISS04305797)\n2016-09-21 - Updated parsing method to consider any wildcard file names (DRDC1-6476)\n2019-02-11 - Updated function to use DiscoveryFunctions.escapePath().\n2019-05-17 - Remove cmd /c from runCommand (DRDC1-12533)\n2026-04-06 - Updated escape sequence (DRDC1-29309)` + }, + "get_virtual_host_list": { + snippets: ["${1:content}"], + description: `This function searches for VirtualHosts in Apache Webserver\nconfig file.\n\nTakes:\ncontent - Apache Webserver config file content\n\nReturns:\nvirtual_hosts - list of found VirtualHosts configuration\nrest - Apache Webserver configuration without VirtualHosts\n\nChange History:\n2021-09-08 - Updated to return Apache Webserver configuration\nwithout VirtualHosts, also (DRDC1-17328)` + }, + "sc_model": { + snippets: ["${1:host}, ${2:apache_si}, ${3:config_file_path}, ${4:website_table}"], + description: `Current function collects information about configured websites and returns a dictionary with websites details and list of included configs\n\nChange History:\n2013-08-23 - Updated regexes for website attributes extraction;\n2014-04-01 - Re-order and reorganize parsing of config files\n2014-05-29 - Updated parsing method to consider complex wildcards (ISS04305797)\n2014-08-06 - Added listening_ports attribute to SC (TKU-2355)\n2015-02-04 - server_root extraction from config file path updated(TKU-3208)\n2015-10-29 - added 'websphere_plugin_path' attribute to the Apache SI(QM001887828)\n2016-07-21 - Updated parsing method to consider any configuration file names (DRDC1-6476)\n2016-09-21 - Added support of IncludeOptional directive (DRDC1-6546)\n2016-11-30 - Updated the 'listen_tcp_sockets' attribute extraction\nAdded 'listen_secure_tcp_sockets' attribute\n2017-01-24 - added filter to avoid setting environment variables into additional attributes(esc 101108)\n2018-06-12 - Updated regex to extract list of includes (DRDC1-10977)\n2020-09-15 - Modeling Default website (DRDC1-15732)\n2021-04-05 - Added support for 'SSLEnable' (DRDC1-16709)\n2021-08-05 - Updated to use clean_apache_config_file function and fast regexes(DRDC1-16920)\n2021-09-08 - Updated to collect the information about Default Website (DRDC1-17328)\n2025-10-17 - Tighten regex to find website name (DRDC1-27788)\n2026-04-06 - Updated escape sequence (DRDC1-29309)\n2026-04-06 - Extract Wordpress path attribute (DRDC1-29389)` + } + }, + "ApacheFoundation.MavenFunctions": { + "pomVersioning": { + snippets: ["${1:host}, ${2:install_dir}"], + description: `Get version from pom.xml or pom.properties. Both are common files in\nmaven apps\n\nTakes:\nHost node\ninstall_dir - Path to pom.xml and pom.properties\n\nReturns: Full_version` + } + }, + "ApacheFoundation.Tomcat": { + "catalina_sh_version": { + snippets: ["${1:host}, ${2:run_cmd}, ${3:run_cmd_path}"], + description: `Gets version from getting run_cmd result.\n\nThis command might be catalina.sh version or might be something else such as\njava org.apache.catalina.util.ServerInfo. The function name is a legacy name,\nkept to ensure backward compatibility\nChange History:\n2016-02-18 - Updated full_version regex and added server type extraction (DRDC1-5597)\n2017-03-17 - Added runActiveCommand() common function\nto run the command with elevated privileges if necessary(DRDC1-8319)\n2018-04-06 - Change versioning order (DRDC1-11084)\n2018-11-19 - Catalina.sh should not be executed for DACS application (DRDC1-12261)\n2019-04-04 - Updated to use modern discovery functions\n2020-03-20 - Catalina.sh may return version of "0.0.0.0", should ignore it (DRDC1-14843)\n2020-11-23 - Catalina.sh may return non-existent version, like '0.0.1',\nshould ignore it (DRDC1-16141)\n2023-05-12 - Check for run command updated (DRDC1-19945)` + }, + "convertPathVersion": { + snippets: ["${1:raw}"], + description: `Take a raw path version and process it into something we can use\n\nConvert a version of\n* 85 to 8.5\n* 10 to 10 (no change)\n* 101 to 10.1\n\nTakes : Raw value (e.g. 85)\nReturns : Processed value (e.g. 8.5)` + } + }, + "Apache_LoadBalancer": { + "cleanContent": { + snippets: ["${1:config_content}"], + description: `Removes empty and commented-out lines from the config file - i.e.\nlines starting with "#"\nReturned content has "\n" as end of line character` + }, + "findIpPort": { + snippets: ["${1:host}, ${2:address_port}, ${3:resolved}"], + description: `Keine Beschreibung verfügbar.` + }, + "getContentList": { + snippets: ["${1:config_content}, ${2:tag}"], + description: `There are the directives in the Apache configuration file which have\nopen and close tags, such as Proxy, Location, VirtualHost and so on.\nThis function excludes required sections from the config file and\nprovides them as a list.\nExample:\n[' ... ',\n' ... ']` + } + }, "AppDevAndDeploymentSoftware.SimpleIdentifiers": { }, "Appian.Appian": { @@ -906,274 +908,6 @@ export const generatedTplBuiltIns: Record