Previously, `ngtsc` detected class inheritance in a way that only worked
in TS or ES2015 code. As a result, inheritance would not be detected for
code in ES5 format, such as when running `ngtsc` through `ngcc` to
transform old-style Angular code to ivy format.
This commit fixes it by delegating class inheritance detection to the
current `ReflectionHost`, which is able to correctly interpret the used
code format.
PR Close#28773
Accounts for schemas in when validating properties in Ivy.
This PR resolves FW-819.
A couple of notes:
* I had to rework the test slightly, in order to have it fail when we expect it to. The one in master is passing since Ivy's validation runs during the update phase, rather than creation.
* I had to deviate from the design in FW-819 and not add an `enableSchema` instruction, because the schema is part of the `NgModule` scope, however the scope is only assigned to a component once all of the module's declarations have been resolved and some of them can be async. Instead, I opted to have the `schemas` on the component definition.
PR Close#28637
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
This commit introduces a new ngtsc sub-library, 'path', which contains
branded string types for the different kind of paths that ngtsc manipulates.
Having static types for these paths will reduce the number of path-related
bugs (especially on Windows) and will eliminate unnecessary defensive
normalizing.
See the README.md file for more detail.
PR Close#28523
Previously, ngtsc would throw an error if two decorators were matched on
the same class simultaneously. However, @Injectable is a special case, and
it appears frequently on component, directive, and pipe classes. For pipes
in particular, it's a common pattern to treat the pipe class also as an
injectable service.
ngtsc actually lacked the capability to compile multiple matching
decorators on a class, so this commit adds support for that. Decorator
handlers (and thus the decorators they match) are classified into three
categories: PRIMARY, SHARED, and WEAK.
PRIMARY handlers compile decorators that cannot coexist with other primary
decorators. The handlers for Component, Directive, Pipe, and NgModule are
marked as PRIMARY. A class may only have one decorator from this group.
SHARED handlers compile decorators that can coexist with others. Injectable
is the only decorator in this category, meaning it's valid to put an
@Injectable decorator on a previously decorated class.
WEAK handlers behave like SHARED, but are dropped if any non-WEAK handler
matches a class. The handler which compiles ngBaseDef is WEAK, since
ngBaseDef is only needed if a class doesn't otherwise have a decorator.
Tests are added to validate that @Injectable can coexist with the other
decorators and that an error is generated when mixing the primaries.
PR Close#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
Translation of WriteKeyExpr expressions was not implemented in the ngtsc
expression translator. This resulted in binding expressions like
"target[key] = $event" not compiling.
This commit fixes the bug by implementing WriteKeyExpr translation.
PR Close#28523
Some applications use enum values in their host bindings:
@Component({
host: {
'[prop]': EnumType.Key,
}, ...
})
This commit changes the resolution of host properties to follow the enum
declaration and extract the correct value for the binding.
PR Close#28523
With #28594 we refactored the `@angular/compiler` slightly to
allow opting out from external symbol re-exports which are
enabled by default.
Since symbol re-exports only benefit projects which have a
very strict dependency enforcement, external symbols should
not be re-exported by default as this could grow the size of
factory files and cause unexpected behavior with Angular's
AOT symbol resolving (e.g. see: #25644).
Note that the common strict dependency enforcement for source
files does still work with external symbol re-exports disabled,
but there are also strict dependency checks that enforce strict
module dependencies also for _generated files_ (such as the
ngfactory files). This is how Google3 manages it's dependencies
and therefore external symbol re-exports need to be enabled within
Google3.
Also "ngtsc" also does not provide any way of using external symbol
re-exports, so this means that with this change, NGC can partially
match the behavior of "ngtsc" then (unless explicitly opted-out).
As mentioned before, internally at Google symbol re-exports need to
be still enabled, so the `ng_module` Bazel rule will enable the symbol
re-exports by default when running within Blaze.
Fixes#25644.
PR Close#28633
During analysis, the `ComponentDecoratorHandler` passes the component
template to the `parseTemplate()` function. Previously, there was little or
no information about the original source file, where the template is found,
passed when calling this function.
Now, we correctly compute the URL of the source of the template, both
for external `templateUrl` and in-line `template` cases. Further in the
in-line template case we compute the character range of the template
in its containing source file; *but only in the case that the template is
a simple string literal*. If the template is actually a dynamic value like
an interpolated string or a function call, then we do not try to add the
originating source file information.
The translator that converts Ivy AST nodes to TypeScript now adds these
template specific source mappings, which account for the file where
the template was found, to the templates to support stepping through the
template creation and update code when debugging an Angular application.
Note that some versions of TypeScript have a bug which means they cannot
support external template source-maps. We check for this via the
`canSourceMapExternalTemplates()` helper function and avoid trying to
add template mappings to external templates if not supported.
PR Close#28055
When tokenizing markup (e.g. HTML) element attributes
can have quoted or unquoted values (e.g. `a=b` or `a="b"`).
The `ATTR_VALUE` tokens were capturing the quotes, which
was inconsistent and also affected source-mapping.
Now the tokenizer captures additional `ATTR_QUOTE` tokens,
which the HTML related parsers understand and factor into their
token parsing.
PR Close#28055
I don't know of any use of this API with a project-root-relative path
(i.e. the cli will always call it with an absolute path), but keeping
the API backwards compatible just in case.
PR Close#28542
This will make it easier to retrieve routes for specific entry points in
`listLazyRoutes()` (which is necessary for CLI integration but not yet
implemented).
PR Close#28542
In https://github.com/angular/angular/pull/27697 the listLazyRoutes was fixed to work with ivy.
Since the entryRoute argument is not supported, it was made to also error.
But by erroring it breaks existing usage with Angular CLI where the entry route is sent in as an argument.
This commit changes listLazyRoutes to not error out, but instead ignore the argument.
PR Close#28372
This lets us run ngtsc under the tsc_wrapped custom compiler (Used in Bazel)
It also allows others to simply wire ngtsc into an existing typescript compilation binary
PR Close#28435
In View Engine, we supported @Input and @ContentChild annotations
on the same property. This feature was somewhat brittle because
it would only work for static queries, so it would break if a
content child was passed in wrapped in an *ngIf. Due to the
inconsistent behavior and low usage both internally and externally,
we will likely be deprecating it in the next version, and it does
not make sense to perpetuate it in Ivy.
This commit ensures that we now throw in Ivy if we encounter the
two annotations on the same property.
PR Close#28415
Prior to this change we may encounter some errors (like pipes being used where they should not be used) while compiling Host Bindings and Listeners. With this update we move validation logic to the analyze phase and throw an error if something is wrong. This also aligns error messages between Ivy and VE.
PR Close#28356
The TypeTranslatorVisitor visitor returned strings because before it wasn't possible to transform declaration files directly through the TypeScript custom transformer API.
Now that's possible though, so it should return nodes instead.
PR Close#28342
The current DtsFileTransformer works by intercepting file writes and editing the source string directly.
This PR refactors it as a afterDeclaration transform in order to fit better in the TypeScript API.
This is part of a greater effort of converting ngtsc to be usable as a TS transform plugin.
PR Close#28342
This lets us run ngtsc under the tsc_wrapped custom compiler (Used in Bazel)
It also allows others to simply wire ngtsc into an existing typescript compilation binary
PR Close#28431
This lets us run ngtsc under the tsc_wrapped custom compiler (Used in Bazel)
It also allows others to simply wire ngtsc into an existing typescript compilation binary
PR Close#27806
In some cases, calling getSourceFile() on a node from within a TS
transform can return undefined (against the signature of the method).
In these cases, getting the original node first will work.
PR Close#28412
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
This commit implements a cycle detector which looks at the import graph of
TypeScript programs and can determine whether the addition of an edge is
sufficient to create a cycle. As part of the implementation, module name
to source file resolution is implemented via a ModuleResolver, using TS
APIs.
PR Close#28169
ngcc's reflection host needs to be able to determine all members of a
class, which it does by using the `ts.Symbol` from TypeScript's
TypeChecker. Such Symbol however may represent multiple class members
in the case of accessors; an equally named getter/setter accessor pair
is combined into a single `ts.Symbol`.
This commit introduces logic to recognize such accessors in order for
both the getter and setter to be considered as class member, similar to
ngtsc's behavior when operating on original TypeScript code.
One difference wrt the TypeScript host is that ngcc cannot see to which
accessor originally had any decorators applied to them, as decorators
are applied to the property descriptor in general, not a specific accessor.
If an accessor has both a setter and getter, any decorators are only
attached to the setter member.
PR Close#28357
Prior to this change, accessor functions for getters and setters would
not be considered as class member, as their declaration is vastly
different from ES2015 syntax.
With this change, the ES5 reflection host has learned to recognize the
downleveled syntax for accessors, allowing for them to be considered as
class member once again.
Fixes#28226
PR Close#28357
This commit uses the NgModuleRouteAnalyzer introduced previously to
implement listLazyRoutes() for NgtscProgram. Currently this implementation
is limited to listing routes globally and cannot list routes for a given lazy
module. Testing seems to indicate that the CLI uses the global form, but this
should be verified.
Jira issue: FW-629
PR Close#27697
This commit introduces a new mode for the NgtscTestEnvironment which
builds the NgtscProgram and then asks for the list of lazy routes,
instead of running the TS emit phase.
PR Close#27697
This commit introduces the NgModuleRouteAnalyzer & friends, which given
metadata about the NgModules in a program can extract the list of lazy
routes in the same format that the ngtools API uses.
PR Close#27697
This commit changes the partial evaluation mechanism to propagate
DynamicValue errors internally during evaluation, and not to "poison"
entire data structures when a single value is dynamic. For example,
previously if any entry in an array was dynamic, evaluating the entire
array would return DynamicValue. Now, the array is returned with only
the specific dynamic entry as DynamicValue.
Instances of DynamicValue also report the node that was determined to
be dynamic, as well as a potential reason for the dynamic-ness. These
can be nested, so an expression `a + b` may have a DynamicValue that
indicates the 'a' term was DynamicValue, which will itself contain a
reason for the dynamic-ness.
This work was undertaken for the implementation of listLazyRoutes(),
which needs to partially evaluate provider arrays, parts of which are
dynamic and parts of which contain useful information.
PR Close#27697
`ngtsc` currently fails building a flat module out file on Windows because it generates an invalid flat module TypeScript source file. e.g:
```ts
5 export * from './C:\Users\Paul\Desktop\test\src\export';
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
```
This is because `path.posix.relative` does not properly with non-posix paths, and only expects posix paths in order to work.
PR Close#27993
Resources can be loaded in the context of another file, which
means that the path to the resource file must be resolved
before it can be loaded.
Previously the API of this interface did not allow the client
code to get access to the resolved URL which is used to load
the resource.
Now this API has been refactored so that you must do the
resource URL resolving first and the loading expects a
resolved URL.
PR Close#28199
Users might have run the CSS Preprocessor tool *before* the Angular
compiler. For example, we do it that way under Bazel. This means that
the design-time reference is different from the compile-time one - the
input to the Angular compiler is a plain .css file.
We assume that the preprocessor does a trivial 1:1 mapping using the same
basename with a different extension.
PR Close#28166
At the moment, paths stored in `maps` are not normalized and in Windows is causing files not to be found when enabling factory shimming.
For example, the map contents will be
```
Map {
'C:\\git\\cli-repos\\ng-factory-shims\\index.ngfactory.ts' => 'C:\\git\\cli-repos\\ng-factory-shims\\index.ts' }
```
However, ts compiler normalized the paths and is causing;
```
error TS6053: File 'C:/git/cli-repos/ng-factory-shims/index.ngfactory.ts' not found.
error TS6053: File 'C:/git/cli-repos/ng-factory-shims/index.ngsummary.ts' not found.
```
The changes normalized the paths that are stored within the factory and summary maps.
PR Close#28006
This code was throwing if the `deps` array of a provider has several elements, but at the next line it resolves them... With this check `ngtsc` couldn’t compile `ng-bootstrap` for example.
PR Close#28076