-
Notifications
You must be signed in to change notification settings - Fork 60
Expand file tree
/
Copy pathlanguage-service-context.ts
More file actions
140 lines (120 loc) · 3.96 KB
/
language-service-context.ts
File metadata and controls
140 lines (120 loc) · 3.96 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* @license
* Copyright 2021 Google LLC
* SPDX-License-Identifier: BSD-3-Clause
*/
import ts from '../internal/typescript.js';
const compilerOptions = {
target: ts.ScriptTarget.ES2017,
module: ts.ModuleKind.ESNext,
experimentalDecorators: true,
skipDefaultLibCheck: true,
skipLibCheck: true,
allowJs: true,
checkJs: true,
moduleResolution: ts.ModuleResolutionKind.NodeJs,
jsx: ts.JsxEmit.React,
lib: ['dom', 'esnext'],
};
/**
* Compiles a project, returning a Map of compiled file contents. The map only
* contains context for files that are compiled. Other files are skipped.
*
* TODO (justinfagnani): We could share a DocumentRegistry across
* multiple <playground-project> instances to save memory and type analysis of
* common lib files like lit-element, lib.d.ts and dom.d.ts.
*/
export class LanguageServiceContext {
readonly compilerOptions = compilerOptions;
readonly serviceHost = new WorkerLanguageServiceHost(
self.origin,
compilerOptions
);
readonly service = ts.createLanguageService(
this.serviceHost,
ts.createDocumentRegistry()
);
}
interface VersionedFile {
version: number;
content: string;
}
class WorkerLanguageServiceHost implements ts.LanguageServiceHost {
readonly compilerOptions: ts.CompilerOptions;
readonly packageRoot: string;
readonly files: Map<string, VersionedFile> = new Map<string, VersionedFile>();
constructor(packageRoot: string, compilerOptions: ts.CompilerOptions) {
this.packageRoot = packageRoot;
this.compilerOptions = compilerOptions;
}
/*
* When a new new "process" command is received, we iterate through all of the files,
* and update files accordingly depending on if they have new content or not.
*
* With how the TS API works, we can use simple versioning to tell the
* Language service that a file has been updated
*
* If the file submitted is a new file, we add it to our collection
*/
updateFileContentIfNeeded(fileName: string, content: string) {
const file = this.files.get(fileName);
if (file) {
if (file.content === content) {
// The file hasn't changed, exit early.
return;
}
file.content = content;
file.version += 1;
return;
}
this.files.set(fileName, {content, version: 0});
}
/**
* Sync up the freshly acquired project files.
* In the syncing process files yet to be added are added, and versioned.
* Files that existed already but are modified are updated, and their version number
* gets bumped fo that the languageservice knows to update these files.
* */
sync(files: Map<string, string>) {
files.forEach((file, fileName) =>
this.updateFileContentIfNeeded(fileName, file)
);
this._removeDeletedFiles(files);
}
private _removeDeletedFiles(files: Map<string, string>) {
this.getScriptFileNames().forEach((fileName) => {
// Do not delete the dependency files, as then they will get re-applied every compilation.
// This is because the compilation step is aware of these files, but the completion step isn't.
if (!fileName.includes('node_modules') && !files.has(fileName)) {
this.files.delete(fileName);
}
});
}
getCompilationSettings(): ts.CompilerOptions {
return this.compilerOptions;
}
getScriptFileNames(): string[] {
return [...this.files.keys()];
}
getScriptVersion(fileName: string) {
return this.files.get(fileName)?.version.toString() ?? '-1';
}
fileExists(fileName: string): boolean {
return this.files.has(fileName);
}
readFile(fileName: string): string | undefined {
return this.files.get(fileName)?.content;
}
getScriptSnapshot(fileName: string): ts.IScriptSnapshot | undefined {
if (!this.fileExists(fileName)) {
return undefined;
}
return ts.ScriptSnapshot.fromString(this.readFile(fileName)!);
}
getCurrentDirectory(): string {
return this.packageRoot;
}
getDefaultLibFileName(): string {
return '__lib.d.ts';
}
}