From 74bfa529db91b113b44465db9363a5f1a7e45321 Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Wed, 26 Aug 2026 18:16:25 -0700 Subject: [PATCH 1/5] Use native file type mappings for extension-side classification --- .../src/Debugger/configurationProvider.ts | 8 +- Extension/src/LanguageServer/client.ts | 42 ++++--- .../LanguageServer/cppBuildTaskProvider.ts | 8 +- Extension/src/common.ts | 29 ++--- Extension/src/fileType.ts | 110 ++++++++++++++++++ Extension/test/unit/fileType.test.ts | 64 ++++++++++ 6 files changed, 223 insertions(+), 38 deletions(-) create mode 100644 Extension/src/fileType.ts create mode 100644 Extension/test/unit/fileType.test.ts diff --git a/Extension/src/Debugger/configurationProvider.ts b/Extension/src/Debugger/configurationProvider.ts index 5bc427759..d03393326 100644 --- a/Extension/src/Debugger/configurationProvider.ts +++ b/Extension/src/Debugger/configurationProvider.ts @@ -81,7 +81,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv throw new Error("Default config not found in provideDebugConfigurations()"); } const editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; - if (!editor || !util.isCppOrCFile(editor.document.uri) || configs.length <= 1) { + if (!editor || !util.isCppOrCFile(editor.document.uri, editor.document.languageId) || configs.length <= 1) { return [defaultTemplateConfig]; } @@ -560,8 +560,8 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv } // Don't offer tasks if the active file's extension is not a recognized C/C++ extension. - const fileIsCpp: boolean = util.isCppFile(editor.document.uri); - const fileIsC: boolean = util.isCFile(editor.document.uri); + const fileIsCpp: boolean = util.isCppFile(editor.document.uri, editor.document.languageId); + const fileIsC: boolean = util.isCFile(editor.document.uri, editor.document.languageId); if (!(fileIsCpp || fileIsC)) { DebugConfigurationProvider.detectedBuildTasks = emptyTasks; return; @@ -982,7 +982,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv private async selectConfiguration(textEditor: vscode.TextEditor, pickDefault: boolean = true, onlyWorkspaceFolder: boolean = false): Promise { const folder: vscode.WorkspaceFolder | undefined = vscode.workspace.getWorkspaceFolder(textEditor.document.uri); - if (!util.isCppOrCFile(textEditor.document.uri)) { + if (!util.isCppOrCFile(textEditor.document.uri, textEditor.document.languageId)) { void vscode.window.showErrorMessage(localize("cannot.build.non.cpp", 'Cannot build and debug because the active file is not a C or C++ source file.')); return; } diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index e76eaa4bf..3725024e2 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -35,6 +35,7 @@ import { ManualPromise } from '../Utility/Async/manualPromise'; import { logAndReturn } from '../Utility/Async/returns'; import * as util from '../common'; import { isWindows } from '../constants'; +import { FileTypeMappings, hasNativeFileTypeMappings, isTagParsableFile, resetFileTypeMappings, updateFileTypeMappings } from '../fileType'; import { instrument, isInstrumentationEnabled } from '../instrumentation'; import { DebugProtocolParams, Logger, ShowWarningParams, getDiagnosticsChannel, getOutputChannelLogger, logDebugProtocol, logLocalized, showWarning } from '../logger'; import { localizedStringCount, lookupString } from '../nativeStrings'; @@ -528,6 +529,7 @@ interface CppInitializationParams { interface CppInitializationResult { shouldShutdown: boolean; + fileTypeMappings?: FileTypeMappings; } interface TagParseStatus { @@ -683,6 +685,7 @@ const ReportStatusNotification: NotificationType = const DebugProtocolNotification: NotificationType = new NotificationType('cpptools/debugProtocol'); const DebugLogNotification: NotificationType = new NotificationType('cpptools/debugLog'); const CompileCommandsPathsNotification: NotificationType = new NotificationType('cpptools/compileCommandsPaths'); +const FileTypeMappingsNotification: NotificationType = new NotificationType('cpptools/fileTypeMappings'); const ReferencesNotification: NotificationType = new NotificationType('cpptools/references'); const ReportReferencesProgressNotification: NotificationType = new NotificationType('cpptools/reportReferencesProgress'); const RequestCustomConfigs: NotificationType = new NotificationType('cpptools/requestCustomConfigs'); @@ -1406,6 +1409,7 @@ export class DefaultClient implements Client { // Ideally this would be set earlier, but the task provider expects it to also mean that `this.innerConfiguration` is set. this.languageClient.isStarted = true; + this.updateActiveDocumentTextOptions(); telemetry.logLanguageServerEvent("NonDefaultInitialCppSettings", this.settingsTracker.getUserModifiedSettings()); failureMessageShown = false; @@ -1843,6 +1847,12 @@ export class DefaultClient implements Client { // A request is used in order to wait for completion and ensure that no subsequent // higher priority message may be processed before the Initialization request. const initializeResult = await client.sendRequest(InitializationRequest, cppInitializationParams); + if (initializeResult.fileTypeMappings) { + updateFileTypeMappings(initializeResult.fileTypeMappings); + } else { + resetFileTypeMappings(); + } + DebugConfigurationProvider.ClearDetectedBuildTasks(); // If the server requested shutdown, then reload with the failsafe (null) client. if (initializeResult.shouldShutdown) { @@ -2589,6 +2599,11 @@ export class DefaultClient implements Client { this.languageClient.onNotification(ReportStatusNotification, (e) => void this.updateStatus(e)); this.languageClient.onNotification(ReportTagParseStatusNotification, (e) => this.updateTagParseStatus(e)); this.languageClient.onNotification(CompileCommandsPathsNotification, (e) => void this.promptCompileCommands(e)); + this.languageClient.onNotification(FileTypeMappingsNotification, (mappings) => { + updateFileTypeMappings(mappings); + DebugConfigurationProvider.ClearDetectedBuildTasks(); + clients.ActiveClient.updateActiveDocumentTextOptions(); + }); this.languageClient.onNotification(ReferencesNotification, (e) => this.processReferencesPreview(e)); this.languageClient.onNotification(ReportReferencesProgressNotification, (e) => this.handleReferencesProgress(e)); this.languageClient.onNotification(RequestCustomConfigs, (e) => this.handleRequestCustomConfigs(e)); @@ -2718,7 +2733,7 @@ export class DefaultClient implements Client { void this.languageClient.sendNotification(FileCreatedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined); }); - // TODO: Handle new associations without a reload. + // Fallback for native binaries that do not publish effective file type mappings. this.associations_for_did_change = new Set(["cu", "cuh", "c", "i", "cpp", "cc", "cxx", "c++", "cp", "hpp", "hh", "hxx", "h++", "hp", "h", "ii", "ino", "inl", "ipp", "tcc", "txx", "tpp", "idl"]); const assocs: any = new OtherSettings().filesAssociations; for (const assoc in assocs) { @@ -2739,17 +2754,18 @@ export class DefaultClient implements Client { cachedEditorConfigLookups.clear(); this.updateActiveDocumentTextOptions(); } - if (dotIndex !== -1) { - const ext: string = uri.fsPath.substring(dotIndex + 1); - if (this.associations_for_did_change?.has(ext)) { - // VS Code has a bug that causes onDidChange events to happen to files that aren't changed, - // which causes a large backlog of "files to parse" to accumulate. - // We workaround this via only sending the change message if the modified time is within 10 seconds. - const mtime: Date = fs.statSync(uri.fsPath).mtime; - const duration: number = Date.now() - mtime.getTime(); - if (duration < 10000) { - void this.languageClient.sendNotification(FileChangedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined); - } + const ext: string | undefined = dotIndex !== -1 ? uri.fsPath.substring(dotIndex + 1) : undefined; + const isTrackedFile: boolean = hasNativeFileTypeMappings() + ? isTagParsableFile(uri.fsPath) + : ext !== undefined && this.associations_for_did_change?.has(ext) === true; + if (isTrackedFile) { + // VS Code has a bug that causes onDidChange events to happen to files that aren't changed, + // which causes a large backlog of "files to parse" to accumulate. + // We workaround this via only sending the change message if the modified time is within 10 seconds. + const mtime: Date = fs.statSync(uri.fsPath).mtime; + const duration: number = Date.now() - mtime.getTime(); + if (duration < 10000) { + void this.languageClient.sendNotification(FileChangedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined); } } }); @@ -3079,7 +3095,7 @@ export class DefaultClient implements Client { public updateActiveDocumentTextOptions(): void { const editor: vscode.TextEditor | undefined = vscode.window.activeTextEditor; if (editor && util.isCpp(editor.document)) { - void SessionState.buildAndDebugIsSourceFile.set(util.isCppOrCFile(editor.document.uri)); + void SessionState.buildAndDebugIsSourceFile.set(util.isCppOrCFile(editor.document.uri, editor.document.languageId)); void SessionState.buildAndDebugIsFolderOpen.set(util.isFolderOpen(editor.document.uri)); // If using vcFormat, check for a ".editorconfig" file, and apply those text options to the active document. const settings: CppSettings = new CppSettings(this.RootUri); diff --git a/Extension/src/LanguageServer/cppBuildTaskProvider.ts b/Extension/src/LanguageServer/cppBuildTaskProvider.ts index 486e3df64..736560f76 100644 --- a/Extension/src/LanguageServer/cppBuildTaskProvider.ts +++ b/Extension/src/LanguageServer/cppBuildTaskProvider.ts @@ -93,8 +93,8 @@ export class CppBuildTaskProvider implements TaskProvider { } // Don't offer tasks if the active file's extension is not a recognized C/C++ extension. - const fileIsCpp: boolean = util.isCppFile(editor.document.uri); - const fileIsC: boolean = util.isCFile(editor.document.uri); + const fileIsCpp: boolean = util.isCppFile(editor.document.uri, editor.document.languageId); + const fileIsC: boolean = util.isCFile(editor.document.uri, editor.document.languageId); if (!(fileIsCpp || fileIsC)) { return emptyTasks; } @@ -421,7 +421,9 @@ class CustomBuildTaskTerminal implements Pseudoterminal { } async openAsync(_initialDimensions: TerminalDimensions | undefined): Promise { - if (this.buildOptions.taskUsesActiveFile && !util.isCppOrCFile(window.activeTextEditor?.document.uri)) { + if (this.buildOptions.taskUsesActiveFile && !util.isCppOrCFile( + window.activeTextEditor?.document.uri, + window.activeTextEditor?.document.languageId)) { this.writeEmitter.fire(localize("cannot.build.non.cpp", 'Cannot build and debug because the active file is not a C or C++ source file.') + this.endOfLine); this.closeEmitter.fire(-1); return; diff --git a/Extension/src/common.ts b/Extension/src/common.ts index 7bfda0ec8..700d8ab44 100644 --- a/Extension/src/common.ts +++ b/Extension/src/common.ts @@ -16,6 +16,7 @@ import * as nls from 'vscode-nls'; import { TargetPopulation } from 'vscode-tas-client'; import { ManualPromise } from './Utility/Async/manualPromise'; import { isWindows } from './constants'; +import { classifyFilePath, FileTypeMapping } from './fileType'; import { getOutputChannelLogger, showOutputChannel } from './logger'; import { PlatformInformation } from './platform'; import * as Telemetry from './telemetry'; @@ -157,34 +158,26 @@ export function getVcpkgRoot(): string { return vcpkgRoot; } -/** - * This is a fuzzy determination of whether a uri represents a header file. - * For the purposes of this function, a header file has no extension, or an extension that begins with the letter 'h'. - * @param document The document to check. - */ export function isHeaderFile(uri: vscode.Uri): boolean { - const fileExt: string = path.extname(uri.fsPath); - const fileExtLower: string = fileExt.toLowerCase(); - return !fileExt || [".cuh", ".hpp", ".hh", ".hxx", ".h++", ".hp", ".h", ".inl", ".ipp", ".tcc", ".txx", ".tpp", ".tlh", ".tli", ""].some(ext => fileExtLower === ext); + return classifyFilePath(uri.fsPath)?.kind === 'header'; } -export function isCppFile(uri: vscode.Uri): boolean { - const fileExt: string = path.extname(uri.fsPath); - const fileExtLower: string = fileExt.toLowerCase(); - return (fileExt === ".C") || [".cu", ".cpp", ".cc", ".cxx", ".c++", ".cp", ".ii", ".ino"].some(ext => fileExtLower === ext); +export function isCppFile(uri: vscode.Uri, languageId?: string): boolean { + const fileType: FileTypeMapping | undefined = classifyFilePath(uri.fsPath, languageId); + return fileType?.kind === 'source' && (fileType.language === 'cpp' || fileType.language === 'cuda'); } -export function isCFile(uri: vscode.Uri): boolean { - const fileExt: string = path.extname(uri.fsPath); - const fileExtLower: string = fileExt.toLowerCase(); - return fileExt === ".c" || fileExtLower === ".i"; +export function isCFile(uri: vscode.Uri, languageId?: string): boolean { + const fileType: FileTypeMapping | undefined = classifyFilePath(uri.fsPath, languageId); + return fileType?.kind === 'source' && fileType.language === 'c'; } -export function isCppOrCFile(uri: vscode.Uri | undefined): boolean { +export function isCppOrCFile(uri: vscode.Uri | undefined, languageId?: string): boolean { if (!uri) { return false; } - return isCppFile(uri) || isCFile(uri); + const fileType: FileTypeMapping | undefined = classifyFilePath(uri.fsPath, languageId); + return fileType?.kind === 'source' && fileType.language !== undefined; } export function isFolderOpen(uri: vscode.Uri): boolean { diff --git a/Extension/src/fileType.ts b/Extension/src/fileType.ts new file mode 100644 index 000000000..bef80e509 --- /dev/null +++ b/Extension/src/fileType.ts @@ -0,0 +1,110 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import * as path from 'path'; + +export type FileTypeKind = 'source' | 'header' | 'idl' | 'resource' | 'other'; +export type FileTypeLanguage = 'c' | 'cpp' | 'cuda'; + +export interface FileTypeMapping { + name: string; + kind: FileTypeKind; + language?: FileTypeLanguage; +} + +export interface FileTypeMappings { + extensions: FileTypeMapping[]; + filenames: FileTypeMapping[]; +} + +const bootstrapMappings: FileTypeMappings = { + extensions: [ + ...['.cuh', '.hpp', '.hh', '.hxx', '.h++', '.hp', '.h', '.inl', '.ipp', '.tcc', '.txx', '.tpp', '.tlh', '.tli'] + .map(name => ({ name, kind: 'header' as const, language: name === '.cuh' ? 'cuda' as const : 'cpp' as const })), + ...['.cu', '.cpp', '.cc', '.cxx', '.c++', '.cp', '.ii', '.ino'] + .map(name => ({ name, kind: 'source' as const, language: name === '.cu' ? 'cuda' as const : 'cpp' as const })), + ...['.c', '.i'].map(name => ({ name, kind: 'source' as const, language: 'c' as const })), + { name: '.idl', kind: 'idl' } + ], + filenames: [] +}; + +let extensionMappings: ReadonlyMap; +let filenameMappings: ReadonlyMap; +let nativeMappingsAvailable: boolean = false; + +function createMappingMap(mappings: FileTypeMapping[]): ReadonlyMap { + const result: Map = new Map(); + for (const mapping of mappings) { + result.set(mapping.name.toLowerCase(), { ...mapping, name: mapping.name.toLowerCase() }); + } + return result; +} + +export function resetFileTypeMappings(): void { + extensionMappings = createMappingMap(bootstrapMappings.extensions); + filenameMappings = createMappingMap(bootstrapMappings.filenames); + nativeMappingsAvailable = false; +} + +export function updateFileTypeMappings(mappings: FileTypeMappings | undefined): void { + if (!mappings) { + resetFileTypeMappings(); + return; + } + + extensionMappings = createMappingMap(mappings.extensions); + filenameMappings = createMappingMap(mappings.filenames); + nativeMappingsAvailable = true; +} + +export function hasNativeFileTypeMappings(): boolean { + return nativeMappingsAvailable; +} + +function getRegisteredFileType(filePath: string): FileTypeMapping | undefined { + const filename: string = path.basename(filePath); + const filenameMapping: FileTypeMapping | undefined = filenameMappings.get(filename.toLowerCase()); + if (filenameMapping) { + return filenameMapping; + } + + const extension: string = path.extname(filename); + // VS Code initially assigns uppercase .C files to C. Preserve the extension's + // long-standing correction until the exact filename association is installed. + if (extension === '.C') { + return { name: extension, kind: 'source', language: 'cpp' }; + } + return extensionMappings.get(extension.toLowerCase()); +} + +export function classifyFilePath(filePath: string, languageId?: string): FileTypeMapping | undefined { + const registeredType: FileTypeMapping | undefined = getRegisteredFileType(filePath); + if (registeredType) { + return registeredType; + } + + if (!nativeMappingsAvailable && !path.extname(filePath)) { + return { name: '', kind: 'header' }; + } + + switch (languageId) { + case 'c': + return { name: '', kind: 'source', language: 'c' }; + case 'cpp': + return { name: '', kind: 'source', language: 'cpp' }; + case 'cuda-cpp': + return { name: '', kind: 'source', language: 'cuda' }; + default: + return undefined; + } +} + +export function isTagParsableFile(filePath: string): boolean { + const type: FileTypeMapping | undefined = getRegisteredFileType(filePath); + return type?.kind === 'source' || type?.kind === 'header' || type?.kind === 'idl'; +} + +resetFileTypeMappings(); diff --git a/Extension/test/unit/fileType.test.ts b/Extension/test/unit/fileType.test.ts new file mode 100644 index 000000000..c8a3ef0ce --- /dev/null +++ b/Extension/test/unit/fileType.test.ts @@ -0,0 +1,64 @@ +/* -------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All Rights Reserved. + * See 'LICENSE' in the project root for license information. + * ------------------------------------------------------------------------------------------ */ + +import { deepStrictEqual, equal } from 'node:assert'; +import { afterEach, describe, it } from 'mocha'; +import { + classifyFilePath, + hasNativeFileTypeMappings, + isTagParsableFile, + resetFileTypeMappings, + updateFileTypeMappings +} from '../../src/fileType'; + +describe('file type mappings', () => { + afterEach(() => resetFileTypeMappings()); + + it('uses legacy classifications before native initialization', () => { + equal(hasNativeFileTypeMappings(), false); + deepStrictEqual(classifyFilePath('file.hpp'), { name: '.hpp', kind: 'header', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('Makefile'), { name: '', kind: 'header' }); + }); + + it('atomically replaces bootstrap mappings with native mappings', () => { + updateFileTypeMappings({ + extensions: [ + { name: '.c', kind: 'source', language: 'c' }, + { name: '.cppm', kind: 'source', language: 'cpp' }, + { name: '.h', kind: 'header', language: 'cpp' }, + { name: '.idl', kind: 'idl' } + ], + filenames: [ + { name: 'foo.h', kind: 'source', language: 'c' }, + { name: 'vector', kind: 'header' }, + { name: 'kernel.custom', kind: 'source', language: 'cuda' } + ] + }); + + equal(hasNativeFileTypeMappings(), true); + deepStrictEqual(classifyFilePath('module.CPPM'), { name: '.cppm', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('foo.h'), { name: 'foo.h', kind: 'source', language: 'c' }); + deepStrictEqual(classifyFilePath('VECTOR'), { name: 'vector', kind: 'header' }); + deepStrictEqual(classifyFilePath('kernel.custom'), { name: 'kernel.custom', kind: 'source', language: 'cuda' }); + equal(classifyFilePath('Makefile'), undefined); + equal(isTagParsableFile('schema.idl'), true); + equal(isTagParsableFile('kernel.custom'), true); + equal(isTagParsableFile('vector'), true); + equal(isTagParsableFile('unknown.txt'), false); + }); + + it('uses the editor language only for unregistered paths', () => { + updateFileTypeMappings({ + extensions: [{ name: '.h', kind: 'header', language: 'cpp' }], + filenames: [] + }); + + deepStrictEqual(classifyFilePath('file.h', 'c'), { name: '.h', kind: 'header', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.special', 'c'), { name: '', kind: 'source', language: 'c' }); + deepStrictEqual(classifyFilePath('file.special', 'cuda-cpp'), { name: '', kind: 'source', language: 'cuda' }); + }); +}); From f6e8c984227c2ccd92268600db940591fbdcae29 Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Wed, 26 Aug 2026 19:04:26 -0700 Subject: [PATCH 2/5] Add additional file extensions --- Extension/package.json | 7 +++++++ Extension/src/LanguageServer/client.ts | 2 +- Extension/src/fileType.ts | 2 +- Extension/test/unit/fileType.test.ts | 4 ++++ 4 files changed, 13 insertions(+), 2 deletions(-) diff --git a/Extension/package.json b/Extension/package.json index 805ab83ff..6039ee542 100644 --- a/Extension/package.json +++ b/Extension/package.json @@ -68,6 +68,13 @@ "languages": [ { "id": "cpp", + "extensions": [ + ".ccm", + ".cppm", + ".hip", + ".ixx", + ".sycl" + ], "filenames": [ "algorithm", "any", diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 3725024e2..5800b7766 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -2734,7 +2734,7 @@ export class DefaultClient implements Client { }); // Fallback for native binaries that do not publish effective file type mappings. - this.associations_for_did_change = new Set(["cu", "cuh", "c", "i", "cpp", "cc", "cxx", "c++", "cp", "hpp", "hh", "hxx", "h++", "hp", "h", "ii", "ino", "inl", "ipp", "tcc", "txx", "tpp", "idl"]); + this.associations_for_did_change = new Set(["cu", "cuh", "c", "i", "cpp", "cc", "ccm", "cxx", "c++", "cp", "cppm", "hip", "hpp", "hh", "hxx", "h++", "hp", "h", "ii", "ino", "inl", "ipp", "ixx", "sycl", "tcc", "txx", "tpp", "idl"]); const assocs: any = new OtherSettings().filesAssociations; for (const assoc in assocs) { const dotIndex: number = assoc.lastIndexOf('.'); diff --git a/Extension/src/fileType.ts b/Extension/src/fileType.ts index bef80e509..71ca5503d 100644 --- a/Extension/src/fileType.ts +++ b/Extension/src/fileType.ts @@ -23,7 +23,7 @@ const bootstrapMappings: FileTypeMappings = { extensions: [ ...['.cuh', '.hpp', '.hh', '.hxx', '.h++', '.hp', '.h', '.inl', '.ipp', '.tcc', '.txx', '.tpp', '.tlh', '.tli'] .map(name => ({ name, kind: 'header' as const, language: name === '.cuh' ? 'cuda' as const : 'cpp' as const })), - ...['.cu', '.cpp', '.cc', '.cxx', '.c++', '.cp', '.ii', '.ino'] + ...['.cu', '.cpp', '.cc', '.ccm', '.cxx', '.c++', '.cp', '.cppm', '.hip', '.ii', '.ino', '.ixx', '.sycl'] .map(name => ({ name, kind: 'source' as const, language: name === '.cu' ? 'cuda' as const : 'cpp' as const })), ...['.c', '.i'].map(name => ({ name, kind: 'source' as const, language: 'c' as const })), { name: '.idl', kind: 'idl' } diff --git a/Extension/test/unit/fileType.test.ts b/Extension/test/unit/fileType.test.ts index c8a3ef0ce..a7e4595c6 100644 --- a/Extension/test/unit/fileType.test.ts +++ b/Extension/test/unit/fileType.test.ts @@ -20,6 +20,10 @@ describe('file type mappings', () => { equal(hasNativeFileTypeMappings(), false); deepStrictEqual(classifyFilePath('file.hpp'), { name: '.hpp', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); + for (const extension of ['ccm', 'cppm', 'hip', 'ixx', 'sycl']) { + deepStrictEqual(classifyFilePath(`file.${extension}`), { name: `.${extension}`, kind: 'source', language: 'cpp' }); + equal(isTagParsableFile(`file.${extension}`), true); + } deepStrictEqual(classifyFilePath('Makefile'), { name: '', kind: 'header' }); }); From 9a1d5aabab5e4bbb1ba79a7859afef305bb31861 Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Thu, 27 Aug 2026 18:40:48 -0700 Subject: [PATCH 3/5] Address PR feedback --- Extension/src/LanguageServer/client.ts | 20 +++++++++----- Extension/src/fileType.ts | 23 ++++++++++------ Extension/test/unit/fileType.test.ts | 36 ++++++++++++++++++++++++-- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 5800b7766..6e5d5179d 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -1409,7 +1409,7 @@ export class DefaultClient implements Client { // Ideally this would be set earlier, but the task provider expects it to also mean that `this.innerConfiguration` is set. this.languageClient.isStarted = true; - this.updateActiveDocumentTextOptions(); + clients.ActiveClient.updateActiveDocumentTextOptions(); telemetry.logLanguageServerEvent("NonDefaultInitialCppSettings", this.settingsTracker.getUserModifiedSettings()); failureMessageShown = false; @@ -1736,6 +1736,7 @@ export class DefaultClient implements Client { localizedStrings: localizedStrings, settings: this.getAllSettings() }; + resetFileTypeMappings(cppInitializationParams.caseSensitiveFileSupport); this.loggingLevel = util.getNumericLoggingLevel(cppInitializationParams.settings.loggingLevel); const lspInitializationOptions: LspInitializationOptions = { @@ -1848,9 +1849,9 @@ export class DefaultClient implements Client { // higher priority message may be processed before the Initialization request. const initializeResult = await client.sendRequest(InitializationRequest, cppInitializationParams); if (initializeResult.fileTypeMappings) { - updateFileTypeMappings(initializeResult.fileTypeMappings); + updateFileTypeMappings(initializeResult.fileTypeMappings, cppInitializationParams.caseSensitiveFileSupport); } else { - resetFileTypeMappings(); + resetFileTypeMappings(cppInitializationParams.caseSensitiveFileSupport); } DebugConfigurationProvider.ClearDetectedBuildTasks(); @@ -2733,14 +2734,18 @@ export class DefaultClient implements Client { void this.languageClient.sendNotification(FileCreatedNotification, { uri: uri.toString() }).catch(logAndReturn.undefined); }); - // Fallback for native binaries that do not publish effective file type mappings. - this.associations_for_did_change = new Set(["cu", "cuh", "c", "i", "cpp", "cc", "ccm", "cxx", "c++", "cp", "cppm", "hip", "hpp", "hh", "hxx", "h++", "hp", "h", "ii", "ino", "inl", "ipp", "ixx", "sycl", "tcc", "txx", "tpp", "idl"]); + // Fallback for custom associations when native binaries do not publish effective file type mappings. + const caseSensitiveFileSupport: boolean = new CppSettings().isCaseSensitiveFileSupportEnabled; + const getAssociationKey: (extension: string) => string = caseSensitiveFileSupport + ? (extension: string): string => extension + : (extension: string): string => extension.toLowerCase(); + this.associations_for_did_change = new Set(); const assocs: any = new OtherSettings().filesAssociations; for (const assoc in assocs) { const dotIndex: number = assoc.lastIndexOf('.'); if (dotIndex !== -1) { const ext: string = assoc.substring(dotIndex + 1); - this.associations_for_did_change.add(ext); + this.associations_for_did_change.add(getAssociationKey(ext)); } } this.rootPathFileWatcher.onDidChange(async (uri) => { @@ -2757,7 +2762,8 @@ export class DefaultClient implements Client { const ext: string | undefined = dotIndex !== -1 ? uri.fsPath.substring(dotIndex + 1) : undefined; const isTrackedFile: boolean = hasNativeFileTypeMappings() ? isTagParsableFile(uri.fsPath) - : ext !== undefined && this.associations_for_did_change?.has(ext) === true; + : isTagParsableFile(uri.fsPath) || + (ext !== undefined && this.associations_for_did_change?.has(getAssociationKey(ext)) === true); if (isTrackedFile) { // VS Code has a bug that causes onDidChange events to happen to files that aren't changed, // which causes a large backlog of "files to parse" to accumulate. diff --git a/Extension/src/fileType.ts b/Extension/src/fileType.ts index 71ca5503d..2e7c29014 100644 --- a/Extension/src/fileType.ts +++ b/Extension/src/fileType.ts @@ -4,6 +4,7 @@ * ------------------------------------------------------------------------------------------ */ import * as path from 'path'; +import { isWindows } from './constants'; export type FileTypeKind = 'source' | 'header' | 'idl' | 'resource' | 'other'; export type FileTypeLanguage = 'c' | 'cpp' | 'cuda'; @@ -34,27 +35,34 @@ const bootstrapMappings: FileTypeMappings = { let extensionMappings: ReadonlyMap; let filenameMappings: ReadonlyMap; let nativeMappingsAvailable: boolean = false; +let caseSensitiveFileSupport: boolean = !isWindows; + +function getMappingKey(name: string): string { + return caseSensitiveFileSupport ? name : name.toLowerCase(); +} function createMappingMap(mappings: FileTypeMapping[]): ReadonlyMap { const result: Map = new Map(); for (const mapping of mappings) { - result.set(mapping.name.toLowerCase(), { ...mapping, name: mapping.name.toLowerCase() }); + result.set(getMappingKey(mapping.name), { ...mapping }); } return result; } -export function resetFileTypeMappings(): void { +export function resetFileTypeMappings(caseSensitive: boolean = !isWindows): void { + caseSensitiveFileSupport = caseSensitive; extensionMappings = createMappingMap(bootstrapMappings.extensions); filenameMappings = createMappingMap(bootstrapMappings.filenames); nativeMappingsAvailable = false; } -export function updateFileTypeMappings(mappings: FileTypeMappings | undefined): void { +export function updateFileTypeMappings(mappings: FileTypeMappings | undefined, caseSensitive: boolean = caseSensitiveFileSupport): void { if (!mappings) { - resetFileTypeMappings(); + resetFileTypeMappings(caseSensitive); return; } + caseSensitiveFileSupport = caseSensitive; extensionMappings = createMappingMap(mappings.extensions); filenameMappings = createMappingMap(mappings.filenames); nativeMappingsAvailable = true; @@ -66,18 +74,17 @@ export function hasNativeFileTypeMappings(): boolean { function getRegisteredFileType(filePath: string): FileTypeMapping | undefined { const filename: string = path.basename(filePath); - const filenameMapping: FileTypeMapping | undefined = filenameMappings.get(filename.toLowerCase()); + const filenameMapping: FileTypeMapping | undefined = filenameMappings.get(getMappingKey(filename)); if (filenameMapping) { return filenameMapping; } const extension: string = path.extname(filename); - // VS Code initially assigns uppercase .C files to C. Preserve the extension's - // long-standing correction until the exact filename association is installed. + // Uppercase .C is an exact, case-sensitive C++ association by convention. if (extension === '.C') { return { name: extension, kind: 'source', language: 'cpp' }; } - return extensionMappings.get(extension.toLowerCase()); + return extensionMappings.get(getMappingKey(extension)); } export function classifyFilePath(filePath: string, languageId?: string): FileTypeMapping | undefined { diff --git a/Extension/test/unit/fileType.test.ts b/Extension/test/unit/fileType.test.ts index a7e4595c6..61cdb10f8 100644 --- a/Extension/test/unit/fileType.test.ts +++ b/Extension/test/unit/fileType.test.ts @@ -16,18 +16,34 @@ import { describe('file type mappings', () => { afterEach(() => resetFileTypeMappings()); - it('uses legacy classifications before native initialization', () => { + it('uses case-sensitive legacy classifications when configured', () => { + resetFileTypeMappings(true); + equal(hasNativeFileTypeMappings(), false); deepStrictEqual(classifyFilePath('file.hpp'), { name: '.hpp', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); for (const extension of ['ccm', 'cppm', 'hip', 'ixx', 'sycl']) { deepStrictEqual(classifyFilePath(`file.${extension}`), { name: `.${extension}`, kind: 'source', language: 'cpp' }); equal(isTagParsableFile(`file.${extension}`), true); } + equal(classifyFilePath('file.CPPM'), undefined); + equal(isTagParsableFile('file.CPPM'), false); deepStrictEqual(classifyFilePath('Makefile'), { name: '', kind: 'header' }); }); + it('uses case-insensitive legacy classifications when configured', () => { + resetFileTypeMappings(false); + + for (const extension of ['HPP', 'CCM', 'CPPM', 'HIP', 'IXX', 'SYCL']) { + equal(isTagParsableFile(`file.${extension}`), true); + } + deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); + }); + it('atomically replaces bootstrap mappings with native mappings', () => { + resetFileTypeMappings(false); updateFileTypeMappings({ extensions: [ { name: '.c', kind: 'source', language: 'c' }, @@ -55,11 +71,27 @@ describe('file type mappings', () => { equal(isTagParsableFile('unknown.txt'), false); }); + it('preserves exact mapping case when configured', () => { + resetFileTypeMappings(true); + updateFileTypeMappings({ + extensions: [ + { name: '.c', kind: 'source', language: 'c' }, + { name: '.cppm', kind: 'source', language: 'cpp' } + ], + filenames: [{ name: 'vector', kind: 'header' }] + }); + + deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); + equal(classifyFilePath('file.CPPM'), undefined); + equal(classifyFilePath('VECTOR'), undefined); + }); + it('uses the editor language only for unregistered paths', () => { updateFileTypeMappings({ extensions: [{ name: '.h', kind: 'header', language: 'cpp' }], filenames: [] - }); + }, true); deepStrictEqual(classifyFilePath('file.h', 'c'), { name: '.h', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.special', 'c'), { name: '', kind: 'source', language: 'c' }); From 804be12908b6250ca8a43a5bd312aefb9af93b17 Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Thu, 27 Aug 2026 18:49:46 -0700 Subject: [PATCH 4/5] Address PR feedback --- Extension/src/Debugger/configurationProvider.ts | 8 +------- Extension/src/LanguageServer/cppBuildTaskProvider.ts | 9 ++------- Extension/test/unit/fileType.test.ts | 4 ++++ 3 files changed, 7 insertions(+), 14 deletions(-) diff --git a/Extension/src/Debugger/configurationProvider.ts b/Extension/src/Debugger/configurationProvider.ts index d03393326..e675516f8 100644 --- a/Extension/src/Debugger/configurationProvider.ts +++ b/Extension/src/Debugger/configurationProvider.ts @@ -546,12 +546,6 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv return; } - const fileExt: string = path.extname(editor.document.fileName); - if (!fileExt) { - DebugConfigurationProvider.detectedBuildTasks = emptyTasks; - return; - } - // Don't offer tasks for header files. const isHeader: boolean = util.isHeaderFile(editor.document.uri); if (isHeader) { @@ -559,7 +553,7 @@ export class DebugConfigurationProvider implements vscode.DebugConfigurationProv return; } - // Don't offer tasks if the active file's extension is not a recognized C/C++ extension. + // Don't offer tasks if the active file is not a recognized C/C++ source file. const fileIsCpp: boolean = util.isCppFile(editor.document.uri, editor.document.languageId); const fileIsC: boolean = util.isCFile(editor.document.uri, editor.document.languageId); if (!(fileIsCpp || fileIsC)) { diff --git a/Extension/src/LanguageServer/cppBuildTaskProvider.ts b/Extension/src/LanguageServer/cppBuildTaskProvider.ts index 736560f76..8112602f9 100644 --- a/Extension/src/LanguageServer/cppBuildTaskProvider.ts +++ b/Extension/src/LanguageServer/cppBuildTaskProvider.ts @@ -73,7 +73,7 @@ export class CppBuildTaskProvider implements TaskProvider { return _task; } - // Generate tasks to build the current file based on the user's detected compilers, the user's compilerPath setting, and the current file's extension. + // Generate tasks to build the current file based on the user's detected compilers, compilerPath setting, and file type. public async getTasks(appendSourceToName: boolean = false): Promise { const editor: TextEditor | undefined = window.activeTextEditor; const emptyTasks: CppBuildTask[] = []; @@ -81,18 +81,13 @@ export class CppBuildTaskProvider implements TaskProvider { return emptyTasks; } - const fileExt: string = path.extname(editor.document.fileName); - if (!fileExt) { - return emptyTasks; - } - // Don't offer tasks for header files. const isHeader: boolean = util.isHeaderFile(editor.document.uri); if (isHeader) { return emptyTasks; } - // Don't offer tasks if the active file's extension is not a recognized C/C++ extension. + // Don't offer tasks if the active file is not a recognized C/C++ source file. const fileIsCpp: boolean = util.isCppFile(editor.document.uri, editor.document.languageId); const fileIsC: boolean = util.isCFile(editor.document.uri, editor.document.languageId); if (!(fileIsCpp || fileIsC)) { diff --git a/Extension/test/unit/fileType.test.ts b/Extension/test/unit/fileType.test.ts index 61cdb10f8..f35047b25 100644 --- a/Extension/test/unit/fileType.test.ts +++ b/Extension/test/unit/fileType.test.ts @@ -53,6 +53,7 @@ describe('file type mappings', () => { ], filenames: [ { name: 'foo.h', kind: 'source', language: 'c' }, + { name: 'build', kind: 'source', language: 'cpp' }, { name: 'vector', kind: 'header' }, { name: 'kernel.custom', kind: 'source', language: 'cuda' } ] @@ -62,11 +63,13 @@ describe('file type mappings', () => { deepStrictEqual(classifyFilePath('module.CPPM'), { name: '.cppm', kind: 'source', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); deepStrictEqual(classifyFilePath('foo.h'), { name: 'foo.h', kind: 'source', language: 'c' }); + deepStrictEqual(classifyFilePath('build'), { name: 'build', kind: 'source', language: 'cpp' }); deepStrictEqual(classifyFilePath('VECTOR'), { name: 'vector', kind: 'header' }); deepStrictEqual(classifyFilePath('kernel.custom'), { name: 'kernel.custom', kind: 'source', language: 'cuda' }); equal(classifyFilePath('Makefile'), undefined); equal(isTagParsableFile('schema.idl'), true); equal(isTagParsableFile('kernel.custom'), true); + equal(isTagParsableFile('build'), true); equal(isTagParsableFile('vector'), true); equal(isTagParsableFile('unknown.txt'), false); }); @@ -96,5 +99,6 @@ describe('file type mappings', () => { deepStrictEqual(classifyFilePath('file.h', 'c'), { name: '.h', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.special', 'c'), { name: '', kind: 'source', language: 'c' }); deepStrictEqual(classifyFilePath('file.special', 'cuda-cpp'), { name: '', kind: 'source', language: 'cuda' }); + deepStrictEqual(classifyFilePath('extensionless', 'cpp'), { name: '', kind: 'source', language: 'cpp' }); }); }); From f4bbb630ed3c19d42ce6966edc33eeaf63c7a09a Mon Sep 17 00:00:00 2001 From: Colen Garoutte-Carson Date: Fri, 28 Aug 2026 11:48:51 -0700 Subject: [PATCH 5/5] Treat file associations as case insensitive --- Extension/src/LanguageServer/client.ts | 14 +++----- Extension/src/fileType.ts | 21 ++++------- Extension/test/unit/fileType.test.ts | 50 ++++++-------------------- 3 files changed, 23 insertions(+), 62 deletions(-) diff --git a/Extension/src/LanguageServer/client.ts b/Extension/src/LanguageServer/client.ts index 6e5d5179d..67836f6f3 100644 --- a/Extension/src/LanguageServer/client.ts +++ b/Extension/src/LanguageServer/client.ts @@ -1736,7 +1736,7 @@ export class DefaultClient implements Client { localizedStrings: localizedStrings, settings: this.getAllSettings() }; - resetFileTypeMappings(cppInitializationParams.caseSensitiveFileSupport); + resetFileTypeMappings(); this.loggingLevel = util.getNumericLoggingLevel(cppInitializationParams.settings.loggingLevel); const lspInitializationOptions: LspInitializationOptions = { @@ -1849,9 +1849,9 @@ export class DefaultClient implements Client { // higher priority message may be processed before the Initialization request. const initializeResult = await client.sendRequest(InitializationRequest, cppInitializationParams); if (initializeResult.fileTypeMappings) { - updateFileTypeMappings(initializeResult.fileTypeMappings, cppInitializationParams.caseSensitiveFileSupport); + updateFileTypeMappings(initializeResult.fileTypeMappings); } else { - resetFileTypeMappings(cppInitializationParams.caseSensitiveFileSupport); + resetFileTypeMappings(); } DebugConfigurationProvider.ClearDetectedBuildTasks(); @@ -2735,17 +2735,13 @@ export class DefaultClient implements Client { }); // Fallback for custom associations when native binaries do not publish effective file type mappings. - const caseSensitiveFileSupport: boolean = new CppSettings().isCaseSensitiveFileSupportEnabled; - const getAssociationKey: (extension: string) => string = caseSensitiveFileSupport - ? (extension: string): string => extension - : (extension: string): string => extension.toLowerCase(); this.associations_for_did_change = new Set(); const assocs: any = new OtherSettings().filesAssociations; for (const assoc in assocs) { const dotIndex: number = assoc.lastIndexOf('.'); if (dotIndex !== -1) { const ext: string = assoc.substring(dotIndex + 1); - this.associations_for_did_change.add(getAssociationKey(ext)); + this.associations_for_did_change.add(ext.toLowerCase()); } } this.rootPathFileWatcher.onDidChange(async (uri) => { @@ -2763,7 +2759,7 @@ export class DefaultClient implements Client { const isTrackedFile: boolean = hasNativeFileTypeMappings() ? isTagParsableFile(uri.fsPath) : isTagParsableFile(uri.fsPath) || - (ext !== undefined && this.associations_for_did_change?.has(getAssociationKey(ext)) === true); + (ext !== undefined && this.associations_for_did_change?.has(ext.toLowerCase()) === true); if (isTrackedFile) { // VS Code has a bug that causes onDidChange events to happen to files that aren't changed, // which causes a large backlog of "files to parse" to accumulate. diff --git a/Extension/src/fileType.ts b/Extension/src/fileType.ts index 2e7c29014..4712511ed 100644 --- a/Extension/src/fileType.ts +++ b/Extension/src/fileType.ts @@ -4,7 +4,6 @@ * ------------------------------------------------------------------------------------------ */ import * as path from 'path'; -import { isWindows } from './constants'; export type FileTypeKind = 'source' | 'header' | 'idl' | 'resource' | 'other'; export type FileTypeLanguage = 'c' | 'cpp' | 'cuda'; @@ -35,34 +34,28 @@ const bootstrapMappings: FileTypeMappings = { let extensionMappings: ReadonlyMap; let filenameMappings: ReadonlyMap; let nativeMappingsAvailable: boolean = false; -let caseSensitiveFileSupport: boolean = !isWindows; - -function getMappingKey(name: string): string { - return caseSensitiveFileSupport ? name : name.toLowerCase(); -} function createMappingMap(mappings: FileTypeMapping[]): ReadonlyMap { const result: Map = new Map(); for (const mapping of mappings) { - result.set(getMappingKey(mapping.name), { ...mapping }); + // VS Code matches file associations case-insensitively on every platform. + result.set(mapping.name.toLowerCase(), { ...mapping }); } return result; } -export function resetFileTypeMappings(caseSensitive: boolean = !isWindows): void { - caseSensitiveFileSupport = caseSensitive; +export function resetFileTypeMappings(): void { extensionMappings = createMappingMap(bootstrapMappings.extensions); filenameMappings = createMappingMap(bootstrapMappings.filenames); nativeMappingsAvailable = false; } -export function updateFileTypeMappings(mappings: FileTypeMappings | undefined, caseSensitive: boolean = caseSensitiveFileSupport): void { +export function updateFileTypeMappings(mappings: FileTypeMappings | undefined): void { if (!mappings) { - resetFileTypeMappings(caseSensitive); + resetFileTypeMappings(); return; } - caseSensitiveFileSupport = caseSensitive; extensionMappings = createMappingMap(mappings.extensions); filenameMappings = createMappingMap(mappings.filenames); nativeMappingsAvailable = true; @@ -74,7 +67,7 @@ export function hasNativeFileTypeMappings(): boolean { function getRegisteredFileType(filePath: string): FileTypeMapping | undefined { const filename: string = path.basename(filePath); - const filenameMapping: FileTypeMapping | undefined = filenameMappings.get(getMappingKey(filename)); + const filenameMapping: FileTypeMapping | undefined = filenameMappings.get(filename.toLowerCase()); if (filenameMapping) { return filenameMapping; } @@ -84,7 +77,7 @@ function getRegisteredFileType(filePath: string): FileTypeMapping | undefined { if (extension === '.C') { return { name: extension, kind: 'source', language: 'cpp' }; } - return extensionMappings.get(getMappingKey(extension)); + return extensionMappings.get(extension.toLowerCase()); } export function classifyFilePath(filePath: string, languageId?: string): FileTypeMapping | undefined { diff --git a/Extension/test/unit/fileType.test.ts b/Extension/test/unit/fileType.test.ts index f35047b25..84c122b83 100644 --- a/Extension/test/unit/fileType.test.ts +++ b/Extension/test/unit/fileType.test.ts @@ -16,39 +16,25 @@ import { describe('file type mappings', () => { afterEach(() => resetFileTypeMappings()); - it('uses case-sensitive legacy classifications when configured', () => { - resetFileTypeMappings(true); - + it('uses case-insensitive legacy classifications before native initialization', () => { equal(hasNativeFileTypeMappings(), false); - deepStrictEqual(classifyFilePath('file.hpp'), { name: '.hpp', kind: 'header', language: 'cpp' }); + deepStrictEqual(classifyFilePath('file.HPP'), { name: '.hpp', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); - for (const extension of ['ccm', 'cppm', 'hip', 'ixx', 'sycl']) { - deepStrictEqual(classifyFilePath(`file.${extension}`), { name: `.${extension}`, kind: 'source', language: 'cpp' }); + for (const extension of ['CCM', 'CPPM', 'HIP', 'IXX', 'SYCL']) { + deepStrictEqual(classifyFilePath(`file.${extension}`), { name: `.${extension.toLowerCase()}`, kind: 'source', language: 'cpp' }); equal(isTagParsableFile(`file.${extension}`), true); } - equal(classifyFilePath('file.CPPM'), undefined); - equal(isTagParsableFile('file.CPPM'), false); deepStrictEqual(classifyFilePath('Makefile'), { name: '', kind: 'header' }); }); - it('uses case-insensitive legacy classifications when configured', () => { - resetFileTypeMappings(false); - - for (const extension of ['HPP', 'CCM', 'CPPM', 'HIP', 'IXX', 'SYCL']) { - equal(isTagParsableFile(`file.${extension}`), true); - } - deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); - deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); - }); - it('atomically replaces bootstrap mappings with native mappings', () => { - resetFileTypeMappings(false); updateFileTypeMappings({ extensions: [ { name: '.c', kind: 'source', language: 'c' }, { name: '.cppm', kind: 'source', language: 'cpp' }, { name: '.h', kind: 'header', language: 'cpp' }, + { name: '.hpp', kind: 'header', language: 'cpp' }, { name: '.idl', kind: 'idl' } ], filenames: [ @@ -60,11 +46,13 @@ describe('file type mappings', () => { }); equal(hasNativeFileTypeMappings(), true); - deepStrictEqual(classifyFilePath('module.CPPM'), { name: '.cppm', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('module.CPPM', 'cpp'), { name: '.cppm', kind: 'source', language: 'cpp' }); + deepStrictEqual(classifyFilePath('header.HPP', 'cpp'), { name: '.hpp', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); - deepStrictEqual(classifyFilePath('foo.h'), { name: 'foo.h', kind: 'source', language: 'c' }); + deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); + deepStrictEqual(classifyFilePath('FOO.H', 'cpp'), { name: 'foo.h', kind: 'source', language: 'c' }); deepStrictEqual(classifyFilePath('build'), { name: 'build', kind: 'source', language: 'cpp' }); - deepStrictEqual(classifyFilePath('VECTOR'), { name: 'vector', kind: 'header' }); + deepStrictEqual(classifyFilePath('VECTOR', 'cpp'), { name: 'vector', kind: 'header' }); deepStrictEqual(classifyFilePath('kernel.custom'), { name: 'kernel.custom', kind: 'source', language: 'cuda' }); equal(classifyFilePath('Makefile'), undefined); equal(isTagParsableFile('schema.idl'), true); @@ -74,27 +62,11 @@ describe('file type mappings', () => { equal(isTagParsableFile('unknown.txt'), false); }); - it('preserves exact mapping case when configured', () => { - resetFileTypeMappings(true); - updateFileTypeMappings({ - extensions: [ - { name: '.c', kind: 'source', language: 'c' }, - { name: '.cppm', kind: 'source', language: 'cpp' } - ], - filenames: [{ name: 'vector', kind: 'header' }] - }); - - deepStrictEqual(classifyFilePath('file.C'), { name: '.C', kind: 'source', language: 'cpp' }); - deepStrictEqual(classifyFilePath('file.c'), { name: '.c', kind: 'source', language: 'c' }); - equal(classifyFilePath('file.CPPM'), undefined); - equal(classifyFilePath('VECTOR'), undefined); - }); - it('uses the editor language only for unregistered paths', () => { updateFileTypeMappings({ extensions: [{ name: '.h', kind: 'header', language: 'cpp' }], filenames: [] - }, true); + }); deepStrictEqual(classifyFilePath('file.h', 'c'), { name: '.h', kind: 'header', language: 'cpp' }); deepStrictEqual(classifyFilePath('file.special', 'c'), { name: '', kind: 'source', language: 'c' });