Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/cozy-foxes-shine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'svelte-language-server': patch
---

perf: optimize path normalization
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ class ModuleResolutionCache {
}

oneOfResolvedModuleChanged(path: string) {
return this.pendingInvalidations.has(path);
return this.pendingInvalidations.size > 0 && this.pendingInvalidations.has(path);
}
}

Expand Down Expand Up @@ -325,7 +325,7 @@ export function createSvelteModuleLoader(
return (
moduleCache.oneOfResolvedModuleChanged(path) ||
// tried but failed file might now exist
failedLocationInvalidated.has(path)
(failedLocationInvalidated.size > 0 && failedLocationInvalidated.has(path))
);
}

Expand Down
24 changes: 14 additions & 10 deletions packages/language-server/src/plugins/typescript/svelte-sys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,23 +14,29 @@ export function createSvelteSys(tsSystem: ts.System) {

// First check if there's a `.svelte.d.ts` or `.d.svelte.ts` file, which should take precedence
const dtsPath = sveltePath.slice(0, -7) + '.svelte.d.ts';
const dtsPathExists = fileExistsCache.get(dtsPath) ?? tsSystem.fileExists(dtsPath);
fileExistsCache.set(dtsPath, dtsPathExists);
const dtsPathExists = getOrCreateFileExistCache(dtsPath);
if (dtsPathExists) return false;

const svelteDtsPathExists = fileExistsCache.get(path) ?? tsSystem.fileExists(path);
fileExistsCache.set(path, svelteDtsPathExists);
const svelteDtsPathExists = getOrCreateFileExistCache(path);
if (svelteDtsPathExists) return false;

const sveltePathExists =
fileExistsCache.get(sveltePath) ?? tsSystem.fileExists(sveltePath);
fileExistsCache.set(sveltePath, sveltePathExists);
const sveltePathExists = getOrCreateFileExistCache(sveltePath);
return sveltePathExists;
} else {
return false;
}
}

function getOrCreateFileExistCache(path: string) {
const cached = fileExistsCache.get(path);
if (cached !== undefined) {
return cached;
}
const exists = tsSystem.fileExists(path);
fileExistsCache.set(path, exists);
return exists;
}

function getRealSveltePathIfExists(path: string) {
return svelteFileExists(path) ? toRealSvelteFilePath(path) : path;
}
Expand All @@ -48,9 +54,7 @@ export function createSvelteSys(tsSystem: ts.System) {
const sveltePathExists = svelteFileExists(path);
if (sveltePathExists) return true;

const exists = fileExistsCache.get(path) ?? tsSystem.fileExists(path);
fileExistsCache.set(path, exists);
return exists;
return getOrCreateFileExistCache(path);
},
readFile(path: string) {
// No getSnapshot here, because TS will very rarely call this and only for files that are not in the project
Expand Down
22 changes: 18 additions & 4 deletions packages/language-server/src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { isEqual, sum, uniqWith } from 'lodash';
import { FoldingRange, Node } from 'vscode-html-languageservice';
import { isEqual, uniqWith } from 'lodash';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Isn't lodash's deep equal method known to be quite slow? Maybe replacing lodash with faster alternatives can give some free performance boosts

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

import { Node } from 'vscode-html-languageservice';
import { Position, Range } from 'vscode-languageserver';
import { URI } from 'vscode-uri';
import { Document, TagInformation } from './lib/documents';

type Predicate<T> = (x: T) => boolean;

Expand Down Expand Up @@ -38,12 +37,27 @@ export function pathToUrl(path: string) {
return URI.file(path).toString();
}

const backslashRegEx = /\\/g;

/**
* Some paths (on windows) start with a upper case driver letter, some don't.
* This is normalized here.
*/
export function normalizePath(path: string): string {
return URI.file(path).fsPath.replace(/\\/g, '/');
return normalizeDriveLetter(path.replace(backslashRegEx, '/'));
}

function normalizeDriveLetter(path: string): string {
if (path.charCodeAt(1) !== /*:*/ 58) {
return path;
}

const driveLetter = path.charCodeAt(0);
if (driveLetter >= /*A*/ 65 && driveLetter <= /*Z*/ 90) {
return String.fromCharCode(driveLetter + 32) + path.slice(1);
}

return path;
}

/**
Expand Down
32 changes: 31 additions & 1 deletion packages/language-server/test/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import { isBeforeOrEqualToPosition, modifyLines, regexLastIndexOf } from '../src/utils';
import {
isBeforeOrEqualToPosition,
modifyLines,
normalizePath,
regexLastIndexOf
} from '../src/utils';
import { Position } from 'vscode-languageserver';
import * as assert from 'assert';
import { URI } from 'vscode-uri';

describe('utils', () => {
describe('#isBeforeOrEqualToPosition', () => {
Expand Down Expand Up @@ -61,4 +67,28 @@ describe('utils', () => {
assert.deepStrictEqual(idxs, [0, 1, 2, 3]);
});
});

describe('path normalization on Windows', () => {
it('should lowercase drive letters and normalize slashes', () => {
assert.strictEqual(
normalizePath('C:\\Users\\Test\\project\\file.ts'),
'c:/Users/Test/project/file.ts'
);

assert.strictEqual(
normalizePath('D:/Some/Other/Path/file.ts'),
'd:/Some/Other/Path/file.ts'
);

assert.strictEqual(
normalizePath('e:\\Mixed/Slashes\\Path/file.ts'),
'e:/Mixed/Slashes/Path/file.ts'
);

assert.strictEqual(
normalizePath('/already/normalized/path/file.ts'),
'/already/normalized/path/file.ts'
);
});
});
});