2020-09-30 13:59:38 -04:00
|
|
|
/**
|
|
|
|
* @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
|
|
|
|
*/
|
|
|
|
|
2020-10-30 18:16:39 -04:00
|
|
|
/** @fileoverview provides adapters for communicating with the ng compiler */
|
|
|
|
|
|
|
|
import {ConfigurationHost} from '@angular/compiler-cli';
|
2020-09-30 13:59:38 -04:00
|
|
|
import {NgCompilerAdapter} from '@angular/compiler-cli/src/ngtsc/core/api';
|
2020-12-22 18:07:45 -05:00
|
|
|
import {AbsoluteFsPath, FileStats, PathSegment, PathString} from '@angular/compiler-cli/src/ngtsc/file_system';
|
2020-09-30 13:59:38 -04:00
|
|
|
import {isShim} from '@angular/compiler-cli/src/ngtsc/shims';
|
2020-12-22 18:07:45 -05:00
|
|
|
import {getRootDirs} from '@angular/compiler-cli/src/ngtsc/util/src/typescript';
|
2020-11-09 19:20:02 -05:00
|
|
|
import * as p from 'path';
|
2020-09-30 13:59:38 -04:00
|
|
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
2020-10-09 14:22:03 -04:00
|
|
|
|
|
|
|
import {isTypeScriptFile} from './utils';
|
|
|
|
|
2021-04-09 13:24:08 -04:00
|
|
|
const PRE_COMPILED_STYLE_EXTENSIONS = ['.scss', '.sass', '.less', '.styl'];
|
|
|
|
|
2020-10-21 14:01:10 -04:00
|
|
|
export class LanguageServiceAdapter implements NgCompilerAdapter {
|
2020-09-30 13:59:38 -04:00
|
|
|
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[];
|
2021-01-21 18:54:40 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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>();
|
2020-09-30 13:59:38 -04:00
|
|
|
|
|
|
|
constructor(private readonly project: ts.server.Project) {
|
2020-12-22 18:07:45 -05:00
|
|
|
this.rootDirs = getRootDirs(this, project.getCompilationSettings());
|
2020-09-30 13:59:38 -04:00
|
|
|
}
|
|
|
|
|
2021-04-09 13:24:08 -04:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2020-09-30 13:59:38 -04:00
|
|
|
isShim(sf: ts.SourceFile): boolean {
|
|
|
|
return isShim(sf);
|
|
|
|
}
|
|
|
|
|
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-01-27 12:17:03 -05:00
|
|
|
/**
|
|
|
|
* 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;
|
|
|
|
}
|
|
|
|
|
2020-09-30 13:59:38 -04:00
|
|
|
/**
|
|
|
|
* 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 snapshot = this.project.getScriptSnapshot(fileName);
|
|
|
|
if (!snapshot) {
|
|
|
|
// This would fail if the file does not exist, or readFile() fails for
|
|
|
|
// whatever reasons.
|
|
|
|
throw new Error(`Failed to get script snapshot while trying to read ${fileName}`);
|
|
|
|
}
|
|
|
|
const version = this.project.getScriptVersion(fileName);
|
2021-01-21 18:54:40 -05:00
|
|
|
this.lastReadResourceVersion.set(fileName, version);
|
2020-09-30 13:59:38 -04:00
|
|
|
return snapshot.getText(0, snapshot.getLength());
|
|
|
|
}
|
|
|
|
|
2021-01-21 18:54:40 -05:00
|
|
|
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;
|
2020-09-30 13:59:38 -04:00
|
|
|
}
|
|
|
|
}
|
2020-11-09 19:20:02 -05:00
|
|
|
|
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2020-10-30 18:16:39 -04:00
|
|
|
export class LSParseConfigHost implements ConfigurationHost {
|
|
|
|
constructor(private readonly serverHost: ts.server.ServerHost) {}
|
2020-11-09 19:20:02 -05:00
|
|
|
exists(path: AbsoluteFsPath): boolean {
|
2020-10-30 18:16:39 -04:00
|
|
|
return this.serverHost.fileExists(path) || this.serverHost.directoryExists(path);
|
2020-11-09 19:20:02 -05:00
|
|
|
}
|
|
|
|
readFile(path: AbsoluteFsPath): string {
|
2020-10-30 18:16:39 -04:00
|
|
|
const content = this.serverHost.readFile(path);
|
2020-11-09 19:20:02 -05:00
|
|
|
if (content === undefined) {
|
|
|
|
throw new Error(`LanguageServiceFS#readFile called on unavailable file ${path}`);
|
|
|
|
}
|
|
|
|
return content;
|
|
|
|
}
|
|
|
|
lstat(path: AbsoluteFsPath): FileStats {
|
|
|
|
return {
|
|
|
|
isFile: () => {
|
2020-10-30 18:16:39 -04:00
|
|
|
return this.serverHost.fileExists(path);
|
2020-11-09 19:20:02 -05:00
|
|
|
},
|
|
|
|
isDirectory: () => {
|
2020-10-30 18:16:39 -04:00
|
|
|
return this.serverHost.directoryExists(path);
|
2020-11-09 19:20:02 -05:00
|
|
|
},
|
|
|
|
isSymbolicLink: () => {
|
|
|
|
throw new Error(`LanguageServiceFS#lstat#isSymbolicLink not implemented`);
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
pwd(): AbsoluteFsPath {
|
2020-10-30 18:16:39 -04:00
|
|
|
return this.serverHost.getCurrentDirectory() as AbsoluteFsPath;
|
2020-11-09 19:20:02 -05:00
|
|
|
}
|
|
|
|
extname(path: AbsoluteFsPath|PathSegment): string {
|
|
|
|
return p.extname(path);
|
|
|
|
}
|
|
|
|
resolve(...paths: string[]): AbsoluteFsPath {
|
2020-12-22 17:15:49 -05:00
|
|
|
return p.resolve(...paths) as AbsoluteFsPath;
|
2020-11-09 19:20:02 -05:00
|
|
|
}
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|