Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Next batch of ngtsc changes #24862

Closed
wants to merge 23 commits into from
Closed

Conversation

alxhub
Copy link
Member

@alxhub alxhub commented Jul 12, 2018

No description provided.

@mary-poppins
Copy link

You can preview 0e78341 at https://pr24862-0e78341.ngbuilds.io/.

@mary-poppins
Copy link

You can preview 3b182aa at https://pr24862-3b182aa.ngbuilds.io/.

@mhevery mhevery added the area: core Issues related to the framework runtime label Jul 12, 2018
@mary-poppins
Copy link

You can preview 3dd7c1d at https://pr24862-3dd7c1d.ngbuilds.io/.

@@ -80,17 +80,19 @@ export function extractDirectiveMetadata(
const decoratedElements =
members.filter(member => !member.isStatic && member.decorators !== null);

const coreModule = isCore && '@angular/core' || undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Write isCore ? '@angular/core' : undefined instead?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

// Construct the list of view queries.
const coreModule = this.isCore && '@angular/core' || undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@mary-poppins
Copy link

You can preview 6fcbc8f at https://pr24862-6fcbc8f.ngbuilds.io/.

@mary-poppins
Copy link

You can preview 9e3851c at https://pr24862-9e3851c.ngbuilds.io/.

@mary-poppins
Copy link

You can preview e34dec4 at https://pr24862-e34dec4.ngbuilds.io/.

@@ -87,3 +87,10 @@ export function referenceToExpression(ref: Reference, context: ts.SourceFile): E
export function isAngularCore(decorator: Decorator): boolean {
return decorator.import !== null && decorator.import.from === '@angular/core';
}

export function unwrapExpression(node: ts.Expression): ts.Expression {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add a one liner comment ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@mary-poppins
Copy link

You can preview 504f626 at https://pr24862-504f626.ngbuilds.io/.

* Given a `FunctionDeclaration` or `MethodDeclaration`, check if it is typed as a
* `ModuleWithProviders` and return an expression referencing the module if available.
*/
private _resolveModuleWithProviders(node: ts.FunctionDeclaration|
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO the name doesn't reflect what the method does

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to _extractModuleFromModuleWithProvidersFn

node, {absoluteModuleName: null, scope: new Map<ts.ParameterDeclaration, ResolvedValue>()});
export function staticallyResolve(
node: ts.Expression, checker: ts.TypeChecker,
foreignFunctionResolver?: (node: ts.FunctionDeclaration | ts.MethodDeclaration) =>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pls update the api doc

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -356,6 +358,22 @@ class StaticInterpreter {
return this.visitSymbol(symbol, context);
}

private visitTemplateExpression(node: ts.TemplateExpression, context: Context): ResolvedValue {
const pieces: string[] = [node.head.text];
for (let i = 0; i < node.templateSpans.length; i++) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what about tagged template ?
if they are of the same type you should return DYNAMIC_VALUE too.
Add a comment / test ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tagged templates are not handled yet, so they'll fall through and return DYNAMIC_VALUE. I don't even know if ngc handles them.

predicate = new WrappedNodeExpr(args[0]);
} else if (typeof arg === 'string') {
predicate = [arg];
} else if (assertIsStringArray(arg, '@' + name)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

assert are often debug time checks only, rename/refactor ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to isStringArrayOrDie()

} else if (assertIsStringArray(arg, '@' + name)) {
predicate = arg as string[];
}
if (predicate === null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should it be a final else {} ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

if (options.has('descendants')) {
const descendantsValue = staticallyResolve(options.get('descendants') !, checker);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: remove the local variable and use descendants ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The assignment isn't legal until after the typeof check.


// Extract the read and descendants options.
let read: Expression|null = null;
let descendants: boolean = true;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't descendants be a function of first ? (I think the behavior is different, can't remember exactly)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's part of the options argument, as far as I know. first means whether it's ViewChild or ViewChildren.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See

({selector, first: false, isViewQuery: false, descendants: false, ...data}),

ContentChild and ContentChildren have a different default (ViewChild and ViewChildren both default to true)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

if (decorator.args.length === 2) {
const resolvedArgs = staticallyResolve(decorator.args[1], checker);
if (!assertIsStringArray(resolvedArgs, '@HostListener.args')) {
throw new Error(`@HostListener arguments must be a string array`);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: arguments -> second argument would be clearer IMO

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed.

})
class FooCmp {
@ContentChild('bar', {read: TemplateRef}) child: any;
@ContentChildren(TemplateRef) children: any;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Add tests for @View... and getters / setters ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

template: 'Test',
host: {
'[attr.hello]': 'foo',
'(click)': 'onClick($event)',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add test for args ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

@@ -128,7 +128,7 @@ export function extractDirectiveMetadata(

// Determine if `ngOnChanges` is a lifecycle hook defined on the component.
const usesOnChanges = members.find(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use members.some() instead

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

export class Context {
constructor(readonly isStatement: boolean) {}

get setExpressionMode(): Context { return this.isStatement ? new Context(false) : this; }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: weird that a getter as side effect. make it a regular method ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Renamed to withExpressionMode / withStatementMode.

The getters are pure and do not have side effects (they do not modify the current context class). They are, however, lazy - a new Context is only created if it would be a change from the current Context.

@alxhub alxhub added target: major This PR is targeted for the next major release action: merge The PR is ready for merge by the caretaker labels Jul 20, 2018
@vicb vicb closed this in f58f3dc Jul 20, 2018
vicb pushed a commit that referenced this pull request Jul 20, 2018
This commit adds the ivy-local tag to //packages/router. Since the
router depends on //packages/upgrade, it makes that package
compatible with ngtsc as well.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
Metadata in Ivy must be literal. For example,

@NgModule({...})

is legal, whereas

const meta = {...};
@NgModule(meta)

is not.

However, some code contains additional superfluous parentheses:

@NgModule(({...}))

It is desirable that ngtsc accept this form of literal object.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
…4862)

Within an @NgModule it's common to include in the imports a call to
a ModuleWithProviders function, for example RouterModule.forRoot().
The old ngc compiler was able to handle this pattern because it had
global knowledge of metadata of not only the input compilation unit
but also all dependencies.

The ngtsc compiler for Ivy doesn't have this knowledge, so the
pattern of ModuleWithProviders functions is more difficult. ngtsc
must be able to determine which module is imported via the function
in order to expand the selector scope and properly tree-shake
directives and pipes.

This commit implements a solution to this problem, by adding a type
parameter to ModuleWithProviders through which the actual module
type can be passed between compilation units.

The provider side isn't a problem because the imports are always
copied directly to the ngInjectorDef.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
vicb pushed a commit that referenced this pull request Jul 20, 2018
This commit adds support for template substitution expressions for
ngtsc static resolution.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
vicb pushed a commit that referenced this pull request Jul 20, 2018
Previously ngtsc had a few bugs handling special token types:

* Injector was not properly translated to INJECTOR
* ChangeDetectorRef was not injected via injectChangeDetectorRef()

This commit fixes these two bugs, and also adds a test to ensure
they continue to work correctly.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
@NgModule()s get compiled to two fields: ngModuleDef and ngInjectorDef.
Both fields contain imports, as both selector scopes and injectors have
the concept of composed units of configuration. Previously these fields
were generated by static resolution of imports and exports in metadata.

Support for ModuleWithProviders requires they be generated differently.
ngModuleDef's imports/exports are generated as resolved lists of types,
whereas ngInjectorDef's imports should reflect the raw expressions that
the developer wrote in the metadata.

This change modifies the NgModule handler and properly copies raw nodes
for the imports and exports into the ngInjectorDef.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
vicb pushed a commit that referenced this pull request Jul 20, 2018
ngInjectorDef.imports is generated from @NgModule.imports plus
@NgModule.exports. A problem arises as a result, because @NgModule
exports contain not only other modules (which will have ngInjectorDef
fields), but components, directives, and pipes as well. Because of
locality, it's difficult for the compiler to filter these out at
build time.

It's not impossible, but for now filtering them out at runtime will
allow testing of the compiler against complex applications.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
This commit adds support for @ContentChild[ren] and @ViewChild[ren] in
ngtsc. Previously queries were ignored.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
This change adds support for host bindings to ngtsc, and parses them
both from decorators and from the metadata in the top-level annotation.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
Previously, the static resolver did its own interpretation of statements
in the TypeScript AST, which only functioned on TypeScript code. ES5
code in particular would not work with the resolver as it had hard-coded
assumptions about AST structure.

This commit changes the resolver to use a ReflectionHost instead, which
abstracts away understanding of the structural side of the AST. It adds 3
new methods to the ReflectionHost in support of this functionality:

* getDeclarationOfIdentifier
* getExportsOfModule
* isClass

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
Previously ngtsc had a bug where it would only detect the presence of
ngOnChanges as a static method. This commit flips the condition and only
recognizes ngOnChanges as a non-static method.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
…24862)

Previously, references had the concept of an identifier, but would not
properly detect whether the identifier should be used or not when
generating an expression. This change fixes that logic.

Additionally, now whenever an identifier resolves to a reference (even
one imported from another module) as part of resolving an expression,
the reference is updated to use that identifier. This ensures that for
a class Foo declared in foo.ts, but referenced in an expression in
bar.ts, the Reference returned includes the identifier from bar.ts,
meaning that writing an expression in bar.ts for the Reference will not
generate an import.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
…24862)

Previously, when translating an assignment expression (e.g. x = 3), the
translator would always print the statement as X = Y. However, if the
expression is included in a larger expression (X = (Y = Z)), the
translator would print "X = Y = Z" without regard for the outer
expression context.

Now, the translator understands when it's printing an expression
statement (X = Y;) vs an expression in a larger context (X = (Y = Z);)
and encapsulates the latter in parentheses.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
This commit moves the compiler compliance tests into compiler-cli,
and uses ngtsc to run them instead of the custom compilation
pipeline used before. Testing against ngtsc allows for validation
of the real compiler output.

This commit also fixes a few small issues that prevented the tests
from passing.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
Previously, some of the *Def symbols were not exported or were exported
as public API. This commit ensures every definition type is in the
private export namespace.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
Previously ngtsc would use a tuple of class types for listing metadata
in .d.ts files. For example, an @NgModule's declarations might be
represented with the type:

[NgIf, NgForOf, NgClass]

If the module had no declarations, an empty tuple [] would be produced.

This has two problems.

1. If the class type has generic type parameters, TypeScript will
complain that they're not provided.

2. The empty tuple type is not actually legal.

This commit addresses both problems.

1. Class types are now represented using the `typeof` operator, so the
above declarations would be represented as:

[typeof NgIf, typeof NgForOf, typeof NgClass].

Since typeof operates on a value, it doesn't require generic type
arguments.

2. Instead of an empty tuple, `never` is used to indicate no metadata.

PR Close #24862
vicb pushed a commit that referenced this pull request Jul 20, 2018
vicb pushed a commit that referenced this pull request Jul 20, 2018
Ivy definition types have a generic type which specifies the return
type of the factory function. For example:

static ngDirectiveDef<NgForOf, '[ngFor][ngForOf]'>

However, in this case NgForOf itself has a type parameter <T>. Thus,
writing the above is incorrect.

This commit modifies ngtsc to understand the genericness of NgForOf and
to write the following:

static ngDirectiveDef<NgForOf<any>, '[ngFor][ngForOf]'>

PR Close #24862
@angular-automatic-lock-bot
Copy link

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

This action has been performed automatically by a bot.

@angular-automatic-lock-bot angular-automatic-lock-bot bot locked and limited conversation to collaborators Sep 13, 2019
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
action: merge The PR is ready for merge by the caretaker area: core Issues related to the framework runtime cla: yes target: major This PR is targeted for the next major release
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

6 participants