From de9f9f1bd13b1a8737fc32542c7d57ee890adc6f Mon Sep 17 00:00:00 2001 From: Jason Dent Date: Mon, 4 Dec 2023 20:06:41 +0100 Subject: [PATCH] Make sure only existing config targets are shown. --- packages/_server/src/codeActions.mts | 4 +- .../src/config/configTargetsHelper.mts | 23 ++++++++--- .../src/config/configTargetsHelper.test.mts | 6 ++- .../_server/src/config/documentSettings.mts | 39 +++++++++++++++++-- .../src/config/documentSettings.test.mts | 2 - packages/_server/src/server.mts | 2 +- packages/client/src/client/client.ts | 4 +- 7 files changed, 63 insertions(+), 17 deletions(-) diff --git a/packages/_server/src/codeActions.mts b/packages/_server/src/codeActions.mts index 5fbee18258..456671ba9a 100644 --- a/packages/_server/src/codeActions.mts +++ b/packages/_server/src/codeActions.mts @@ -170,7 +170,7 @@ class CodeActionHandler { // Only suggest adding if it is our diagnostic and there is a word. if (isSpellingIssue && word && spellCheckerDiags.length) { const wConfig = await pWorkspaceConfig; - const targets = calculateConfigTargets(docSetting, wConfig); + const targets = await calculateConfigTargets(docSetting, wConfig); debugTargets && logTargets(targets); if (!docSetting.hideAddToDictionaryCodeActions) { @@ -201,7 +201,7 @@ class CodeActionHandler { // Only suggest adding if it is our diagnostic and there is a word. if (word && eslintSpellCheckerDiags.length) { const wConfig = await pWorkspaceConfig; - const targets = calculateConfigTargets(docSetting, wConfig); + const targets = await calculateConfigTargets(docSetting, wConfig); debugTargets && logTargets(targets); if (!docSetting.hideAddToDictionaryCodeActions) { diff --git a/packages/_server/src/config/configTargetsHelper.mts b/packages/_server/src/config/configTargetsHelper.mts index 27d5ac2bfe..f827884822 100644 --- a/packages/_server/src/config/configTargetsHelper.mts +++ b/packages/_server/src/config/configTargetsHelper.mts @@ -1,5 +1,5 @@ import type { DictionaryDefinitionCustom } from '@cspell/cspell-types'; -import { toUri } from '@internal/common-utils/uriHelper'; +import { toFileUri, toUri } from '@internal/common-utils/uriHelper'; import { capitalize } from '@internal/common-utils/util'; import * as Path from 'path'; @@ -15,11 +15,24 @@ import type { import { ConfigKinds, ConfigScopes, weight } from './configTargets.mjs'; import type { CSpellUserSettings } from './cspellConfig/index.mjs'; import type { CSpellSettingsWithFileSource } from './documentSettings.mjs'; -import { extractCSpellFileConfigurations, extractTargetDictionaries } from './documentSettings.mjs'; - -export function calculateConfigTargets(settings: CSpellUserSettings, workspaceConfig: WorkspaceConfigForDocument): ConfigTarget[] { +import { extractCSpellFileConfigurations, extractTargetDictionaries, filterExistingCSpellFileConfigurations } from './documentSettings.mjs'; + +export async function calculateConfigTargets( + settings: CSpellUserSettings, + workspaceConfig: WorkspaceConfigForDocument, + configFilesFound?: string[], +): Promise { + const found = new Set(configFilesFound); + async function isFound(filename: string) { + if (found.has(filename)) return true; + const href = toFileUri(filename).toString(); + return found.has(href); + } const targets: ConfigTarget[] = []; - const sources = extractCSpellFileConfigurations(settings).filter((cfg) => !cfg.readonly); + const possibleSources = extractCSpellFileConfigurations(settings).filter((cfg) => !cfg.readonly); + const sources = configFilesFound + ? possibleSources.filter((cfg) => isFound(cfg.source.filename)) + : await filterExistingCSpellFileConfigurations(possibleSources); const dictionaries = extractTargetDictionaries(settings); targets.push(...workspaceConfigToTargets(workspaceConfig)); diff --git a/packages/_server/src/config/configTargetsHelper.test.mts b/packages/_server/src/config/configTargetsHelper.test.mts index fd58654e08..1605e3cf17 100644 --- a/packages/_server/src/config/configTargetsHelper.test.mts +++ b/packages/_server/src/config/configTargetsHelper.test.mts @@ -178,7 +178,7 @@ describe('Validate configTargetsHelper', () => { }; const cfg = mustBeDefined(await searchForConfig(__dirname)); const settings = { ...cfg }; - const r = calculateConfigTargets(settings, wConfig); + const r = await calculateConfigTargets(settings, wConfig); expect(r).toEqual([ oc({ kind: 'dictionary', @@ -231,7 +231,9 @@ describe('Validate configTargetsHelper', () => { const defs: DictionaryDef[] = [cd('custom-words', 'path/to/custom-words.txt', false)]; const dictionaries: string[] = (cfg.dictionaries || []).concat('custom-words'); const settings: CSpellUserSettings = { ...cfg, dictionaryDefinitions: defs, dictionaries }; - const r = calculateConfigTargets(settings, wConfig).sort((a, b) => col.compare(a.kind, b.kind) || col.compare(a.name, b.name)); + const r = (await calculateConfigTargets(settings, wConfig)).sort( + (a, b) => col.compare(a.kind, b.kind) || col.compare(a.name, b.name), + ); expect(r).toEqual([ oc({ kind: 'cspell', diff --git a/packages/_server/src/config/documentSettings.mts b/packages/_server/src/config/documentSettings.mts index 984d86cc05..94621abd64 100644 --- a/packages/_server/src/config/documentSettings.mts +++ b/packages/_server/src/config/documentSettings.mts @@ -1,3 +1,5 @@ +import { stat } from 'node:fs/promises'; + import { opConcatMap, opFilter, pipe } from '@cspell/cspell-pipe/sync'; import type { CSpellSettingsWithSourceTrace, @@ -220,6 +222,7 @@ export class DocumentSettings { const cfg = loader.createCSpellConfigFile(pathToFileURL(fileConfigsToImport), { name: 'VS Code Imports', import: importPaths, + readonly: true, }); return await loader.mergeConfigFileWithImports(cfg); } @@ -298,9 +301,9 @@ export class DocumentSettings { const uri = typeof docUri === 'string' ? Uri.parse(docUri) : docUri; const docUriAsString = uri.toString(); const settings = await this.fetchSettingsForUri(docUriAsString); - return this.extractCSpellConfigurationFiles(settings.settings) - .filter((u) => !u.path.endsWith(fileConfigsToImport)) - .filter((u) => !u.path.endsWith(fileVSCodeSettings)); + const uris = this.extractCSpellConfigurationFiles(settings.settings); + const found = await Promise.all(uris.map(filterUrl)); + return found.filter(isDefined); } /** @@ -328,6 +331,7 @@ export class DocumentSettings { id: 'VSCode-Config', name: 'VS Code Settings', ignorePaths: ignorePaths.concat(ExclusionHelper.extractGlobsFromExcludeFilesGlobMap(exclude)), + readonly: true, }; if (cSpellConfigSettings.useLocallyInstalledCSpellDictionaries) { @@ -650,11 +654,26 @@ export function extractCSpellFileConfigurations(settings: CSpellUserSettings): C .filter(isCSpellSettingsWithFileSource) .filter(({ source }) => !regExIsOwnedByCspell.test(source.filename)) .filter(({ source }) => !regExIsOwnedByExtension.test(source.filename)) + .filter(({ source }) => !source.filename.endsWith(fileConfigsToImport)) + .filter(({ source }) => !source.filename.endsWith(fileVSCodeSettings)) .reverse(); return configs; } +export async function filterExistingCSpellFileConfigurations( + configs: CSpellSettingsWithFileSource[], +): Promise { + const existingConfigs = await Promise.all( + configs.map(async (cfg) => { + const { source } = cfg; + const found = await filterUrl(toFileUri(source.filename)); + return found ? cfg : undefined; + }), + ); + return existingConfigs.filter(isDefined); +} + /** * * @param settings - finalized settings @@ -709,6 +728,20 @@ export function isExcluded(settings: ExtSettings, uri: Uri): boolean { return settings.excludeGlobMatcher.match(uri.fsPath); } +async function filterUrl(uri: Uri): Promise { + if (uri.scheme !== 'file') return undefined; + const url = new URL(uri.toString()); + try { + const stats = await stat(url); + const found = stats.isFile() ? uri : undefined; + console.error('filterUrl %o', { uri: uri.toString(), found: !!found }); + return found; + } catch (e) { + console.error('filterUrl Not found', uri.toString()); + return undefined; + } +} + export const __testing__ = { extractTargetDictionaries, extractEnableFiletypes, diff --git a/packages/_server/src/config/documentSettings.test.mts b/packages/_server/src/config/documentSettings.test.mts index ba7b0c0482..d652fb287b 100644 --- a/packages/_server/src/config/documentSettings.test.mts +++ b/packages/_server/src/config/documentSettings.test.mts @@ -260,11 +260,9 @@ describe('Validate DocumentSettings', () => { expect(configs.map((c) => c.name)).toEqual([ shortPathName(Path.join(pathWorkspaceServer, 'cspell.json')), shortPathName(Path.join(pathWorkspaceRoot, 'cspell.config.yaml')), - 'VS Code Settings', // name of the mock config. 'sampleSourceFiles/cSpell.json', 'sampleSourceFiles/cspell-ext.json', 'overrides/cspell.json', - 'VS Code Imports', // ]); }); diff --git a/packages/_server/src/server.mts b/packages/_server/src/server.mts index a4efb70c10..53d1eec559 100644 --- a/packages/_server/src/server.mts +++ b/packages/_server/src/server.mts @@ -378,7 +378,7 @@ export function run(): void { const activeSettings = await getActiveUriSettings(uri); const settings = stringifyPatterns(activeSettings); const configFiles = uri ? (await documentSettings.findCSpellConfigurationFilesForUri(uri)).map((uri) => uri.toString()) : []; - const configTargets = workspaceConfig ? calculateConfigTargets(settings, workspaceConfig) : []; + const configTargets = workspaceConfig ? await calculateConfigTargets(settings, workspaceConfig, configFiles) : []; const ieInfo = await calcIncludeExcludeInfo(activeSettings, params); return { diff --git a/packages/client/src/client/client.ts b/packages/client/src/client/client.ts index ac3da79102..7e6d4fd127 100644 --- a/packages/client/src/client/client.ts +++ b/packages/client/src/client/client.ts @@ -141,7 +141,7 @@ export class CSpellClient implements Disposable { const { uri, languageId } = document || {}; const workspaceConfig = calculateWorkspaceConfigForDocument(uri); - if (!uri) { + if (!uri || !workspaceConfig.uri) { return this.serverApi.getConfigurationForDocument({ workspaceConfig }); } return this.serverApi.getConfigurationForDocument({ uri: uri.toString(), languageId, workspaceConfig }); @@ -289,7 +289,7 @@ function calculateWorkspaceConfigForDocument(docUri: Uri | undefined): Workspace tWords.user = tUserWords.user; const resp: WorkspaceConfigForDocumentResponse = { - uri: docUri?.toString(), + uri: scope?.toString(), workspaceFile, workspaceFolder, words: tWords,