2018-04-06 09:53:10 -07: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
|
|
|
|
*/
|
|
|
|
|
|
|
|
import {GeneratedFile} from '@angular/compiler';
|
|
|
|
import * as ts from 'typescript';
|
|
|
|
|
|
|
|
import * as api from '../transformers/api';
|
2018-08-28 14:13:22 -07:00
|
|
|
import {nocollapseHack} from '../transformers/nocollapse_hack';
|
2018-04-06 09:53:10 -07:00
|
|
|
|
2019-02-19 12:05:03 -08:00
|
|
|
import {ComponentDecoratorHandler, DirectiveDecoratorHandler, InjectableDecoratorHandler, NgModuleDecoratorHandler, NoopReferencesRegistry, PipeDecoratorHandler, ReferencesRegistry} from './annotations';
|
2018-08-07 12:04:39 -07:00
|
|
|
import {BaseDefDecoratorHandler} from './annotations/src/base_def';
|
feat(ivy): detect cycles and use remote scoping of components if needed (#28169)
By its nature, Ivy alters the import graph of a TS program, adding imports
where template dependencies exist. For example, if ComponentA uses PipeB
in its template, Ivy will insert an import of PipeB into the file in which
ComponentA is declared.
Any insertion of an import into a program has the potential to introduce a
cycle into the import graph. If for some reason the file in which PipeB is
declared imports the file in which ComponentA is declared (maybe it makes
use of a service or utility function that happens to be in the same file as
ComponentA) then this could create an import cycle. This turns out to
happen quite regularly in larger Angular codebases.
TypeScript and the Ivy runtime have no issues with such cycles. However,
other tools are not so accepting. In particular the Closure Compiler is
very anti-cycle.
To mitigate this problem, it's necessary to detect when the insertion of
an import would create a cycle. ngtsc can then use a different strategy,
known as "remote scoping", instead of directly writing a reference from
one component to another. Under remote scoping, a function
'setComponentScope' is called after the declaration of the component's
module, which does not require the addition of new imports.
FW-647 #resolve
PR Close #28169
2019-01-15 12:32:10 -08:00
|
|
|
import {CycleAnalyzer, ImportGraph} from './cycles';
|
2018-12-13 11:52:20 -08:00
|
|
|
import {ErrorCode, ngErrorCode} from './diagnostics';
|
|
|
|
import {FlatIndexGenerator, ReferenceGraph, checkForPrivateExports, findFlatIndexEntryPoint} from './entry_point';
|
2019-06-06 20:22:32 +01:00
|
|
|
import {AbsoluteFsPath, LogicalFileSystem, absoluteFrom} from './file_system';
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
import {AbsoluteModuleStrategy, AliasGenerator, AliasStrategy, DefaultImportTracker, FileToModuleHost, FileToModuleStrategy, ImportRewriter, LocalIdentifierStrategy, LogicalProjectStrategy, ModuleResolver, NoopImportRewriter, R3SymbolsImportRewriter, Reference, ReferenceEmitter} from './imports';
|
2019-03-18 12:25:26 -07:00
|
|
|
import {IncrementalState} from './incremental';
|
2019-06-19 17:23:59 -07:00
|
|
|
import {IndexedComponent, IndexingContext} from './indexer';
|
|
|
|
import {generateAnalysis} from './indexer/src/transform';
|
2019-04-01 14:20:34 -07:00
|
|
|
import {CompoundMetadataReader, CompoundMetadataRegistry, DtsMetadataReader, LocalMetadataRegistry, MetadataReader} from './metadata';
|
2018-12-18 09:48:15 -08:00
|
|
|
import {PartialEvaluator} from './partial_evaluator';
|
2019-03-18 11:16:38 -07:00
|
|
|
import {NOOP_PERF_RECORDER, PerfRecorder, PerfTracker} from './perf';
|
2018-12-18 09:48:15 -08:00
|
|
|
import {TypeScriptReflectionHost} from './reflection';
|
2019-01-16 17:22:53 +00:00
|
|
|
import {HostResourceLoader} from './resource_loader';
|
2019-02-07 19:03:13 +02:00
|
|
|
import {NgModuleRouteAnalyzer, entryPointKeyFor} from './routing';
|
2019-07-31 17:11:33 +01:00
|
|
|
import {CompoundComponentScopeReader, LocalModuleScopeRegistry, MetadataDtsModuleScopeResolver} from './scope';
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
import {FactoryGenerator, FactoryInfo, GeneratedShimsHostWrapper, ShimGenerator, SummaryGenerator, TypeCheckShimGenerator, generatedFactoryTransform} from './shims';
|
refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally
compile one TS file or another depending on whether ngc or ngtsc was the
selected compiler. This was done because we wanted to avoid importing
certain modules (and thus pulling them into the build) if Ivy was on or
off. This mechanism had a major drawback: ivy_switch became a bottleneck
in the import graph, as it both imports from many places in the codebase
and is imported by many modules in the codebase. This frequently resulted
in cyclic imports which caused issues both with TS and Closure compilation.
It turns out ngcc needs both code paths in the bundle to perform the switch
during its operation anyway, so import switching was later abandoned. This
means that there's no real reason why the ivy_switch mechanism needed to
operate at the Bazel level, and for the ivy_switch file to be a bottleneck.
This commit removes the Bazel-level ivy_switch mechanism, and introduces
an additional TypeScript transform in ngtsc (and the pass-through tsc
compiler used for testing JIT) to perform the same operation that ngcc
does, and flip the switch during ngtsc compilation. This allows the
ivy_switch file to be removed, and the individual switches to be located
directly next to their consumers in the codebase, greatly mitigating the
circular import issues and making the mechanism much easier to use.
As part of this commit, the tag for marking switched variables was changed
from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which
flips these tags. Most variables were renamed from R3_* to SWITCH_* as well,
since they're referenced mostly in render2 code.
Test strategy: existing test coverage is more than sufficient - if this
didn't work correctly it would break the hello world and todo apps.
PR Close #26550
2018-10-17 15:44:44 -07:00
|
|
|
import {ivySwitchTransform} from './switch';
|
2019-01-24 10:38:58 +00:00
|
|
|
import {IvyCompilation, declarationTransformFactory, ivyTransformFactory} from './transform';
|
2019-02-19 17:36:26 -08:00
|
|
|
import {aliasTransformFactory} from './transform/src/alias';
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
import {TypeCheckContext, TypeCheckingConfig, typeCheckFilePath} from './typecheck';
|
2019-01-12 19:00:39 +01:00
|
|
|
import {normalizeSeparators} from './util/src/path';
|
2019-06-06 20:22:32 +01:00
|
|
|
import {getRootDirs, getSourceFileOrNull, isDtsPath, resolveModuleName} from './util/src/typescript';
|
2018-04-06 09:53:10 -07:00
|
|
|
|
|
|
|
export class NgtscProgram implements api.Program {
|
|
|
|
private tsProgram: ts.Program;
|
2019-04-24 10:26:47 -07:00
|
|
|
private reuseTsProgram: ts.Program;
|
2019-01-16 17:22:53 +00:00
|
|
|
private resourceManager: HostResourceLoader;
|
2018-06-26 15:01:09 -07:00
|
|
|
private compilation: IvyCompilation|undefined = undefined;
|
2018-07-27 22:57:44 -07:00
|
|
|
private factoryToSourceInfo: Map<string, FactoryInfo>|null = null;
|
|
|
|
private sourceToFactorySymbols: Map<string, Set<string>>|null = null;
|
2018-06-26 15:01:09 -07:00
|
|
|
private _coreImportsFrom: ts.SourceFile|null|undefined = undefined;
|
2019-01-08 11:49:58 -08:00
|
|
|
private _importRewriter: ImportRewriter|undefined = undefined;
|
2018-06-26 15:01:09 -07:00
|
|
|
private _reflector: TypeScriptReflectionHost|undefined = undefined;
|
|
|
|
private _isCore: boolean|undefined = undefined;
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
private rootDirs: AbsoluteFsPath[];
|
2018-08-28 14:13:22 -07:00
|
|
|
private closureCompilerEnabled: boolean;
|
2018-12-13 11:52:20 -08:00
|
|
|
private entryPoint: ts.SourceFile|null;
|
|
|
|
private exportReferenceGraph: ReferenceGraph|null = null;
|
|
|
|
private flatIndexGenerator: FlatIndexGenerator|null = null;
|
2018-11-16 17:56:18 +01:00
|
|
|
private routeAnalyzer: NgModuleRouteAnalyzer|null = null;
|
2018-12-13 11:52:20 -08:00
|
|
|
|
|
|
|
private constructionDiagnostics: ts.Diagnostic[] = [];
|
2018-11-16 17:01:56 +01:00
|
|
|
private moduleResolver: ModuleResolver;
|
feat(ivy): detect cycles and use remote scoping of components if needed (#28169)
By its nature, Ivy alters the import graph of a TS program, adding imports
where template dependencies exist. For example, if ComponentA uses PipeB
in its template, Ivy will insert an import of PipeB into the file in which
ComponentA is declared.
Any insertion of an import into a program has the potential to introduce a
cycle into the import graph. If for some reason the file in which PipeB is
declared imports the file in which ComponentA is declared (maybe it makes
use of a service or utility function that happens to be in the same file as
ComponentA) then this could create an import cycle. This turns out to
happen quite regularly in larger Angular codebases.
TypeScript and the Ivy runtime have no issues with such cycles. However,
other tools are not so accepting. In particular the Closure Compiler is
very anti-cycle.
To mitigate this problem, it's necessary to detect when the insertion of
an import would create a cycle. ngtsc can then use a different strategy,
known as "remote scoping", instead of directly writing a reference from
one component to another. Under remote scoping, a function
'setComponentScope' is called after the declaration of the component's
module, which does not require the addition of new imports.
FW-647 #resolve
PR Close #28169
2019-01-15 12:32:10 -08:00
|
|
|
private cycleAnalyzer: CycleAnalyzer;
|
2019-04-01 14:20:34 -07:00
|
|
|
private metaReader: MetadataReader|null = null;
|
2018-04-06 09:53:10 -07:00
|
|
|
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
private refEmitter: ReferenceEmitter|null = null;
|
|
|
|
private fileToModuleHost: FileToModuleHost|null = null;
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
private defaultImportTracker: DefaultImportTracker;
|
2019-03-18 11:16:38 -07:00
|
|
|
private perfRecorder: PerfRecorder = NOOP_PERF_RECORDER;
|
|
|
|
private perfTracker: PerfTracker|null = null;
|
2019-03-18 12:25:26 -07:00
|
|
|
private incrementalState: IncrementalState;
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
private typeCheckFilePath: AbsoluteFsPath;
|
2018-07-27 22:57:44 -07:00
|
|
|
|
2019-06-10 16:22:56 +01:00
|
|
|
private modifiedResourceFiles: Set<string>|null;
|
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
constructor(
|
|
|
|
rootNames: ReadonlyArray<string>, private options: api.CompilerOptions,
|
2019-06-10 16:22:56 +01:00
|
|
|
private host: api.CompilerHost, oldProgram?: NgtscProgram) {
|
2019-03-18 11:16:38 -07:00
|
|
|
if (shouldEnablePerfTracing(options)) {
|
|
|
|
this.perfTracker = PerfTracker.zeroedToNow();
|
|
|
|
this.perfRecorder = this.perfTracker;
|
|
|
|
}
|
|
|
|
|
2019-06-10 16:22:56 +01:00
|
|
|
this.modifiedResourceFiles =
|
|
|
|
this.host.getModifiedResourceFiles && this.host.getModifiedResourceFiles() || null;
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
this.rootDirs = getRootDirs(host, options);
|
2018-08-28 14:13:22 -07:00
|
|
|
this.closureCompilerEnabled = !!options.annotateForClosureCompiler;
|
2019-01-16 17:22:53 +00:00
|
|
|
this.resourceManager = new HostResourceLoader(host, options);
|
2019-10-18 12:15:25 -07:00
|
|
|
// TODO(alxhub): remove the fallback to allowEmptyCodegenFiles after verifying that the rest of
|
|
|
|
// our build tooling is no longer relying on it.
|
|
|
|
const allowEmptyCodegenFiles = options.allowEmptyCodegenFiles || false;
|
|
|
|
const shouldGenerateFactoryShims = options.generateNgFactoryShims !== undefined ?
|
|
|
|
options.generateNgFactoryShims :
|
|
|
|
allowEmptyCodegenFiles;
|
|
|
|
const shouldGenerateSummaryShims = options.generateNgSummaryShims !== undefined ?
|
|
|
|
options.generateNgSummaryShims :
|
|
|
|
allowEmptyCodegenFiles;
|
2019-06-06 20:22:32 +01:00
|
|
|
const normalizedRootNames = rootNames.map(n => absoluteFrom(n));
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
if (host.fileNameToModuleName !== undefined) {
|
|
|
|
this.fileToModuleHost = host as FileToModuleHost;
|
|
|
|
}
|
2018-07-27 22:57:44 -07:00
|
|
|
let rootFiles = [...rootNames];
|
2018-12-05 16:05:29 -08:00
|
|
|
|
|
|
|
const generators: ShimGenerator[] = [];
|
2019-10-18 12:15:25 -07:00
|
|
|
let summaryGenerator: SummaryGenerator|null = null;
|
|
|
|
if (shouldGenerateSummaryShims) {
|
2018-10-16 14:47:08 -07:00
|
|
|
// Summary generation.
|
2019-10-18 12:15:25 -07:00
|
|
|
summaryGenerator = SummaryGenerator.forRootFiles(normalizedRootNames);
|
|
|
|
generators.push(summaryGenerator);
|
|
|
|
}
|
2018-10-16 14:47:08 -07:00
|
|
|
|
2019-10-18 12:15:25 -07:00
|
|
|
if (shouldGenerateFactoryShims) {
|
2018-10-16 14:47:08 -07:00
|
|
|
// Factory generation.
|
2019-03-26 23:39:12 +01:00
|
|
|
const factoryGenerator = FactoryGenerator.forRootFiles(normalizedRootNames);
|
2018-10-16 14:47:08 -07:00
|
|
|
const factoryFileMap = factoryGenerator.factoryFileMap;
|
2018-07-27 22:57:44 -07:00
|
|
|
this.factoryToSourceInfo = new Map<string, FactoryInfo>();
|
|
|
|
this.sourceToFactorySymbols = new Map<string, Set<string>>();
|
|
|
|
factoryFileMap.forEach((sourceFilePath, factoryPath) => {
|
|
|
|
const moduleSymbolNames = new Set<string>();
|
|
|
|
this.sourceToFactorySymbols !.set(sourceFilePath, moduleSymbolNames);
|
|
|
|
this.factoryToSourceInfo !.set(factoryPath, {sourceFilePath, moduleSymbolNames});
|
|
|
|
});
|
2018-10-16 15:07:46 -07:00
|
|
|
|
|
|
|
const factoryFileNames = Array.from(factoryFileMap.keys());
|
2019-10-18 12:15:25 -07:00
|
|
|
rootFiles.push(...factoryFileNames);
|
|
|
|
generators.push(factoryGenerator);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Done separately to preserve the order of factory files before summary files in rootFiles.
|
|
|
|
// TODO(alxhub): validate that this is necessary.
|
|
|
|
if (shouldGenerateSummaryShims) {
|
|
|
|
rootFiles.push(...summaryGenerator !.getSummaryFileNames());
|
2018-12-05 16:05:29 -08:00
|
|
|
}
|
|
|
|
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
this.typeCheckFilePath = typeCheckFilePath(this.rootDirs);
|
|
|
|
generators.push(new TypeCheckShimGenerator(this.typeCheckFilePath));
|
|
|
|
rootFiles.push(this.typeCheckFilePath);
|
|
|
|
|
2019-06-06 20:22:32 +01:00
|
|
|
let entryPoint: AbsoluteFsPath|null = null;
|
2019-08-21 20:51:24 +02:00
|
|
|
if (options.flatModuleOutFile != null && options.flatModuleOutFile !== '') {
|
2019-03-26 23:39:12 +01:00
|
|
|
entryPoint = findFlatIndexEntryPoint(normalizedRootNames);
|
2018-12-13 11:52:20 -08:00
|
|
|
if (entryPoint === null) {
|
2018-12-05 16:05:29 -08:00
|
|
|
// This error message talks specifically about having a single .ts file in "files". However
|
|
|
|
// the actual logic is a bit more permissive. If a single file exists, that will be taken,
|
|
|
|
// otherwise the highest level (shortest path) "index.ts" file will be used as the flat
|
|
|
|
// module entry point instead. If neither of these conditions apply, the error below is
|
|
|
|
// given.
|
|
|
|
//
|
|
|
|
// The user is not informed about the "index.ts" option as this behavior is deprecated -
|
|
|
|
// an explicit entrypoint should always be specified.
|
2018-12-13 11:52:20 -08:00
|
|
|
this.constructionDiagnostics.push({
|
|
|
|
category: ts.DiagnosticCategory.Error,
|
|
|
|
code: ngErrorCode(ErrorCode.CONFIG_FLAT_MODULE_NO_INDEX),
|
|
|
|
file: undefined,
|
|
|
|
start: undefined,
|
|
|
|
length: undefined,
|
|
|
|
messageText:
|
|
|
|
'Angular compiler option "flatModuleOutFile" requires one and only one .ts file in the "files" field.',
|
|
|
|
});
|
|
|
|
} else {
|
|
|
|
const flatModuleId = options.flatModuleId || null;
|
2019-01-12 19:00:39 +01:00
|
|
|
const flatModuleOutFile = normalizeSeparators(options.flatModuleOutFile);
|
2018-12-13 11:52:20 -08:00
|
|
|
this.flatIndexGenerator =
|
2019-01-12 19:00:39 +01:00
|
|
|
new FlatIndexGenerator(entryPoint, flatModuleOutFile, flatModuleId);
|
2018-12-13 11:52:20 -08:00
|
|
|
generators.push(this.flatIndexGenerator);
|
|
|
|
rootFiles.push(this.flatIndexGenerator.flatIndexPath);
|
2018-12-05 16:05:29 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (generators.length > 0) {
|
2019-10-01 16:44:50 -07:00
|
|
|
// FIXME: Remove the any cast once google3 is fully on TS3.6.
|
|
|
|
this.host = (new GeneratedShimsHostWrapper(host, generators) as any);
|
2018-07-27 22:57:44 -07:00
|
|
|
}
|
2018-06-26 15:01:09 -07:00
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
this.tsProgram =
|
2019-04-24 10:26:47 -07:00
|
|
|
ts.createProgram(rootFiles, options, this.host, oldProgram && oldProgram.reuseTsProgram);
|
|
|
|
this.reuseTsProgram = this.tsProgram;
|
2018-12-13 11:52:20 -08:00
|
|
|
|
2019-06-06 20:22:32 +01:00
|
|
|
this.entryPoint = entryPoint !== null ? getSourceFileOrNull(this.tsProgram, entryPoint) : null;
|
2018-11-16 17:01:56 +01:00
|
|
|
this.moduleResolver = new ModuleResolver(this.tsProgram, options, this.host);
|
feat(ivy): detect cycles and use remote scoping of components if needed (#28169)
By its nature, Ivy alters the import graph of a TS program, adding imports
where template dependencies exist. For example, if ComponentA uses PipeB
in its template, Ivy will insert an import of PipeB into the file in which
ComponentA is declared.
Any insertion of an import into a program has the potential to introduce a
cycle into the import graph. If for some reason the file in which PipeB is
declared imports the file in which ComponentA is declared (maybe it makes
use of a service or utility function that happens to be in the same file as
ComponentA) then this could create an import cycle. This turns out to
happen quite regularly in larger Angular codebases.
TypeScript and the Ivy runtime have no issues with such cycles. However,
other tools are not so accepting. In particular the Closure Compiler is
very anti-cycle.
To mitigate this problem, it's necessary to detect when the insertion of
an import would create a cycle. ngtsc can then use a different strategy,
known as "remote scoping", instead of directly writing a reference from
one component to another. Under remote scoping, a function
'setComponentScope' is called after the declaration of the component's
module, which does not require the addition of new imports.
FW-647 #resolve
PR Close #28169
2019-01-15 12:32:10 -08:00
|
|
|
this.cycleAnalyzer = new CycleAnalyzer(new ImportGraph(this.moduleResolver));
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
this.defaultImportTracker = new DefaultImportTracker();
|
2019-03-18 12:25:26 -07:00
|
|
|
if (oldProgram === undefined) {
|
|
|
|
this.incrementalState = IncrementalState.fresh();
|
|
|
|
} else {
|
|
|
|
this.incrementalState = IncrementalState.reconcile(
|
2019-06-10 16:22:56 +01:00
|
|
|
oldProgram.incrementalState, oldProgram.reuseTsProgram, this.tsProgram,
|
|
|
|
this.modifiedResourceFiles);
|
2019-03-18 12:25:26 -07:00
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
getTsProgram(): ts.Program { return this.tsProgram; }
|
|
|
|
|
|
|
|
getTsOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
|
|
|
undefined): ReadonlyArray<ts.Diagnostic> {
|
|
|
|
return this.tsProgram.getOptionsDiagnostics(cancellationToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
getNgOptionDiagnostics(cancellationToken?: ts.CancellationToken|
|
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 15:01:55 -07:00
|
|
|
undefined): ReadonlyArray<ts.Diagnostic> {
|
2018-12-13 11:52:20 -08:00
|
|
|
return this.constructionDiagnostics;
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
getTsSyntacticDiagnostics(
|
|
|
|
sourceFile?: ts.SourceFile|undefined,
|
|
|
|
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
|
|
|
return this.tsProgram.getSyntacticDiagnostics(sourceFile, cancellationToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
getNgStructuralDiagnostics(cancellationToken?: ts.CancellationToken|
|
|
|
|
undefined): ReadonlyArray<api.Diagnostic> {
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
getTsSemanticDiagnostics(
|
|
|
|
sourceFile?: ts.SourceFile|undefined,
|
|
|
|
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
|
|
|
return this.tsProgram.getSemanticDiagnostics(sourceFile, cancellationToken);
|
|
|
|
}
|
|
|
|
|
|
|
|
getNgSemanticDiagnostics(
|
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 15:01:55 -07:00
|
|
|
fileName?: string|undefined,
|
|
|
|
cancellationToken?: ts.CancellationToken|undefined): ReadonlyArray<ts.Diagnostic> {
|
2018-08-23 14:34:55 -07:00
|
|
|
const compilation = this.ensureAnalyzed();
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
const diagnostics = [...compilation.diagnostics, ...this.getTemplateDiagnostics()];
|
2018-12-13 11:52:20 -08:00
|
|
|
if (this.entryPoint !== null && this.exportReferenceGraph !== null) {
|
|
|
|
diagnostics.push(...checkForPrivateExports(
|
|
|
|
this.entryPoint, this.tsProgram.getTypeChecker(), this.exportReferenceGraph));
|
|
|
|
}
|
2018-09-21 14:03:55 -07:00
|
|
|
return diagnostics;
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
2018-06-26 15:01:09 -07:00
|
|
|
async loadNgStructureAsync(): Promise<void> {
|
|
|
|
if (this.compilation === undefined) {
|
|
|
|
this.compilation = this.makeCompilation();
|
|
|
|
}
|
2019-03-18 11:16:38 -07:00
|
|
|
const analyzeSpan = this.perfRecorder.start('analyze');
|
2018-07-26 13:32:23 -07:00
|
|
|
await Promise.all(this.tsProgram.getSourceFiles()
|
|
|
|
.filter(file => !file.fileName.endsWith('.d.ts'))
|
2019-03-18 11:16:38 -07:00
|
|
|
.map(file => {
|
|
|
|
|
|
|
|
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', file);
|
|
|
|
let analysisPromise = this.compilation !.analyzeAsync(file);
|
|
|
|
if (analysisPromise === undefined) {
|
|
|
|
this.perfRecorder.stop(analyzeFileSpan);
|
|
|
|
} else if (this.perfRecorder.enabled) {
|
|
|
|
analysisPromise = analysisPromise.then(
|
|
|
|
() => this.perfRecorder.stop(analyzeFileSpan));
|
|
|
|
}
|
|
|
|
return analysisPromise;
|
|
|
|
})
|
2018-07-26 13:32:23 -07:00
|
|
|
.filter((result): result is Promise<void> => result !== undefined));
|
2019-03-18 11:16:38 -07:00
|
|
|
this.perfRecorder.stop(analyzeSpan);
|
feat(ivy): detect cycles and use remote scoping of components if needed (#28169)
By its nature, Ivy alters the import graph of a TS program, adding imports
where template dependencies exist. For example, if ComponentA uses PipeB
in its template, Ivy will insert an import of PipeB into the file in which
ComponentA is declared.
Any insertion of an import into a program has the potential to introduce a
cycle into the import graph. If for some reason the file in which PipeB is
declared imports the file in which ComponentA is declared (maybe it makes
use of a service or utility function that happens to be in the same file as
ComponentA) then this could create an import cycle. This turns out to
happen quite regularly in larger Angular codebases.
TypeScript and the Ivy runtime have no issues with such cycles. However,
other tools are not so accepting. In particular the Closure Compiler is
very anti-cycle.
To mitigate this problem, it's necessary to detect when the insertion of
an import would create a cycle. ngtsc can then use a different strategy,
known as "remote scoping", instead of directly writing a reference from
one component to another. Under remote scoping, a function
'setComponentScope' is called after the declaration of the component's
module, which does not require the addition of new imports.
FW-647 #resolve
PR Close #28169
2019-01-15 12:32:10 -08:00
|
|
|
this.compilation.resolve();
|
2018-06-26 15:01:09 -07:00
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
|
2018-11-16 17:56:18 +01:00
|
|
|
listLazyRoutes(entryRoute?: string|undefined): api.LazyRoute[] {
|
2019-02-07 19:03:13 +02:00
|
|
|
if (entryRoute) {
|
|
|
|
// Note:
|
|
|
|
// This resolution step is here to match the implementation of the old `AotCompilerHost` (see
|
|
|
|
// https://github.com/angular/angular/blob/50732e156/packages/compiler-cli/src/transformers/compiler_host.ts#L175-L188).
|
|
|
|
//
|
|
|
|
// `@angular/cli` will always call this API with an absolute path, so the resolution step is
|
|
|
|
// not necessary, but keeping it backwards compatible in case someone else is using the API.
|
|
|
|
|
|
|
|
// Relative entry paths are disallowed.
|
|
|
|
if (entryRoute.startsWith('.')) {
|
|
|
|
throw new Error(
|
2019-02-19 17:29:04 +01:00
|
|
|
`Failed to list lazy routes: Resolution of relative paths (${entryRoute}) is not supported.`);
|
2019-02-07 19:03:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Non-relative entry paths fall into one of the following categories:
|
|
|
|
// - Absolute system paths (e.g. `/foo/bar/my-project/my-module`), which are unaffected by the
|
|
|
|
// logic below.
|
|
|
|
// - Paths to enternal modules (e.g. `some-lib`).
|
|
|
|
// - Paths mapped to directories in `tsconfig.json` (e.g. `shared/my-module`).
|
|
|
|
// (See https://www.typescriptlang.org/docs/handbook/module-resolution.html#path-mapping.)
|
|
|
|
//
|
|
|
|
// In all cases above, the `containingFile` argument is ignored, so we can just take the first
|
|
|
|
// of the root files.
|
|
|
|
const containingFile = this.tsProgram.getRootFileNames()[0];
|
|
|
|
const [entryPath, moduleName] = entryRoute.split('#');
|
2019-05-01 14:03:39 +01:00
|
|
|
const resolvedModule = resolveModuleName(entryPath, containingFile, this.options, this.host);
|
2019-02-07 19:03:13 +02:00
|
|
|
|
2019-05-01 14:03:39 +01:00
|
|
|
if (resolvedModule) {
|
|
|
|
entryRoute = entryPointKeyFor(resolvedModule.resolvedFileName, moduleName);
|
2019-02-07 19:03:13 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-11-16 17:56:18 +01:00
|
|
|
this.ensureAnalyzed();
|
2019-02-05 16:47:35 +02:00
|
|
|
return this.routeAnalyzer !.listLazyRoutes(entryRoute);
|
2018-11-16 17:56:18 +01:00
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
|
|
|
|
getLibrarySummaries(): Map<string, api.LibrarySummary> {
|
|
|
|
throw new Error('Method not implemented.');
|
|
|
|
}
|
|
|
|
|
|
|
|
getEmittedGeneratedFiles(): Map<string, GeneratedFile> {
|
|
|
|
throw new Error('Method not implemented.');
|
|
|
|
}
|
|
|
|
|
|
|
|
getEmittedSourceFiles(): Map<string, ts.SourceFile> {
|
|
|
|
throw new Error('Method not implemented.');
|
|
|
|
}
|
|
|
|
|
2018-08-23 14:34:55 -07:00
|
|
|
private ensureAnalyzed(): IvyCompilation {
|
|
|
|
if (this.compilation === undefined) {
|
2019-03-18 11:16:38 -07:00
|
|
|
const analyzeSpan = this.perfRecorder.start('analyze');
|
2018-08-23 14:34:55 -07:00
|
|
|
this.compilation = this.makeCompilation();
|
|
|
|
this.tsProgram.getSourceFiles()
|
|
|
|
.filter(file => !file.fileName.endsWith('.d.ts'))
|
2019-03-18 11:16:38 -07:00
|
|
|
.forEach(file => {
|
|
|
|
const analyzeFileSpan = this.perfRecorder.start('analyzeFile', file);
|
|
|
|
this.compilation !.analyzeSync(file);
|
|
|
|
this.perfRecorder.stop(analyzeFileSpan);
|
|
|
|
});
|
|
|
|
this.perfRecorder.stop(analyzeSpan);
|
feat(ivy): detect cycles and use remote scoping of components if needed (#28169)
By its nature, Ivy alters the import graph of a TS program, adding imports
where template dependencies exist. For example, if ComponentA uses PipeB
in its template, Ivy will insert an import of PipeB into the file in which
ComponentA is declared.
Any insertion of an import into a program has the potential to introduce a
cycle into the import graph. If for some reason the file in which PipeB is
declared imports the file in which ComponentA is declared (maybe it makes
use of a service or utility function that happens to be in the same file as
ComponentA) then this could create an import cycle. This turns out to
happen quite regularly in larger Angular codebases.
TypeScript and the Ivy runtime have no issues with such cycles. However,
other tools are not so accepting. In particular the Closure Compiler is
very anti-cycle.
To mitigate this problem, it's necessary to detect when the insertion of
an import would create a cycle. ngtsc can then use a different strategy,
known as "remote scoping", instead of directly writing a reference from
one component to another. Under remote scoping, a function
'setComponentScope' is called after the declaration of the component's
module, which does not require the addition of new imports.
FW-647 #resolve
PR Close #28169
2019-01-15 12:32:10 -08:00
|
|
|
this.compilation.resolve();
|
2018-08-23 14:34:55 -07:00
|
|
|
}
|
|
|
|
return this.compilation;
|
|
|
|
}
|
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
emit(opts?: {
|
|
|
|
emitFlags?: api.EmitFlags,
|
|
|
|
cancellationToken?: ts.CancellationToken,
|
|
|
|
customTransformers?: api.CustomTransformers,
|
|
|
|
emitCallback?: api.TsEmitCallback,
|
|
|
|
mergeEmitResultsCallback?: api.TsMergeEmitResultsCallback
|
|
|
|
}): ts.EmitResult {
|
|
|
|
const emitCallback = opts && opts.emitCallback || defaultEmitCallback;
|
|
|
|
|
2019-01-24 10:38:58 +00:00
|
|
|
const compilation = this.ensureAnalyzed();
|
2018-04-06 09:53:10 -07:00
|
|
|
|
|
|
|
const writeFile: ts.WriteFileCallback =
|
|
|
|
(fileName: string, data: string, writeByteOrderMark: boolean,
|
|
|
|
onError: ((message: string) => void) | undefined,
|
2019-03-20 17:47:55 -07:00
|
|
|
sourceFiles: ReadonlyArray<ts.SourceFile>| undefined) => {
|
2019-01-24 10:38:58 +00:00
|
|
|
if (this.closureCompilerEnabled && fileName.endsWith('.js')) {
|
2018-08-28 14:13:22 -07:00
|
|
|
data = nocollapseHack(data);
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
this.host.writeFile(fileName, data, writeByteOrderMark, onError, sourceFiles);
|
|
|
|
};
|
|
|
|
|
2019-01-03 12:23:00 +02:00
|
|
|
const customTransforms = opts && opts.customTransformers;
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
|
2019-02-19 17:36:26 -08:00
|
|
|
const beforeTransforms = [
|
|
|
|
ivyTransformFactory(
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
compilation, this.reflector, this.importRewriter, this.defaultImportTracker, this.isCore,
|
2019-02-19 17:36:26 -08:00
|
|
|
this.closureCompilerEnabled),
|
|
|
|
aliasTransformFactory(compilation.exportStatements) as ts.TransformerFactory<ts.SourceFile>,
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
this.defaultImportTracker.importPreservingTransformer(),
|
2019-02-19 17:36:26 -08:00
|
|
|
];
|
|
|
|
const afterDeclarationsTransforms = [
|
|
|
|
declarationTransformFactory(compilation),
|
|
|
|
];
|
|
|
|
|
2019-01-03 12:23:00 +02:00
|
|
|
|
2018-07-27 22:57:44 -07:00
|
|
|
if (this.factoryToSourceInfo !== null) {
|
2019-01-03 12:23:00 +02:00
|
|
|
beforeTransforms.push(
|
2019-01-08 13:02:11 -08:00
|
|
|
generatedFactoryTransform(this.factoryToSourceInfo, this.importRewriter));
|
2018-07-27 22:57:44 -07:00
|
|
|
}
|
2019-02-01 15:33:41 -08:00
|
|
|
beforeTransforms.push(ivySwitchTransform);
|
2019-01-03 12:23:00 +02:00
|
|
|
if (customTransforms && customTransforms.beforeTs) {
|
|
|
|
beforeTransforms.push(...customTransforms.beforeTs);
|
refactor(ivy): obviate the Bazel component of the ivy_switch (#26550)
Originally, the ivy_switch mechanism used Bazel genrules to conditionally
compile one TS file or another depending on whether ngc or ngtsc was the
selected compiler. This was done because we wanted to avoid importing
certain modules (and thus pulling them into the build) if Ivy was on or
off. This mechanism had a major drawback: ivy_switch became a bottleneck
in the import graph, as it both imports from many places in the codebase
and is imported by many modules in the codebase. This frequently resulted
in cyclic imports which caused issues both with TS and Closure compilation.
It turns out ngcc needs both code paths in the bundle to perform the switch
during its operation anyway, so import switching was later abandoned. This
means that there's no real reason why the ivy_switch mechanism needed to
operate at the Bazel level, and for the ivy_switch file to be a bottleneck.
This commit removes the Bazel-level ivy_switch mechanism, and introduces
an additional TypeScript transform in ngtsc (and the pass-through tsc
compiler used for testing JIT) to perform the same operation that ngcc
does, and flip the switch during ngtsc compilation. This allows the
ivy_switch file to be removed, and the individual switches to be located
directly next to their consumers in the codebase, greatly mitigating the
circular import issues and making the mechanism much easier to use.
As part of this commit, the tag for marking switched variables was changed
from __PRE_NGCC__ to __PRE_R3__, since it's no longer just ngcc which
flips these tags. Most variables were renamed from R3_* to SWITCH_* as well,
since they're referenced mostly in render2 code.
Test strategy: existing test coverage is more than sufficient - if this
didn't work correctly it would break the hello world and todo apps.
PR Close #26550
2018-10-17 15:44:44 -07:00
|
|
|
}
|
2019-01-03 12:23:00 +02:00
|
|
|
|
2019-03-18 11:16:38 -07:00
|
|
|
const emitSpan = this.perfRecorder.start('emit');
|
2019-03-06 14:26:56 -08:00
|
|
|
const emitResults: ts.EmitResult[] = [];
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
|
2019-06-06 20:22:32 +01:00
|
|
|
const typeCheckFile = getSourceFileOrNull(this.tsProgram, this.typeCheckFilePath);
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
|
2019-03-06 14:26:56 -08:00
|
|
|
for (const targetSourceFile of this.tsProgram.getSourceFiles()) {
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
if (targetSourceFile.isDeclarationFile || targetSourceFile === typeCheckFile) {
|
2019-03-06 14:26:56 -08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-05-08 15:10:50 +01:00
|
|
|
if (this.incrementalState.safeToSkip(targetSourceFile)) {
|
2019-03-18 12:25:26 -07:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2019-03-18 11:16:38 -07:00
|
|
|
const fileEmitSpan = this.perfRecorder.start('emitFile', targetSourceFile);
|
2019-03-06 14:26:56 -08:00
|
|
|
emitResults.push(emitCallback({
|
|
|
|
targetSourceFile,
|
|
|
|
program: this.tsProgram,
|
|
|
|
host: this.host,
|
|
|
|
options: this.options,
|
|
|
|
emitOnlyDtsFiles: false, writeFile,
|
|
|
|
customTransformers: {
|
|
|
|
before: beforeTransforms,
|
|
|
|
after: customTransforms && customTransforms.afterTs,
|
|
|
|
afterDeclarations: afterDeclarationsTransforms,
|
|
|
|
},
|
|
|
|
}));
|
2019-03-18 11:16:38 -07:00
|
|
|
this.perfRecorder.stop(fileEmitSpan);
|
|
|
|
}
|
|
|
|
this.perfRecorder.stop(emitSpan);
|
|
|
|
|
|
|
|
if (this.perfTracker !== null && this.options.tracePerformance !== undefined) {
|
|
|
|
this.perfTracker.serializeToFile(this.options.tracePerformance, this.host);
|
2019-03-06 14:26:56 -08:00
|
|
|
}
|
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
// Run the emit, including a custom transformer that will downlevel the Ivy decorators in code.
|
2019-03-06 14:26:56 -08:00
|
|
|
return ((opts && opts.mergeEmitResultsCallback) || mergeEmitResults)(emitResults);
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
2018-06-26 15:01:09 -07:00
|
|
|
|
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 15:01:55 -07:00
|
|
|
private getTemplateDiagnostics(): ReadonlyArray<ts.Diagnostic> {
|
2019-04-02 11:52:19 -07:00
|
|
|
// Skip template type-checking if it's disabled.
|
|
|
|
if (this.options.ivyTemplateTypeCheck === false &&
|
|
|
|
this.options.fullTemplateTypeCheck !== true) {
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
|
|
|
const compilation = this.ensureAnalyzed();
|
|
|
|
|
|
|
|
// Run template type-checking.
|
|
|
|
|
|
|
|
// First select a type-checking configuration, based on whether full template type-checking is
|
|
|
|
// requested.
|
|
|
|
let typeCheckingConfig: TypeCheckingConfig;
|
|
|
|
if (this.options.fullTemplateTypeCheck) {
|
|
|
|
typeCheckingConfig = {
|
|
|
|
applyTemplateContextGuards: true,
|
2019-04-24 11:53:12 -07:00
|
|
|
checkQueries: false,
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
checkTemplateBodies: true,
|
2019-08-16 16:45:33 -07:00
|
|
|
checkTypeOfInputBindings: true,
|
2019-10-11 19:41:05 +02:00
|
|
|
strictNullInputBindings: true,
|
2019-08-16 16:45:33 -07:00
|
|
|
// Even in full template type-checking mode, DOM binding checks are not quite ready yet.
|
|
|
|
checkTypeOfDomBindings: false,
|
2019-10-12 18:38:47 +02:00
|
|
|
checkTypeOfOutputEvents: true,
|
|
|
|
checkTypeOfAnimationEvents: true,
|
|
|
|
// Checking of DOM events currently has an adverse effect on developer experience,
|
|
|
|
// e.g. for `<input (blur)="update($event.target.value)">` enabling this check results in:
|
|
|
|
// - error TS2531: Object is possibly 'null'.
|
|
|
|
// - error TS2339: Property 'value' does not exist on type 'EventTarget'.
|
|
|
|
checkTypeOfDomEvents: false,
|
2019-04-02 10:27:33 -07:00
|
|
|
checkTypeOfPipes: true,
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
strictSafeNavigationTypes: true,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
typeCheckingConfig = {
|
|
|
|
applyTemplateContextGuards: false,
|
2019-04-24 11:53:12 -07:00
|
|
|
checkQueries: false,
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
checkTemplateBodies: false,
|
2019-08-16 16:45:33 -07:00
|
|
|
checkTypeOfInputBindings: false,
|
2019-10-11 19:41:05 +02:00
|
|
|
strictNullInputBindings: false,
|
2019-08-16 16:45:33 -07:00
|
|
|
checkTypeOfDomBindings: false,
|
2019-10-12 18:38:47 +02:00
|
|
|
checkTypeOfOutputEvents: false,
|
|
|
|
checkTypeOfAnimationEvents: false,
|
|
|
|
checkTypeOfDomEvents: false,
|
2019-04-02 10:27:33 -07:00
|
|
|
checkTypeOfPipes: false,
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
strictSafeNavigationTypes: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
// Execute the typeCheck phase of each decorator in the program.
|
|
|
|
const prepSpan = this.perfRecorder.start('typeCheckPrep');
|
|
|
|
const ctx = new TypeCheckContext(typeCheckingConfig, this.refEmitter !, this.typeCheckFilePath);
|
|
|
|
compilation.typeCheck(ctx);
|
|
|
|
this.perfRecorder.stop(prepSpan);
|
|
|
|
|
|
|
|
// Get the diagnostics.
|
|
|
|
const typeCheckSpan = this.perfRecorder.start('typeCheckDiagnostics');
|
2019-04-24 10:26:47 -07:00
|
|
|
const {diagnostics, program} =
|
|
|
|
ctx.calculateTemplateDiagnostics(this.tsProgram, this.host, this.options);
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
this.perfRecorder.stop(typeCheckSpan);
|
2019-04-24 10:26:47 -07:00
|
|
|
this.reuseTsProgram = program;
|
perf(ivy): template type-check the entire program in 1 file if possible (#29698)
The template type-checking engine previously would assemble a type-checking
program by inserting Type Check Blocks (TCBs) into existing user files. This
approach proved expensive, as TypeScript has to re-parse and re-type-check
those files when processing the type-checking program.
Instead, a far more performant approach is to augment the program with a
single type-checking file, into which all TCBs are generated. Additionally,
type constructors are also inlined into this file.
This is not always possible - both TCBs and type constructors can sometimes
require inlining into user code, particularly if bound generic type
parameters are present, so the approach taken is actually a hybrid. These
operations are inlined if necessary, but are otherwise generated in a single
file.
It is critically important that the original program also include an empty
version of the type-checking file, otherwise the shape of the two programs
will be different and TypeScript will throw away all the old program
information. This leads to a painfully slow type checking pass, on the same
order as the original program creation. A shim to generate this file in the
original program is therefore added.
Testing strategy: this commit is largely a refactor with no externally
observable behavioral differences, and thus no tests are needed.
PR Close #29698
2019-04-02 11:25:33 -07:00
|
|
|
|
|
|
|
return diagnostics;
|
2018-09-21 14:03:55 -07:00
|
|
|
}
|
|
|
|
|
2019-06-19 17:23:59 -07:00
|
|
|
getIndexedComponents(): Map<ts.Declaration, IndexedComponent> {
|
|
|
|
const compilation = this.ensureAnalyzed();
|
|
|
|
const context = new IndexingContext();
|
|
|
|
compilation.index(context);
|
|
|
|
return generateAnalysis(context);
|
|
|
|
}
|
|
|
|
|
2018-06-26 15:01:09 -07:00
|
|
|
private makeCompilation(): IvyCompilation {
|
|
|
|
const checker = this.tsProgram.getTypeChecker();
|
2019-02-19 17:36:26 -08:00
|
|
|
|
|
|
|
let aliasGenerator: AliasGenerator|null = null;
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
// Construct the ReferenceEmitter.
|
|
|
|
if (this.fileToModuleHost === null || !this.options._useHostForImportGeneration) {
|
|
|
|
// The CompilerHost doesn't have fileNameToModuleName, so build an NPM-centric reference
|
|
|
|
// resolution strategy.
|
|
|
|
this.refEmitter = new ReferenceEmitter([
|
|
|
|
// First, try to use local identifiers if available.
|
|
|
|
new LocalIdentifierStrategy(),
|
|
|
|
// Next, attempt to use an absolute import.
|
2019-05-01 14:58:59 +01:00
|
|
|
new AbsoluteModuleStrategy(
|
|
|
|
this.tsProgram, checker, this.options, this.host, this.reflector),
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
// Finally, check if the reference is being written into a file within the project's logical
|
|
|
|
// file system, and use a relative import if so. If this fails, ReferenceEmitter will throw
|
|
|
|
// an error.
|
2019-10-02 10:54:23 +03:00
|
|
|
new LogicalProjectStrategy(this.reflector, new LogicalFileSystem(this.rootDirs)),
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
]);
|
|
|
|
} else {
|
|
|
|
// The CompilerHost supports fileNameToModuleName, so use that to emit imports.
|
|
|
|
this.refEmitter = new ReferenceEmitter([
|
|
|
|
// First, try to use local identifiers if available.
|
|
|
|
new LocalIdentifierStrategy(),
|
2019-02-19 17:36:26 -08:00
|
|
|
// Then use aliased references (this is a workaround to StrictDeps checks).
|
|
|
|
new AliasStrategy(),
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
// Then use fileNameToModuleName to emit imports.
|
2019-10-02 10:54:23 +03:00
|
|
|
new FileToModuleStrategy(this.reflector, this.fileToModuleHost),
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
]);
|
2019-02-19 17:36:26 -08:00
|
|
|
aliasGenerator = new AliasGenerator(this.fileToModuleHost);
|
feat(ivy): use fileNameToModuleName to emit imports when it's available (#28523)
The ultimate goal of this commit is to make use of fileNameToModuleName to
get the module specifier to use when generating an import, when that API is
available in the CompilerHost that ngtsc is created with.
As part of getting there, the way in which ngtsc tracks references and
generates import module specifiers is refactored considerably. References
are tracked with the Reference class, and previously ngtsc had several
different kinds of Reference. An AbsoluteReference represented a declaration
which needed to be imported via an absolute module specifier tracked in the
AbsoluteReference, and a RelativeReference represented a declaration from
the local program, imported via relative path or referred to directly by
identifier if possible. Thus, how to refer to a particular declaration was
encoded into the Reference type _at the time of creation of the Reference_.
This commit refactors that logic and reduces Reference to a single class
with no subclasses. A Reference represents a node being referenced, plus
context about how the node was located. This context includes a
"bestGuessOwningModule", the compiler's best guess at which absolute
module specifier has defined this reference. For example, if the compiler
arrives at the declaration of CommonModule via an import to @angular/common,
then any references obtained from CommonModule (e.g. NgIf) will also be
considered to be owned by @angular/common.
A ReferenceEmitter class and accompanying ReferenceEmitStrategy interface
are introduced. To produce an Expression referring to a given Reference'd
node, the ReferenceEmitter consults a sequence of ReferenceEmitStrategy
implementations.
Several different strategies are defined:
- LocalIdentifierStrategy: use local ts.Identifiers if available.
- AbsoluteModuleStrategy: if the Reference has a bestGuessOwningModule,
import the node via an absolute import from that module specifier.
- LogicalProjectStrategy: if the Reference is in the logical project
(is under the project rootDirs), import the node via a relative import.
- FileToModuleStrategy: use a FileToModuleHost to generate the module
specifier by which to import the node.
Depending on the availability of fileNameToModuleName in the CompilerHost,
then, a different collection of these strategies is used for compilation.
PR Close #28523
2019-02-01 17:24:21 -08:00
|
|
|
}
|
|
|
|
|
2019-05-08 17:36:11 +01:00
|
|
|
const evaluator = new PartialEvaluator(this.reflector, checker, this.incrementalState);
|
2019-03-26 14:02:16 -07:00
|
|
|
const dtsReader = new DtsMetadataReader(checker, this.reflector);
|
|
|
|
const localMetaRegistry = new LocalMetadataRegistry();
|
2019-05-08 15:10:50 +01:00
|
|
|
const localMetaReader = new CompoundMetadataReader([localMetaRegistry, this.incrementalState]);
|
2019-03-26 14:02:16 -07:00
|
|
|
const depScopeReader = new MetadataDtsModuleScopeResolver(dtsReader, aliasGenerator);
|
|
|
|
const scopeRegistry = new LocalModuleScopeRegistry(
|
2019-07-31 17:11:33 +01:00
|
|
|
localMetaReader, depScopeReader, this.refEmitter, aliasGenerator, this.incrementalState);
|
|
|
|
const scopeReader = new CompoundComponentScopeReader([scopeRegistry, this.incrementalState]);
|
2019-05-08 15:10:50 +01:00
|
|
|
const metaRegistry =
|
|
|
|
new CompoundMetadataRegistry([localMetaRegistry, scopeRegistry, this.incrementalState]);
|
2019-03-26 14:02:16 -07:00
|
|
|
|
2019-05-08 15:10:50 +01:00
|
|
|
this.metaReader = new CompoundMetadataReader([localMetaReader, dtsReader]);
|
2019-04-01 14:20:34 -07:00
|
|
|
|
2019-03-26 14:02:16 -07:00
|
|
|
|
|
|
|
// If a flat module entrypoint was specified, then track references via a `ReferenceGraph`
|
|
|
|
// in
|
|
|
|
// order to produce proper diagnostics for incorrectly exported directives/pipes/etc. If
|
|
|
|
// there
|
2018-12-13 11:52:20 -08:00
|
|
|
// is no flat module entrypoint then don't pay the cost of tracking references.
|
|
|
|
let referencesRegistry: ReferencesRegistry;
|
|
|
|
if (this.entryPoint !== null) {
|
|
|
|
this.exportReferenceGraph = new ReferenceGraph();
|
|
|
|
referencesRegistry = new ReferenceGraphAdapter(this.exportReferenceGraph);
|
|
|
|
} else {
|
|
|
|
referencesRegistry = new NoopReferencesRegistry();
|
|
|
|
}
|
2018-06-26 15:01:09 -07:00
|
|
|
|
2018-11-16 17:56:18 +01:00
|
|
|
this.routeAnalyzer = new NgModuleRouteAnalyzer(this.moduleResolver, evaluator);
|
|
|
|
|
2018-06-26 15:01:09 -07:00
|
|
|
// Set up the IvyCompilation, which manages state for the Ivy transformer.
|
|
|
|
const handlers = [
|
2019-03-08 15:32:31 -08:00
|
|
|
new BaseDefDecoratorHandler(this.reflector, evaluator, this.isCore),
|
2018-06-26 15:01:09 -07:00
|
|
|
new ComponentDecoratorHandler(
|
2019-07-31 17:11:33 +01:00
|
|
|
this.reflector, evaluator, metaRegistry, this.metaReader !, scopeReader, scopeRegistry,
|
|
|
|
this.isCore, this.resourceManager, this.rootDirs,
|
|
|
|
this.options.preserveWhitespaces || false, this.options.i18nUseExternalIds !== false,
|
2019-10-09 12:34:37 +01:00
|
|
|
this.getI18nLegacyMessageFormat(), this.moduleResolver, this.cycleAnalyzer,
|
2019-10-01 14:58:49 +01:00
|
|
|
this.refEmitter, this.defaultImportTracker, this.incrementalState),
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
new DirectiveDecoratorHandler(
|
2019-03-26 14:02:16 -07:00
|
|
|
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
|
feat(ivy): compile @Injectable on classes not meant for DI (#28523)
In the past, @Injectable had no side effects and existing Angular code is
therefore littered with @Injectable usage on classes which are not intended
to be injected.
A common example is:
@Injectable()
class Foo {
constructor(private notInjectable: string) {}
}
and somewhere else:
providers: [{provide: Foo, useFactory: ...})
Here, there is no need for Foo to be injectable - indeed, it's impossible
for the DI system to create an instance of it, as it has a non-injectable
constructor. The provider configures a factory for the DI system to be
able to create instances of Foo.
Adding @Injectable in Ivy signifies that the class's own constructor, and
not a provider, determines how the class will be created.
This commit adds logic to compile classes which are marked with @Injectable
but are otherwise not injectable, and create an ngInjectableDef field with
a factory function that throws an error. This way, existing code in the wild
continues to compile, but if someone attempts to use the injectable it will
fail with a useful error message.
In the case where strictInjectionParameters is set to true, a compile-time
error is thrown instead of the runtime error, as ngtsc has enough
information to determine when injection couldn't possibly be valid.
PR Close #28523
2019-01-31 14:23:54 -08:00
|
|
|
new InjectableDecoratorHandler(
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
this.reflector, this.defaultImportTracker, this.isCore,
|
|
|
|
this.options.strictInjectionParameters || false),
|
2018-11-13 14:40:54 +00:00
|
|
|
new NgModuleDecoratorHandler(
|
2019-08-12 14:56:30 -07:00
|
|
|
this.reflector, evaluator, this.metaReader, metaRegistry, scopeRegistry,
|
|
|
|
referencesRegistry, this.isCore, this.routeAnalyzer, this.refEmitter,
|
|
|
|
this.defaultImportTracker, this.options.i18nInLocale),
|
fix(ivy): reuse default imports in type-to-value references (#29266)
This fixes an issue with commit b6f6b117. In this commit, default imports
processed in a type-to-value conversion were recorded as non-local imports
with a '*' name, and the ImportManager generated a new default import for
them. When transpiled to ES2015 modules, this resulted in the following
correct code:
import i3 from './module';
// somewhere in the file, a value reference of i3:
{type: i3}
However, when the AST with this synthetic import and reference was
transpiled to non-ES2015 modules (for example, to commonjs) an issue
appeared:
var module_1 = require('./module');
{type: i3}
TypeScript renames the imported identifier from i3 to module_1, but doesn't
substitute later references to i3. This is because the import and reference
are both synthetic, and never went through the TypeScript AST step of
"binding" which associates the reference to its import. This association is
important during emit when the identifiers might change.
Synthetic (transformer-added) imports will never be bound properly. The only
possible solution is to reuse the user's original import and the identifier
from it, which will be properly downleveled. The issue with this approach
(which prompted the fix in b6f6b117) is that if the import is only used in a
type position, TypeScript will mark it for deletion in the generated JS,
even though additional non-type usages are added in the transformer. This
again would leave a dangling import.
To work around this, it's necessary for the compiler to keep track of
identifiers that it emits which came from default imports, and tell TS not
to remove those imports during transpilation. A `DefaultImportTracker` class
is implemented to perform this tracking. It implements a
`DefaultImportRecorder` interface, which is used to record two significant
pieces of information:
* when a WrappedNodeExpr is generated which refers to a default imported
value, the ts.Identifier is associated to the ts.ImportDeclaration via
the recorder.
* when that WrappedNodeExpr is later emitted as part of the statement /
expression translators, the fact that the ts.Identifier was used is
also recorded.
Combined, this tracking gives the `DefaultImportTracker` enough information
to implement another TS transformer, which can recognize default imports
which were used in the output of the Ivy transform and can prevent them
from being elided. This is done by creating a new ts.ImportDeclaration for
the imports with the same ts.ImportClause. A test verifies that this works.
PR Close #29266
2019-03-11 16:54:07 -07:00
|
|
|
new PipeDecoratorHandler(
|
2019-03-26 14:02:16 -07:00
|
|
|
this.reflector, evaluator, metaRegistry, this.defaultImportTracker, this.isCore),
|
2018-06-26 15:01:09 -07:00
|
|
|
];
|
|
|
|
|
2018-07-27 22:57:44 -07:00
|
|
|
return new IvyCompilation(
|
2019-05-08 15:10:50 +01:00
|
|
|
handlers, this.reflector, this.importRewriter, this.incrementalState, this.perfRecorder,
|
|
|
|
this.sourceToFactorySymbols, scopeRegistry);
|
2018-06-26 15:01:09 -07:00
|
|
|
}
|
|
|
|
|
2019-10-09 12:34:37 +01:00
|
|
|
private getI18nLegacyMessageFormat(): string {
|
|
|
|
return this.options.enableI18nLegacyMessageIdFormat !== false && this.options.i18nInFormat ||
|
|
|
|
'';
|
|
|
|
}
|
|
|
|
|
2018-06-26 15:01:09 -07:00
|
|
|
private get reflector(): TypeScriptReflectionHost {
|
|
|
|
if (this._reflector === undefined) {
|
|
|
|
this._reflector = new TypeScriptReflectionHost(this.tsProgram.getTypeChecker());
|
|
|
|
}
|
|
|
|
return this._reflector;
|
|
|
|
}
|
|
|
|
|
|
|
|
private get coreImportsFrom(): ts.SourceFile|null {
|
|
|
|
if (this._coreImportsFrom === undefined) {
|
|
|
|
this._coreImportsFrom = this.isCore && getR3SymbolsFile(this.tsProgram) || null;
|
|
|
|
}
|
|
|
|
return this._coreImportsFrom;
|
|
|
|
}
|
|
|
|
|
|
|
|
private get isCore(): boolean {
|
|
|
|
if (this._isCore === undefined) {
|
|
|
|
this._isCore = isAngularCorePackage(this.tsProgram);
|
|
|
|
}
|
|
|
|
return this._isCore;
|
|
|
|
}
|
2019-01-08 11:49:58 -08:00
|
|
|
|
|
|
|
private get importRewriter(): ImportRewriter {
|
|
|
|
if (this._importRewriter === undefined) {
|
|
|
|
const coreImportsFrom = this.coreImportsFrom;
|
|
|
|
this._importRewriter = coreImportsFrom !== null ?
|
|
|
|
new R3SymbolsImportRewriter(coreImportsFrom.fileName) :
|
|
|
|
new NoopImportRewriter();
|
|
|
|
}
|
|
|
|
return this._importRewriter;
|
|
|
|
}
|
2018-04-06 09:53:10 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
const defaultEmitCallback: api.TsEmitCallback =
|
|
|
|
({program, targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles,
|
|
|
|
customTransformers}) =>
|
|
|
|
program.emit(
|
|
|
|
targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
|
|
|
|
|
|
|
|
function mergeEmitResults(emitResults: ts.EmitResult[]): ts.EmitResult {
|
|
|
|
const diagnostics: ts.Diagnostic[] = [];
|
|
|
|
let emitSkipped = false;
|
|
|
|
const emittedFiles: string[] = [];
|
|
|
|
for (const er of emitResults) {
|
|
|
|
diagnostics.push(...er.diagnostics);
|
|
|
|
emitSkipped = emitSkipped || er.emitSkipped;
|
|
|
|
emittedFiles.push(...(er.emittedFiles || []));
|
|
|
|
}
|
2019-03-18 11:16:38 -07:00
|
|
|
|
2018-04-06 09:53:10 -07:00
|
|
|
return {diagnostics, emitSkipped, emittedFiles};
|
|
|
|
}
|
2018-06-20 15:54:16 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Find the 'r3_symbols.ts' file in the given `Program`, or return `null` if it wasn't there.
|
|
|
|
*/
|
|
|
|
function getR3SymbolsFile(program: ts.Program): ts.SourceFile|null {
|
|
|
|
return program.getSourceFiles().find(file => file.fileName.indexOf('r3_symbols.ts') >= 0) || null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Determine if the given `Program` is @angular/core.
|
|
|
|
*/
|
|
|
|
function isAngularCorePackage(program: ts.Program): boolean {
|
|
|
|
// Look for its_just_angular.ts somewhere in the program.
|
|
|
|
const r3Symbols = getR3SymbolsFile(program);
|
|
|
|
if (r3Symbols === null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Look for the constant ITS_JUST_ANGULAR in that file.
|
|
|
|
return r3Symbols.statements.some(stmt => {
|
|
|
|
// The statement must be a variable declaration statement.
|
|
|
|
if (!ts.isVariableStatement(stmt)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// It must be exported.
|
|
|
|
if (stmt.modifiers === undefined ||
|
|
|
|
!stmt.modifiers.some(mod => mod.kind === ts.SyntaxKind.ExportKeyword)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// It must declare ITS_JUST_ANGULAR.
|
|
|
|
return stmt.declarationList.declarations.some(decl => {
|
|
|
|
// The declaration must match the name.
|
|
|
|
if (!ts.isIdentifier(decl.name) || decl.name.text !== 'ITS_JUST_ANGULAR') {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// It must initialize the variable to true.
|
|
|
|
if (decl.initializer === undefined || decl.initializer.kind !== ts.SyntaxKind.TrueKeyword) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// This definition matches.
|
|
|
|
return true;
|
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
2018-12-13 11:52:20 -08:00
|
|
|
|
|
|
|
export class ReferenceGraphAdapter implements ReferencesRegistry {
|
|
|
|
constructor(private graph: ReferenceGraph) {}
|
|
|
|
|
|
|
|
add(source: ts.Declaration, ...references: Reference<ts.Declaration>[]): void {
|
|
|
|
for (const {node} of references) {
|
|
|
|
let sourceFile = node.getSourceFile();
|
|
|
|
if (sourceFile === undefined) {
|
|
|
|
sourceFile = ts.getOriginalNode(node).getSourceFile();
|
|
|
|
}
|
|
|
|
|
|
|
|
// Only record local references (not references into .d.ts files).
|
|
|
|
if (sourceFile === undefined || !isDtsPath(sourceFile.fileName)) {
|
|
|
|
this.graph.add(source, node);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-03-18 11:16:38 -07:00
|
|
|
|
|
|
|
function shouldEnablePerfTracing(options: api.CompilerOptions): boolean {
|
|
|
|
return options.tracePerformance !== undefined;
|
|
|
|
}
|