diff --git a/src/LanguageServer.spec.ts b/src/LanguageServer.spec.ts index 77a770c31..73a826c31 100644 --- a/src/LanguageServer.spec.ts +++ b/src/LanguageServer.spec.ts @@ -1,7 +1,7 @@ import { expect } from './chai-config.spec'; import * as fsExtra from 'fs-extra'; import * as path from 'path'; -import type { DidChangeWatchedFilesParams, Location, PublishDiagnosticsParams, WorkspaceFolder } from 'vscode-languageserver'; +import type { ConfigurationItem, DidChangeWatchedFilesParams, Location, PublishDiagnosticsParams, WorkspaceFolder } from 'vscode-languageserver'; import { FileChangeType } from 'vscode-languageserver'; import { Deferred } from './deferred'; import { CustomCommands, LanguageServer } from './LanguageServer'; @@ -402,15 +402,41 @@ describe('LanguageServer', () => { }); it('ignores bsconfig.json files from vscode ignored paths', async () => { + const mapItem = (item: ConfigurationItem) => { + if (item.section === 'files') { + return { + exclude: { + '**/vendor': true + } + }; + } else if (item.section === 'search') { + return { + exclude: { + '**/temp': true + } + }; + } else { + return {}; + } + }; + server.run(); - sinon.stub(server['connection'].workspace, 'getConfiguration').returns(Promise.resolve({ - exclude: { - '**/vendor': true + sinon.stub(server['connection'].workspace, 'getConfiguration').callsFake( + // @ts-expect-error Sinon incorrectly infers the type of this function + (items: any) => { + if (typeof items === 'string') { + return Promise.resolve({}); + } + if (Array.isArray(items)) { + return Promise.resolve(items.map(mapItem)); + } + return Promise.resolve(mapItem(items)); } - }) as any); + ); await server.onInitialized(); fsExtra.outputJsonSync(s`${workspacePath}/vendor/someProject/bsconfig.json`, {}); + fsExtra.outputJsonSync(s`${workspacePath}/temp/someProject/bsconfig.json`, {}); //it always ignores node_modules fsExtra.outputJsonSync(s`${workspacePath}/node_modules/someProject/bsconfig.json`, {}); await server['syncProjects'](); diff --git a/src/LanguageServer.ts b/src/LanguageServer.ts index 030661112..9ccb6172b 100644 --- a/src/LanguageServer.ts +++ b/src/LanguageServer.ts @@ -653,10 +653,22 @@ export class LanguageServer { * Ask the client for the list of `files.exclude` patterns. Useful when determining if we should process a file */ private async getWorkspaceExcludeGlobs(workspaceFolder: string): Promise { - const config = await this.getClientConfiguration<{ exclude: string[] }>(workspaceFolder, 'files'); - const result = Object - .keys(config?.exclude ?? {}) - .filter(x => config?.exclude?.[x]) + const filesConfig = await this.getClientConfiguration<{ exclude: string[] }>(workspaceFolder, 'files'); + const fileExcludes = this.extractExcludes(filesConfig); + + const searchConfig = await this.getClientConfiguration<{ exclude: string[] }>(workspaceFolder, 'search'); + const searchExcludes = this.extractExcludes(searchConfig); + + return [...fileExcludes, ...searchExcludes]; + } + + private extractExcludes(config: { exclude: string[] }): string[] { + if (!config?.exclude) { + return []; + } + return Object + .keys(config.exclude) + .filter(x => config.exclude[x]) //vscode files.exclude patterns support ignoring folders without needing to add `**/*`. So for our purposes, we need to //append **/* to everything without a file extension or magic at the end .map(pattern => [ @@ -666,7 +678,6 @@ export class LanguageServer { `${pattern}/**/*` ]) .flat(1); - return result; } /** diff --git a/src/lsp/ProjectManager.spec.ts b/src/lsp/ProjectManager.spec.ts index 43b7c227a..93a55e561 100644 --- a/src/lsp/ProjectManager.spec.ts +++ b/src/lsp/ProjectManager.spec.ts @@ -152,7 +152,7 @@ describe('ProjectManager', () => { fsExtra.outputFileSync(`${rootDir}/subdir/bsconfig.json`, ''); await manager.syncProjects([{ workspaceFolder: rootDir, - excludePatterns: ['subdir/**/*'] + excludePatterns: ['**/subdir/**/*'] }]); expect( manager.projects.map(x => x.projectPath) diff --git a/src/lsp/ProjectManager.ts b/src/lsp/ProjectManager.ts index 03cd083cc..ee75a197d 100644 --- a/src/lsp/ProjectManager.ts +++ b/src/lsp/ProjectManager.ts @@ -646,20 +646,22 @@ export class ProjectManager { * If none are found, then the workspaceFolder itself is treated as a project */ private async getProjectPaths(workspaceConfig: WorkspaceConfig) { - //get the list of exclude patterns, and negate them (so they actually work like excludes) - const excludePatterns = (workspaceConfig.excludePatterns ?? []).map(x => s`!${x}`); - let files = await rokuDeploy.getFilePaths([ - '**/bsconfig.json', - //exclude all files found in `files.exclude` - ...excludePatterns - ], workspaceConfig.workspaceFolder); + //get the list of exclude patterns, negate them so they actually work like excludes), and coerce to forward slashes since that's what fast-glob expects + const excludePatterns = (workspaceConfig.excludePatterns ?? []).map(x => s`!${x}`.replace(/[\\/]+/g, '/')); + + let files = await fastGlob(['**/bsconfig.json', ...excludePatterns], { + cwd: workspaceConfig.workspaceFolder, + followSymbolicLinks: false, + absolute: true, + onlyFiles: true + }); //filter the files to only include those that are allowed by the path filterer - files = this.pathFilterer.filter(files, x => x.src); + files = this.pathFilterer.filter(files); //if we found at least one bsconfig.json, then ALL projects must have a bsconfig.json. if (files.length > 0) { - return files.map(file => s`${path.dirname(file.src)}`); + return files.map(file => s`${path.dirname(file)}`); } //look for roku project folders