2020-10-09 19:46:55 -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
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {NgCompiler} from '@angular/compiler-cli/src/ngtsc/core';
|
|
|
|
import {NgCompilerOptions} from '@angular/compiler-cli/src/ngtsc/core/api';
|
|
|
|
import {TrackedIncrementalBuildStrategy} from '@angular/compiler-cli/src/ngtsc/incremental';
|
|
|
|
import {TypeCheckingProgramStrategy} from '@angular/compiler-cli/src/ngtsc/typecheck/api';
|
|
|
|
import * as ts from 'typescript/lib/tsserverlibrary';
|
|
|
|
|
2020-10-30 18:16:39 -04:00
|
|
|
import {LanguageServiceAdapter} from './adapters';
|
2020-10-09 14:22:03 -04:00
|
|
|
import {isExternalTemplate} from './utils';
|
2020-10-09 19:46:55 -04:00
|
|
|
|
2020-11-04 15:28:59 -05:00
|
|
|
/**
|
|
|
|
* Manages the `NgCompiler` instance which backs the language service, updating or replacing it as
|
|
|
|
* needed to produce an up-to-date understanding of the current program.
|
|
|
|
*
|
|
|
|
* TODO(alxhub): currently the options used for the compiler are specified at `CompilerFactory`
|
|
|
|
* construction, and are not changable. In a real project, users can update `tsconfig.json`. We need
|
|
|
|
* to properly handle a change in the compiler options, either by having an API to update the
|
|
|
|
* `CompilerFactory` to use new options, or by replacing it entirely.
|
|
|
|
*/
|
2020-10-09 19:46:55 -04:00
|
|
|
export class CompilerFactory {
|
|
|
|
private readonly incrementalStrategy = new TrackedIncrementalBuildStrategy();
|
|
|
|
private compiler: NgCompiler|null = null;
|
|
|
|
private lastKnownProgram: ts.Program|null = null;
|
|
|
|
|
|
|
|
constructor(
|
|
|
|
private readonly adapter: LanguageServiceAdapter,
|
|
|
|
private readonly programStrategy: TypeCheckingProgramStrategy,
|
2020-11-04 15:28:59 -05:00
|
|
|
private readonly options: NgCompilerOptions,
|
2020-10-09 19:46:55 -04:00
|
|
|
) {}
|
|
|
|
|
2020-11-04 15:28:59 -05:00
|
|
|
getOrCreate(): NgCompiler {
|
2020-10-09 19:46:55 -04:00
|
|
|
const program = this.programStrategy.getProgram();
|
2020-11-04 15:28:59 -05:00
|
|
|
if (this.compiler === null || program !== this.lastKnownProgram) {
|
2020-10-09 19:46:55 -04:00
|
|
|
this.compiler = new NgCompiler(
|
|
|
|
this.adapter, // like compiler host
|
2020-11-04 15:28:59 -05:00
|
|
|
this.options, // angular compiler options
|
2020-10-09 19:46:55 -04:00
|
|
|
program,
|
|
|
|
this.programStrategy,
|
|
|
|
this.incrementalStrategy,
|
|
|
|
true, // enableTemplateTypeChecker
|
fix(compiler-cli): track poisoned scopes with a flag (#39923)
To avoid overwhelming a user with secondary diagnostics that derive from a
"root cause" error, the compiler has the notion of a "poisoned" NgModule.
An NgModule becomes poisoned when its declaration contains semantic errors:
declarations which are not components or pipes, imports which are not other
NgModules, etc. An NgModule also becomes poisoned if it imports or exports
another poisoned NgModule.
Previously, the compiler tracked this poisoned status as an alternate state
for each scope. Either a correct scope could be produced, or the entire
scope would be set to a sentinel error value. This meant that the compiler
would not track any information about a scope that was determined to be in
error.
This method presents several issues:
1. The compiler is unable to support the language service and return results
when a component or its module scope is poisoned.
This is fine for compilation, since diagnostics will be produced showing the
error(s), but the language service needs to still work for incorrect code.
2. `getComponentScopes()` does not return components with a poisoned scope,
which interferes with resource tracking of incremental builds.
If the component isn't included in that list, then the NgModule for it will
not have its dependencies properly tracked, and this can cause future
incremental build steps to produce incorrect results.
This commit changes the tracking of poisoned module scopes to use a flag on
the scope itself, rather than a sentinel value that replaces the scope. This
means that the scope itself will still be tracked, even if it contains
semantic errors. A test is added to the language service which verifies that
poisoned scopes can still be used in template type-checking.
PR Close #39923
2020-11-25 18:02:23 -05:00
|
|
|
true, // usePoisonedData
|
2020-10-09 19:46:55 -04:00
|
|
|
this.lastKnownProgram,
|
|
|
|
undefined, // perfRecorder (use default)
|
|
|
|
);
|
|
|
|
this.lastKnownProgram = program;
|
|
|
|
}
|
2020-11-04 15:28:59 -05:00
|
|
|
return this.compiler;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new instance of the Ivy compiler if the program has changed since
|
|
|
|
* the last time the compiler was instantiated. If the program has not changed,
|
|
|
|
* return the existing instance.
|
|
|
|
* @param fileName override the template if this is an external template file
|
|
|
|
* @param options angular compiler options
|
|
|
|
*/
|
|
|
|
getOrCreateWithChangedFile(fileName: string): NgCompiler {
|
|
|
|
const compiler = this.getOrCreate();
|
2020-10-09 19:46:55 -04:00
|
|
|
if (isExternalTemplate(fileName)) {
|
2020-11-04 15:28:59 -05:00
|
|
|
this.overrideTemplate(fileName, compiler);
|
2020-10-09 19:46:55 -04:00
|
|
|
}
|
2020-11-04 15:28:59 -05:00
|
|
|
return compiler;
|
2020-10-09 19:46:55 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
private overrideTemplate(fileName: string, compiler: NgCompiler) {
|
|
|
|
if (!this.adapter.isTemplateDirty(fileName)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// 1. Get the latest snapshot
|
|
|
|
const latestTemplate = this.adapter.readResource(fileName);
|
|
|
|
// 2. Find all components that use the template
|
|
|
|
const ttc = compiler.getTemplateTypeChecker();
|
|
|
|
const components = compiler.getComponentsWithTemplateFile(fileName);
|
|
|
|
// 3. Update component template
|
|
|
|
for (const component of components) {
|
|
|
|
if (ts.isClassDeclaration(component)) {
|
|
|
|
ttc.overrideComponentTemplate(component, latestTemplate);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
registerLastKnownProgram() {
|
|
|
|
this.lastKnownProgram = this.programStrategy.getProgram();
|
|
|
|
}
|
|
|
|
}
|