2018-09-25 18:35:03 -04:00
|
|
|
/**
|
|
|
|
* @license
|
|
|
|
* Copyright Google Inc. 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-04-07 15:43:43 -04:00
|
|
|
import {CustomTransformers, defaultGatherDiagnostics, Program} from '@angular/compiler-cli';
|
2019-04-27 18:26:13 -04:00
|
|
|
import * as api from '@angular/compiler-cli/src/transformers/api';
|
2018-09-25 18:35:03 -04:00
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
2019-10-17 11:36:04 -04:00
|
|
|
import {createCompilerHost, createProgram} from '../../index';
|
2018-11-16 11:54:43 -05:00
|
|
|
import {main, mainDiagnosticsForTest, readNgcCommandLineAndConfiguration} from '../../src/main';
|
2020-04-07 15:43:43 -04:00
|
|
|
import {absoluteFrom, AbsoluteFsPath, FileSystem, getFileSystem, NgtscCompilerHost} from '../../src/ngtsc/file_system';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {Folder, MockFileSystem} from '../../src/ngtsc/file_system/testing';
|
|
|
|
import {IndexedComponent} from '../../src/ngtsc/indexer';
|
|
|
|
import {NgtscProgram} from '../../src/ngtsc/program';
|
2018-11-16 11:54:43 -05:00
|
|
|
import {LazyRoute} from '../../src/ngtsc/routing';
|
2019-06-06 15:22:32 -04:00
|
|
|
import {setWrapHostForTest} from '../../src/transformers/compiler_host';
|
2018-09-25 18:35:03 -04:00
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Manages a temporary testing directory structure and environment for testing ngtsc by feeding it
|
|
|
|
* TypeScript code.
|
|
|
|
*/
|
|
|
|
export class NgtscTestEnvironment {
|
2019-03-18 14:21:29 -04:00
|
|
|
private multiCompileHostExt: MultiCompileHostExt|null = null;
|
|
|
|
private oldProgram: Program|null = null;
|
2019-06-27 19:25:00 -04:00
|
|
|
private changedResources: Set<string>|null = null;
|
2019-03-18 14:21:29 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
private constructor(
|
|
|
|
private fs: FileSystem, readonly outDir: AbsoluteFsPath, readonly basePath: AbsoluteFsPath) {}
|
2018-09-25 18:35:03 -04:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Set up a new testing environment.
|
|
|
|
*/
|
2019-11-13 17:45:46 -05:00
|
|
|
static setup(files?: Folder, workingDir: AbsoluteFsPath = absoluteFrom('/')):
|
|
|
|
NgtscTestEnvironment {
|
2019-06-06 15:22:32 -04:00
|
|
|
const fs = getFileSystem();
|
|
|
|
if (files !== undefined && fs instanceof MockFileSystem) {
|
|
|
|
fs.init(files);
|
|
|
|
}
|
2018-09-25 18:35:03 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
const host = new AugmentedCompilerHost(fs);
|
|
|
|
setWrapHostForTest(makeWrapHost(host));
|
2018-09-25 18:35:03 -04:00
|
|
|
|
2019-11-13 17:45:46 -05:00
|
|
|
const env = new NgtscTestEnvironment(fs, fs.resolve('/built'), workingDir);
|
|
|
|
fs.chdir(workingDir);
|
2018-09-25 18:35:03 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
env.write(absoluteFrom('/tsconfig-base.json'), `{
|
2018-09-25 18:35:03 -04:00
|
|
|
"compilerOptions": {
|
2019-02-22 21:06:25 -05:00
|
|
|
"emitDecoratorMetadata": true,
|
2018-09-25 18:35:03 -04:00
|
|
|
"experimentalDecorators": true,
|
|
|
|
"skipLibCheck": true,
|
|
|
|
"noImplicitAny": true,
|
2019-11-12 16:38:23 -05:00
|
|
|
"noEmitOnError": true,
|
2018-09-25 18:35:03 -04:00
|
|
|
"strictNullChecks": true,
|
|
|
|
"outDir": "built",
|
perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single
__ngtypecheck__.ts file which was previously added to the user's program as
a container for all template type-checking code into multiple .ngtypecheck
shim files, one for each original file in the user's program.
In larger applications, the generation, parsing, and checking of this single
type-checking file was a huge performance bottleneck, with the file often
exceeding 1 MB in text content. Particularly in incremental builds,
regenerating this single file for the entire application proved especially
expensive.
This commit introduces a new strategy for template type-checking code which
makes use of a new interface, the `TypeCheckingProgramStrategy`. This
interface abstracts the process of creating a new `ts.Program` to type-check
a particular compilation, and allows the mechanism there to be kept separate
from the more complex logic around dealing with multiple .ngtypecheck files.
A new `TemplateTypeChecker` hosts that logic and interacts with the
`TypeCheckingProgramStrategy` to actually generate and return diagnostics.
The `TypeCheckContext` class, previously the workhorse of template type-
checking, is now solely focused on collecting and generating type-checking
file contents.
A side effect of implementing the new `TypeCheckingProgramStrategy` in this
way is that the API is designed to be suitable for use by the Angular
Language Service as well. The LS also needs to type-check components, but
has its own method for constructing a `ts.Program` with type-checking code.
Note that this commit does not make the actual checking of templates at all
_incremental_ just yet. That will happen in a future commit.
PR Close #36211
2020-03-04 18:50:12 -05:00
|
|
|
"rootDir": ".",
|
2018-09-25 18:35:03 -04:00
|
|
|
"baseUrl": ".",
|
2020-05-07 15:57:41 -04:00
|
|
|
"allowJs": true,
|
2018-09-25 18:35:03 -04:00
|
|
|
"declaration": true,
|
|
|
|
"target": "es5",
|
2019-01-25 13:49:08 -05:00
|
|
|
"newLine": "lf",
|
2018-09-25 18:35:03 -04:00
|
|
|
"module": "es2015",
|
|
|
|
"moduleResolution": "node",
|
|
|
|
"lib": ["es6", "dom"],
|
|
|
|
"typeRoots": ["node_modules/@types"]
|
|
|
|
},
|
|
|
|
"angularCompilerOptions": {
|
2019-04-02 14:52:19 -04:00
|
|
|
"enableIvy": true,
|
|
|
|
"ivyTemplateTypeCheck": false
|
2019-03-18 14:21:29 -04:00
|
|
|
},
|
|
|
|
"exclude": [
|
|
|
|
"built"
|
|
|
|
]
|
2018-09-25 18:35:03 -04:00
|
|
|
}`);
|
|
|
|
|
|
|
|
return env;
|
|
|
|
}
|
|
|
|
|
|
|
|
assertExists(fileName: string) {
|
2019-06-06 15:22:32 -04:00
|
|
|
if (!this.fs.exists(this.fs.resolve(this.outDir, fileName))) {
|
2018-09-25 18:35:03 -04:00
|
|
|
throw new Error(`Expected ${fileName} to be emitted (outDir: ${this.outDir})`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
assertDoesNotExist(fileName: string) {
|
2019-06-06 15:22:32 -04:00
|
|
|
if (this.fs.exists(this.fs.resolve(this.outDir, fileName))) {
|
2018-09-25 18:35:03 -04:00
|
|
|
throw new Error(`Did not expect ${fileName} to be emitted (outDir: ${this.outDir})`);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
getContents(fileName: string): string {
|
|
|
|
this.assertExists(fileName);
|
2019-06-06 15:22:32 -04:00
|
|
|
const modulePath = this.fs.resolve(this.outDir, fileName);
|
|
|
|
return this.fs.readFile(modulePath);
|
2018-09-25 18:35:03 -04:00
|
|
|
}
|
|
|
|
|
2019-03-18 14:21:29 -04:00
|
|
|
enableMultipleCompilations(): void {
|
2019-06-10 11:22:56 -04:00
|
|
|
this.changedResources = new Set();
|
2019-06-06 15:22:32 -04:00
|
|
|
this.multiCompileHostExt = new MultiCompileHostExt(this.fs);
|
2019-03-18 14:21:29 -04:00
|
|
|
setWrapHostForTest(makeWrapHost(this.multiCompileHostExt));
|
|
|
|
}
|
|
|
|
|
2019-09-07 17:48:32 -04:00
|
|
|
/**
|
|
|
|
* Installs a compiler host that allows for asynchronous reading of resources by implementing the
|
|
|
|
* `CompilerHost.readResource` method. Note that only asynchronous compilations are affected, as
|
|
|
|
* synchronous compilations do not use the asynchronous resource loader.
|
|
|
|
*/
|
|
|
|
enablePreloading(): void {
|
|
|
|
setWrapHostForTest(makeWrapHost(new ResourceLoadingCompileHost(this.fs)));
|
|
|
|
}
|
|
|
|
|
2019-03-18 14:21:29 -04:00
|
|
|
flushWrittenFileTracking(): void {
|
|
|
|
if (this.multiCompileHostExt === null) {
|
|
|
|
throw new Error(`Not tracking written files - call enableMultipleCompilations()`);
|
|
|
|
}
|
2020-04-07 15:43:43 -04:00
|
|
|
this.changedResources!.clear();
|
2019-03-18 14:21:29 -04:00
|
|
|
this.multiCompileHostExt.flushWrittenFileTracking();
|
|
|
|
}
|
|
|
|
|
perf(compiler-cli): split Ivy template type-checking into multiple files (#36211)
As a performance optimization, this commit splits the single
__ngtypecheck__.ts file which was previously added to the user's program as
a container for all template type-checking code into multiple .ngtypecheck
shim files, one for each original file in the user's program.
In larger applications, the generation, parsing, and checking of this single
type-checking file was a huge performance bottleneck, with the file often
exceeding 1 MB in text content. Particularly in incremental builds,
regenerating this single file for the entire application proved especially
expensive.
This commit introduces a new strategy for template type-checking code which
makes use of a new interface, the `TypeCheckingProgramStrategy`. This
interface abstracts the process of creating a new `ts.Program` to type-check
a particular compilation, and allows the mechanism there to be kept separate
from the more complex logic around dealing with multiple .ngtypecheck files.
A new `TemplateTypeChecker` hosts that logic and interacts with the
`TypeCheckingProgramStrategy` to actually generate and return diagnostics.
The `TypeCheckContext` class, previously the workhorse of template type-
checking, is now solely focused on collecting and generating type-checking
file contents.
A side effect of implementing the new `TypeCheckingProgramStrategy` in this
way is that the API is designed to be suitable for use by the Angular
Language Service as well. The LS also needs to type-check components, but
has its own method for constructing a `ts.Program` with type-checking code.
Note that this commit does not make the actual checking of templates at all
_incremental_ just yet. That will happen in a future commit.
PR Close #36211
2020-03-04 18:50:12 -05:00
|
|
|
getTsProgram(): ts.Program {
|
|
|
|
if (this.oldProgram === null) {
|
|
|
|
throw new Error('No ts.Program has been created yet.');
|
|
|
|
}
|
|
|
|
return this.oldProgram.getTsProgram();
|
|
|
|
}
|
|
|
|
|
2019-06-27 19:25:00 -04:00
|
|
|
/**
|
|
|
|
* Older versions of the CLI do not provide the `CompilerHost.getModifiedResourceFiles()` method.
|
|
|
|
* This results in the `changedResources` set being `null`.
|
|
|
|
*/
|
2020-04-07 15:43:43 -04:00
|
|
|
simulateLegacyCLICompilerHost() {
|
|
|
|
this.changedResources = null;
|
|
|
|
}
|
2019-06-27 19:25:00 -04:00
|
|
|
|
2019-03-18 14:21:29 -04:00
|
|
|
getFilesWrittenSinceLastFlush(): Set<string> {
|
|
|
|
if (this.multiCompileHostExt === null) {
|
|
|
|
throw new Error(`Not tracking written files - call enableMultipleCompilations()`);
|
|
|
|
}
|
|
|
|
const writtenFiles = new Set<string>();
|
|
|
|
this.multiCompileHostExt.getFilesWrittenSinceLastFlush().forEach(rawFile => {
|
2019-06-06 15:22:32 -04:00
|
|
|
if (rawFile.startsWith(this.outDir)) {
|
|
|
|
writtenFiles.add(rawFile.substr(this.outDir.length));
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
|
|
|
});
|
|
|
|
return writtenFiles;
|
|
|
|
}
|
|
|
|
|
|
|
|
write(fileName: string, content: string) {
|
2019-06-06 15:22:32 -04:00
|
|
|
const absFilePath = this.fs.resolve(this.basePath, fileName);
|
2019-03-18 14:21:29 -04:00
|
|
|
if (this.multiCompileHostExt !== null) {
|
|
|
|
this.multiCompileHostExt.invalidate(absFilePath);
|
2020-04-07 15:43:43 -04:00
|
|
|
this.changedResources!.add(absFilePath);
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
2019-06-06 15:22:32 -04:00
|
|
|
this.fs.ensureDir(this.fs.dirname(absFilePath));
|
|
|
|
this.fs.writeFile(absFilePath, content);
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
invalidateCachedFile(fileName: string): void {
|
2019-06-06 15:22:32 -04:00
|
|
|
const absFilePath = this.fs.resolve(this.basePath, fileName);
|
2019-03-18 14:21:29 -04:00
|
|
|
if (this.multiCompileHostExt === null) {
|
|
|
|
throw new Error(`Not caching files - call enableMultipleCompilations()`);
|
|
|
|
}
|
2019-06-06 15:22:32 -04:00
|
|
|
this.multiCompileHostExt.invalidate(absFilePath);
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
2018-09-25 18:35:03 -04:00
|
|
|
|
fix(compiler): switch to 'referencedFiles' for shim generation (#36211)
Shim generation was built on a lie.
Shims are files added to the program which aren't original files authored by
the user, but files authored effectively by the compiler. These fall into
two categories: files which will be generated (like the .ngfactory shims we
generate for View Engine compatibility) as well as files used internally in
compilation (like the __ng_typecheck__.ts file).
Previously, shim generation was driven by the `rootFiles` passed to the
compiler as input. These are effectively the `files` listed in the
`tsconfig.json`. Each shim generator (e.g. the `FactoryGenerator`) would
examine the `rootFiles` and produce a list of shim file names which it would
be responsible for generating. These names would then be added to the
`rootFiles` when the program was created.
The fatal flaw here is that `rootFiles` does not always account for all of
the files in the program. In fact, it's quite rare that it does. Users don't
typically specify every file directly in `files`. Instead, they rely on
TypeScript, during program creation, starting with a few root files and
transitively discovering all of the files in the program.
This happens, however, during `ts.createProgram`, which is too late to add
new files to the `rootFiles` list.
As a result, shim generation was only including shims for files actually
listed in the `tsconfig.json` file, and not for the transitive set of files
in the user's program as it should.
This commit completely rewrites shim generation to use a different technique
for adding files to the program, inspired by View Engine's shim generator.
In this new technique, as the program is being created and `ts.SourceFile`s
are being requested from the `NgCompilerHost`, shims for those files are
generated and a reference to them is patched onto the original file's
`ts.SourceFile.referencedFiles`. This causes TS to think that the original
file references the shim, and causes the shim to be included in the program.
The original `referencedFiles` array is saved and restored after program
creation, hiding this little hack from the rest of the system.
The new shim generation engine differentiates between two kinds of shims:
top-level shims (such as the flat module entrypoint file and
__ng_typecheck__.ts) and per-file shims such as ngfactory or ngsummary
files. The former are included via `rootFiles` as before, the latter are
included via the `referencedFiles` of their corresponding original files.
As a result of this change, shims are now correctly generated for all files
in the program, not just the ones named in `tsconfig.json`.
A few mitigating factors prevented this bug from being realized until now:
* in g3, `files` does include the transitive closure of files in the program
* in CLI apps, shims are not really used
This change also makes use of a novel technique for associating information
with source files: the use of an `NgExtension` `Symbol` to patch the
information directly onto the AST object. This is used in several
circumstances:
* For shims, metadata about a `ts.SourceFile`'s status as a shim and its
origins are held in the extension data.
* For original files, the original `referencedFiles` are stashed in the
extension data for later restoration.
The main benefit of this technique is a lot less bookkeeping around `Map`s
of `ts.SourceFile`s to various kinds of data, which need to be tracked/
invalidated as part of incremental builds.
This technique is based on designs used internally in the TypeScript
compiler and is serving as a prototype of this design in ngtsc. If it works
well, it could have benefits across the rest of the compiler.
PR Close #36211
2020-02-26 19:12:39 -05:00
|
|
|
tsconfig(
|
|
|
|
extraOpts: {[key: string]: string|boolean|null} = {}, extraRootDirs?: string[],
|
|
|
|
files?: string[]): void {
|
2018-11-30 13:37:06 -05:00
|
|
|
const tsconfig: {[key: string]: any} = {
|
|
|
|
extends: './tsconfig-base.json',
|
2019-02-08 06:37:21 -05:00
|
|
|
angularCompilerOptions: {...extraOpts, enableIvy: true},
|
2018-11-30 13:37:06 -05:00
|
|
|
};
|
fix(compiler): switch to 'referencedFiles' for shim generation (#36211)
Shim generation was built on a lie.
Shims are files added to the program which aren't original files authored by
the user, but files authored effectively by the compiler. These fall into
two categories: files which will be generated (like the .ngfactory shims we
generate for View Engine compatibility) as well as files used internally in
compilation (like the __ng_typecheck__.ts file).
Previously, shim generation was driven by the `rootFiles` passed to the
compiler as input. These are effectively the `files` listed in the
`tsconfig.json`. Each shim generator (e.g. the `FactoryGenerator`) would
examine the `rootFiles` and produce a list of shim file names which it would
be responsible for generating. These names would then be added to the
`rootFiles` when the program was created.
The fatal flaw here is that `rootFiles` does not always account for all of
the files in the program. In fact, it's quite rare that it does. Users don't
typically specify every file directly in `files`. Instead, they rely on
TypeScript, during program creation, starting with a few root files and
transitively discovering all of the files in the program.
This happens, however, during `ts.createProgram`, which is too late to add
new files to the `rootFiles` list.
As a result, shim generation was only including shims for files actually
listed in the `tsconfig.json` file, and not for the transitive set of files
in the user's program as it should.
This commit completely rewrites shim generation to use a different technique
for adding files to the program, inspired by View Engine's shim generator.
In this new technique, as the program is being created and `ts.SourceFile`s
are being requested from the `NgCompilerHost`, shims for those files are
generated and a reference to them is patched onto the original file's
`ts.SourceFile.referencedFiles`. This causes TS to think that the original
file references the shim, and causes the shim to be included in the program.
The original `referencedFiles` array is saved and restored after program
creation, hiding this little hack from the rest of the system.
The new shim generation engine differentiates between two kinds of shims:
top-level shims (such as the flat module entrypoint file and
__ng_typecheck__.ts) and per-file shims such as ngfactory or ngsummary
files. The former are included via `rootFiles` as before, the latter are
included via the `referencedFiles` of their corresponding original files.
As a result of this change, shims are now correctly generated for all files
in the program, not just the ones named in `tsconfig.json`.
A few mitigating factors prevented this bug from being realized until now:
* in g3, `files` does include the transitive closure of files in the program
* in CLI apps, shims are not really used
This change also makes use of a novel technique for associating information
with source files: the use of an `NgExtension` `Symbol` to patch the
information directly onto the AST object. This is used in several
circumstances:
* For shims, metadata about a `ts.SourceFile`'s status as a shim and its
origins are held in the extension data.
* For original files, the original `referencedFiles` are stashed in the
extension data for later restoration.
The main benefit of this technique is a lot less bookkeeping around `Map`s
of `ts.SourceFile`s to various kinds of data, which need to be tracked/
invalidated as part of incremental builds.
This technique is based on designs used internally in the TypeScript
compiler and is serving as a prototype of this design in ngtsc. If it works
well, it could have benefits across the rest of the compiler.
PR Close #36211
2020-02-26 19:12:39 -05:00
|
|
|
if (files !== undefined) {
|
|
|
|
tsconfig['files'] = files;
|
|
|
|
}
|
2018-11-30 13:37:06 -05:00
|
|
|
if (extraRootDirs !== undefined) {
|
|
|
|
tsconfig.compilerOptions = {
|
|
|
|
rootDirs: ['.', ...extraRootDirs],
|
|
|
|
};
|
|
|
|
}
|
|
|
|
this.write('tsconfig.json', JSON.stringify(tsconfig, null, 2));
|
2019-02-19 20:36:26 -05:00
|
|
|
|
|
|
|
if (extraOpts['_useHostForImportGeneration'] === true) {
|
2019-06-06 15:22:32 -04:00
|
|
|
setWrapHostForTest(makeWrapHost(new FileNameToModuleNameHost(this.fs)));
|
2019-02-19 20:36:26 -05:00
|
|
|
}
|
2018-09-25 18:35:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run the compiler to completion, and assert that no errors occurred.
|
|
|
|
*/
|
2019-01-03 05:23:00 -05:00
|
|
|
driveMain(customTransformers?: CustomTransformers): void {
|
2018-09-25 18:35:03 -04:00
|
|
|
const errorSpy = jasmine.createSpy('consoleError').and.callFake(console.error);
|
2020-04-07 15:43:43 -04:00
|
|
|
let reuseProgram: {program: Program|undefined}|undefined = undefined;
|
2019-03-18 14:21:29 -04:00
|
|
|
if (this.multiCompileHostExt !== null) {
|
|
|
|
reuseProgram = {
|
|
|
|
program: this.oldProgram || undefined,
|
|
|
|
};
|
|
|
|
}
|
2019-06-10 11:22:56 -04:00
|
|
|
const exitCode = main(
|
|
|
|
['-p', this.basePath], errorSpy, undefined, customTransformers, reuseProgram,
|
|
|
|
this.changedResources);
|
2018-09-25 18:35:03 -04:00
|
|
|
expect(errorSpy).not.toHaveBeenCalled();
|
|
|
|
expect(exitCode).toBe(0);
|
2019-03-18 14:21:29 -04:00
|
|
|
if (this.multiCompileHostExt !== null) {
|
2020-04-07 15:43:43 -04:00
|
|
|
this.oldProgram = reuseProgram!.program!;
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
2018-09-25 18:35:03 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Run the compiler to completion, and return any `ts.Diagnostic` errors that may have occurred.
|
|
|
|
*/
|
feat(ivy): convert all ngtsc diagnostics to ts.Diagnostics (#31952)
Historically, the Angular Compiler has produced both native TypeScript
diagnostics (called ts.Diagnostics) and its own internal Diagnostic format
(called an api.Diagnostic). This was done because TypeScript ts.Diagnostics
cannot be produced for files not in the ts.Program, and template type-
checking diagnostics are naturally produced for external .html template
files.
This design isn't optimal for several reasons:
1) Downstream tooling (such as the CLI) must support multiple formats of
diagnostics, adding to the maintenance burden.
2) ts.Diagnostics have gotten a lot better in recent releases, with support
for suggested changes, highlighting of the code in question, etc. None of
these changes have been of any benefit for api.Diagnostics, which have
continued to be reported in a very primitive fashion.
3) A future plugin model will not support anything but ts.Diagnostics, so
generating api.Diagnostics is a blocker for ngtsc-as-a-plugin.
4) The split complicates both the typings and the testing of ngtsc.
To fix this issue, this commit changes template type-checking to produce
ts.Diagnostics instead. Instead of reporting a special kind of diagnostic
for external template files, errors in a template are always reported in
a ts.Diagnostic that highlights the portion of the template which contains
the error. When this template text is distinct from the source .ts file
(for example, when the template is parsed from an external resource file),
additional contextual information links the error back to the originating
component.
A template error can thus be reported in 3 separate ways, depending on how
the template was configured:
1) For inline template strings which can be directly mapped to offsets in
the TS code, ts.Diagnostics point to real ranges in the source.
This is the case if an inline template is used with a string literal or a
"no-substitution" string. For example:
```typescript
@Component({..., template: `
<p>Bar: {{baz}}</p>
`})
export class TestCmp {
bar: string;
}
```
The above template contains an error (no 'baz' property of `TestCmp`). The
error produced by TS will look like:
```
<p>Bar: {{baz}}</p>
~~~
test.ts:2:11 - error TS2339: Property 'baz' does not exist on type 'TestCmp'. Did you mean 'bar'?
```
2) For template strings which cannot be directly mapped to offsets in the
TS code, a logical offset into the template string will be included in
the error message. For example:
```typescript
const SOME_TEMPLATE = '<p>Bar: {{baz}}</p>';
@Component({..., template: SOME_TEMPLATE})
export class TestCmp {
bar: string;
}
```
Because the template is a reference to another variable and is not an
inline string constant, the compiler will not be able to use "absolute"
positions when parsing the template. As a result, errors will report logical
offsets into the template string:
```
<p>Bar: {{baz}}</p>
~~~
test.ts (TestCmp template):2:15 - error TS2339: Property 'baz' does not exist on type 'TestCmp'.
test.ts:3:28
@Component({..., template: TEMPLATE})
~~~~~~~~
Error occurs in the template of component TestCmp.
```
This error message uses logical offsets into the template string, and also
gives a reference to the `TEMPLATE` expression from which the template was
parsed. This helps in locating the component which contains the error.
3) For external templates (templateUrl), the error message is delivered
within the HTML template file (testcmp.html) instead, and additional
information contextualizes the error on the templateUrl expression from
which the template file was determined:
```
<p>Bar: {{baz}}</p>
~~~
testcmp.html:2:15 - error TS2339: Property 'baz' does not exist on type 'TestCmp'.
test.ts:10:31
@Component({..., templateUrl: './testcmp.html'})
~~~~~~~~~~~~~~~~
Error occurs in the template of component TestCmp.
```
PR Close #31952
2019-08-01 18:01:55 -04:00
|
|
|
driveDiagnostics(): ReadonlyArray<ts.Diagnostic> {
|
|
|
|
// ngtsc only produces ts.Diagnostic messages.
|
2020-04-07 15:43:43 -04:00
|
|
|
let reuseProgram: {program: Program|undefined}|undefined = undefined;
|
2019-11-12 16:38:23 -05:00
|
|
|
if (this.multiCompileHostExt !== null) {
|
|
|
|
reuseProgram = {
|
|
|
|
program: this.oldProgram || undefined,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
const diags = mainDiagnosticsForTest(
|
|
|
|
['-p', this.basePath], undefined, reuseProgram, this.changedResources);
|
|
|
|
|
|
|
|
|
|
|
|
if (this.multiCompileHostExt !== null) {
|
2020-04-07 15:43:43 -04:00
|
|
|
this.oldProgram = reuseProgram!.program!;
|
2019-11-12 16:38:23 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
// In ngtsc, only `ts.Diagnostic`s are produced.
|
|
|
|
return diags as ReadonlyArray<ts.Diagnostic>;
|
2018-09-25 18:35:03 -04:00
|
|
|
}
|
|
|
|
|
2019-09-07 17:48:32 -04:00
|
|
|
async driveDiagnosticsAsync(): Promise<ReadonlyArray<ts.Diagnostic>> {
|
|
|
|
const {rootNames, options} = readNgcCommandLineAndConfiguration(['-p', this.basePath]);
|
|
|
|
const host = createCompilerHost({options});
|
|
|
|
const program = createProgram({rootNames, host, options});
|
|
|
|
await program.loadNgStructureAsync();
|
|
|
|
|
|
|
|
// ngtsc only produces ts.Diagnostic messages.
|
|
|
|
return defaultGatherDiagnostics(program as api.Program) as ts.Diagnostic[];
|
|
|
|
}
|
|
|
|
|
2018-11-16 11:54:43 -05:00
|
|
|
driveRoutes(entryPoint?: string): LazyRoute[] {
|
|
|
|
const {rootNames, options} = readNgcCommandLineAndConfiguration(['-p', this.basePath]);
|
|
|
|
const host = createCompilerHost({options});
|
|
|
|
const program = createProgram({rootNames, host, options});
|
|
|
|
return program.listLazyRoutes(entryPoint);
|
|
|
|
}
|
2019-06-19 20:23:59 -04:00
|
|
|
|
|
|
|
driveIndexer(): Map<ts.Declaration, IndexedComponent> {
|
|
|
|
const {rootNames, options} = readNgcCommandLineAndConfiguration(['-p', this.basePath]);
|
|
|
|
const host = createCompilerHost({options});
|
|
|
|
const program = createProgram({rootNames, host, options});
|
|
|
|
return (program as NgtscProgram).getIndexedComponents();
|
|
|
|
}
|
2018-09-25 18:35:03 -04:00
|
|
|
}
|
2019-03-18 14:21:29 -04:00
|
|
|
|
2019-06-06 15:22:32 -04:00
|
|
|
class AugmentedCompilerHost extends NgtscCompilerHost {
|
2020-04-07 15:43:43 -04:00
|
|
|
delegate!: ts.CompilerHost;
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
|
|
|
|
2019-12-19 19:33:56 -05:00
|
|
|
const ROOT_PREFIX = 'root/';
|
|
|
|
|
2019-03-18 14:21:29 -04:00
|
|
|
class FileNameToModuleNameHost extends AugmentedCompilerHost {
|
|
|
|
fileNameToModuleName(importedFilePath: string): string {
|
2019-06-06 15:22:32 -04:00
|
|
|
const relativeFilePath = this.fs.relative(this.fs.pwd(), this.fs.resolve(importedFilePath));
|
|
|
|
const rootedPath = this.fs.join('root', relativeFilePath);
|
|
|
|
return rootedPath.replace(/(\.d)?.ts$/, '');
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
2019-12-19 19:33:56 -05:00
|
|
|
|
|
|
|
resolveModuleNames(
|
|
|
|
moduleNames: string[], containingFile: string, reusedNames: string[]|undefined,
|
|
|
|
redirectedReference: ts.ResolvedProjectReference|undefined,
|
|
|
|
options: ts.CompilerOptions): (ts.ResolvedModule|undefined)[] {
|
|
|
|
return moduleNames.map(moduleName => {
|
|
|
|
if (moduleName.startsWith(ROOT_PREFIX)) {
|
|
|
|
// Strip the artificially added root prefix.
|
|
|
|
moduleName = '/' + moduleName.substr(ROOT_PREFIX.length);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ts
|
|
|
|
.resolveModuleName(
|
|
|
|
moduleName, containingFile, options, this, /* cache */ undefined, redirectedReference)
|
|
|
|
.resolvedModule;
|
|
|
|
});
|
|
|
|
}
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
class MultiCompileHostExt extends AugmentedCompilerHost implements Partial<ts.CompilerHost> {
|
|
|
|
private cache = new Map<string, ts.SourceFile>();
|
|
|
|
private writtenFiles = new Set<string>();
|
|
|
|
|
|
|
|
getSourceFile(
|
|
|
|
fileName: string, languageVersion: ts.ScriptTarget, onError?: (message: string) => void,
|
|
|
|
shouldCreateNewSourceFile?: boolean): ts.SourceFile|undefined {
|
|
|
|
if (this.cache.has(fileName)) {
|
2020-04-07 15:43:43 -04:00
|
|
|
return this.cache.get(fileName)!;
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
2019-06-06 15:22:32 -04:00
|
|
|
const sf = super.getSourceFile(fileName, languageVersion);
|
2019-03-18 14:21:29 -04:00
|
|
|
if (sf !== undefined) {
|
|
|
|
this.cache.set(sf.fileName, sf);
|
|
|
|
}
|
|
|
|
return sf;
|
|
|
|
}
|
|
|
|
|
2020-04-07 15:43:43 -04:00
|
|
|
flushWrittenFileTracking(): void {
|
|
|
|
this.writtenFiles.clear();
|
|
|
|
}
|
2019-03-18 14:21:29 -04:00
|
|
|
|
|
|
|
writeFile(
|
|
|
|
fileName: string, data: string, writeByteOrderMark: boolean,
|
|
|
|
onError: ((message: string) => void)|undefined,
|
|
|
|
sourceFiles?: ReadonlyArray<ts.SourceFile>): void {
|
2019-06-06 15:22:32 -04:00
|
|
|
super.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
2019-03-18 14:21:29 -04:00
|
|
|
this.writtenFiles.add(fileName);
|
|
|
|
}
|
|
|
|
|
2020-04-07 15:43:43 -04:00
|
|
|
getFilesWrittenSinceLastFlush(): Set<string> {
|
|
|
|
return this.writtenFiles;
|
|
|
|
}
|
2019-03-18 14:21:29 -04:00
|
|
|
|
2020-04-07 15:43:43 -04:00
|
|
|
invalidate(fileName: string): void {
|
|
|
|
this.cache.delete(fileName);
|
|
|
|
}
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
|
|
|
|
2019-09-07 17:48:32 -04:00
|
|
|
class ResourceLoadingCompileHost extends AugmentedCompilerHost implements api.CompilerHost {
|
|
|
|
readResource(fileName: string): Promise<string>|string {
|
|
|
|
const resource = this.readFile(fileName);
|
|
|
|
if (resource === undefined) {
|
|
|
|
throw new Error(`Resource ${fileName} not found`);
|
|
|
|
}
|
|
|
|
return resource;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-18 14:21:29 -04:00
|
|
|
function makeWrapHost(wrapped: AugmentedCompilerHost): (host: ts.CompilerHost) => ts.CompilerHost {
|
|
|
|
return (delegate) => {
|
|
|
|
wrapped.delegate = delegate;
|
|
|
|
return new Proxy(delegate, {
|
|
|
|
get: (target: ts.CompilerHost, name: string): any => {
|
|
|
|
if ((wrapped as any)[name] !== undefined) {
|
2020-04-07 15:43:43 -04:00
|
|
|
return (wrapped as any)[name]!.bind(wrapped);
|
2019-03-18 14:21:29 -04:00
|
|
|
}
|
|
|
|
return (target as any)[name];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
};
|
|
|
|
}
|