Currently the decorator handlers are run against all `SourceFile`s in the compilation, but we shouldn't be doing it against declaration files. This initially came up as a CI issue in #33264 where it was worked around only for the `DirectiveDecoratorHandler`. These changes move the logic into the `TraitCompiler` and `DecorationAnalyzer` so that it applies to all of the handlers.
PR Close#34557
The major one that affects the angular repo is the removal of the bootstrap attribute in nodejs_binary, nodejs_test and jasmine_node_test in favor of using templated_args --node_options=--require=/path/to/script. The side-effect of this is that the bootstrap script does not get the require.resolve patches with explicitly loading the targets _loader.js file.
PR Close#34589
Previously, the `CommonJsReflectionHost` and `UmdReflectionHost` would
only recognize re-exports of the form `__export(...)`. This is what
re-exports look like, when the TypeScript helpers are emitted inline
(i.e. when compiling with the default [TypeScript compiler options][1]
that include `noEmitHelpers: false` and `importHelpers: false`).
However, when compiling with `importHelpers: true` and [tslib][2] (which
is the recommended way for optimized bundles), the re-exports will look
like: `tslib_1.__exportStar(..., exports)`
These types of re-exports were previously not recognized by the
CommonJS/UMD `ReflectionHost`s and thus ignored.
This commit fixes this by ensuring both re-export formats are
recognized.
[1]: https://www.typescriptlang.org/docs/handbook/compiler-options.html
[2]: https://www.npmjs.com/package/tslib
PR Close#34527
If a class was defined as a class expression
in a variable declaration, the definitions
were being inserted before the statment's
final semi-colon.
Now the insertion point will be after the
full statement.
Fixes#34648
PR Close#34677
In some cases, where a module imports a dependency
but does not actually use it, UMD bundlers may remove
the dependency parameter from the UMD factory function
definition.
For example:
```
import * as x from 'x';
import * as z from 'z';
export const y = x;
```
may result in a UMD bundle including:
```
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ?
factory(exports, require('x'), require('z')) :
typeof define === 'function' && define.amd ?
define(['exports', 'x', 'z'], factory) :
(global = global || self, factory(global.myBundle = {}, global.x));
}(this, (function (exports, x) { 'use strict';
...
})));
```
Note that while the `z` dependency is provide in the call,
the factory itself only accepts `exports` and `x` as parameters.
Previously ngcc appended new dependencies to the end of the factory
function, but this breaks in the above scenario. Now the new
dependencies are prefixed at the front of parameters/arguments
already in place.
Fixes#34653
PR Close#34660
In some cases TypeScript is unable to identify a valid
symbol for an export. In this case it returns an "unknown"
symbol, which does not reference any declarations.
This fix ensures that ngcc does not crash if such a symbol
is encountered by checking whether `symbol.declarations`
exists before accessing it.
The commit does not contain a unit test as it was not possible
to recreate a scenario that had such an "unknown" symbol in
the unit test environment. The fix has been manually checked
against that original issue; and also this check is equivalent to
similar checks elsewhere in the code, e.g.
https://github.com/angular/angular/blob/8d0de89e/packages/compiler-cli/src/ngtsc/reflection/src/typescript.ts#L309Fixes#34560
PR Close#34658
Previously, in cases were values were expensive to compute and would be
used multiple times, a combination of a regular `Map` and a helper
function (`getOrDefault()`) was used to ensure values were only computed
once.
This commit uses a special `Map`-like structure to compute and memoize
such expensive values without the need to a helper function.
PR Close#34512
This change should not have any impact on the code's behavior (based on
how the function is currently used), but it will avoid unnecessary work.
PR Close#34512
While different, CommonJS and UMD have a lot in common regarding the
their exports are constructed. Therefore, there was some code
duplication between `CommonJsReflectionHost` and `UmdReflectionHost`.
This commit extracts some of the common bits into a separate file as
helpers to allow reusing the code in both `ReflectionHost`s.
PR Close#34512
Previously, `UmdReflectionHost` would only recognize re-exports of the
form `__export(someIdentifier)` and not `__export(require('...'))`.
However, it is possible in some UMD variations to have the latter format
as well. See discussion in https://github.com/angular/angular/pull/34254/files#r359515373
This commit adds support for re-export of the form
`__export(require('...'))` in UMD.
PR Close#34512
This fix was part of a broader `ngtsc`/`ngcc` fix in 02bab8cf9 (see
there for details). In 02bab8cf9, the fix was only applied to
`CommonJsReflectionHost`, but it is equally applicable to
`UmdReflectionHost`. Later in #34254, the fix was partially ported to
`UmdReflectionHost` by fixing the `extractUmdReexports()` method.
This commit fully fixes `ngcc`'s handling of inline exports for code in
UMD format.
PR Close#34512
The `getProjectAsAttrValue` in `node_selector_matcher` finds the
ProjectAs marker and then additionally checks that the marker appears in
an even index of the node attributes because "attribute names are stored
at even indexes". This is true for "regular" attribute bindings but
classes, styles, bindings, templates, and i18n do not necessarily follow
this rule because there can be an uneven number of them, causing the
next "special" attribute "name" to appear at an odd index. To address
this issue, ensure ngProjectAs is placed right after "regular"
attributes.
PR Close#34617
Previously, if `UmdRenderingFormatter#addImports()` was called with an
empty list of imports to add (i.e. no new imports were needed), it would
add trailing commas in several locations (arrays, function arguments,
function parameters), thus making the code imcompatible with legacy
browsers such as IE11.
This commit fixes it by ensuring that no trailing commas are added if
`addImports()` is called with an empty list of imports.
This is a follow-up to #34353.
Fixes#34525
PR Close#34545
ngcc computes a dependency graph of entry-points to ensure that
entry-points are processed in the correct order. Previously only the imports
in source files were analysed to determine the dependencies for each
entry-point.
This is not sufficient when an entry-point has a "type-only" dependency
- for example only importing an interface from another entry-point.
In this case the "type-only" import does not appear in the
source code. It only appears in the typings files. This can cause a
dependency to be missed on the entry-point.
This commit fixes this by additionally processing the imports in the
typings program, as well as the source program.
Note that these missing dependencies could cause unexpected flakes when
running ngcc in async mode on multiple processes due to the way that
ngcc caches files when they are first read from disk.
Fixes#34411
// FW-1781
PR Close#34494
The `DependencyHost` implementations were duplicating the "postfix" strings
which are used to find matching paths when resolving module specifiers.
Now the hosts reuse the postfixes given to the `ModuleResolver` that is
passed to the host.
PR Close#34494
Rather than return a new object of dependency info from calls to
`collectDependencies()` we now pass in an object that will be updated
with the dependency info. This is in preparation of a change where
we will collect dependency information from more than one
`DependencyHost`.
Also to better fit with this approach the name is changed from
`findDependencies()` to `collectDependencies()`.
PR Close#34494
Prior to this commit, there were no `advance` instructions generated before `i18nExp` instructions and as a result, lifecycle hooks for components used inside i18n blocks were flushed too late. This commit adds the logic to generate `advance` instructions in front of `i18nExp` ones (similar to what we have in other places like interpolations, property bindings, etc), so that the necessary lifecycle hooks are flushed before expression value is captured.
PR Close#34436
Previously, it was required that both `fullTemplateTypeCheck` and
`strictTemplates` had to be enabled for strict mode to be enabled. This
is strange, as `strictTemplates` implies `fullTemplateTypeCheck`. This
commit makes setting the `fullTemplateTypeCheck` flag optional so that
strict mode can be enabled by just setting `strictTemplates`.
PR Close#34195
It is now an error if '"fullTemplateTypeCheck"' is disabled while
`"strictTemplates"` is enabled, as enabling the latter implies that the
former is also enabled.
PR Close#34195
The compiler has a translation mechanism to convert from an Angular
`Type` to a `ts.TypeNode`, as appropriate. Prior to this change, it
would translate certain Angular expressions into their value equivalent
in TypeScript, instead of the correct type equivalent. This was possible
as the `ExpressionVisitor` interface is not strictly typed, with `any`s
being used for return values.
For example, a literal object was translated into a
`ts.ObjectLiteralExpression`, containing `ts.PropertyAssignment` nodes
as its entries. This has worked without issues as their printed
representation is identical, however it was incorrect from a semantic
point of view. Instead, a `ts.TypeLiteralNode` is created with
`ts.PropertySignature` as its members, which corresponds with the type
declaration of an object literal.
PR Close#34021
In Ivy's template type checker, type constructors are created for all
directive types to allow for accurate type inference to work. The type
checker has two strategies for dealing with such type constructors:
1. They can be emitted local to the type check block/type check file.
2. They can be emitted as static `ngTypeCtor` field into the directive
itself.
The first strategy is preferred, as it avoids having to update the
directive type which would cause a more expensive rebuild. However, this
strategy is not suitable for directives that have constrained generic
types, as those constraints would need to be present on the local type
constructor declaration. This is not trivial, as it requires that any
type references within a type parameter's constraint are imported into
the local context of the type check block.
For example, lets consider the `NgForOf` directive from '@angular/core'
looks as follows:
```typescript
import {NgIterable} from '@angular/core';
export class NgForOf<T, U extends NgIterable<T>> {}
```
The type constructor will then have the signature:
`(o: Pick<i1.NgForOf<T, U>, 'ngForOf'>) => i1.NgForOf<T, U>`
Notice how this refers to the type parameters `T` and `U`, so the type
constructor needs to be emitted into a scope where those types are
available, _and_ have the correct constraints.
Previously, the template type checker would detect the situation where a
type parameter is constrained, and would emit the type constructor
using strategy 2; within the directive type itself. This approach makes
any type references within the generic type constraints lexically
available:
```typescript
export class NgForOf<T, U extends NgIterable<T>> {
static ngTypeCtor<T = any, U extends NgIterable<T> = any>
(o: Pick<NgForOf<T, U>, 'ngForOf'>): NgForOf<T, U> { return null!; }
}
```
This commit introduces the ability to emit a type parameter with
constraints into a different context, under the condition that it can
be imported from an absolute module. This allows a generic type
constructor to be emitted into a type check block or type check file
according to strategy 1, as imports have been generated for all type
references within generic type constraints. For example:
```typescript
import * as i0 from '@angular/core';
import * as i1 from '@angular/common';
const _ctor1: <T = any, U extends i0.NgIterable<T> = any>
(o: Pick<i1.NgForOf<T, U>, 'ngForOf'>) => i1.NgForOf<T, U> = null!;
```
Notice how the generic type constraint of `U` has resulted in an import
of `@angular/core`, and the `NgIterable` is transformed into a qualified
name during the emitting process.
Resolves FW-1739
PR Close#34021
Angular View Engine uses global knowledge to compile the following code:
```typescript
export class Base {
constructor(private vcr: ViewContainerRef) {}
}
@Directive({...})
export class Dir extends Base {
// constructor inherited from base
}
```
Here, `Dir` extends `Base` and inherits its constructor. To create a `Dir`
the arguments to this inherited constructor must be obtained via dependency
injection. View Engine is able to generate a correct factory for `Dir` to do
this because via metadata it knows the arguments of `Base`'s constructor,
even if `Base` is declared in a different library.
In Ivy, DI is entirely a runtime concept. Currently `Dir` is compiled with
an ngDirectiveDef field that delegates its factory to `getInheritedFactory`.
This looks for some kind of factory function on `Base`, which comes up
empty. This case looks identical to an inheritance chain with no
constructors, which works today in Ivy.
Both of these cases will now become an error in this commit. If a decorated
class inherits from an undecorated base class, a diagnostic is produced
informing the user of the need to either explicitly declare a constructor or
to decorate the base class.
PR Close#34460
Adds a compilation error if the consumer tries to pass in an undecorated class into the `providers` of an `NgModule`, or the `providers`/`viewProviders` arrays of a `Directive`/`Component`.
PR Close#34460
The function `makeTemplateDiagnostic` was accepting an error code of type
`number`, making it easy to accidentally pass an `ErrorCode` directly and
not convert it to an Angular diagnostic code first.
This commit refactors `makeTemplateDiagnostic` to accept `ErrorCode` up
front, and convert it internally. This is less error-prone.
PR Close#34460
Previously, ngtsc would perform scope analysis (which directives/pipes are
available inside a component's template) and template type-checking of that
template as separate steps. If a component's scope was somehow invalid (e.g.
its NgModule imported something which wasn't another NgModule), the
component was treated as not having a scope. This meant that during template
type-checking, errors would be produced for any invalid expressions/usage of
other components that should have been in the scope.
This commit changes ngtsc to skip template type-checking of a component if
its scope is erroneous (as opposed to not present in the first place). Thus,
users aren't overwhelmed with diagnostic errors for the template and are
only informed of the root cause of the problem: an invalid NgModule scope.
Fixes#33849
PR Close#34460
Previously each NgModule trait checked its own scope for valid declarations
during 'resolve'. This worked, but caused the LocalModuleScopeRegistry to
declare that NgModule scopes were valid even if they contained invalid
declarations.
This commit moves the generation of diagnostic errors to the
LocalModuleScopeRegistry where it belongs. Now the registry can consider an
NgModule's scope to be invalid if it contains invalid declarations.
PR Close#34460
The template type checker generates TypeScript expressions for any
expression that occurs in a template, so that TypeScript can check it
and produce errors. Some expressions as they occur in a template may be
translated into TypeScript code multiple times, for instance a binding
to a directive input that has a template guard.
One example would be the `NgIf` directive, which has a template guard to
narrow the type in the template as appropriate. Given the following
template:
```typescript
@Component({
template: '<div *ngIf="person">{{ person.name }}</div>'
})
class AppComponent {
person?: { name: string };
}
```
A type check block (TCB) with roughly the following structure is
created:
```typescript
function tcb(ctx: AppComponent) {
const t1 = NgIf.ngTypeCtor({ ngIf: ctx.person });
if (ctx.person) {
"" + ctx.person.name;
}
}
```
Notice how the `*ngIf="person"` binding is present twice: once in the
type constructor call and once in the `if` guard. As such, TypeScript
will check both instances and would produce duplicate errors, if any
were found.
Another instance is when the safe navigation operator is used, where an
expression such as `person?.name` is emitted into the TCB as
`person != null ? person!.name : undefined`. As can be seen, the
left-hand side expression `person` occurs twice in the TCB.
This commit adds the ability to insert markers into the TCB that
indicate that any errors within the expression should be ignored. This
is similar to `@ts-ignore`, however it can be applied more granularly.
PR Close#34417
Previously, the type checker would compute an absolute source span by
combining an expression AST node's `ParseSpan` (relative to the start of
the expression) together with the absolute offset of the expression as
represented in a `ParseSourceSpan`, to arrive at a span relative to the
start of the file. This information is now directly available on an
expression AST node in the `AST.sourceSpan` property, which can be used
instead.
PR Close#34417
Now that the source to typings matching is able to handle
aliasing of exports, there is no need to handle aliases in private
declarations analysis.
These were originally added to cope when the typings files had
to use the name that the original source files used when exporting.
PR Close#34254
Previously the identifiers used in the typings files were the same as
those used in the source files.
When the typings files and the source files do not match exactly, e.g.
when one of them is flattened, while the other is a deep tree, it is
possible for identifiers to be renamed.
This commit ensures that the correct identifier is used in typings files
when the typings file does not export the same name as the source file.
Fixes https://github.com/angular/ngcc-validation/pull/608
PR Close#34254
The naïve matching algorithm we previously used to match declarations in
source files to declarations in typings files was based only on the name
of the thing being declared. This did not handle cases where the declared
item had been exported via an alias - a common scenario when one of the two
file sets (source or typings) has been flattened, while the other has not.
The new algorithm tries to overcome this by creating two maps of export
name to declaration (i.e. `Map<string, ts.Declaration>`).
One for the source files and one for the typings files.
It then joins these two together by matching export names, resulting in a
new map that maps source declarations to typings declarations directly
(i.e. `Map<ts.Declaration, ts.Declaration>`).
This new map can handle the declaration names being different between the
source and typings as long as they are ultimately both exported with the
same alias name.
Further more, there is one map for "public exports", i.e. exported via the
root of the source tree (the entry-point), and another map for "private
exports", which are exported from individual files in the source tree but
not necessarily from the root. This second map can be used to "guess"
the mapping between exports in a deep (non-flat) file tree, which can be
used by ngcc to add required private exports to the entry-point.
Fixes#33593
PR Close#34254
In TS we can re-export imports using statements of the form:
```
export * from 'some-import';
```
This is downleveled in UMD to:
```
function factory(exports, someImport) {
function __export(m) {
for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p];
}
__export(someImport);
}
```
This commit adds support for this.
PR Close#34254
In TS we can re-export imports using statements of the form:
```
export * from 'some-import';
```
This can be downleveled in CommonJS to either:
```
__export(require('some-import'));
```
or
```
var someImport = require('some-import');
__export(someImport);
```
Previously we only supported the first downleveled version.
This commit adds support for the second version.
PR Close#34254
Previously individual properties of the src bundle program were
passed to the reflection host constructors. But going forward,
more properties will be required. To prevent the signature getting
continually larger and more unwieldy, this change just passes the
whole src bundle to the constructor, allowing it to extract what it
needs.
PR Close#34254
This is not expected to have any noticeable perf impact, but it wasteful
nonetheless (and annoying when stepping through the code while debugging
`ngtsc`/`ngcc`).
PR Close#34441
This commit adds three previously missing validations to
NgModule.declarations:
1. It checks that declared classes are actually within the current
compilation.
2. It checks that declared classes are directives, components, or pipes.
3. It checks that classes are declared in at most one NgModule.
PR Close#34404
A quirk of the Angular template parser is that when parsing templates in the
"default" mode, with options specified by the user, the source mapping
information in the template AST may be inaccurate. As a result, the compiler
parses the template twice: once for "emit" and once to produce an AST with
accurate sourcemaps for diagnostic production.
Previously, only the first parse was performed during analysis. The second
parse occurred during the template type-checking phase, just in time to
produce the template type-checking file.
However, with the reuse of analysis results during incremental builds, it
makes more sense to do the diagnostic parse eagerly during analysis so that
the work isn't unnecessarily repeated in subsequent builds. This commit
refactors the `ComponentDecoratorHandler` to do both parses eagerly, which
actually cleans up some complexity around template parsing as well.
PR Close#34334