diff --git a/src/LanguageServerManager.spec.ts b/src/LanguageServerManager.spec.ts index 61734784..d43d825d 100644 --- a/src/LanguageServerManager.spec.ts +++ b/src/LanguageServerManager.spec.ts @@ -270,6 +270,97 @@ describe('LanguageServerManager', () => { ); }); + it('resolves relative bsdk path from the workspace file directory, not the project root', async () => { + // .code-workspace file lives in a subdirectory (.vscode/) + vscode.workspace.workspaceFile = URI.file(s`${tempDir}/.vscode/workspace.code-workspace`); + vscode.workspace.workspaceFolders.push({ + index: 0, + name: 'SDK', + uri: URI.file(s`${tempDir}`) + }); + + setConfig(vscode.workspace.workspaceFile.fsPath, { + 'brightscript.bsdk': '../node_modules/brighterscript' + }); + + expect( + s(await languageServerManager['getBsdkVersionInfo']()) + ).to.eql( + // resolves from .vscode/ → one level up → tempDir/node_modules/brighterscript + s`${tempDir}/node_modules/brighterscript` + ); + }); + + it('expands ${workspaceFolder:name} variable in bsdk path', async () => { + vscode.workspace.workspaceFile = URI.file(s`${tempDir}/.vscode/workspace.code-workspace`); + vscode.workspace.workspaceFolders.push({ + index: 0, + name: 'SDK', + uri: URI.file(s`${tempDir}`) + }); + + setConfig(vscode.workspace.workspaceFile.fsPath, { + 'brightscript.bsdk': '${workspaceFolder:SDK}/node_modules/brighterscript' + }); + + expect( + s(await languageServerManager['getBsdkVersionInfo']()) + ).to.eql(s`${tempDir}/node_modules/brighterscript`); + }); + + it('expands ${workspaceFolder} variable to the first workspace folder', async () => { + vscode.workspace.workspaceFile = URI.file(s`${tempDir}/.vscode/workspace.code-workspace`); + vscode.workspace.workspaceFolders.push({ + index: 0, + name: 'SDK', + uri: URI.file(s`${tempDir}`) + }); + + setConfig(vscode.workspace.workspaceFile.fsPath, { + 'brightscript.bsdk': '${workspaceFolder}/node_modules/brighterscript' + }); + + expect( + s(await languageServerManager['getBsdkVersionInfo']()) + ).to.eql(s`${tempDir}/node_modules/brighterscript`); + }); + + it('throws for an unrecognized variable in bsdk path', async () => { + vscode.workspace.workspaceFile = URI.file(s`${tempDir}/.vscode/workspace.code-workspace`); + vscode.workspace.workspaceFolders.push({ + index: 0, + name: 'SDK', + uri: URI.file(s`${tempDir}`) + }); + + setConfig(vscode.workspace.workspaceFile.fsPath, { + 'brightscript.bsdk': '${env:MY_VAR}/node_modules/brighterscript' + }); + + await expectThrowsAsync( + () => languageServerManager['getBsdkVersionInfo'](), + 'brightscript.bsdk: unsupported variable in bsdk "${env:MY_VAR}/node_modules/brighterscript"' + ); + }); + + it('throws for an unknown workspace folder name in bsdk path', async () => { + vscode.workspace.workspaceFile = URI.file(s`${tempDir}/.vscode/workspace.code-workspace`); + vscode.workspace.workspaceFolders.push({ + index: 0, + name: 'SDK', + uri: URI.file(s`${tempDir}`) + }); + + setConfig(vscode.workspace.workspaceFile.fsPath, { + 'brightscript.bsdk': '${workspaceFolder:Unknown}/node_modules/brighterscript' + }); + + await expectThrowsAsync( + () => languageServerManager['getBsdkVersionInfo'](), + 'brightscript.bsdk: unknown workspace folder name "Unknown"' + ); + }); + it('returns folder version when not in a workspace', async () => { vscode.workspace.workspaceFolders.push({ index: 0, diff --git a/src/LanguageServerManager.ts b/src/LanguageServerManager.ts index b426d5ea..12adab97 100644 --- a/src/LanguageServerManager.ts +++ b/src/LanguageServerManager.ts @@ -524,9 +524,10 @@ export class LanguageServerManager { //use bsdk entry in the code-workspace file if (this.workspaceConfigIncludesBsdkKey()) { let result = this.parseVersionInfo( - util.getConfiguration('brightscript', vscode.workspace.workspaceFile).get('bsdk')?.trim?.(), + this.getWorkspaceBsdkInfo(vscode.workspace.workspaceFile), path.dirname(vscode.workspace.workspaceFile.fsPath) ); + if (result) { return result.value; } @@ -534,7 +535,7 @@ export class LanguageServerManager { //collect `brightscript.bsdk` setting value from each workspaceFolder const folderResults = vscode.workspace.workspaceFolders?.reduce((acc, workspaceFolder) => { - const versionInfo = util.getConfiguration('brightscript', workspaceFolder).get('bsdk'); + const versionInfo = this.getWorkspaceBsdkInfo(workspaceFolder); const parsed = this.parseVersionInfo(versionInfo, workspaceFolder.uri.fsPath); if (parsed) { acc.set(parsed.value, parsed); @@ -557,6 +558,56 @@ export class LanguageServerManager { } } + /** + * Get the `brightscript.bsdk` value from the .code-workspace settings block with workspace + * folder variables expanded. `inspect().workspaceValue` returns the unresolved string from + * the .code-workspace file — VSCode does not expand variables like ${workspaceFolder} in this + * value, so we expand ${workspaceFolder} and ${workspaceFolder:name} ourselves. + */ + private getWorkspaceBsdkInfo(workspaceFolder: vscode.ConfigurationScope) { + const config = util.getConfiguration('brightscript', workspaceFolder); + const rawValue = config.inspect('bsdk')?.workspaceValue?.trim?.() ?? config.get('bsdk')?.trim?.(); + + const hasVariable = rawValue?.startsWith('${'); + if (!hasVariable) { + return rawValue; + } + + return this.expandWorkspaceBsdkInfo(rawValue); + } + + /** + * Expand ${workspaceFolder} and ${workspaceFolder:name} variables in a bsdk path string. + * VSCode does not expand these variables in inspect().workspaceValue, so we do it ourselves. + * Throws if an unrecognized variable is encountered. + */ + private expandWorkspaceBsdkInfo(value: string): string { + if (!value) { + return value; + } + + const [match, workspaceName, relativePath] = /^\$\{workspaceFolder:?([^}]*)\}(.*)$/.exec(value) ?? []; + + if (!match) { + throw new Error(`brightscript.bsdk: unsupported variable in bsdk "${value}"`); + } + + // if ${workspaceFolder}, use workspaceFolders[0] + let workspaceFolder = vscode.workspace.workspaceFolders?.[0]; + + // if ${workspaceFolder:name}, find by name + if (workspaceName) { + workspaceFolder = vscode.workspace.workspaceFolders?.find(f => f.name === workspaceName); + } + + if (!workspaceFolder) { + throw new Error(`brightscript.bsdk: unknown workspace folder name "${workspaceName}"`); + } + + const bsdkInfo = path.join(workspaceFolder.uri.fsPath, relativePath); + return bsdkInfo ?? ''; + } + private workspaceConfigIncludesBsdkKey() { return vscode.workspace.workspaceFile && fsExtra.pathExistsSync(vscode.workspace.workspaceFile.fsPath) && diff --git a/src/mockVscode.spec.ts b/src/mockVscode.spec.ts index f16eb2f7..2f841285 100644 --- a/src/mockVscode.spec.ts +++ b/src/mockVscode.spec.ts @@ -191,7 +191,8 @@ export let vscode = { inspect: (name: string) => { return { key: name, - globalValue: store?.[`${configurationName}.${name}`] + globalValue: store?.[`${configurationName}.${name}`], + workspaceValue: store?.[`${configurationName}.${name}`] } as ReturnType; }, update: (name: string, value: any) => {