-
Notifications
You must be signed in to change notification settings - Fork 24.8k
/
adapters.ts
184 lines (166 loc) Β· 6.72 KB
/
adapters.ts
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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
/**
* @license
* Copyright Google LLC All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/** @fileoverview provides adapters for communicating with the ng compiler */
import {ConfigurationHost} from '@angular/compiler-cli';
import {NgCompilerAdapter} from '@angular/compiler-cli/src/ngtsc/core/api';
import {AbsoluteFsPath, FileStats, PathSegment, PathString} from '@angular/compiler-cli/src/ngtsc/file_system';
import {isShim} from '@angular/compiler-cli/src/ngtsc/shims';
import {getRootDirs} from '@angular/compiler-cli/src/ngtsc/util/src/typescript';
import * as p from 'path';
import * as ts from 'typescript/lib/tsserverlibrary';
import {isTypeScriptFile} from './utils';
const PRE_COMPILED_STYLE_EXTENSIONS = ['.scss', '.sass', '.less', '.styl'];
export class LanguageServiceAdapter implements NgCompilerAdapter {
readonly entryPoint = null;
readonly constructionDiagnostics: ts.Diagnostic[] = [];
readonly ignoreForEmit: Set<ts.SourceFile> = new Set();
readonly factoryTracker = null; // no .ngfactory shims
readonly unifiedModulesHost = null; // only used in Bazel
readonly rootDirs: AbsoluteFsPath[];
/**
* Map of resource filenames to the version of the file last read via `readResource`.
*
* Used to implement `getModifiedResourceFiles`.
*/
private readonly lastReadResourceVersion = new Map<string, string>();
constructor(private readonly project: ts.server.Project) {
this.rootDirs = getRootDirs(this, project.getCompilationSettings());
}
resourceNameToFileName(
url: string, fromFile: string,
fallbackResolve?: (url: string, fromFile: string) => string | null): string|null {
// If we are trying to resolve a `.css` file, see if we can find a pre-compiled file with the
// same name instead. That way, we can provide go-to-definition for the pre-compiled files which
// would generally be the desired behavior.
if (url.endsWith('.css')) {
const styleUrl = p.resolve(fromFile, '..', url);
for (const ext of PRE_COMPILED_STYLE_EXTENSIONS) {
const precompiledFileUrl = styleUrl.replace(/\.css$/, ext);
if (this.fileExists(precompiledFileUrl)) {
return precompiledFileUrl;
}
}
}
return fallbackResolve?.(url, fromFile) ?? null;
}
isShim(sf: ts.SourceFile): boolean {
return isShim(sf);
}
isResource(sf: ts.SourceFile): boolean {
const scriptInfo = this.project.getScriptInfo(sf.fileName);
return scriptInfo?.scriptKind === ts.ScriptKind.Unknown;
}
fileExists(fileName: string): boolean {
return this.project.fileExists(fileName);
}
readFile(fileName: string): string|undefined {
return this.project.readFile(fileName);
}
getCurrentDirectory(): string {
return this.project.getCurrentDirectory();
}
getCanonicalFileName(fileName: string): string {
return this.project.projectService.toCanonicalFileName(fileName);
}
/**
* Return the real path of a symlink. This method is required in order to
* resolve symlinks in node_modules.
*/
realpath(path: string): string {
return this.project.realpath?.(path) ?? path;
}
/**
* readResource() is an Angular-specific method for reading files that are not
* managed by the TS compiler host, namely templates and stylesheets.
* It is a method on ExtendedTsCompilerHost, see
* packages/compiler-cli/src/ngtsc/core/api/src/interfaces.ts
*/
readResource(fileName: string): string {
if (isTypeScriptFile(fileName)) {
throw new Error(`readResource() should not be called on TS file: ${fileName}`);
}
// Calling getScriptSnapshot() will actually create a ScriptInfo if it does
// not exist! The same applies for getScriptVersion().
// getScriptInfo() will not create one if it does not exist.
// In this case, we *want* a script info to be created so that we could
// keep track of its version.
const version = this.project.getScriptVersion(fileName);
this.lastReadResourceVersion.set(fileName, version);
const scriptInfo = this.project.getScriptInfo(fileName);
if (!scriptInfo) {
// // This should not happen because it would have failed already at `getScriptVersion`.
throw new Error(`Failed to get script info when trying to read ${fileName}`);
}
// Add external resources as root files to the project since we project language service
// features for them (this is currently only the case for HTML files, but we could investigate
// css file features in the future). This prevents the project from being closed when navigating
// away from a resource file.
if (!this.project.isRoot(scriptInfo)) {
this.project.addRoot(scriptInfo);
}
const snapshot = scriptInfo.getSnapshot();
return snapshot.getText(0, snapshot.getLength());
}
getModifiedResourceFiles(): Set<string>|undefined {
const modifiedFiles = new Set<string>();
for (const [fileName, oldVersion] of this.lastReadResourceVersion) {
if (this.project.getScriptVersion(fileName) !== oldVersion) {
modifiedFiles.add(fileName);
}
}
return modifiedFiles.size > 0 ? modifiedFiles : undefined;
}
}
/**
* Used to read configuration files.
*
* A language service parse configuration host is independent of the adapter
* because signatures of calls like `FileSystem#readFile` are a bit stricter
* than those on the adapter.
*/
export class LSParseConfigHost implements ConfigurationHost {
constructor(private readonly serverHost: ts.server.ServerHost) {}
exists(path: AbsoluteFsPath): boolean {
return this.serverHost.fileExists(path) || this.serverHost.directoryExists(path);
}
readFile(path: AbsoluteFsPath): string {
const content = this.serverHost.readFile(path);
if (content === undefined) {
throw new Error(`LanguageServiceFS#readFile called on unavailable file ${path}`);
}
return content;
}
lstat(path: AbsoluteFsPath): FileStats {
return {
isFile: () => {
return this.serverHost.fileExists(path);
},
isDirectory: () => {
return this.serverHost.directoryExists(path);
},
isSymbolicLink: () => {
throw new Error(`LanguageServiceFS#lstat#isSymbolicLink not implemented`);
},
};
}
pwd(): AbsoluteFsPath {
return this.serverHost.getCurrentDirectory() as AbsoluteFsPath;
}
extname(path: AbsoluteFsPath|PathSegment): string {
return p.extname(path);
}
resolve(...paths: string[]): AbsoluteFsPath {
return p.resolve(...paths) as AbsoluteFsPath;
}
dirname<T extends PathString>(file: T): T {
return p.dirname(file) as T;
}
join<T extends PathString>(basePath: T, ...paths: string[]): T {
return p.join(basePath, ...paths) as T;
}
}