diff --git a/vscode/react/src/pages/lineage.tsx b/vscode/react/src/pages/lineage.tsx index cca63e5f9a..97da386e8b 100644 --- a/vscode/react/src/pages/lineage.tsx +++ b/vscode/react/src/pages/lineage.tsx @@ -15,6 +15,7 @@ import { useEventBus } from '@/hooks/eventBus' import type { VSCodeEvent } from '@bus/callbacks' import type { Model } from '@/api/client' import { useRpc } from '@/utils/rpc' +import { isSameFilePath } from '@/utils/index' import { type ModelPath, type ModelFullPath, @@ -98,12 +99,9 @@ function Lineage() { } // @ts-ignore const filePath: string = activeFile.fileUri - const model = models.find((m: Model) => { - if (!m.full_path) { - return false - } - return m.full_path === filePath - }) + const model = models.find((m: Model) => + isSameFilePath(m.full_path, filePath), + ) if (model) { return model.name } @@ -132,8 +130,8 @@ function Lineage() { React.useEffect(() => { const handleChangeFocusedFile = (fileUri: { fileUri: string }) => { - const model = Object.values(modelsRecord).find( - m => m.full_path === fileUri.fileUri, + const model = Object.values(modelsRecord).find(m => + isSameFilePath(m.full_path, fileUri.fileUri), ) if (model) { setSelectedModel(model.name) diff --git a/vscode/react/src/utils/index.spec.ts b/vscode/react/src/utils/index.spec.ts index d1d815e48b..02b0ffd1b6 100644 --- a/vscode/react/src/utils/index.spec.ts +++ b/vscode/react/src/utils/index.spec.ts @@ -11,6 +11,7 @@ import { toDateFormat, isStringEmpty, ensureString, + isSameFilePath, } from './index' describe('isArrayNotEmpty', () => { @@ -209,3 +210,31 @@ describe('ensureString', () => { expect(ensureString('')).toBe('') }) }) + +describe('isSameFilePath', () => { + test('matches identical paths', () => { + expect(isSameFilePath('/a/models/m.sql', '/a/models/m.sql')).toBe(true) + }) + + test('ignores drive letter casing and separator differences', () => { + expect( + isSameFilePath( + 'C:\\proj\\models\\core\\dim_product.sql', + 'c:\\proj\\models\\core\\dim_product.sql', + ), + ).toBe(true) + expect( + isSameFilePath('C:\\proj\\models\\m.sql', 'c:/proj/models/m.sql'), + ).toBe(true) + }) + + test('returns false for different paths', () => { + expect(isSameFilePath('/a/models/a.sql', '/a/models/b.sql')).toBe(false) + }) + + test('returns false when either path is missing', () => { + expect(isSameFilePath(undefined, '/a.sql')).toBe(false) + expect(isSameFilePath('/a.sql', undefined)).toBe(false) + expect(isSameFilePath('', '')).toBe(false) + }) +}) diff --git a/vscode/react/src/utils/index.ts b/vscode/react/src/utils/index.ts index ed0faab1db..65093b0fcf 100644 --- a/vscode/react/src/utils/index.ts +++ b/vscode/react/src/utils/index.ts @@ -209,3 +209,19 @@ export function truncate( text.substring(textLength - displayAfter) ) } + +/** + * Compares two file system paths, ignoring path separator and casing + * differences (e.g. `C:\a\b.sql` vs `c:/a/b.sql` on Windows). Casing is + * ignored on every platform, which is fine for paths within a single project. + */ +export function isSameFilePath( + a: string | undefined | null, + b: string | undefined | null, +): boolean { + if (!a || !b) { + return false + } + const normalize = (path: string) => path.replace(/\\/g, '/').toLowerCase() + return normalize(a) === normalize(b) +}