diff --git a/.gitignore b/.gitignore index ef860c5..af5ae95 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ node_modules .vscode-test/ *.vsix src/*.js* -src/test/*.js* \ No newline at end of file +src/test/*.js* +out/* +out/*/* +out/*/*/* \ No newline at end of file diff --git a/src/TplBuiltIns.ts b/src/TplBuiltIns.ts new file mode 100644 index 0000000..abeb109 --- /dev/null +++ b/src/TplBuiltIns.ts @@ -0,0 +1,92 @@ +export const tplBuiltIns: Record> = { + "model": { + "NodeKind": "Erstellt oder aktualisiert einen Knoten basierend auf der Taxonomie (z.B. model.SoftwareInstance(key:=\"...\", name:=\"...\")).", + "destroy": "Zerstört den übergebenen Knoten dauerhaft und sofort aus der Datenbank.", + "withdraw": "Zieht die Behauptung des aktuellen Patterns für diesen Knoten zurück (leitet das Standard-Aging ein).", + "host": "Gibt den übergeordneten Host-Knoten zurück, der mit dem übergebenen Knoten (z.B. Prozess oder Software) verknüpft ist.", + "addDisplayAttribute": "Fügt dem Knoten ein Attribut hinzu, das in der Web-Oberfläche ganz oben hervorgehoben wird.", + "setRemovalGroup": "Ordnet den Knoten einer Gruppe zu. Knoten dieser Gruppe, die beim nächsten Scan nicht neu bestätigt werden, werden gelöscht.", + "findEq": "Führt eine exakte Suche nach Knoten in der Datenbank durch (z.B. Suche nach einer bestimmten ID)." + }, + "text": { + "split": "Teilt einen String anhand eines Trennzeichens in eine Liste von Strings auf.", + "join": "Verbindet eine Liste von Strings zu einem einzigen String unter Verwendung eines Trennzeichens.", + "lower": "Konvertiert alle Zeichen eines Strings in Kleinbuchstaben.", + "upper": "Konvertiert alle Zeichen eines Strings in Großbuchstaben.", + "strip": "Entfernt führende und nachfolgende Leerzeichen (Whitespaces) aus einem String.", + "replace": "Ersetzt alle Vorkommen eines Suchstrings durch einen neuen String.", + "hasPrefix": "Prüft, ob ein String mit einem bestimmten Präfix beginnt (gibt true oder false zurück).", + "hasSuffix": "Prüft, ob ein String mit einem bestimmten Suffix endet (gibt true oder false zurück).", + "find": "Sucht einen Teilstring innerhalb eines Strings und gibt die numerische Position zurück (oder -1, wenn nicht gefunden).", + "hash": "Generiert einen numerischen Hash-Wert aus dem übergebenen String (nützlich für die Key-Generierung)." + }, + "regex": { + "extract": "Extrahiert den ersten Treffer einer Capture-Group (in runden Klammern) aus einem String. Nimmt optional einen Fallback-Wert.", + "match": "Prüft, ob ein regulärer Ausdruck auf einen String zutrifft (gibt true oder false zurück).", + "findAll": "Findet alle Treffer eines regulären Ausdrucks in einem String und gibt sie als Liste zurück.", + "replace": "Ersetzt Muster in einem String, die auf den regulären Ausdruck zutreffen, durch einen neuen String." + }, + "discovery": { + "runCommand": "Führt einen Kommandozeilenbefehl auf dem gescannten Zielsystem (Host) aus und gibt das Ergebnis zurück.", + "fileGet": "Ruft den vollständigen Inhalt einer Datei (z.B. Config-File) vom Zielsystem ab.", + "fileInfo": "Ruft nur die Metadaten (Größe, Rechte, Zeitstempel) einer Datei ab, ohne den Inhalt zu lesen.", + "listDirectory": "Listet den Inhalt (Dateien und Unterordner) eines Verzeichnisses auf dem Zielsystem auf.", + "registryKey": "Liest einen bestimmten Registry-Schlüssel auf einem Windows-Host aus.", + "wmiQuery": "Führt eine WQL-Abfrage per WMI auf einem Windows-Host aus.", + "snmpGet": "Führt einen SNMP GET-Request für eine spezifische OID aus (z.B. für Netzwerkgeräte).", + "snmpGetNext": "Führt einen SNMP GETNEXT-Request aus.", + "snmpGetTable": "Ruft eine komplette SNMP-Tabelle vom Zielgerät ab.", + "databaseQuery": "Führt eine SQL-Abfrage über eine in Discovery konfigurierte Datenbank-Integration aus.", + "restRequest": "Führt einen REST-API Request gegen ein konfiguriertes Ziel aus." + }, + "log": { + "debug": "Schreibt eine Debug-Nachricht in das Discovery-Log (nur bei Loglevel DEBUG sichtbar, für tiefe Fehleranalyse).", + "info": "Schreibt eine Informationsnachricht in das Log (verfolgt den normalen Ablauf).", + "warn": "Schreibt eine Warnung in das Log (für unkritische Fehler, z.B. wenn eine Datei leer ist).", + "error": "Schreibt eine Fehlermeldung in das Log (für kritische Pattern-Fehler, die den Ablauf stören).", + "critical": "Schreibt einen kritischen Systemfehler in das Log." + }, + "time": { + "current": "Gibt den aktuellen Zeitpunkt der Ausführung in TPL-Epochenzeit zurück.", + "format": "Formatiert einen TPL-Zeitstempel in einen für Menschen lesbaren String (z.B. \"%Y-%m-%d %H:%M:%S\").", + "parseUTC": "Parst einen Zeit-String als UTC-Zeit und konvertiert ihn in einen TPL-Zeitstempel.", + "parseLocal": "Parst einen Zeit-String als lokale Zeit des Hosts und konvertiert ihn in einen TPL-Zeitstempel.", + "delta": "Berechnet die Differenz zwischen zwei Zeitstempeln (gibt eine Dauer zurück)." + }, + "json": { + "decode": "Parst einen JSON-String und konvertiert ihn in durchsuchbare TPL-Datenstrukturen (Listen und Dictionaries).", + "encode": "Konvertiert eine TPL-Datenstruktur (wie eine Liste oder ein Dictionary) zurück in einen JSON-formatierten String." + }, + "xpath": { + "evaluate": "Führt eine XPath-Abfrage direkt auf einem XML-String aus und gibt die gefundenen Elemente oder Texte zurück.", + "open": "Öffnet und parst ein XML-Dokument für komplexere oder mehrfache Abfragen, was performanter als wiederholtes evaluate ist." + }, + "hash": { + "md5": "Generiert einen MD5-Hash aus dem übergebenen String.", + "sha1": "Generiert einen SHA-1-Hash aus dem übergebenen String.", + "sha256": "Generiert einen SHA-256-Hash aus dem übergebenen String." + }, + "math": { + "abs": "Gibt den absoluten (positiven) Wert einer Zahl zurück.", + "ceil": "Rundet eine Zahl auf die nächste Ganzzahl auf.", + "floor": "Rundet eine Zahl auf die nächste Ganzzahl ab.", + "round": "Rundet eine Zahl nach kaufmännischen Regeln.", + "min": "Gibt die kleinste Zahl aus einer übergebenen Liste von Zahlen zurück.", + "max": "Gibt die größte Zahl aus einer übergebenen Liste von Zahlen zurück." + }, + "net": { + "ipFromString": "Konvertiert einen IP-String in ein internes TPL-IP-Objekt zur weiteren Verarbeitung.", + "ipToString": "Konvertiert ein TPL-IP-Objekt zurück in einen lesbaren String.", + "isIPv4": "Prüft, ob es sich bei dem String um eine gültige IPv4-Adresse handelt (true/false).", + "isIPv6": "Prüft, ob es sich bei dem String um eine gültige IPv6-Adresse handelt (true/false).", + "isMAC": "Prüft, ob der String das Format einer gültigen MAC-Adresse hat (true/false).", + "inSubnet": "Prüft, ob eine bestimmte IP-Adresse innerhalb eines angegebenen Subnetzes liegt." + }, + "built_in_globals": { + "size": "Gibt die Länge oder Anzahl der Elemente einer Liste, eines Strings oder eines Dictionaries zurück.", + "toNumber": "Konvertiert einen String (z.B. \"123\") in einen Integer (Ganzzahl).", + "toString": "Konvertiert einen Wert (z.B. eine Zahl oder ein Objekt) in einen String.", + "type": "Gibt den Datentyp der übergebenen Variable als String zurück (z.B. \"list\", \"string\").", + "defined": "Prüft, ob eine Variable existiert oder ob ein Attribut an einem Knoten vorhanden ist, bevor darauf zugegriffen wird." + } +}; \ No newline at end of file diff --git a/src/TplCompletionProvider.ts b/src/TplCompletionProvider.ts index 8775a00..60e03d9 100644 --- a/src/TplCompletionProvider.ts +++ b/src/TplCompletionProvider.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { tplBuiltIns } from './TplBuiltIns'; export class TplCompletionProvider implements vscode.CompletionItemProvider { async provideCompletionItems(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { @@ -6,15 +7,6 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { const items: vscode.CompletionItem[] = []; const text = document.getText(); - // 1. Die feste "log." Regel - if (linePrefix.endsWith('log.')) { - ['debug', 'info', 'warn', 'error'].forEach(method => { - items.push(new vscode.CompletionItem(method, vscode.CompletionItemKind.Method)); - }); - return items; // Zwingt VS Code, nur diese 4 anzuzeigen! - } - - // 2. Importe und lokale Definitions-Blöcke (Namespaces) sammeln const importMap = new Map(); const importRegex = /from\s+([\w.]+)\s+import\s+(\w+)/g; let m; @@ -28,11 +20,10 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { localNamespaces.add(m[1]); } - // 3. Wenn wir auf freier Fläche tippen (kein Punkt davor) const matchDot = linePrefix.match(/([a-zA-Z0-9_]+)\.$/); if (!matchDot) { - // A) Namespaces (Importiert und Lokal) vorschlagen + // A) Externe & Lokale Module importMap.forEach((_, key) => { const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Module); item.detail = "Importiertes Modul"; @@ -44,7 +35,23 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { items.push(item); }); - // B) Lokale Variablen vorschlagen (z.B. attribute_list :=) + // B) Standard BMC Namespaces (text, regex, discovery etc.) + for (const key of Object.keys(tplBuiltIns)) { + if (key !== 'built_in_globals') { + const item = new vscode.CompletionItem(key, vscode.CompletionItemKind.Module); + item.detail = "BMC Built-In Namespace"; + items.push(item); + } else { + for (const glob of Object.keys(tplBuiltIns[key])) { + const item = new vscode.CompletionItem(glob, vscode.CompletionItemKind.Function); + item.detail = "BMC Globale Funktion"; + item.documentation = new vscode.MarkdownString(tplBuiltIns[key][glob]); + items.push(item); + } + } + } + + // C) Lokale Variablen const varRegex = /\b([a-zA-Z0-9_]+)\s*:=/g; const variables = new Set(); while ((m = varRegex.exec(text)) !== null) { @@ -54,39 +61,20 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { items.push(new vscode.CompletionItem(v, vscode.CompletionItemKind.Variable)); }); - // C) Das große Skeleton Snippet + // D) Snippets const skeletonItem = new vscode.CompletionItem('tpl_skeleton', vscode.CompletionItemKind.Snippet); skeletonItem.insertText = new vscode.SnippetString( 'tpl 1.23 module ${1:module_name};\n\n' + - 'metadata\n' + - ' origin := \'${2:origin}\';\n' + - ' author := \'${3:author}\';\n' + - ' products := \'${4:products}\';\n' + - ' publishers := \'${5:publishers}\';\n' + - ' tree_path := \'${6:company}\', \'${7:category}\', \'${8:subcategory}\';\n' + - 'end metadata;\n\n' + - 'pattern ${9:pattern_name} 1.0\n' + - ' """\n' + - ' TODO: explain\n' + - ' """\n\n' + - ' triggers\n' + - ' on process := DiscoveredProcess where cmd matches regex_escape("${10:process}");\n' + - ' end triggers;\n\n' + - ' body\n' + - ' log.debug("Start pattern ${9:pattern_name}");\n' + - ' $0\n' + - ' end body;\n' + - 'end pattern;' + 'metadata\n origin := \'${2:origin}\';\n author := \'${3:author}\';\n products := \'${4:products}\';\n publishers := \'${5:publishers}\';\n tree_path := \'${6:company}\', \'${7:category}\', \'${8:subcategory}\';\nend metadata;\n\n' + + 'pattern ${9:pattern_name} 1.0\n """\n TODO: explain\n """\n\n triggers\n on process := DiscoveredProcess where cmd matches regex_escape("${10:process}");\n end triggers;\n\n body\n log.debug("Start pattern ${9:pattern_name}");\n $0\n end body;\nend pattern;' ); skeletonItem.detail = "Generiert das komplette TPL Modul-Grundgerüst"; items.push(skeletonItem); - // D) Block-Snippets const blocks = ['metadata', 'overview', 'constants', 'triggers', 'body', 'mapping', 'removal', 'definitions', 'enumeration', 'configuration']; for (const block of blocks) { const item = new vscode.CompletionItem(block, vscode.CompletionItemKind.Snippet); item.insertText = new vscode.SnippetString(`${block}\n $0\nend ${block};`); - item.detail = `Fügt ${block} Block mit 'end' ein`; items.push(item); } @@ -106,39 +94,36 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { tableItem.insertText = new vscode.SnippetString('table ${1:name}${2:1.0}\n $0\n default -> "unknown";\nend table;'); items.push(tableItem); - const ifItem = new vscode.CompletionItem('if', vscode.CompletionItemKind.Snippet); - ifItem.insertText = new vscode.SnippetString('if ${1:condition} then\n $0\nend if;'); - items.push(ifItem); - - const forItem = new vscode.CompletionItem('for', vscode.CompletionItemKind.Snippet); - forItem.insertText = new vscode.SnippetString('for ${1:item} in${2:list} do\n $0\nend for;'); - items.push(forItem); - return items; } else { - // 4. Wir haben nach einem Punkt getippt (z.B. helpers.) const ns = matchDot[1]; - const moduleMethods: vscode.CompletionItem[] = []; + + // NEU: Ist es ein BMC Built-In Namespace? (z.B. text. oder log.) + if (tplBuiltIns[ns]) { + const bmcMethods: vscode.CompletionItem[] = []; + for (const [method, desc] of Object.entries(tplBuiltIns[ns])) { + const item = new vscode.CompletionItem(method, vscode.CompletionItemKind.Method); + item.documentation = new vscode.MarkdownString(desc); + bmcMethods.push(item); + } + return bmcMethods; + } - // Regex sucht nach dem Wort direkt vor der Klammer: define [add] method_name( + const moduleMethods: vscode.CompletionItem[] = []; const extractDefineRegex = /^\s*define\s+.*?\b([a-zA-Z0-9_]+)\s*\(/gm; - // Fall A: Lokaler Definitions-Block (gleiche Datei!) if (localNamespaces.has(ns)) { let defMatch; while ((defMatch = extractDefineRegex.exec(text)) !== null) { const methodName = defMatch[1]; const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method); const docStr = this.extractDocString(text, defMatch.index); - if (docStr) { - item.documentation = new vscode.MarkdownString(docStr); - } + if (docStr) item.documentation = new vscode.MarkdownString(docStr); moduleMethods.push(item); } return moduleMethods; } - // Fall B: Importiertes Modul (externe Datei) if (importMap.has(ns)) { const modPath = importMap.get(ns)!; const uris = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**', 50); @@ -146,28 +131,21 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { try { const docBytes = await vscode.workspace.fs.readFile(uri); const docText = Buffer.from(docBytes).toString('utf8'); - const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`); if (modRegex.test(docText)) { let defMatch; while ((defMatch = extractDefineRegex.exec(docText)) !== null) { const methodName = defMatch[1]; const item = new vscode.CompletionItem(methodName, vscode.CompletionItemKind.Method); - const docStr = this.extractDocString(docText, defMatch.index); - if (docStr) { - item.documentation = new vscode.MarkdownString(docStr); - } + if (docStr) item.documentation = new vscode.MarkdownString(docStr); moduleMethods.push(item); } break; } } catch(e) {} } - - if (moduleMethods.length > 0) { - return moduleMethods; - } + if (moduleMethods.length > 0) return moduleMethods; } } return items; @@ -178,32 +156,23 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider { let docString = ""; let inDocString = false; let quoteType = ""; - for (let j = 1; j < Math.min(15, lines.length); j++) { const line = lines[j].trim(); if (line.length === 0) continue; - if (!inDocString) { if (line.startsWith('"""') || line.startsWith("'''")) { inDocString = true; quoteType = line.substring(0, 3); - if (line.length > 3 && line.endsWith(quoteType)) { - docString = line.substring(3, line.length - 3).trim(); - break; - } + if (line.length > 3 && line.endsWith(quoteType)) { docString = line.substring(3, line.length - 3).trim(); break; } const content = line.substring(3).trim(); if (content) docString += content + "\n"; - } else { - break; - } + } else { break; } } else { if (line.endsWith(quoteType)) { const content = line.substring(0, line.length - 3).trim(); if (content) docString += content; break; - } else { - docString += line + "\n"; - } + } else { docString += line + "\n"; } } } return docString.trim() || null; diff --git a/src/TplHoverProvider.ts b/src/TplHoverProvider.ts index 4d73312..149ed0f 100644 --- a/src/TplHoverProvider.ts +++ b/src/TplHoverProvider.ts @@ -1,4 +1,5 @@ import * as vscode from 'vscode'; +import { tplBuiltIns } from './TplBuiltIns'; export class TplHoverProvider implements vscode.HoverProvider { async provideHover(document: vscode.TextDocument, position: vscode.Position, token: vscode.CancellationToken): Promise { @@ -17,32 +18,34 @@ export class TplHoverProvider implements vscode.HoverProvider { methodOrPatternName = parts[1]; } - // FEHLERBEHEBUNG: [ \t] statt \s verwenden! - // \s frisst Zeilenumbrüche (\n), was dazu geführt hat, dass match.index - // auf leere Zeilen oberhalb der Methode gezeigt hat -> Leeres Hover-Fenster. - const searchRegex = new RegExp(`^[ \\t]*(?:(?:define[ \\t]+(?:[a-zA-Z0-9_]+[ \\t]+)*${methodOrPatternName}[ \\t]*\\()|(?:(?:pattern|syncmapping|table|enumeration|configuration)[ \\t]+${methodOrPatternName}\\b))`, 'm'); - - // 1. Suche im aktuellen Dokument (Ohne Namespace) - if (!namespace) { - const match = searchRegex.exec(currentText); - if (match) { - return this.buildHover(currentText, match.index); - } - return null; // Bricht sauber ab, ohne das System zu blockieren + // NEU: BMC Built-Ins abfangen und direkt ausgeben! + if (namespace && tplBuiltIns[namespace] && tplBuiltIns[namespace][methodOrPatternName]) { + const markdown = new vscode.MarkdownString(); + markdown.appendCodeblock(`${namespace}.${methodOrPatternName}`, 'tpl'); + markdown.appendMarkdown(`---\n*${tplBuiltIns[namespace][methodOrPatternName]}*`); + return new vscode.Hover(markdown); + } else if (!namespace && tplBuiltIns['built_in_globals'][methodOrPatternName]) { + const markdown = new vscode.MarkdownString(); + markdown.appendCodeblock(`${methodOrPatternName}`, 'tpl'); + markdown.appendMarkdown(`---\n*${tplBuiltIns['built_in_globals'][methodOrPatternName]}*`); + return new vscode.Hover(markdown); + } + + const searchRegex = new RegExp(`^[ \\t]*(?:(?:define[ \\t]+(?:[a-zA-Z0-9_]+[ \\t]+)*${methodOrPatternName}[ \\t]*\\()|(?:(?:pattern|syncmapping|table|enumeration|configuration)[ \\t]+${methodOrPatternName}\\b))`, 'm'); + + if (!namespace) { + const match = searchRegex.exec(currentText); + if (match) return this.buildHover(currentText, match.index); + return null; } - // 2. Suche mit Namespace (z.B. helpers.method) - // A) Ist der Namespace in derselben Datei? (lokaler definitions block) const localDefRegex = new RegExp(`^[ \\t]*definitions[ \\t]+${namespace}\\b`, 'm'); if (localDefRegex.test(currentText)) { const match = searchRegex.exec(currentText); - if (match) { - return this.buildHover(currentText, match.index); - } + if (match) return this.buildHover(currentText, match.index); return null; } - // B) Ist der Namespace importiert? (Workspace durchsuchen) const importRegex = new RegExp(`from\\s+([\\w.]+)\\s+import\\s+${namespace}\\b`); const importMatch = importRegex.exec(currentText); @@ -53,35 +56,25 @@ export class TplHoverProvider implements vscode.HoverProvider { try { const docBytes = await vscode.workspace.fs.readFile(uri); const docText = Buffer.from(docBytes).toString('utf8'); - const modRegex = new RegExp(`tpl\\s+1\\.\\d+\\s+module\\s+${modPath.replace(/\./g, '\\\\.')}\\s*;`); if (modRegex.test(docText)) { const match = searchRegex.exec(docText); - if (match) { - return this.buildHover(docText, match.index); - } + if (match) return this.buildHover(docText, match.index); break; } } catch(e) {} } } - return null; } private buildHover(text: string, matchIndex: number): vscode.Hover { const docStr = this.extractDocString(text, matchIndex); const markdown = new vscode.MarkdownString(); - - // Schneidet exakt die Code-Zeile aus (ohne vorausgehende Leerzeilen) const declLine = text.substring(matchIndex).split('\n')[0].trim(); markdown.appendCodeblock(declLine, 'tpl'); - - if (docStr) { - markdown.appendMarkdown(`---\n*${docStr}*`); - } else { - markdown.appendMarkdown(`---\n*(Keine Dokumentation gefunden)*`); - } + if (docStr) markdown.appendMarkdown(`---\n*${docStr}*`); + else markdown.appendMarkdown(`---\n*(Keine Dokumentation gefunden)*`); return new vscode.Hover(markdown); } @@ -90,32 +83,23 @@ export class TplHoverProvider implements vscode.HoverProvider { let docString = ""; let inDocString = false; let quoteType = ""; - for (let j = 1; j < Math.min(15, lines.length); j++) { const line = lines[j].trim(); if (line.length === 0) continue; - if (!inDocString) { if (line.startsWith('"""') || line.startsWith("'''")) { inDocString = true; quoteType = line.substring(0, 3); - if (line.length > 3 && line.endsWith(quoteType)) { - docString = line.substring(3, line.length - 3).trim(); - break; - } + if (line.length > 3 && line.endsWith(quoteType)) { docString = line.substring(3, line.length - 3).trim(); break; } const content = line.substring(3).trim(); if (content) docString += content + "\n"; - } else { - break; - } + } else { break; } } else { if (line.endsWith(quoteType)) { const content = line.substring(0, line.length - 3).trim(); if (content) docString += content; break; - } else { - docString += line + "\n"; - } + } else { docString += line + "\n"; } } } return docString.trim() || null; diff --git a/src/extension.ts b/src/extension.ts index 7937b78..fad48dd 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -3,6 +3,7 @@ import { TplCodeActionProvider } from './TplCodeActionProvider'; import { TplDocumentFormatter } from './TplDocumentFormatter'; import { TplCompletionProvider } from './TplCompletionProvider'; import { TplHoverProvider } from './TplHoverProvider'; +import { tplBuiltIns } from './TplBuiltIns'; const SUPPORTED_TPL_VERSION = 23; const warnedDocuments = new Set(); @@ -150,6 +151,21 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D } } + // Linter-Check für falsche BMC Built-In Methoden + const callMatch = rawLine.match(/\b(text|regex|discovery|log|time|json|xpath|hash|math|net)\.([a-zA-Z0-9_]+)\b/g); + if (callMatch && !inMultiLineComment && !isInternalStringLine) { + for (const call of callMatch) { + const parts = call.split('.'); + const ns = parts[0]; + const method = parts[1]; + + if (tplBuiltIns[ns] && !tplBuiltIns[ns][method]) { + const idx = rawLine.indexOf(call); + addDiag(i, idx, idx + call.length, `Unbekannte Methode '${method}' im Namespace '${ns}'.`, 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 ;' beginnen.", vscode.DiagnosticSeverity.Error); hasModuleDef = true; } diff --git a/tsconfig.json b/tsconfig.json index 7e8073b..3b25c4d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -10,6 +10,7 @@ "mocha" ], "sourceMap": true, + "outDir": "out", "rootDir": "src", "strict": true, /* enable all strict type-checking options */ /* Additional Checks */