Skip to content

Commit 10a4096

Browse files
committed
Only use the sync TSServer when on a file:// workspace
1 parent f0f1f86 commit 10a4096

File tree

3 files changed

+57
-33
lines changed

3 files changed

+57
-33
lines changed

extensions/html-language-features/server/src/modes/javascriptMode.ts

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,8 @@ import { normalize, sep } from 'path';
1616

1717
import * as ts from 'typescript';
1818
import { getSemanticTokens, getSemanticTokenLegend } from './javascriptSemanticTokens';
19-
import { statSync } from 'fs';
19+
import { RequestService } from '../requests';
20+
import { NodeRequestService } from '../node/nodeFs';
2021

2122
const JS_WORD_REGEX = /(-?\d*\.\d\w*)|([^\`\~\!\@\#\%\^\&\*\(\)\-\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\?\s]+)/g;
2223

@@ -32,13 +33,13 @@ function deschemeURI(uri: string) {
3233
// Both \ and / must be escaped in regular expressions
3334
newPath = newPath.replace(new RegExp('\\' + sep, 'g'), '/');
3435

35-
if (process.platform !== 'win32') { return newPath; }
36+
if (process.platform !== 'win32') return newPath;
3637

3738
// Windows URIs come in like '/c%3A/Users/orta/dev/...', we need to switch it to 'c:/Users/orta/dev/...'
3839
return newPath.slice(1).replace('%3A', ':');
3940
}
4041

41-
function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
42+
function getLanguageServiceHost(scriptKind: ts.ScriptKind, fs: NodeRequestService) {
4243
const compilerOptions: ts.CompilerOptions = { allowNonTsExtensions: true, allowJs: true, lib: ['lib.es6.d.ts'], target: ts.ScriptTarget.Latest, moduleResolution: ts.ModuleResolutionKind.Classic, experimentalDecorators: false };
4344

4445
let currentTextDocument = TextDocument.create('init', 'javascript', 1, '');
@@ -64,15 +65,15 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
6465
return '1';
6566
}
6667
// Unsure how this could occur, but better to not raise with statSync
67-
if (!ts.sys.fileExists(fileName)) { return '1'; }
68+
if (currentWorkspace && !ts.sys.fileExists(fileName)) { return '1'; }
6869
// Use mtime from the fs
69-
return String(statSync(fileName).mtimeMs);
70+
return String(fs.statSync(fileName).mtime);
7071
},
7172
getScriptSnapshot: (fileName: string) => {
7273
let text = '';
7374
if (fileName === deschemeURI(currentTextDocument.uri)) {
7475
text = currentTextDocument.getText();
75-
} else if (ts.sys.fileExists(fileName)) {
76+
} else if (currentWorkspace && ts.sys.fileExists(fileName)) {
7677
text = ts.sys.readFile(fileName, 'utf8')!;
7778
} else {
7879
text = libs.loadLibrary(fileName);
@@ -83,24 +84,27 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
8384
getChangeRange: () => undefined
8485
};
8586
},
87+
88+
// Realistically the TSServer can only run on file:// workspaces, because it is entirely sync API
89+
// and so `currentWorkspace` is only set when there is at least one root folder in a workspace with a file:// uri
8690
getCurrentDirectory: () => {
8791
const workspace = currentWorkspace && currentWorkspace.folders.find(ws => deschemeURI(currentTextDocument.uri).startsWith(deschemeURI(ws.uri)));
8892
return workspace ? deschemeURI(workspace.uri) : '';
8993
},
9094
getDefaultLibFileName: (_options: ts.CompilerOptions) => 'es6',
91-
fileExists: ts.sys.fileExists,
92-
readFile: ts.sys.readFile,
93-
readDirectory: ts.sys.readDirectory,
94-
directoryExists: ts.sys.directoryExists,
95-
getDirectories: ts.sys.getDirectories,
95+
fileExists: currentWorkspace && ts.sys.fileExists,
96+
readFile: currentWorkspace && ts.sys.readFile,
97+
readDirectory: currentWorkspace && ts.sys.readDirectory,
98+
directoryExists: currentWorkspace && ts.sys.directoryExists,
99+
getDirectories: currentWorkspace && ts.sys.getDirectories,
96100
};
97101

98102
return ts.createLanguageService(host);
99103
});
100104
return {
101105
async getLanguageService(jsDocument: TextDocument, workspace: Workspace): Promise<ts.LanguageService> {
102106
currentTextDocument = jsDocument;
103-
currentWorkspace = workspace;
107+
if (workspace.folders.find(f => f.uri.startsWith('file://'))) currentWorkspace = workspace;
104108
return jsLanguageService;
105109
},
106110
getCompilationSettings() {
@@ -113,10 +117,10 @@ function getLanguageServiceHost(scriptKind: ts.ScriptKind) {
113117
}
114118

115119

116-
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, languageId: 'javascript' | 'typescript', workspace: Workspace): LanguageMode {
120+
export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocumentRegions>, languageId: 'javascript' | 'typescript', workspace: Workspace, fs: RequestService): LanguageMode {
117121
let jsDocuments = getLanguageModelCache<TextDocument>(10, 60, document => documentRegions.get(document).getEmbeddedDocument(languageId));
118122

119-
const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS);
123+
const host = getLanguageServiceHost(languageId === 'javascript' ? ts.ScriptKind.JS : ts.ScriptKind.TS, fs as NodeRequestService);
120124
let globalSettings: Settings = {};
121125

122126
return {
@@ -146,7 +150,7 @@ export function getJavaScriptMode(documentRegions: LanguageModelCache<HTMLDocume
146150
const filePath = deschemeURI(jsDocument.uri);
147151

148152
let offset = jsDocument.offsetAt(position);
149-
let completions = jsLanguageService.getCompletionsAtPosition(filePath, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false, importModuleSpecifierEnding: 'js' });
153+
let completions = jsLanguageService.getCompletionsAtPosition(filePath, offset, { includeExternalModuleExports: false, includeInsertTextCompletions: false });
150154

151155
if (!completions) {
152156
return { isIncomplete: false, items: [] };

extensions/html-language-features/server/src/modes/languageModes.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,8 +112,8 @@ export function getLanguageModes(supportedLanguages: { [languageId: string]: boo
112112
modes['css'] = getCSSMode(cssLanguageService, documentRegions, workspace);
113113
}
114114
if (supportedLanguages['javascript']) {
115-
modes['javascript'] = getJavaScriptMode(documentRegions, 'javascript', workspace);
116-
modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript', workspace);
115+
modes['javascript'] = getJavaScriptMode(documentRegions, 'javascript', workspace, requestService);
116+
modes['typescript'] = getJavaScriptMode(documentRegions, 'typescript', workspace, requestService);
117117
}
118118
return {
119119
async updateDataProviders(dataProviders: IHTMLDataProvider[]): Promise<void> {

extensions/html-language-features/server/src/node/nodeFs.ts

Lines changed: 36 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,41 @@ import { URI as Uri } from 'vscode-uri';
88

99
import * as fs from 'fs';
1010
import { FileType } from 'vscode-css-languageservice';
11+
import { FileStat } from 'vscode-html-languageservice';
1112

12-
export function getNodeFSRequestService(): RequestService {
13+
/**
14+
* This extension is for the TSServer in the JavaScript mode which
15+
* require sync access to stat's mtime due to TSServer API limitations
16+
*/
17+
export interface NodeRequestService extends RequestService {
18+
statSync(location: string): FileStat
19+
}
20+
21+
export function getNodeFSRequestService(): NodeRequestService {
1322
function ensureFileUri(location: string) {
1423
if (getScheme(location) !== 'file') {
1524
throw new Error('fileRequestService can only handle file URLs');
1625
}
1726
}
27+
28+
const fsStatToFileStat = (stats: fs.Stats) => {
29+
let type = FileType.Unknown;
30+
if (stats.isFile()) {
31+
type = FileType.File;
32+
} else if (stats.isDirectory()) {
33+
type = FileType.Directory;
34+
} else if (stats.isSymbolicLink()) {
35+
type = FileType.SymbolicLink;
36+
}
37+
38+
return {
39+
type,
40+
ctime: stats.ctime.getTime(),
41+
mtime: stats.mtime.getTime(),
42+
size: stats.size
43+
};
44+
};
45+
1846
return {
1947
getContent(location: string, encoding?: string) {
2048
ensureFileUri(location);
@@ -42,24 +70,16 @@ export function getNodeFSRequestService(): RequestService {
4270
}
4371
}
4472

45-
let type = FileType.Unknown;
46-
if (stats.isFile()) {
47-
type = FileType.File;
48-
} else if (stats.isDirectory()) {
49-
type = FileType.Directory;
50-
} else if (stats.isSymbolicLink()) {
51-
type = FileType.SymbolicLink;
52-
}
53-
54-
c({
55-
type,
56-
ctime: stats.ctime.getTime(),
57-
mtime: stats.mtime.getTime(),
58-
size: stats.size
59-
});
73+
c(fsStatToFileStat(stats));
6074
});
6175
});
6276
},
77+
statSync(location: string) {
78+
ensureFileUri(location);
79+
const uri = Uri.parse(location);
80+
const stats = fs.statSync(uri.fsPath);
81+
return fsStatToFileStat(stats);
82+
},
6383
readDirectory(location: string) {
6484
ensureFileUri(location);
6585
return new Promise((c, e) => {

0 commit comments

Comments
 (0)