feat(bicep): implement signatureHelp, foldingRange, workspace/symbol locally
Bicep.LangServer 0.46.1 (latest release) does not implement these three LSP
features, so iLSP now answers them itself directly in the proxy instead of
forwarding to the underlying LS:
- textDocument/signatureHelp: curated database of ~50 commonly used Bicep
built-in functions (resourceId, guid, concat, take, format, ...) with a
simple bracket-depth call-context detector (ilsp/bicep_lsp/bicep_functions.py,
ilsp/bicep_lsp/local_features.py:compute_signature_help).
- textDocument/foldingRange: stack-based bracket matcher over {}/[]/() plus
contiguous // comment-block folding (compute_folding_ranges).
- workspace/symbol: best-effort search over documents currently open in the
editor session (iLSP has no full-workspace index, only what's been opened),
matching top-level resource/module/var/param/output/func/type declarations
(compute_workspace_symbols).
The proxy now advertises these in the initialize response capabilities
(signatureHelpProvider, foldingRangeProvider, workspaceSymbolProvider) so
editors actually send the requests, and intercepts them in _client_to_ls
before they'd otherwise be silently dropped/errored by the real Bicep LS.
A send lock (_LockedConn) prevents interleaved frames now that both
_client_to_ls and _ls_to_client can write responses to the client socket.
13 new unit tests in tests/test_local_features.py. Closes the last 3 gaps
found in the full LSP capability audit.
This commit is contained in:
249
ilsp/bicep_lsp/bicep_functions.py
Normal file
249
ilsp/bicep_lsp/bicep_functions.py
Normal file
@@ -0,0 +1,249 @@
|
||||
"""
|
||||
Curated signature database for Bicep's built-in functions.
|
||||
|
||||
Bicep.LangServer (as of 0.46.1) does not implement textDocument/signatureHelp,
|
||||
so iLSP provides its own for the most commonly used built-in functions.
|
||||
Not exhaustive — covers the functions people actually type day to day.
|
||||
"""
|
||||
|
||||
from typing import Any
|
||||
|
||||
# name -> list of parameter labels (one entry per overload, simplest form first)
|
||||
BUILTIN_FUNCTIONS: dict[str, dict[str, Any]] = {
|
||||
"resourceId": {
|
||||
"params": ["resourceType", "resourceName1", "resourceName2..."],
|
||||
"doc": "Returns the unique identifier of a resource. Also usable with a subscriptionId/resourceGroupName prefix.",
|
||||
},
|
||||
"subscriptionResourceId": {
|
||||
"params": ["subscriptionId", "resourceType", "resourceName1", "resourceName2..."],
|
||||
"doc": "Returns the unique identifier for a resource deployed at the subscription level.",
|
||||
},
|
||||
"tenantResourceId": {
|
||||
"params": ["resourceType", "resourceName1", "resourceName2..."],
|
||||
"doc": "Returns the unique identifier for a resource deployed at the tenant level.",
|
||||
},
|
||||
"extensionResourceId": {
|
||||
"params": ["resourceId", "resourceType", "resourceName1", "resourceName2..."],
|
||||
"doc": "Returns the resource ID for an extension resource, which is applied to another resource.",
|
||||
},
|
||||
"guid": {
|
||||
"params": ["baseString1", "baseString2..."],
|
||||
"doc": "Creates a deterministic GUID based on the values passed as parameters.",
|
||||
},
|
||||
"uniqueString": {
|
||||
"params": ["baseString1", "baseString2..."],
|
||||
"doc": "Creates a deterministic hash string based on the values passed as parameters (13 chars).",
|
||||
},
|
||||
"take": {
|
||||
"params": ["originalValue", "count"],
|
||||
"doc": "Returns a string or array with the specified number of elements from the start.",
|
||||
},
|
||||
"skip": {
|
||||
"params": ["originalValue", "count"],
|
||||
"doc": "Returns a string or array with all the elements after the specified number skipped.",
|
||||
},
|
||||
"first": {
|
||||
"params": ["arg1"],
|
||||
"doc": "Returns the first element of an array, or first character of a string.",
|
||||
},
|
||||
"last": {
|
||||
"params": ["arg1"],
|
||||
"doc": "Returns the last element of an array, or last character of a string.",
|
||||
},
|
||||
"length": {
|
||||
"params": ["arg1"],
|
||||
"doc": "Returns the number of elements in an array, characters in a string, or properties in an object.",
|
||||
},
|
||||
"concat": {
|
||||
"params": ["arg1", "arg2..."],
|
||||
"doc": "Combines multiple arrays or strings.",
|
||||
},
|
||||
"union": {
|
||||
"params": ["arg1", "arg2..."],
|
||||
"doc": "Returns a single array/object with all elements from the parameters, merging objects.",
|
||||
},
|
||||
"contains": {
|
||||
"params": ["container", "itemToFind"],
|
||||
"doc": "Checks whether an array contains a value, an object contains a key, or a string contains a substring.",
|
||||
},
|
||||
"empty": {
|
||||
"params": ["itemToTest"],
|
||||
"doc": "Determines if an array, object, or string is empty.",
|
||||
},
|
||||
"format": {
|
||||
"params": ["formatString", "arg1..."],
|
||||
"doc": "Creates a formatted string using .NET-style {0} placeholders.",
|
||||
},
|
||||
"join": {
|
||||
"params": ["inputArray", "delimiter"],
|
||||
"doc": "Joins multiple strings from an array into a single string, separated by delimiter.",
|
||||
},
|
||||
"split": {
|
||||
"params": ["inputString", "delimiter"],
|
||||
"doc": "Splits a string into an array using a delimiter.",
|
||||
},
|
||||
"replace": {
|
||||
"params": ["originalString", "oldString", "newString"],
|
||||
"doc": "Returns a new string with all instances of oldString replaced by newString.",
|
||||
},
|
||||
"toLower": {
|
||||
"params": ["stringToChange"],
|
||||
"doc": "Converts a string to lowercase.",
|
||||
},
|
||||
"toUpper": {
|
||||
"params": ["stringToChange"],
|
||||
"doc": "Converts a string to uppercase.",
|
||||
},
|
||||
"trim": {
|
||||
"params": ["stringToTrim"],
|
||||
"doc": "Removes leading/trailing whitespace from a string.",
|
||||
},
|
||||
"substring": {
|
||||
"params": ["stringToParse", "startIndex", "length"],
|
||||
"doc": "Returns a substring starting at the specified position.",
|
||||
},
|
||||
"padLeft": {
|
||||
"params": ["valueToPad", "totalLength", "paddingCharacter"],
|
||||
"doc": "Pads a string to a fixed total length by adding characters to the left.",
|
||||
},
|
||||
"startsWith": {
|
||||
"params": ["stringToSearch", "stringToFind"],
|
||||
"doc": "Checks whether a string starts with a value (case-insensitive).",
|
||||
},
|
||||
"endsWith": {
|
||||
"params": ["stringToSearch", "stringToFind"],
|
||||
"doc": "Checks whether a string ends with a value (case-insensitive).",
|
||||
},
|
||||
"indexOf": {
|
||||
"params": ["stringToSearch", "stringToFind"],
|
||||
"doc": "Returns the first position of a value within a string (case-insensitive).",
|
||||
},
|
||||
"reference": {
|
||||
"params": ["resourceNameOrIdentifier", "apiVersion", "'Full'"],
|
||||
"doc": "Returns an object representing a resource's runtime state.",
|
||||
},
|
||||
"listKeys": {
|
||||
"params": ["resourceNameOrIdentifier", "apiVersion"],
|
||||
"doc": "Returns the keys for a resource (e.g. storage account access keys).",
|
||||
},
|
||||
"listSecrets": {
|
||||
"params": ["resourceNameOrIdentifier", "apiVersion"],
|
||||
"doc": "Returns the secrets for a resource.",
|
||||
},
|
||||
"resourceGroup": {
|
||||
"params": [],
|
||||
"doc": "Returns an object representing the current resource group.",
|
||||
},
|
||||
"subscription": {
|
||||
"params": [],
|
||||
"doc": "Returns an object representing the current subscription.",
|
||||
},
|
||||
"deployment": {
|
||||
"params": [],
|
||||
"doc": "Returns an object representing the current deployment operation.",
|
||||
},
|
||||
"environment": {
|
||||
"params": [],
|
||||
"doc": "Returns an object describing the Azure cloud environment.",
|
||||
},
|
||||
"az": {
|
||||
"params": [],
|
||||
"doc": "Namespace for Azure Bicep built-in functions.",
|
||||
},
|
||||
"json": {
|
||||
"params": ["jsonString"],
|
||||
"doc": "Converts a valid JSON string into a JSON object / array.",
|
||||
},
|
||||
"string": {
|
||||
"params": ["valueToConvert"],
|
||||
"doc": "Converts the specified value to a string.",
|
||||
},
|
||||
"int": {
|
||||
"params": ["valueToConvert"],
|
||||
"doc": "Converts the specified value to an integer.",
|
||||
},
|
||||
"bool": {
|
||||
"params": ["value"],
|
||||
"doc": "Converts the specified value to a boolean.",
|
||||
},
|
||||
"array": {
|
||||
"params": ["valueToConvert"],
|
||||
"doc": "Converts the specified value to an array.",
|
||||
},
|
||||
"base64": {
|
||||
"params": ["inputString"],
|
||||
"doc": "Returns the base64 representation of the input string.",
|
||||
},
|
||||
"base64ToString": {
|
||||
"params": ["base64Value"],
|
||||
"doc": "Converts a base64 representation to a string.",
|
||||
},
|
||||
"dateTimeAdd": {
|
||||
"params": ["base", "duration", "format"],
|
||||
"doc": "Adds a time duration to a base UTC datetime value.",
|
||||
},
|
||||
"utcNow": {
|
||||
"params": ["format"],
|
||||
"doc": "Returns the current UTC datetime in the specified format.",
|
||||
},
|
||||
"loadTextContent": {
|
||||
"params": ["filePath", "encoding"],
|
||||
"doc": "Loads the content of the specified file as a string at compile time.",
|
||||
},
|
||||
"loadJsonContent": {
|
||||
"params": ["filePath", "jsonPath", "encoding"],
|
||||
"doc": "Loads the content of the specified JSON file at compile time.",
|
||||
},
|
||||
"loadFileAsBase64": {
|
||||
"params": ["filePath"],
|
||||
"doc": "Loads the specified file as a base64 string at compile time.",
|
||||
},
|
||||
"managementGroup": {
|
||||
"params": ["name"],
|
||||
"doc": "Returns an object representing the current or specified management group.",
|
||||
},
|
||||
"newGuid": {
|
||||
"params": [],
|
||||
"doc": "Returns a new (random, non-deterministic) GUID. Only valid for param defaults.",
|
||||
},
|
||||
"filter": {
|
||||
"params": ["array", "lambdaExpression"],
|
||||
"doc": "Filters an array using a custom filtering lambda expression.",
|
||||
},
|
||||
"map": {
|
||||
"params": ["array", "lambdaExpression"],
|
||||
"doc": "Applies a custom mapping lambda expression to each element of an array.",
|
||||
},
|
||||
"reduce": {
|
||||
"params": ["array", "initialValue", "lambdaExpression"],
|
||||
"doc": "Reduces an array with a custom lambda expression.",
|
||||
},
|
||||
"sort": {
|
||||
"params": ["array", "lambdaExpression"],
|
||||
"doc": "Sorts an array with a custom lambda expression comparator.",
|
||||
},
|
||||
"min": {
|
||||
"params": ["arg1", "arg2..."],
|
||||
"doc": "Returns the minimum value from an array of integers, or a comma-separated list.",
|
||||
},
|
||||
"max": {
|
||||
"params": ["arg1", "arg2..."],
|
||||
"doc": "Returns the maximum value from an array of integers, or a comma-separated list.",
|
||||
},
|
||||
"range": {
|
||||
"params": ["startIndex", "count"],
|
||||
"doc": "Creates an array of integers from startIndex to startIndex+count-1.",
|
||||
},
|
||||
"items": {
|
||||
"params": ["object"],
|
||||
"doc": "Returns an array of key/value pair objects that represent the properties of an object.",
|
||||
},
|
||||
"pickZones": {
|
||||
"params": ["providerNamespace", "resourceType", "location", "numberOfZones", "offset"],
|
||||
"doc": "Determines which availability zones are recommended for a resource type/location.",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def get_signature(name: str) -> dict[str, Any] | None:
|
||||
return BUILTIN_FUNCTIONS.get(name)
|
||||
Reference in New Issue
Block a user