Autocomplete erweitert mit standard Funktionen von BMC, Hover geht wieder, autogenerierung von sektionen, erweiterung der autovervollständigung um parameter
This commit is contained in:
parent
a5cd6a74ca
commit
f9f44e0312
30
package.json
30
package.json
|
|
@ -34,6 +34,36 @@
|
|||
],
|
||||
"main": "./dist/extension.js",
|
||||
"contributes": {
|
||||
"configuration": {
|
||||
"title": "BMC TPL Einstellungen",
|
||||
"properties": {
|
||||
"tpl.metadata.author": {
|
||||
"type": "string",
|
||||
"default": "Autor",
|
||||
"description": "Standard-Autor für neu generierte TPL-Module."
|
||||
},
|
||||
"tpl.metadata.origin": {
|
||||
"type": "string",
|
||||
"default": "Custom",
|
||||
"description": "Standard-Origin für neu generierte TPL-Module."
|
||||
},
|
||||
"tpl.metadata.publisher": {
|
||||
"type": "string",
|
||||
"default": "IT-Operations",
|
||||
"description": "Standard-Publisher für neu generierte TPL-Module."
|
||||
},
|
||||
"tpl.metadata.treePathBase": {
|
||||
"type": "string",
|
||||
"default": "Company",
|
||||
"description": "Erster Teil (Basis) des tree_paths in den Metadaten."
|
||||
},
|
||||
"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."
|
||||
}
|
||||
}
|
||||
},
|
||||
"languages": [
|
||||
{
|
||||
"id": "tpl",
|
||||
|
|
|
|||
|
|
@ -1,92 +1,244 @@
|
|||
export const tplBuiltIns: Record<string, Record<string, string>> = {
|
||||
"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)."
|
||||
export interface TplBuiltInMethod {
|
||||
description: string;
|
||||
snippets: string[];
|
||||
}
|
||||
|
||||
export const tplBuiltIns: Record<string, Record<string, TplBuiltInMethod>> = {
|
||||
"built_in_globals": {
|
||||
"datatype": {
|
||||
snippets: ["${1:item}"],
|
||||
description: `Returns a string indicating the type of the item.\n\nPossible return values are:\n"node"\n"relationship"\n"text"\n"boolean"\n"number"\n"float"\n"list"\n"table"\n"none"\n"void"\n"datetime"\n"nodeset"\n"unknown" (used for anything else)`
|
||||
},
|
||||
"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."
|
||||
"size": {
|
||||
snippets: ["${1:collection}"],
|
||||
description: `Returns the number of items in the collection.\n\nWhere the collection is a list, or a set of nodes retrieved with a search expression.\nWhere the collection is a string, the number of characters is returned.\nTo determine the type of the collection, you can use the datatype function.`
|
||||
}
|
||||
},
|
||||
"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."
|
||||
"debug": { snippets: ["${1:output}"], description: `Log the given message with a debug level message.` },
|
||||
"info": { snippets: ["${1:output}"], description: `Log the given message with an info level message.` },
|
||||
"warn": { snippets: ["${1:output}"], description: `Log the given message with a warn level message.` },
|
||||
"error": { snippets: ["${1:output}"], description: `Log the given message with an error level message.` },
|
||||
"critical": { snippets: ["${1:output}"], description: `Log the given message with a critical level message.` }
|
||||
},
|
||||
"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)."
|
||||
"text": {
|
||||
"lower": { snippets: ["${1:input}"], description: `Returns the lower-cased version of the string argument.` },
|
||||
"upper": { snippets: ["${1:input}"], description: `Returns the upper-cased version of the string argument.` },
|
||||
"toNumber": {
|
||||
snippets: ["${1:input}", "${1:input},${2:base}"],
|
||||
description: `Converts its string argument into a number. By default, the number is treated as a base 10 value; if the optional base is provided, the number is treated as being in the specified base. Bases from 2 to 36 are supported.`
|
||||
},
|
||||
"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."
|
||||
"replace": {
|
||||
snippets: ["${1:input}, ${2:old},${3:new}"],
|
||||
description: `Returns a modified version of the string formed by replacing all occurrences of the string old with new.`
|
||||
},
|
||||
"join": {
|
||||
snippets: ["${1:list},${2:separator}"],
|
||||
description: `Returns a string containing all items in a list of strings joined with the specified separator.`
|
||||
},
|
||||
"split": {
|
||||
snippets: ["${1:input}", "${1:input},${2:separator}"],
|
||||
description: `Returns a list consisting of portions of the string split according to the separator string, where specified.`
|
||||
},
|
||||
"strip": {
|
||||
snippets: ["${1:input}", "${1:input},${2:characters}"],
|
||||
description: `Strips unwanted characters from the start and end of the given string.`
|
||||
},
|
||||
"leftStrip": {
|
||||
snippets: ["${1:input}", "${1:input},${2:characters}"],
|
||||
description: `Equivalent to text.strip, but only strips from the left side of the string.`
|
||||
},
|
||||
"rightStrip": {
|
||||
snippets: ["${1:input}", "${1:input},${2:characters}"],
|
||||
description: `Equivalent to text.strip, but only strips from the right side of the string.`
|
||||
},
|
||||
"hash": { snippets: ["${1:input}"], description: `Returns a hashed form of the string, generated with the MD5 hash algorithm.` },
|
||||
"ordinal": { snippets: ["${1:input}"], description: `Returns the ordinal value of the string argument. The string must be one character in length.` }
|
||||
},
|
||||
"number": {
|
||||
"toChar": { snippets: ["${1:number}"], description: `Converts the integer number in the ASCII range to a character.` },
|
||||
"toText": {
|
||||
snippets: ["${1:number}", "${1:number}, ${2:base}", "${1:number}, ${2:base},${3:width}"],
|
||||
description: `Converts the integer number to a text form. The default is to use the base 10 representation. If the optional base argument is given, conversion uses that base instead.`
|
||||
},
|
||||
"range": { snippets: ["${1:number}"], description: `Generate a list containing 0 to number - 1.` }
|
||||
},
|
||||
"regex": {
|
||||
"extract": {
|
||||
snippets: ["${1:string}, ${2:expression}", "${1:string}, ${2:expression},${3:substitution}", "${1:string},${2:expression}, ${3:substitution},${4:no_match}"],
|
||||
description: `Returns the result of extracting the regular expression from the string.`
|
||||
},
|
||||
"extractAll": { snippets: ["${1:string},${2:pattern}"], description: `Returns a list containing all the non-overlapping matches of the pattern in the string.` },
|
||||
"replace": {
|
||||
snippets: ["${1:string}, ${2:pattern},${3:replacement}", "${1:string},${2:pattern}, ${3:replacement}, count:=${4:0}"],
|
||||
description: `Return a string with all non-overlapping matches of pattern replaced by replacement.`
|
||||
},
|
||||
"escape": { snippets: ["${1:string}"], description: `Returns a string with regex special characters suitably escaped.` }
|
||||
},
|
||||
"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."
|
||||
"openDocument": { snippets: ["${1:string}"], description: `Returns the DOM object resulting from parsing the XML string.` },
|
||||
"evaluate": {
|
||||
snippets: ["${1:sourceXML}, ${2:expression}", "${1:sourceXML}, ${2:expression},${3:namespaces}", "${1:sourceXML},${2:expression}, ${3:namespaces},${4:return_elements}"],
|
||||
description: `Returns the result of evaluating the XPath expression against the XML string.`
|
||||
},
|
||||
"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."
|
||||
"closeDocument": { snippets: ["${1:dom_object}"], description: `Closes an XML document opened with xpath.openDocument.` }
|
||||
},
|
||||
"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."
|
||||
"table": {
|
||||
"remove": { snippets: ["${1:table},${2:key}"], description: `Removes the specified key from the specified table.` },
|
||||
"get": {
|
||||
snippets: ["${1:table}, ${2:key}", "${1:table}, ${2:key}, default_val:=${3:none}"],
|
||||
description: `Returns the key from table if it exists, or if it does not exist, returns the specified default_val.`
|
||||
},
|
||||
"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."
|
||||
"keys": { snippets: ["${1:table}"], description: `Returns a list of the keys in the table.` },
|
||||
"values": { snippets: ["${1:table}"], description: `Returns a list of the values in the table.` }
|
||||
},
|
||||
"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."
|
||||
"Future": {
|
||||
"runCommand": {
|
||||
snippets: ["${1:target}, ${2:command},${3:path_to_command}"],
|
||||
description: `Returns a DiscoveredCommandResult node containing the result of running the specified command. Enables command paths to be restricted.`
|
||||
}
|
||||
},
|
||||
"discovery": {
|
||||
"fileGet": {
|
||||
snippets: ["${1:target}, ${2:filename}", "${1:target}, ${2:filename}, container_context:=${3:true}", "${1:target},${2:filename}, container_context:=${3:true}, win64_redirect:=${4:true}"],
|
||||
description: `Retrieves the specified file.\n\nThe function takes the following parameters:\n\ntarget: a node used to identify the discovery target.\nfilename: the name of the file on which to return information.\ncontainer_context: an optional flag to specify how container context is interpreted.\nwin64_redirect: an optional flag which enables you to specify whether the search for the file should take place in the 32 bit directory if not found in the 64 bit directory.\n\nReturns a DiscoveredFile node.`
|
||||
},
|
||||
"fileInfo": {
|
||||
snippets: ["${1:target}, ${2:filename}", "${1:target}, ${2:filename}, container_context:=${3:true}", "${1:target},${2:filename}, container_context:=${3:true}, win64_redirect:=${4:true}"],
|
||||
description: `Retrieves information about the specified file, but not the file content. This information is useful if the file is a binary file or particularly large.\n\nReturns a DiscoveredFile node with no content attribute.`
|
||||
},
|
||||
"listDirectory": {
|
||||
snippets: ["${1:target}, ${2:path}", "${1:target}, ${2:path}, container_context:=${3:true}", "${1:target},${2:path}, container_context:=${3:true}, win64_redirect:=${4:true}"],
|
||||
description: `Retrieves the directory listing of the directory specified by the path on the specified target.\n\nThe function takes the following parameters:\n\ntarget, where target is:\na node used to identify the discovery target, either a directly discovered data node, or a Host node. Or,\na data source against which the request is performed. See also, discovery-dataSource.\npath – the path to the directory on which to return information. You cannot use wildcards in the path. \ncontainer_context is an optional flag to specify how container context is interpreted for the command. When a pattern triggers on a DiscoveredProcess or RuntimeEnvironment within a container, or on a ContainedSoftwareInstance or SoftwareContainer node, then the requests are executed in the container. This behavior is the default; container_context = true. However, on occasions where you want to run the request on the host, for example, you might be examining the /proc file system, you must specify container_context = false. When the pattern triggers outside a container, then there is no container context, and the flag has no effect.\nwin64_redirect is an optional flag which enables you to specify whether the search for the file should take place in the 32 bit directory if not found in the 64 bit directory. TRUE means search the 32 bit directory, FALSE means do not search the 32 bit directory if the file is not found in the 64 bit directory. The default is TRUE.\nReturns a list of Discovered Directory Entry nodes.\n\n64 bit Windows hosts have the following system directories for holding executables, DLLs, and so forth:\n\n%windir%\\system32 — 64 bit executables and DLLs\n%windir%\\SysWOW64 — 32 bit executables and DLLs\nPaths that do not start with %windir%\\system32 or %systemroot%\\system32 are never checked for redirection.\n\nOn non-Windows and 32 bit Windows systems win64_redirect has no effect.\n\nIf the call fails, it returns none and records the failure reason in the Directory Listing node.\n\nRestrictions\nOn Windows systems, owner and group information is not recovered.\nGroup names which include spaces are not supported.\nConfig.sys is not listed as a system file.\nDirectory Listing nodes are not created for empty Windows directories.`
|
||||
},
|
||||
"getNames": { snippets: ["${1:target},${2:ip_address}"], description: `Performs a DNS lookup on the IP address and returns a list of FQDN strings.` },
|
||||
"lookupAddresses": { snippets: ["${1:target},${2:name}"], description: `Performs a DNS lookup on the supplied DNS resolvable name and returns a list of IP addresses.` },
|
||||
"listRegistry": {
|
||||
snippets: ["${1:target}, ${2:key_path}", "${1:target}, ${2:key_path}, win64_redirect:=${3:true}"],
|
||||
description: `Returns a list of the registry entries of the registry key specified by the key_path.`
|
||||
},
|
||||
"registryKey": {
|
||||
snippets: ["${1:target}, ${2:key}", "${1:target}, ${2:key}, win64_redirect:=${3:true}"],
|
||||
description: `Retrieves a registry key from a Windows computer.\nReturns a DiscoveredRegistryValue node.`
|
||||
},
|
||||
"wmiQuery": {
|
||||
snippets: ["${1:target}, ${2:query},${3:namespace}"],
|
||||
description: `Performs a WMI query on a Windows computer.\nReturns a list of DiscoveredWMI nodes.`
|
||||
},
|
||||
"wbemQuery": {
|
||||
snippets: ["${1:target}, ${2:class_name},${3:namespace}", "${1:target},${2:class_name}, ${3:properties},${4:namespace}"],
|
||||
description: `Performs a WBEM query on the target and returns a list of DiscoveredWBEM DDD nodes.`
|
||||
},
|
||||
"wbemEnumInstances": {
|
||||
snippets: ["${1:target}, ${2:class_name},${3:properties}, ${4:namespace}, filter_locally:=${5:false}"],
|
||||
description: `Performs a WBEM query on the target and returns a list of DiscoveredWBEMInstance DDD nodes.`
|
||||
},
|
||||
"runCommand": {
|
||||
snippets: ["${1:target}, ${2:command},${3:restrict_paths}, ${4:dialect}", "${1:target}, ${2:command},${3:restrict_paths}, ${4:dialect}, container_context:=${5:true}"],
|
||||
description: `Returns a DiscoveredCommandResult node containing the result of running the specified command.`
|
||||
},
|
||||
"runPowerShell": {
|
||||
snippets: ["${1:target}, ${2:script},${3:restrict_paths}"],
|
||||
description: `Returns a DiscoveredCommandResult node containing the result of running the specified PowerShell script.`
|
||||
},
|
||||
"snmpGet": {
|
||||
snippets: ["${1:target}, ${2:oid_table}", "${1:target}, ${2:oid_table},${3:binary_oid_list}"],
|
||||
description: `Performs an SNMP query on the target and returns a DiscoveredSNMP node.`
|
||||
},
|
||||
"snmpGetTable": {
|
||||
snippets: ["${1:target}, ${2:table_oid},${3:column_table}", "${1:target},${2:table_oid}, ${3:column_table},${4:binary_oid_list}"],
|
||||
description: `Performs an SNMP query that returns a table on the target. Returns a list of DiscoveredSNMPRow nodes or none if the SNMP query failed.`
|
||||
},
|
||||
"scan": {
|
||||
snippets: ["${1:target},${2:company}, ${3:outpost_id},${4:scope}"],
|
||||
description: `Adds an open scan for the given target and returns a count of the number of endpoints included in the open scan.`
|
||||
},
|
||||
"process": { snippets: ["${1:source}"], description: `Returns the process node corresponding to the source node.` },
|
||||
"children": { snippets: ["${1:process}"], description: `Returns a list of the child processes for the given DiscoveredProcess node.` },
|
||||
"descendents": { snippets: ["${1:process}"], description: `Returns a list consisting of the children of the given DiscoveredProcess node, and recursively all of the children's children.` },
|
||||
"parent": { snippets: ["${1:process}"], description: `Returns the parent process for the given DiscoveredProcess node.` },
|
||||
"allProcesses": { snippets: ["${1:source}"], description: `Returns a list of all processes corresponding to the directly discovered data source node.` },
|
||||
"access": { snippets: ["${1:source}"], description: `Returns the Discovery Access node for the source DDD node, if given.` },
|
||||
"startTime": { snippets: ["${1:source}"], description: `Returns the start time of the scan which discovered the source DDD node.` }
|
||||
},
|
||||
"binary": {
|
||||
"toHexString": { snippets: ["${1:value}"], description: `Returns the given binary value as a hex string.` },
|
||||
"toIPv4": { snippets: ["${1:value}"], description: `Returns the given binary value as the text representation of an IPv4 address.` },
|
||||
"toIPv4z": { snippets: ["${1:value}"], description: `Returns the given binary value as the text representation of an IPv4 address with a zone index.` },
|
||||
"toIPv6": { snippets: ["${1:value}"], description: `Returns the given binary value as the text representation of a canonical IPv6 address.` },
|
||||
"toIPv6z": { snippets: ["${1:value}"], description: `Returns the given binary value as the text representation of a canonical IPv6 address with zone index.` },
|
||||
"toMACAddress": { snippets: ["${1:value}"], description: `Returns the given binary value as the text representation of a MAC address.` },
|
||||
"toValue": { snippets: ["${1:data},${2:format}"], description: `Converts the given binary value into the specified format.` },
|
||||
"toWWN": { snippets: ["${1:value}"], description: `Returns the given binary value as the text representation of a WWN value.` }
|
||||
},
|
||||
"json": {
|
||||
"encode": { snippets: ["${1:value}"], description: `Converts value to a JSON encoded string. Where value is a string, table, or a list including nested structures.` },
|
||||
"decode": { snippets: ["${1:value}"], description: `Decodes a JSON encoded string and returns a structure containing a number, boolean, null, string, table or list including nested structures.` }
|
||||
},
|
||||
"jmespath": {
|
||||
"evaluate": { snippets: ["${1:source},${2:expression}"], description: `Returns the https://jmespath.org/ evaluation for the expression given a source table or list.` }
|
||||
},
|
||||
"yaml": {
|
||||
"encode": { snippets: ["${1:object}"], description: `Converts the given object into a YAML-encoded string.` },
|
||||
"decode": { snippets: ["${1:value}"], description: `Converts the given YAML encoded string into an object.` },
|
||||
"decodeAll": { snippets: ["${1:value}"], description: `Converts the given YAML encoded string into a list of objects.` }
|
||||
},
|
||||
"list": {
|
||||
"append": { snippets: ["${1:list},${2:value}"], description: `Appends an item to a specified list.` },
|
||||
"sorted": { snippets: ["${1:items}", "${1:items}, reverse:=${2:false}"], description: `Returns a sorted copy of items list, leaving the items unchanged. If reverse is true, sorts in reverse order.` }
|
||||
},
|
||||
"model": {
|
||||
"addContainment": { snippets: ["${1:container},${2:containees}"], description: `Adds the containees to the container by creating suitable relationships between the nodes.` },
|
||||
"setContainment": { snippets: ["${1:container},${2:containees}"], description: `Equivalent to addContainment, except that at the end of the pattern body, any relationships to contained nodes that have not been confirmed are removed.` },
|
||||
"destroy": { snippets: ["${1:node}"], description: `Destroy the specified node or relationship in the model.` },
|
||||
"withdraw": { snippets: ["${1:node},${2:attribute}"], description: `Removes the named attribute from the node.` },
|
||||
"setRemovalGroup": { snippets: ["${1:node}", "${1:node},${2:name}"], description: `Add the specified node or nodes to a named removal group.` },
|
||||
"anchorRemovalGroup": { snippets: ["${1:node}", "${1:node},${2:name}"], description: `Specify an anchor node for a named removal group.` },
|
||||
"suppressRemovalGroup": { snippets: ["${1:name}"], description: `Suppress removal of the named removal group.` },
|
||||
"host": { snippets: ["${1:node}"], description: `Returns the Host node corresponding to the given node.` },
|
||||
"hosts": { snippets: ["${1:node}"], description: `Returns a list of all the Host nodes corresponding to the given node.` },
|
||||
"findPackages": { snippets: ["${1:node},${2:regexes}"], description: `Traverses from the node, and returns a set of all Package nodes that have names matching the provided list of regular expressions.` },
|
||||
"addDisplayAttribute": { snippets: ["${1:node},${2:value}"], description: `Adds a named attribute, or a list of named attributes to the additional attributes displayed in a node view.` },
|
||||
"removeDisplayAttribute": { snippets: ["${1:node},${2:value}"], description: `Removes a named attribute, or a list of named attributes from the additional attributes displayed in a node view.` },
|
||||
"kind": { snippets: ["${1:node}"], description: `Returns the node kind corresponding to the given node.` },
|
||||
"confirm": { snippets: ["${1:node}"], description: `Sends a confirmed event for the node.` },
|
||||
"findCandidate": { snippets: ["${1:kind},${2:data}"], description: `Finds a candidate node of a kind that matches table data.` },
|
||||
"findCandidates": { snippets: ["${1:kind}, ${2:data},${3:limit}"], description: `Finds ranked candidates.` },
|
||||
"publish": { snippets: ["${1:node},${2:auto_rels}"], description: `Takes a node of kind ModelDefinition and publishes it, creating a BusinessService, TechnicalService or BusinessApplicationInstance node.` },
|
||||
"unpublish": { snippets: ["${1:node}"], description: `Takes a node of kind ModelDefinition that has been published, and unpublishes it.` }
|
||||
},
|
||||
"related": {
|
||||
"host": { snippets: ["${1:node}"], description: `Returns the Host node corresponding to the given node.` },
|
||||
"detailContainer": { snippets: ["${1:node}"], description: `Returns the Software Component, Software Instance, or Business Application Instance node containing the given node.` },
|
||||
"hostingNode": { snippets: ["${1:fallback_kind},${2:attributes}"], description: `Returns a Host or Cluster node that is associated with the node that triggered the pattern.` }
|
||||
},
|
||||
"mail": {
|
||||
"send": {
|
||||
snippets: ["${1:recipients},${2:subject}, ${3:message}", "${1:recipients}, ${2:subject},${3:message}, ${4:cc_addrs}", "${1:recipients}, ${2:subject},${3:message}, ${4:cc_addrs},${5:bcc_addrs}"],
|
||||
description: `Sends an email.`
|
||||
}
|
||||
},
|
||||
"time": {
|
||||
"current": { snippets: [""], description: `Returns an internal datetime object representing the current UTC time.` },
|
||||
"delta": { snippets: ["${1:days},${2:hours}, ${3:minutes},${4:seconds}"], description: `Creates a time delta that can be added to or subtracted from a time represented by an internal datetime object.` },
|
||||
"parseLocal": { snippets: ["${1:string}"], description: `Converts a string representing a local time into an internal UTC datetime object.` },
|
||||
"parseUTC": { snippets: ["${1:string}"], description: `Takes a UTC time string and converts it into an internal datetime object.` },
|
||||
"formatLocal": { snippets: ["${1:datetime}", "${1:datetime},${2:format}"], description: `Formats an internal datetime object into a human-readable string. Converts the time into the appliance's timezone.` },
|
||||
"formatUTC": { snippets: ["${1:datetime}", "${1:datetime},${2:format}"], description: `Formats an internal datetime object into a human-readable string. Performs no timezone conversion.` },
|
||||
"toTicks": { snippets: ["${1:datetime}"], description: `Converts an internal datetime object (UTC) to ticks.` },
|
||||
"fromTicks": { snippets: ["${1:ticks}"], description: `Converts ticks to an internal datetime object (UTC).` },
|
||||
"deltaFromTicks": { snippets: ["${1:ticks}"], description: `Converts ticks into a time delta.` }
|
||||
},
|
||||
"inference": {
|
||||
"associate": { snippets: ["${1:inferred_node},${2:associate}"], description: `Create associate inference relationship(s) from the specified node(s) to the inferred node.` },
|
||||
"contributor": { snippets: ["${1:inferred_node}, ${2:contributor},${3:contributes}"], description: `Create contributor inference relationship(s).` },
|
||||
"primary": { snippets: ["${1:inferred_node}, ${2:evidence}", "${1:inferred_node}, ${2:evidence}, replace:=${3:false}"], description: `Create or replace primary inference relationship(s).` },
|
||||
"relation": { snippets: ["${1:inferred_relationship},${2:source}"], description: `Create relation inference relationship(s) from the specified node(s) to the inferred relationship.` },
|
||||
"withdrawal": { snippets: ["${1:inferred_node}, ${2:evidence},${3:withdrawn}"], description: `Create withdrawal inference relationship(s).` },
|
||||
"destruction": { snippets: ["${1:destroyed_node},${2:source}"], description: `When destroying a node, indicate that the source node was responsible for its destruction.` }
|
||||
},
|
||||
"system": {
|
||||
"getOption": { snippets: ["${1:option_name}"], description: `Takes the name of a BMC Helix Discovery system option and returns the value.` },
|
||||
"cmdbSync": { snippets: ["${1:nodes}"], description: `Adds the given root node or list of root nodes to the CMDB synchronization queue.` }
|
||||
}
|
||||
};
|
||||
|
|
@ -35,21 +35,30 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
|||
items.push(item);
|
||||
});
|
||||
|
||||
// B) Standard BMC Namespaces (text, regex, discovery etc.)
|
||||
// B) Standard BMC Namespaces
|
||||
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);
|
||||
for (const [glob, def] of Object.entries(tplBuiltIns[key])) {
|
||||
for (const snippet of def.snippets) {
|
||||
// Snippet-Syntax entfernen für das Anzeige-Label (z.B. "${1:number}" -> "number")
|
||||
const cleanSnippet = snippet.replace(/\$\{\d+:([^}]+)\}/g, '$1');
|
||||
|
||||
const item = new vscode.CompletionItem(`${glob}(${cleanSnippet})`, vscode.CompletionItemKind.Function);
|
||||
// Setzt das echte Snippet mit Tab-Stops ein
|
||||
item.insertText = new vscode.SnippetString(`${glob}(${snippet})`);
|
||||
// VS Code darf nur nach dem Methodennamen filtern, nicht nach den Parametern!
|
||||
item.filterText = glob;
|
||||
item.documentation = new vscode.MarkdownString(def.description);
|
||||
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;
|
||||
|
|
@ -62,13 +71,22 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
|||
});
|
||||
|
||||
// D) Snippets
|
||||
|
||||
// Konfiguration laden
|
||||
const config = vscode.workspace.getConfiguration('tpl');
|
||||
const author = config.get<string>('metadata.author', 'Kuro');
|
||||
const origin = config.get<string>('metadata.origin', 'Custom');
|
||||
const publisher = config.get<string>('metadata.publisher', 'IT-Operations');
|
||||
const treePathBase = config.get<string>('metadata.treePathBase', 'Grafing');
|
||||
const tplVersion = config.get<number>('version', 23);
|
||||
|
||||
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}\';\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;'
|
||||
`tpl 1.${tplVersion} module \${1:module_name};\n\n` +
|
||||
`metadata\n origin := '${origin}';\n author := '${author}';\n products := '\${2:products}';\n publishers := '${publisher}';\n tree_path := '${treePathBase}', '\${3:category}', '\${4:subcategory}';\nend metadata;\n\n` +
|
||||
`pattern \${5:pattern_name} 1.0\n """\n TODO: explain\n """\n\n triggers\n on process := DiscoveredProcess where cmd matches regex_escape("\${6:process}");\n end triggers;\n\n body\n log.debug("Start pattern \${5:pattern_name}");\n $0\n end body;\nend pattern;`
|
||||
);
|
||||
skeletonItem.detail = "Generiert das komplette TPL Modul-Grundgerüst";
|
||||
skeletonItem.detail = "Generiert das komplette TPL Modul-Grundgerüst (mit Settings)";
|
||||
items.push(skeletonItem);
|
||||
|
||||
const blocks = ['metadata', 'overview', 'constants', 'triggers', 'body', 'mapping', 'removal', 'definitions', 'enumeration', 'configuration'];
|
||||
|
|
@ -98,14 +116,22 @@ export class TplCompletionProvider implements vscode.CompletionItemProvider {
|
|||
} else {
|
||||
const ns = matchDot[1];
|
||||
|
||||
// NEU: Ist es ein BMC Built-In Namespace? (z.B. text. oder log.)
|
||||
// NEU: BMC Built-Ins abfangen und Snippets bauen
|
||||
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);
|
||||
for (const [method, def] of Object.entries(tplBuiltIns[ns])) {
|
||||
for (const snippet of def.snippets) {
|
||||
const cleanSnippet = snippet.replace(/\$\{\d+:([^}]+)\}/g, '$1');
|
||||
|
||||
const item = new vscode.CompletionItem(`${method}(${cleanSnippet})`, vscode.CompletionItemKind.Method);
|
||||
item.insertText = new vscode.SnippetString(`${method}(${snippet})`);
|
||||
item.filterText = method;
|
||||
item.documentation = new vscode.MarkdownString(def.description);
|
||||
item.detail = `${ns}.${method}`;
|
||||
|
||||
bmcMethods.push(item);
|
||||
}
|
||||
}
|
||||
return bmcMethods;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,106 +2,50 @@ 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<vscode.Hover | null> {
|
||||
const wordRange = document.getWordRangeAtPosition(position, /[a-zA-Z0-9_.]+/);
|
||||
if (!wordRange) return null;
|
||||
public async provideHover(
|
||||
document: vscode.TextDocument,
|
||||
position: vscode.Position,
|
||||
token: vscode.CancellationToken
|
||||
): Promise<vscode.Hover | null> {
|
||||
|
||||
const fullWord = document.getText(wordRange);
|
||||
const currentText = document.getText();
|
||||
// Greift das Wort unter dem Mauszeiger ab, inklusive Punkte für Namespaces
|
||||
const range = document.getWordRangeAtPosition(position, /[a-zA-Z0-9_.]+/);
|
||||
if (!range) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let namespace = "";
|
||||
let methodOrPatternName = fullWord;
|
||||
const word = document.getText(range);
|
||||
const parts = word.split('.');
|
||||
|
||||
if (fullWord.includes('.')) {
|
||||
const parts = fullWord.split('.');
|
||||
let namespace = '';
|
||||
let methodOrPatternName = '';
|
||||
|
||||
if (parts.length === 2) {
|
||||
namespace = parts[0];
|
||||
methodOrPatternName = parts[1];
|
||||
} else if (parts.length === 1) {
|
||||
methodOrPatternName = parts[0];
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
// NEU: BMC Built-Ins abfangen und direkt ausgeben!
|
||||
// BMC Namespace-Methoden (z.B. discovery.listDirectory)
|
||||
if (namespace && tplBuiltIns[namespace] && tplBuiltIns[namespace][methodOrPatternName]) {
|
||||
const def = tplBuiltIns[namespace][methodOrPatternName];
|
||||
const markdown = new vscode.MarkdownString();
|
||||
markdown.appendCodeblock(`${namespace}.${methodOrPatternName}`, 'tpl');
|
||||
markdown.appendMarkdown(`---\n*${tplBuiltIns[namespace][methodOrPatternName]}*`);
|
||||
markdown.appendMarkdown(`---\n\n${def.description}`);
|
||||
return new vscode.Hover(markdown);
|
||||
} else if (!namespace && tplBuiltIns['built_in_globals'][methodOrPatternName]) {
|
||||
}
|
||||
// BMC Globale Funktionen (z.B. size, datatype)
|
||||
else if (!namespace && tplBuiltIns['built_in_globals'] && tplBuiltIns['built_in_globals'][methodOrPatternName]) {
|
||||
const def = tplBuiltIns['built_in_globals'][methodOrPatternName];
|
||||
const markdown = new vscode.MarkdownString();
|
||||
markdown.appendCodeblock(`${methodOrPatternName}`, 'tpl');
|
||||
markdown.appendMarkdown(`---\n*${tplBuiltIns['built_in_globals'][methodOrPatternName]}*`);
|
||||
markdown.appendMarkdown(`---\n\n${def.description}`);
|
||||
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;
|
||||
}
|
||||
|
||||
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);
|
||||
return null;
|
||||
}
|
||||
|
||||
const importRegex = new RegExp(`from\\s+([\\w.]+)\\s+import\\s+${namespace}\\b`);
|
||||
const importMatch = importRegex.exec(currentText);
|
||||
|
||||
if (importMatch) {
|
||||
const modPath = importMatch[1];
|
||||
const uris = await vscode.workspace.findFiles('**/*.tpl', '**/node_modules/**', 50);
|
||||
for (const uri of uris) {
|
||||
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);
|
||||
break;
|
||||
}
|
||||
} catch(e) {}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private buildHover(text: string, matchIndex: number): vscode.Hover {
|
||||
const docStr = this.extractDocString(text, matchIndex);
|
||||
const markdown = new vscode.MarkdownString();
|
||||
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)*`);
|
||||
return new vscode.Hover(markdown);
|
||||
}
|
||||
|
||||
private extractDocString(text: string, startIndex: number): string | null {
|
||||
const lines = text.substring(startIndex).split('\n');
|
||||
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; }
|
||||
const content = line.substring(3).trim();
|
||||
if (content) docString += content + "\n";
|
||||
} 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"; }
|
||||
}
|
||||
}
|
||||
return docString.trim() || null;
|
||||
}
|
||||
}
|
||||
|
|
@ -5,9 +5,6 @@ import { TplCompletionProvider } from './TplCompletionProvider';
|
|||
import { TplHoverProvider } from './TplHoverProvider';
|
||||
import { tplBuiltIns } from './TplBuiltIns';
|
||||
|
||||
const SUPPORTED_TPL_VERSION = 23;
|
||||
const warnedDocuments = new Set<string>();
|
||||
|
||||
enum PatternPhase { None = 0, Metadata = 1, Overview = 2, Constants = 3, Triggers = 4, Mapping = 5, Removal = 6, Body = 7 }
|
||||
|
||||
export function activate(context: vscode.ExtensionContext) {
|
||||
|
|
@ -17,6 +14,15 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
vscode.workspace.onDidOpenTextDocument(doc => validateTplDocument(doc, diagnosticCollection));
|
||||
vscode.workspace.onDidChangeTextDocument(event => validateTplDocument(event.document, diagnosticCollection));
|
||||
|
||||
// NEU: Sofort reagieren, wenn in den VS Code Einstellungen etwas geändert wird
|
||||
vscode.workspace.onDidChangeConfiguration(e => {
|
||||
if (e.affectsConfiguration('tpl')) {
|
||||
for (const doc of vscode.workspace.textDocuments) {
|
||||
validateTplDocument(doc, diagnosticCollection);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
context.subscriptions.push(vscode.languages.registerCodeActionsProvider('tpl', new TplCodeActionProvider(), { providedCodeActionKinds: [vscode.CodeActionKind.QuickFix] }));
|
||||
context.subscriptions.push(vscode.languages.registerDocumentFormattingEditProvider('tpl', new TplDocumentFormatter()));
|
||||
context.subscriptions.push(vscode.languages.registerCompletionItemProvider('tpl', new TplCompletionProvider(), '.'));
|
||||
|
|
@ -30,12 +36,14 @@ export function activate(context: vscode.ExtensionContext) {
|
|||
function validateTplDocument(document: vscode.TextDocument, collection: vscode.DiagnosticCollection) {
|
||||
if (document.languageId !== 'tpl') { return; }
|
||||
|
||||
const config = vscode.workspace.getConfiguration('tpl');
|
||||
const configuredTplVersion = config.get<number>('version', 23);
|
||||
|
||||
const diagnostics: vscode.Diagnostic[] = [];
|
||||
const text = document.getText();
|
||||
const lines = text.split('\n');
|
||||
|
||||
let tplVersion = 10;
|
||||
let toleranceMode = false;
|
||||
let hasModuleDef = false;
|
||||
|
||||
let inBlock: 'none' | 'pattern' | 'table' | 'definitions' | 'enumeration' | 'configuration' = 'none';
|
||||
|
|
@ -60,25 +68,27 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D
|
|||
const bracketStack: number[] = [];
|
||||
const openDefines: number[] = [];
|
||||
|
||||
const versionMatch = text.match(/tpl\s+1\.(\d+)\s+module/);
|
||||
if (versionMatch) {
|
||||
tplVersion = parseInt(versionMatch[1], 10);
|
||||
if (tplVersion > SUPPORTED_TPL_VERSION) {
|
||||
toleranceMode = true;
|
||||
if (!warnedDocuments.has(document.uri.toString())) {
|
||||
vscode.window.showInformationMessage(`TPL 1.${tplVersion} erkannt. Linter läuft im Toleranz-Modus.`);
|
||||
warnedDocuments.add(document.uri.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
// State-Tracking für mehrzeilige Vergleiche / Zuweisungen
|
||||
let inConditionBlock = false;
|
||||
|
||||
function addDiag(lineIdx: number, start: number, end: number, msg: string, severity: vscode.DiagnosticSeverity): vscode.Diagnostic {
|
||||
if (toleranceMode && severity === vscode.DiagnosticSeverity.Error) { severity = vscode.DiagnosticSeverity.Warning; }
|
||||
const diag = new vscode.Diagnostic(new vscode.Range(lineIdx, start, lineIdx, end), msg, severity);
|
||||
diagnostics.push(diag);
|
||||
return diag;
|
||||
}
|
||||
|
||||
// Version prüfen
|
||||
const versionMatch = text.match(/tpl\s+1\.(\d+)\s+module/);
|
||||
if (versionMatch) {
|
||||
tplVersion = parseInt(versionMatch[1], 10);
|
||||
// Warnung nur, wenn die Datei älter ist als die in den Settings eingestellte Version! Höher ist in Ordnung.
|
||||
if (tplVersion < configuredTplVersion) {
|
||||
const linesToSearch = text.substring(0, versionMatch.index! + 30).split('\n');
|
||||
const lineIdx = linesToSearch.length - 1;
|
||||
addDiag(lineIdx, 0, versionMatch[0].length, `Das Modul nutzt eine alte TPL Version (1.${tplVersion}). Aktuell in den Settings definiert ist 1.${configuredTplVersion}.`, vscode.DiagnosticSeverity.Warning);
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const rawLine = lines[i];
|
||||
const line = rawLine.trim();
|
||||
|
|
@ -131,19 +141,27 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D
|
|||
}
|
||||
}
|
||||
|
||||
if (line.startsWith('//') || line.startsWith('/*')) { if (line.startsWith('/*')) inMultiLineComment = true; continue; }
|
||||
if (inMultiLineComment) { if (line.includes('*/')) inMultiLineComment = false; continue; }
|
||||
if (line.startsWith('//') || line.startsWith('/*')) { if (line.startsWith('/*')) {inMultiLineComment = true;} continue; }
|
||||
if (inMultiLineComment) { if (line.includes('*/')) {inMultiLineComment = false;} continue; }
|
||||
if (line.length === 0) { continue; }
|
||||
|
||||
const lineWithoutStrings = rawLine.replace(/(["'])(?:(?=(\\?))\2.)*?\1/g, '""');
|
||||
if (/\b(if|elif|where)\b/.test(lineWithoutStrings)) {
|
||||
inConditionBlock = true;
|
||||
}
|
||||
if (/\b(then|do)\b/.test(lineWithoutStrings) || lineWithoutStrings.endsWith(';')) {
|
||||
inConditionBlock = false;
|
||||
}
|
||||
|
||||
if (isClosingString) {
|
||||
if (stringNeedsSemicolon && !line.endsWith(';')) {
|
||||
addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Zuweisung.", vscode.DiagnosticSeverity.Error);
|
||||
}
|
||||
} else {
|
||||
const noSemicolonNeeded = /^(pattern|syncmapping|table|enumeration|configuration|definitions?|define|metadata|overview|constants|triggers|mapping|removal|body|end\s+|if\s+|else|elif\s+|for\s+|while\s+|\/\/|\/\*|\*)/;
|
||||
const noSemicolonNeeded = /^(pattern|syncmapping|traverse|table|enumeration|configuration|definitions?|define|metadata|overview|constants|triggers|mapping|removal|body|end\s+|if\s+|else|elif\s+|for\s+|while\s+|\/\/|\/\*|\*)/;
|
||||
const continuesOnNextLine = /(,|and|or|not|\+|-|\*|\/|=|:=|matches)$/i.test(line);
|
||||
|
||||
// Enumerations benötigen keine Semikolons
|
||||
if (!noSemicolonNeeded.test(line) && !justOpenedString && parenStack.length === 0 && bracketStack.length === 0 && inBlock !== 'enumeration') {
|
||||
if (!noSemicolonNeeded.test(line) && !justOpenedString && parenStack.length === 0 && bracketStack.length === 0 && inBlock !== 'enumeration' && !inConditionBlock && !continuesOnNextLine) {
|
||||
const lineToCheck = line.split('//')[0].trim();
|
||||
if (!lineToCheck.endsWith(';') && !lineToCheck.endsWith('{') && !lineToCheck.endsWith(',')) {
|
||||
addDiag(i, rawLine.length - 1 < 0 ? 0 : rawLine.length - 1, rawLine.length, "Fehlendes Semikolon am Ende der Anweisung.", vscode.DiagnosticSeverity.Error);
|
||||
|
|
@ -151,27 +169,11 @@ 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 <name>;' beginnen.", vscode.DiagnosticSeverity.Error); hasModuleDef = true; }
|
||||
}
|
||||
|
||||
// --- Modul-Metadaten Logik ---
|
||||
if (line.startsWith('metadata') && inBlock === 'none') {
|
||||
inModuleMetadata = true;
|
||||
hasModuleMetadata = true;
|
||||
|
|
@ -197,7 +199,6 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D
|
|||
continue;
|
||||
}
|
||||
|
||||
// Block checks
|
||||
if (line.match(/^(pattern|syncmapping|table|definitions?|enumeration|configuration)\b/)) {
|
||||
if (!hasModuleMetadata && inBlock === 'none') {
|
||||
addDiag(i, 0, line.length, "Ein 'metadata' Block auf Modul-Ebene ist Pflicht, bevor andere Blöcke definiert werden.", vscode.DiagnosticSeverity.Error);
|
||||
|
|
@ -287,10 +288,12 @@ function validateTplDocument(document: vscode.TextDocument, collection: vscode.D
|
|||
}
|
||||
}
|
||||
|
||||
const eqMatch = rawLine.match(/(?<![<>=!:])=(?![=])/);
|
||||
if (eqMatch && eqMatch.index && !justOpenedString) {
|
||||
if (!line.includes('"') && !line.includes("'")) {
|
||||
addDiag(i, eqMatch.index, eqMatch.index + 1, "Zuweisung in TPL erfolgt mit ':=', nicht mit '='. '=' ist für Vergleiche.", vscode.DiagnosticSeverity.Error);
|
||||
if (lineWithoutStrings.includes('=') && !lineWithoutStrings.includes(':=') && !lineWithoutStrings.includes('>=') && !lineWithoutStrings.includes('<=')) {
|
||||
if (!inConditionBlock && parenStack.length === 0 && bracketStack.length === 0 && !/\b(matches)\b/.test(lineWithoutStrings)) {
|
||||
const idx = rawLine.indexOf('=');
|
||||
if (idx !== -1) {
|
||||
addDiag(i, idx, idx + 1, "Mögliche falsche Zuweisung. In TPL wird ':=' für Zuweisungen genutzt. (Toleriert in Vergleichen/Bedingungen)", vscode.DiagnosticSeverity.Warning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue