diff --git a/server/CHANGELOG.md b/server/CHANGELOG.md index e9f9cdf4b..d43c2addc 100644 --- a/server/CHANGELOG.md +++ b/server/CHANGELOG.md @@ -1,5 +1,9 @@ # Bash Language Server +## 4.6.0 + +- Support parsing `: "${VARIABLE:="default"}"` as a variable definition https://github.com/bash-lsp/bash-language-server/pull/693 + ## 4.5.5 - Use sourcing info even if `includeAllWorkspaceSymbols` is true to ensure that files not matching the `globPattern` (and therefore not part of the background analysis) is still resolved based on source commands. https://github.com/bash-lsp/bash-language-server/pull/695 diff --git a/server/package.json b/server/package.json index 04b7fdaa3..83d203cd5 100644 --- a/server/package.json +++ b/server/package.json @@ -3,7 +3,7 @@ "description": "A language server for Bash", "author": "Mads Hartmann", "license": "MIT", - "version": "4.5.5", + "version": "4.6.0", "main": "./out/server.js", "typings": "./out/server.d.ts", "bin": { diff --git a/server/src/__tests__/__snapshots__/analyzer.test.ts.snap b/server/src/__tests__/__snapshots__/analyzer.test.ts.snap index a4c7e0702..25f1c8438 100644 --- a/server/src/__tests__/__snapshots__/analyzer.test.ts.snap +++ b/server/src/__tests__/__snapshots__/analyzer.test.ts.snap @@ -934,5 +934,22 @@ Array [ }, "name": "ret", }, + Object { + "kind": 13, + "location": Object { + "range": Object { + "end": Object { + "character": 24, + "line": 273, + }, + "start": Object { + "character": 5, + "line": 273, + }, + }, + "uri": "dummy-uri.sh", + }, + "name": "FILE_PATH_EXPANSION", + }, ] `; diff --git a/server/src/__tests__/analyzer.test.ts b/server/src/__tests__/analyzer.test.ts index efc586f3c..f1de65bc2 100644 --- a/server/src/__tests__/analyzer.test.ts +++ b/server/src/__tests__/analyzer.test.ts @@ -979,6 +979,23 @@ describe('getAllVariables', () => { }, "name": "RESET", }, + Object { + "kind": 13, + "location": Object { + "range": Object { + "end": Object { + "character": 14, + "line": 25, + }, + "start": Object { + "character": 5, + "line": 25, + }, + }, + "uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/extension.inc", + }, + "name": "FILE_PATH", + }, ] `) }) diff --git a/server/src/__tests__/server.test.ts b/server/src/__tests__/server.test.ts index c9159e4b2..4b21a0761 100644 --- a/server/src/__tests__/server.test.ts +++ b/server/src/__tests__/server.test.ts @@ -560,61 +560,72 @@ describe('server', () => { // they are all variables expect(result).toMatchInlineSnapshot(` - Array [ - Object { - "data": Object { - "type": 3, - }, - "documentation": undefined, - "kind": 6, - "label": "BOLD", - }, - Object { - "data": Object { - "type": 3, - }, - "documentation": Object { - "kind": "markdown", - "value": "Variable: **RED** - *defined in extension.inc*", - }, - "kind": 6, - "label": "RED", - }, - Object { - "data": Object { - "type": 3, - }, - "documentation": Object { - "kind": "markdown", - "value": "Variable: **GREEN** - *defined in extension.inc*", - }, - "kind": 6, - "label": "GREEN", - }, - Object { - "data": Object { - "type": 3, - }, - "documentation": Object { - "kind": "markdown", - "value": "Variable: **BLUE** - *defined in extension.inc*", - }, - "kind": 6, - "label": "BLUE", - }, - Object { - "data": Object { - "type": 3, - }, - "documentation": Object { - "kind": "markdown", - "value": "Variable: **RESET** - *defined in extension.inc*", - }, - "kind": 6, - "label": "RESET", - }, - ] - `) + Array [ + Object { + "data": Object { + "type": 3, + }, + "documentation": undefined, + "kind": 6, + "label": "BOLD", + }, + Object { + "data": Object { + "type": 3, + }, + "documentation": Object { + "kind": "markdown", + "value": "Variable: **RED** - *defined in extension.inc*", + }, + "kind": 6, + "label": "RED", + }, + Object { + "data": Object { + "type": 3, + }, + "documentation": Object { + "kind": "markdown", + "value": "Variable: **GREEN** - *defined in extension.inc*", + }, + "kind": 6, + "label": "GREEN", + }, + Object { + "data": Object { + "type": 3, + }, + "documentation": Object { + "kind": "markdown", + "value": "Variable: **BLUE** - *defined in extension.inc*", + }, + "kind": 6, + "label": "BLUE", + }, + Object { + "data": Object { + "type": 3, + }, + "documentation": Object { + "kind": "markdown", + "value": "Variable: **RESET** - *defined in extension.inc*", + }, + "kind": 6, + "label": "RESET", + }, + Object { + "data": Object { + "type": 3, + }, + "documentation": Object { + "kind": "markdown", + "value": "Variable: **FILE_PATH** - *defined in extension.inc*", + }, + "kind": 6, + "label": "FILE_PATH", + }, + ] + `) }) }) diff --git a/server/src/util/declarations.ts b/server/src/util/declarations.ts index 0b4bc6f28..7aa5fd1c6 100644 --- a/server/src/util/declarations.ts +++ b/server/src/util/declarations.ts @@ -43,12 +43,10 @@ export function getGlobalDeclarations({ TreeSitterUtil.forEach(tree.rootNode, (node) => { const followChildren = !GLOBAL_DECLARATION_LEAF_NODE_TYPES.has(node.type) - if (TreeSitterUtil.isDefinition(node)) { - const symbol = nodeToSymbolInformation({ node, uri }) - if (symbol) { - const word = symbol.name - globalDeclarations[word] = symbol - } + const symbol = getDeclarationSymbolFromNode({ node, uri }) + if (symbol) { + const word = symbol.name + globalDeclarations[word] = symbol } return followChildren @@ -71,15 +69,10 @@ export function getAllDeclarationsInTree({ const symbols: LSP.SymbolInformation[] = [] TreeSitterUtil.forEach(tree.rootNode, (node) => { - if (TreeSitterUtil.isDefinition(node)) { - const symbol = nodeToSymbolInformation({ node, uri }) - - if (symbol) { - symbols.push(symbol) - } + const symbol = getDeclarationSymbolFromNode({ node, uri }) + if (symbol) { + symbols.push(symbol) } - - return }) return symbols @@ -122,8 +115,6 @@ export function getLocalDeclarations({ uri, }) } - } else if (TreeSitterUtil.isDefinition(childNode)) { - symbol = nodeToSymbolInformation({ node: childNode, uri }) } else if (childNode.type === 'for_statement') { const variableNode = childNode.child(1) if (variableNode && variableNode.type === 'variable_name') { @@ -134,6 +125,8 @@ export function getLocalDeclarations({ uri, ) } + } else { + symbol = getDeclarationSymbolFromNode({ node: childNode, uri }) } if (symbol) { @@ -222,3 +215,33 @@ function nodeToSymbolInformation({ containerName, ) } + +function getDeclarationSymbolFromNode({ + node, + uri, +}: { + node: Parser.SyntaxNode + uri: string +}): LSP.SymbolInformation | null { + if (TreeSitterUtil.isDefinition(node)) { + return nodeToSymbolInformation({ node, uri }) + } else if (node.type === 'command' && node.text.startsWith(': ')) { + // : does argument expansion and retains the side effects. + // A common usage is to define default values of environment variables, e.g. : "${VARIABLE:="default"}". + const variableNode = node.namedChildren + .find((c) => c.type === 'string') + ?.namedChildren.find((c) => c.type === 'expansion') + ?.namedChildren.find((c) => c.type === 'variable_name') + + if (variableNode) { + return LSP.SymbolInformation.create( + variableNode.text, + LSP.SymbolKind.Variable, + TreeSitterUtil.range(variableNode), + uri, + ) + } + } + + return null +} diff --git a/testing/fixtures/extension.inc b/testing/fixtures/extension.inc index 8b519df4f..49309210a 100644 --- a/testing/fixtures/extension.inc +++ b/testing/fixtures/extension.inc @@ -21,4 +21,8 @@ extensionFunc() { if [ "${ENV}" = "prod" ]; then RED="" # Ignored as we do not flowtrace. -fi \ No newline at end of file +fi + +: "${FILE_PATH:="/default/file"}" + +printf 'Printing %s\n' "$FILE_PATH" diff --git a/testing/fixtures/install.sh b/testing/fixtures/install.sh index 452dd2417..847860ec3 100644 --- a/testing/fixtures/install.sh +++ b/testing/fixtures/install.sh @@ -270,3 +270,7 @@ fi exit $ret iverilog -i wave.vpp *{}.v + +: "${FILE_PATH_EXPANSION:="/default/file"}" + +printf 'Printing %s\n' "$FILE_PATH_EXPANSION"