Merge remote-tracking branch 'origin/angular.ossez.com'
# Conflicts: # .husky/commit-msg # .husky/pre-commit # .husky/prepare-commit-msg # package.json
This commit is contained in:
commit
22e109fa8d
|
@ -1,43 +0,0 @@
|
|||
## PR Checklist
|
||||
Please check if your PR fulfills the following requirements:
|
||||
|
||||
- [ ] The commit message follows our guidelines: https://github.com/angular/angular/blob/master/CONTRIBUTING.md#commit
|
||||
- [ ] Tests for the changes have been added (for bug fixes / features)
|
||||
- [ ] Docs have been added / updated (for bug fixes / features)
|
||||
|
||||
|
||||
## PR Type
|
||||
What kind of change does this PR introduce?
|
||||
|
||||
<!-- Please check the one that applies to this PR using "x". -->
|
||||
|
||||
- [ ] Bugfix
|
||||
- [ ] Feature
|
||||
- [ ] Code style update (formatting, local variables)
|
||||
- [ ] Refactoring (no functional changes, no api changes)
|
||||
- [ ] Build related changes
|
||||
- [ ] CI related changes
|
||||
- [ ] Documentation content changes
|
||||
- [ ] angular.io application / infrastructure changes
|
||||
- [ ] Other... Please describe:
|
||||
|
||||
|
||||
## What is the current behavior?
|
||||
<!-- Please describe the current behavior that you are modifying, or link to a relevant issue. -->
|
||||
|
||||
Issue Number: N/A
|
||||
|
||||
|
||||
## What is the new behavior?
|
||||
|
||||
|
||||
## Does this PR introduce a breaking change?
|
||||
|
||||
- [ ] Yes
|
||||
- [ ] No
|
||||
|
||||
|
||||
<!-- If this PR contains a breaking change, please describe the impact and migration path for existing applications below. -->
|
||||
|
||||
|
||||
## Other information
|
File diff suppressed because it is too large
Load Diff
|
@ -104,6 +104,7 @@
|
|||
"@angular/service-worker": "11.2.3",
|
||||
"@bazel/bazelisk": "^1.7.5",
|
||||
"@webcomponents/custom-elements": "1.2.1",
|
||||
"husky": "^6.0.0",
|
||||
"rxjs": "^6.5.3",
|
||||
"tslib": "^2.1.0",
|
||||
"zone.js": "~0.11.4"
|
||||
|
@ -127,7 +128,7 @@
|
|||
"codelyzer": "^6.0.0",
|
||||
"cross-spawn": "^5.1.0",
|
||||
"css-selector-parser": "^1.3.0",
|
||||
"dgeni": "^0.4.13",
|
||||
"dgeni": "^0.4.14",
|
||||
"dgeni-packages": "^0.28.4",
|
||||
"entities": "^1.1.1",
|
||||
"esbuild": "^0.9.0",
|
||||
|
@ -154,9 +155,9 @@
|
|||
"karma-jasmine": "~4.0.0",
|
||||
"karma-jasmine-html-reporter": "^1.5.0",
|
||||
"light-server": "^2.6.2",
|
||||
"lighthouse": "^7.2.0",
|
||||
"lighthouse": "^7.3.0",
|
||||
"lighthouse-logger": "^1.2.0",
|
||||
"lodash": "^4.17.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lunr": "^2.1.0",
|
||||
"npm-run-all": "^4.1.5",
|
||||
"protractor": "~7.0.0",
|
||||
|
|
|
@ -102,10 +102,10 @@ assertSucceeded "Expected 'ngcc' to log 'Compiling'."
|
|||
|
||||
|
||||
# Did it generate a base factory call for synthesized constructors correctly?
|
||||
grep "/\*@__PURE__\*/ function () { let ɵMatTable_BaseFactory; return function MatTable_Factory(t) { return (ɵMatTable_BaseFactory || (ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable)))(t || MatTable); }; }();" node_modules/@angular/material/esm2015/table/table.js
|
||||
grep "const ɵMatTable_BaseFactory = /\*@__PURE__\*/ ɵngcc0.ɵɵgetInheritedFactory(MatTable);" node_modules/@angular/material/esm2015/table/table.js
|
||||
assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm2015)."
|
||||
|
||||
grep "/\*@__PURE__\*/ function () { var ɵMatTable_BaseFactory; return function MatTable_Factory(t) { return (ɵMatTable_BaseFactory || (ɵMatTable_BaseFactory = ɵngcc0.ɵɵgetInheritedFactory(MatTable)))(t || MatTable); }; }();" node_modules/@angular/material/esm5/table/table.js
|
||||
grep "var ɵMatTable_BaseFactory = /\*@__PURE__\*/ ɵngcc0.ɵɵgetInheritedFactory(MatTable);" node_modules/@angular/material/esm5/table/table.js
|
||||
assertSucceeded "Expected 'ngcc' to generate a base factory for 'MatTable' in '@angular/material' (esm5)."
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {fakeAsync} from '@angular/core/testing';
|
||||
import {NoopAnimationPlayer} from '../src/animations';
|
||||
import {AnimationGroupPlayer} from '../src/players/animation_group_player';
|
||||
|
||||
|
||||
describe('AnimationGroupPlayer', () => {
|
||||
it('should getPosition of an empty group', fakeAsync(() => {
|
||||
const players: NoopAnimationPlayer[] = [];
|
||||
const groupPlayer = new AnimationGroupPlayer(players);
|
||||
expect(groupPlayer.getPosition()).toBe(0);
|
||||
}));
|
||||
|
||||
it('should getPosition of a single player in a group', fakeAsync(() => {
|
||||
const player = new NoopAnimationPlayer(5, 5);
|
||||
player.setPosition(0.2);
|
||||
const players = [player];
|
||||
const groupPlayer = new AnimationGroupPlayer(players);
|
||||
expect(groupPlayer.getPosition()).toBe(0.2);
|
||||
}));
|
||||
|
||||
it('should getPosition based on the longest player in the group', fakeAsync(() => {
|
||||
const longestPlayer = new NoopAnimationPlayer(5, 5);
|
||||
longestPlayer.setPosition(0.2);
|
||||
const players = [
|
||||
new NoopAnimationPlayer(1, 4),
|
||||
new NoopAnimationPlayer(4, 1),
|
||||
new NoopAnimationPlayer(7, 0),
|
||||
longestPlayer,
|
||||
new NoopAnimationPlayer(1, 1),
|
||||
];
|
||||
const groupPlayer = new AnimationGroupPlayer(players);
|
||||
expect(groupPlayer.getPosition()).toBe(0.2);
|
||||
}));
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
# Copyright Google LLC All Rights Reserved.
|
||||
#
|
||||
# Use of this source code is governed by an MIT-style license that can be
|
||||
# found in the LICENSE file at https://angular.io/license
|
||||
|
||||
load(":ng_module.bzl", "NgPerfInfo")
|
||||
|
||||
def _ng_perf_flag_impl(ctx):
|
||||
return NgPerfInfo(enable_perf_logging = ctx.build_setting_value)
|
||||
|
||||
# `ng_perf_flag` is a special `build_setting` rule which ultimately enables a command-line boolean
|
||||
# flag to control whether the `ng_module` rule produces performance tracing JSON files (in Ivy mode)
|
||||
# as declared outputs.
|
||||
#
|
||||
# It does this via the `NgPerfInfo` provider and the `perf_flag` attriubute on `ng_module`. For more
|
||||
# details, see: https://docs.bazel.build/versions/master/skylark/config.html
|
||||
ng_perf_flag = rule(
|
||||
implementation = _ng_perf_flag_impl,
|
||||
build_setting = config.bool(flag = True),
|
||||
)
|
|
@ -0,0 +1,45 @@
|
|||
load("//tools:defaults.bzl", "jasmine_node_test", "ng_module", "ts_library")
|
||||
|
||||
ts_library(
|
||||
name = "ng_module_ivy_test_lib",
|
||||
testonly = True,
|
||||
srcs = ["ng_module_ivy_test.ts"],
|
||||
tags = ["ivy-only"],
|
||||
)
|
||||
|
||||
# `ng_module` with `compilation_mode` explicitly set to `partial`.
|
||||
ng_module(
|
||||
name = "test_module_partial_compilation",
|
||||
srcs = ["test_module_partial_compilation.ts"],
|
||||
compilation_mode = "partial",
|
||||
tags = ["ivy-only"],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
# `ng_module` with `compilation_mode` explicitly set to `full`.
|
||||
ng_module(
|
||||
name = "test_module_full_compilation",
|
||||
srcs = ["test_module_full_compilation.ts"],
|
||||
compilation_mode = "full",
|
||||
tags = ["ivy-only"],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
# `ng_module` with no specific `compilation_mode` attribute specified.
|
||||
ng_module(
|
||||
name = "test_module_default_compilation",
|
||||
srcs = ["test_module_default_compilation.ts"],
|
||||
tags = ["ivy-only"],
|
||||
deps = ["//packages/core"],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "ng_module_ivy_test",
|
||||
srcs = [":ng_module_ivy_test_lib"],
|
||||
data = [
|
||||
":test_module_default_compilation",
|
||||
":test_module_full_compilation",
|
||||
":test_module_partial_compilation",
|
||||
],
|
||||
tags = ["ivy-only"],
|
||||
)
|
|
@ -0,0 +1,41 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {readFileSync} from 'fs';
|
||||
|
||||
/** Runfiles helper from bazel to resolve file name paths. */
|
||||
const runfiles = require(process.env['BAZEL_NODE_RUNFILES_HELPER']!);
|
||||
|
||||
describe('ng_module with ivy enabled', () => {
|
||||
describe('default compilation mode', () => {
|
||||
it('should generate definitions', () => {
|
||||
const outputFile = runfiles.resolveWorkspaceRelative(
|
||||
'packages/bazel/test/ngc-wrapped/ivy_enabled/test_module_default_compilation.js');
|
||||
const fileContent = readFileSync(outputFile, 'utf8');
|
||||
expect(fileContent).toContain(`TestComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent(`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('full compilation mode', () => {
|
||||
it('should generate definitions', () => {
|
||||
const outputFile = runfiles.resolveWorkspaceRelative(
|
||||
'packages/bazel/test/ngc-wrapped/ivy_enabled/test_module_full_compilation.js');
|
||||
const fileContent = readFileSync(outputFile, 'utf8');
|
||||
expect(fileContent).toContain(`TestComponent.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent(`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('partial compilation mode', () => {
|
||||
it('should generate declarations', () => {
|
||||
const outputFile = runfiles.resolveWorkspaceRelative(
|
||||
'packages/bazel/test/ngc-wrapped/ivy_enabled/test_module_partial_compilation.js');
|
||||
const fileContent = readFileSync(outputFile, 'utf8');
|
||||
expect(fileContent).toContain(`TestComponent.ɵcmp = i0.ɵɵngDeclareComponent(`);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: 'Hello',
|
||||
})
|
||||
export class TestComponent {
|
||||
}
|
|
@ -0,0 +1,99 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* A token used to manipulate and access values stored in `HttpContext`.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class HttpContextToken<T> {
|
||||
constructor(public readonly defaultValue: () => T) {}
|
||||
}
|
||||
|
||||
/**
|
||||
* Http context stores arbitrary user defined values and ensures type safety without
|
||||
* actually knowing the types. It is backed by a `Map` and guarantees that keys do not clash.
|
||||
*
|
||||
* This context is mutable and is shared between cloned requests unless explicitly specified.
|
||||
*
|
||||
* @usageNotes
|
||||
*
|
||||
* ### Usage Example
|
||||
*
|
||||
* ```typescript
|
||||
* // inside cache.interceptors.ts
|
||||
* export const IS_CACHE_ENABLED = new HttpContextToken<boolean>(() => false);
|
||||
*
|
||||
* export class CacheInterceptor implements HttpInterceptor {
|
||||
*
|
||||
* intercept(req: HttpRequest<any>, delegate: HttpHandler): Observable<HttpEvent<any>> {
|
||||
* if (req.context.get(IS_CACHE_ENABLED) === true) {
|
||||
* return ...;
|
||||
* }
|
||||
* return delegate.handle(req);
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* // inside a service
|
||||
*
|
||||
* this.httpClient.get('/api/weather', {
|
||||
* context: new HttpContext().set(IS_CACHE_ENABLED, true)
|
||||
* }).subscribe(...);
|
||||
* ```
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export class HttpContext {
|
||||
private readonly map = new Map<HttpContextToken<unknown>, unknown>();
|
||||
|
||||
/**
|
||||
* Store a value in the context. If a value is already present it will be overwritten.
|
||||
*
|
||||
* @param token The reference to an instance of `HttpContextToken`.
|
||||
* @param value The value to store.
|
||||
*
|
||||
* @returns A reference to itself for easy chaining.
|
||||
*/
|
||||
set<T>(token: HttpContextToken<T>, value: T): HttpContext {
|
||||
this.map.set(token, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieve the value associated with the given token.
|
||||
*
|
||||
* @param token The reference to an instance of `HttpContextToken`.
|
||||
*
|
||||
* @returns The stored value or default if one is defined.
|
||||
*/
|
||||
get<T>(token: HttpContextToken<T>): T {
|
||||
if (!this.map.has(token)) {
|
||||
this.map.set(token, token.defaultValue());
|
||||
}
|
||||
return this.map.get(token) as T;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete the value associated with the given token.
|
||||
*
|
||||
* @param token The reference to an instance of `HttpContextToken`.
|
||||
*
|
||||
* @returns A reference to itself for easy chaining.
|
||||
*/
|
||||
delete(token: HttpContextToken<unknown>): HttpContext {
|
||||
this.map.delete(token);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns a list of tokens currently stored in the context.
|
||||
*/
|
||||
keys(): IterableIterator<HttpContextToken<unknown>> {
|
||||
return this.map.keys();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {HttpContext, HttpContextToken} from '../src/context';
|
||||
|
||||
const IS_ENABLED = new HttpContextToken<boolean>(() => false);
|
||||
const CACHE_OPTION =
|
||||
new HttpContextToken<{cache: boolean, expiresIn?: number}>(() => ({cache: false}));
|
||||
|
||||
describe('HttpContext', () => {
|
||||
let context: HttpContext;
|
||||
|
||||
beforeEach(() => {
|
||||
context = new HttpContext();
|
||||
});
|
||||
|
||||
describe('with basic value', () => {
|
||||
it('should test public api', () => {
|
||||
expect(context.get(IS_ENABLED)).toBe(false);
|
||||
expect([...context.keys()]).toEqual([
|
||||
IS_ENABLED
|
||||
]); // value from factory function is stored in the map upon access
|
||||
|
||||
context.set(IS_ENABLED, true);
|
||||
expect(context.get(IS_ENABLED)).toBe(true);
|
||||
expect([...context.keys()]).toEqual([IS_ENABLED]);
|
||||
|
||||
context.delete(IS_ENABLED);
|
||||
expect([...context.keys()]).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('with complex value', () => {
|
||||
it('should test public api', () => {
|
||||
expect(context.get(CACHE_OPTION)).toEqual({cache: false});
|
||||
expect([...context.keys()]).toEqual([CACHE_OPTION]);
|
||||
|
||||
const value = {cache: true, expiresIn: 30};
|
||||
context.set(CACHE_OPTION, value);
|
||||
expect(context.get(CACHE_OPTION)).toBe(value);
|
||||
expect([...context.keys()]).toEqual([CACHE_OPTION]);
|
||||
|
||||
context.delete(CACHE_OPTION);
|
||||
expect([...context.keys()]).toEqual([]);
|
||||
});
|
||||
|
||||
it('should ensure that same reference is returned for default value between multiple accesses',
|
||||
() => {
|
||||
const value = context.get(CACHE_OPTION); // will get default value
|
||||
expect(value).toEqual({cache: false});
|
||||
expect(context.get(CACHE_OPTION)).toBe(value);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* A wrapper around the `XMLHttpRequest` constructor.
|
||||
*
|
||||
* @publicApi
|
||||
*/
|
||||
export abstract class XhrFactory {
|
||||
abstract build(): XMLHttpRequest;
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {LinkerOptions} from '../..';
|
||||
import {ReadonlyFileSystem} from '../../../src/ngtsc/file_system';
|
||||
import {Logger} from '../../../src/ngtsc/logging';
|
||||
|
||||
export interface LinkerPluginOptions extends Partial<LinkerOptions> {
|
||||
/**
|
||||
* File-system, used to load up the input source-map and content.
|
||||
*/
|
||||
fileSystem: ReadonlyFileSystem;
|
||||
|
||||
/**
|
||||
* Logger used by the linker.
|
||||
*/
|
||||
logger: Logger;
|
||||
}
|
|
@ -0,0 +1,36 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {AbsoluteFsPath} from '../../../src/ngtsc/file_system';
|
||||
import {SourceFile, SourceFileLoader} from '../../../src/ngtsc/sourcemaps';
|
||||
|
||||
/**
|
||||
* A function that will return a `SourceFile` object (or null) for the current file being linked.
|
||||
*/
|
||||
export type GetSourceFileFn = () => SourceFile|null;
|
||||
|
||||
/**
|
||||
* Create a `GetSourceFileFn` that will return the `SourceFile` being linked or `null`, if not
|
||||
* available.
|
||||
*/
|
||||
export function createGetSourceFile(
|
||||
sourceUrl: AbsoluteFsPath, code: string, loader: SourceFileLoader|null): GetSourceFileFn {
|
||||
if (loader === null) {
|
||||
// No source-mapping so just return a function that always returns `null`.
|
||||
return () => null;
|
||||
} else {
|
||||
// Source-mapping is available so return a function that will load (and cache) the `SourceFile`.
|
||||
let sourceFile: SourceFile|null|undefined = undefined;
|
||||
return () => {
|
||||
if (sourceFile === undefined) {
|
||||
sourceFile = loader.loadSourceFile(sourceUrl, code);
|
||||
}
|
||||
return sourceFile;
|
||||
};
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {declarationFunctions} from './partial_linkers/partial_linker_selector';
|
||||
|
||||
/**
|
||||
* Determines if the provided source file may need to be processed by the linker, i.e. whether it
|
||||
* potentially contains any declarations. If true is returned, then the source file should be
|
||||
* processed by the linker as it may contain declarations that need to be fully compiled. If false
|
||||
* is returned, parsing and processing of the source file can safely be skipped to improve
|
||||
* performance.
|
||||
*
|
||||
* This function may return true even for source files that don't actually contain any declarations
|
||||
* that need to be compiled.
|
||||
*
|
||||
* @param path the absolute path of the source file for which to determine whether linking may be
|
||||
* needed.
|
||||
* @param source the source file content as a string.
|
||||
* @returns whether the source file may contain declarations that need to be linked.
|
||||
*/
|
||||
export function needsLinking(path: string, source: string): boolean {
|
||||
return declarationFunctions.some(fn => source.includes(fn));
|
||||
}
|
|
@ -0,0 +1,88 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {compileFactoryFunction, ConstantPool, FactoryTarget, R3DeclareDependencyMetadata, R3DeclareFactoryMetadata, R3DependencyMetadata, R3FactoryMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {parseEnum, wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareFactory()` call expressions.
|
||||
*/
|
||||
export class PartialFactoryLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3FactoryMeta(metaObj);
|
||||
const def = compileFactoryFunction(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3FactoryMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3FactoryMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>): R3FactoryMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
return {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
typeArgumentCount: 0,
|
||||
target: parseEnum(metaObj.getValue('target'), FactoryTarget),
|
||||
deps: getDeps(metaObj, 'deps'),
|
||||
};
|
||||
}
|
||||
|
||||
function getDeps<TExpression>(
|
||||
metaObj: AstObject<R3DeclareFactoryMetadata, TExpression>,
|
||||
propName: keyof R3DeclareFactoryMetadata): R3DependencyMetadata[]|null|'invalid' {
|
||||
if (!metaObj.has(propName)) {
|
||||
return null;
|
||||
}
|
||||
const deps = metaObj.getValue(propName);
|
||||
if (deps.isArray()) {
|
||||
return deps.getArray().map(dep => getDep(dep.getObject()));
|
||||
}
|
||||
if (deps.isString()) {
|
||||
return 'invalid';
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function getDep<TExpression>(depObj: AstObject<R3DeclareDependencyMetadata, TExpression>):
|
||||
R3DependencyMetadata {
|
||||
const isAttribute = depObj.has('attribute') && depObj.getBoolean('attribute');
|
||||
const token = depObj.getOpaque('token');
|
||||
// Normally `attribute` is a string literal and so its `attributeNameType` is the same string
|
||||
// literal. If the `attribute` is some other expression, the `attributeNameType` would be the
|
||||
// `unknown` type. It is not possible to generate this when linking, since it only deals with JS
|
||||
// and not typings. When linking the existence of the `attributeNameType` only acts as a marker to
|
||||
// change the injection instruction that is generated, so we just pass the literal string
|
||||
// `"unknown"`.
|
||||
const attributeNameType = isAttribute ? o.literal('unknown') : null;
|
||||
const dep: R3DependencyMetadata = {
|
||||
token,
|
||||
attributeNameType,
|
||||
host: depObj.has('host') && depObj.getBoolean('host'),
|
||||
optional: depObj.has('optional') && depObj.getBoolean('optional'),
|
||||
self: depObj.has('self') && depObj.getBoolean('self'),
|
||||
skipSelf: depObj.has('skipSelf') && depObj.getBoolean('skipSelf'),
|
||||
};
|
||||
return dep;
|
||||
}
|
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {compileInjector, ConstantPool, R3DeclareInjectorMetadata, R3InjectorMetadata, R3PartialDeclaration} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareInjector()` call expressions.
|
||||
*/
|
||||
export class PartialInjectorLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3InjectorMeta(metaObj);
|
||||
const def = compileInjector(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3InjectorMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3InjectorMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareInjectorMetadata, TExpression>): R3InjectorMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
return {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
providers: metaObj.has('providers') ? metaObj.getOpaque('providers') : null,
|
||||
imports: metaObj.has('imports') ? metaObj.getArray('imports').map(i => i.getOpaque()) : [],
|
||||
};
|
||||
}
|
|
@ -0,0 +1,128 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {compileNgModule, ConstantPool, R3DeclareNgModuleMetadata, R3NgModuleMetadata, R3PartialDeclaration, R3Reference} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject, AstValue} from '../../ast/ast_value';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclareNgModule()` call expressions.
|
||||
*/
|
||||
export class PartialNgModuleLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
constructor(
|
||||
/**
|
||||
* If true then emit the additional declarations, imports, exports, etc in the NgModule
|
||||
* definition. These are only used by JIT compilation.
|
||||
*/
|
||||
private emitInline: boolean) {}
|
||||
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3NgModuleMeta(metaObj, this.emitInline);
|
||||
const def = compileNgModule(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3NgModuleMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3NgModuleMeta<TExpression>(
|
||||
metaObj: AstObject<R3DeclareNgModuleMetadata, TExpression>,
|
||||
emitInline: boolean): R3NgModuleMetadata {
|
||||
const wrappedType = metaObj.getOpaque('type');
|
||||
|
||||
const meta: R3NgModuleMetadata = {
|
||||
type: wrapReference(wrappedType),
|
||||
internalType: wrappedType,
|
||||
adjacentType: wrappedType,
|
||||
bootstrap: [],
|
||||
declarations: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
emitInline,
|
||||
containsForwardDecls: false,
|
||||
schemas: [],
|
||||
id: metaObj.has('id') ? metaObj.getOpaque('id') : null,
|
||||
};
|
||||
|
||||
// Each of `bootstrap`, `declarations`, `imports` and `exports` are normally an array. But if any
|
||||
// of the references are not yet declared, then the arrays must be wrapped in a function to
|
||||
// prevent errors at runtime when accessing the values.
|
||||
|
||||
// The following blocks of code will unwrap the arrays from such functions, because
|
||||
// `R3NgModuleMetadata` expects arrays of `R3Reference` objects.
|
||||
|
||||
// Further, since the `ɵɵdefineNgModule()` will also suffer from the forward declaration problem,
|
||||
// we must update the `containsForwardDecls` property if a function wrapper was found.
|
||||
|
||||
if (metaObj.has('bootstrap')) {
|
||||
const bootstrap: AstValue<unknown, TExpression> = metaObj.getValue('bootstrap');
|
||||
if (bootstrap.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.bootstrap = wrapReferences(unwrapForwardRefs(bootstrap));
|
||||
} else
|
||||
meta.bootstrap = wrapReferences(bootstrap);
|
||||
}
|
||||
|
||||
if (metaObj.has('declarations')) {
|
||||
const declarations: AstValue<unknown, TExpression> = metaObj.getValue('declarations');
|
||||
if (declarations.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.declarations = wrapReferences(unwrapForwardRefs(declarations));
|
||||
} else
|
||||
meta.declarations = wrapReferences(declarations);
|
||||
}
|
||||
|
||||
if (metaObj.has('imports')) {
|
||||
const imports: AstValue<unknown, TExpression> = metaObj.getValue('imports');
|
||||
if (imports.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.imports = wrapReferences(unwrapForwardRefs(imports));
|
||||
} else
|
||||
meta.imports = wrapReferences(imports);
|
||||
}
|
||||
|
||||
if (metaObj.has('exports')) {
|
||||
const exports: AstValue<unknown, TExpression> = metaObj.getValue('exports');
|
||||
if (exports.isFunction()) {
|
||||
meta.containsForwardDecls = true;
|
||||
meta.exports = wrapReferences(unwrapForwardRefs(exports));
|
||||
} else
|
||||
meta.exports = wrapReferences(exports);
|
||||
}
|
||||
|
||||
if (metaObj.has('schemas')) {
|
||||
const schemas: AstValue<unknown, TExpression> = metaObj.getValue('schemas');
|
||||
meta.schemas = wrapReferences(schemas);
|
||||
}
|
||||
|
||||
return meta;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract an array from the body of the function.
|
||||
*
|
||||
* If `field` is `function() { return [exp1, exp2, exp3]; }` then we return `[exp1, exp2, exp3]`.
|
||||
*
|
||||
*/
|
||||
function unwrapForwardRefs<TExpression>(field: AstValue<unknown, TExpression>):
|
||||
AstValue<TExpression[], TExpression> {
|
||||
return (field as AstValue<Function, TExpression>).getFunctionReturnValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Wrap the array of expressions into an array of R3 references.
|
||||
*/
|
||||
function wrapReferences<TExpression>(values: AstValue<TExpression[], TExpression>): R3Reference[] {
|
||||
return values.getArray().map(i => wrapReference(i.getOpaque()));
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {compilePipeFromMetadata, ConstantPool, R3DeclarePipeMetadata, R3PartialDeclaration, R3PipeMetadata} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
|
||||
import {AstObject} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
import {PartialLinker} from './partial_linker';
|
||||
import {wrapReference} from './util';
|
||||
|
||||
/**
|
||||
* A `PartialLinker` that is designed to process `ɵɵngDeclarePipe()` call expressions.
|
||||
*/
|
||||
export class PartialPipeLinkerVersion1<TExpression> implements PartialLinker<TExpression> {
|
||||
constructor() {}
|
||||
|
||||
linkPartialDeclaration(
|
||||
constantPool: ConstantPool,
|
||||
metaObj: AstObject<R3PartialDeclaration, TExpression>): o.Expression {
|
||||
const meta = toR3PipeMeta(metaObj);
|
||||
const def = compilePipeFromMetadata(meta);
|
||||
return def.expression;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the `R3PipeMetadata` structure from the AST object.
|
||||
*/
|
||||
export function toR3PipeMeta<TExpression>(metaObj: AstObject<R3DeclarePipeMetadata, TExpression>):
|
||||
R3PipeMetadata {
|
||||
const typeExpr = metaObj.getValue('type');
|
||||
const typeName = typeExpr.getSymbolName();
|
||||
if (typeName === null) {
|
||||
throw new FatalLinkerError(
|
||||
typeExpr.expression, 'Unsupported type, its name could not be determined');
|
||||
}
|
||||
|
||||
const pure = metaObj.has('pure') ? metaObj.getBoolean('pure') : true;
|
||||
|
||||
return {
|
||||
name: typeName,
|
||||
type: wrapReference(typeExpr.getOpaque()),
|
||||
internalType: metaObj.getOpaque('type'),
|
||||
typeArgumentCount: 0,
|
||||
deps: null,
|
||||
pipeName: metaObj.getString('name'),
|
||||
pure,
|
||||
};
|
||||
}
|
|
@ -0,0 +1,31 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {R3Reference} from '@angular/compiler';
|
||||
import * as o from '@angular/compiler/src/output/output_ast';
|
||||
import {AstValue} from '../../ast/ast_value';
|
||||
import {FatalLinkerError} from '../../fatal_linker_error';
|
||||
|
||||
export function wrapReference<TExpression>(wrapped: o.WrappedNodeExpr<TExpression>): R3Reference {
|
||||
return {value: wrapped, type: wrapped};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the value of an enum from the AST value's symbol name.
|
||||
*/
|
||||
export function parseEnum<TExpression, TEnum>(
|
||||
value: AstValue<unknown, TExpression>, Enum: TEnum): TEnum[keyof TEnum] {
|
||||
const symbolName = value.getSymbolName();
|
||||
if (symbolName === null) {
|
||||
throw new FatalLinkerError(value.expression, 'Expected value to have a symbol name');
|
||||
}
|
||||
const enumValue = Enum[symbolName as keyof typeof Enum];
|
||||
if (enumValue === undefined) {
|
||||
throw new FatalLinkerError(value.expression, `Unsupported enum value for ${Enum}`);
|
||||
}
|
||||
return enumValue;
|
||||
}
|
|
@ -0,0 +1,67 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {needsLinking} from '../../src/file_linker/needs_linking';
|
||||
|
||||
describe('needsLinking', () => {
|
||||
it('should return true for directive declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Dir {
|
||||
ɵdir = ɵɵngDeclareDirective({type: Dir});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for namespaced directive declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Dir {
|
||||
ɵdir = ng.ɵɵngDeclareDirective({type: Dir});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for unrelated usages of ɵɵngDeclareDirective', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const fnName = 'ɵɵngDeclareDirective';
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return false when the file does not contain ɵɵngDeclareDirective', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const foo = ngDeclareDirective;
|
||||
`)).toBeFalse();
|
||||
});
|
||||
|
||||
it('should return true for component declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Cmp {
|
||||
ɵdir = ɵɵngDeclareComponent({type: Cmp});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for namespaced component declarations', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
export class Cmp {
|
||||
ɵdir = ng.ɵɵngDeclareComponent({type: Cmp});
|
||||
}
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return true for unrelated usages of ɵɵngDeclareComponent', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const fnName = 'ɵɵngDeclareComponent';
|
||||
`)).toBeTrue();
|
||||
});
|
||||
|
||||
it('should return false when the file does not contain ɵɵngDeclareComponent', () => {
|
||||
expect(needsLinking('file.js', `
|
||||
const foo = ngDeclareComponent;
|
||||
`)).toBeFalse();
|
||||
});
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
// This file exists as a target for g3 patches which change the Angular compiler's behavior.
|
||||
// Separating the patched code in a separate file eliminates the possibility of conflicts with the
|
||||
// patch diffs when making changes to the rest of the compiler codebase.
|
||||
|
||||
// In ngtsc we no longer want to compile undecorated classes with Angular features.
|
||||
// Migrations for these patterns ran as part of `ng update` and we want to ensure
|
||||
// that projects do not regress. See https://hackmd.io/@alx/ryfYYuvzH for more details.
|
||||
export const compileUndecoratedClassesWithAngularFeatures = false;
|
|
@ -0,0 +1,16 @@
|
|||
load("//tools:defaults.bzl", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "semantic_graph",
|
||||
srcs = ["index.ts"] + glob([
|
||||
"src/**/*.ts",
|
||||
]),
|
||||
deps = [
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
export {SemanticReference, SemanticSymbol} from './src/api';
|
||||
export {SemanticDepGraph, SemanticDepGraphUpdater} from './src/graph';
|
||||
export {areTypeParametersEqual, extractSemanticTypeParameters, SemanticTypeParameter} from './src/type_parameters';
|
||||
export {isArrayEqual, isReferenceEqual, isSetEqual, isSymbolEqual} from './src/util';
|
|
@ -0,0 +1,127 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFromSourceFile, AbsoluteFsPath} from '../../../file_system';
|
||||
import {ClassDeclaration} from '../../../reflection';
|
||||
|
||||
/**
|
||||
* Represents a symbol that is recognizable across incremental rebuilds, which enables the captured
|
||||
* metadata to be compared to the prior compilation. This allows for semantic understanding of
|
||||
* the changes that have been made in a rebuild, which potentially enables more reuse of work
|
||||
* from the prior compilation.
|
||||
*/
|
||||
export abstract class SemanticSymbol {
|
||||
/**
|
||||
* The path of the file that declares this symbol.
|
||||
*/
|
||||
public readonly path: AbsoluteFsPath;
|
||||
|
||||
/**
|
||||
* The identifier of this symbol, or null if no identifier could be determined. It should
|
||||
* uniquely identify the symbol relative to `file`. This is typically just the name of a
|
||||
* top-level class declaration, as that uniquely identifies the class within the file.
|
||||
*
|
||||
* If the identifier is null, then this symbol cannot be recognized across rebuilds. In that
|
||||
* case, the symbol is always assumed to have semantically changed to guarantee a proper
|
||||
* rebuild.
|
||||
*/
|
||||
public readonly identifier: string|null;
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The declaration for this symbol.
|
||||
*/
|
||||
public readonly decl: ClassDeclaration,
|
||||
) {
|
||||
this.path = absoluteFromSourceFile(decl.getSourceFile());
|
||||
this.identifier = getSymbolIdentifier(decl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows the symbol to be compared to the equivalent symbol in the previous compilation. The
|
||||
* return value indicates whether the symbol has been changed in a way such that its public API
|
||||
* is affected.
|
||||
*
|
||||
* This method determines whether a change to _this_ symbol require the symbols that
|
||||
* use to this symbol to be re-emitted.
|
||||
*
|
||||
* Note: `previousSymbol` is obtained from the most recently succeeded compilation. Symbols of
|
||||
* failed compilations are never provided.
|
||||
*
|
||||
* @param previousSymbol The symbol from a prior compilation.
|
||||
*/
|
||||
abstract isPublicApiAffected(previousSymbol: SemanticSymbol): boolean;
|
||||
|
||||
/**
|
||||
* Allows the symbol to determine whether its emit is affected. The equivalent symbol from a prior
|
||||
* build is given, in addition to the set of symbols of which the public API has changed.
|
||||
*
|
||||
* This method determines whether a change to _other_ symbols, i.e. those present in
|
||||
* `publicApiAffected`, should cause _this_ symbol to be re-emitted.
|
||||
*
|
||||
* @param previousSymbol The equivalent symbol from a prior compilation. Note that it may be a
|
||||
* different type of symbol, if e.g. a Component was changed into a Directive with the same name.
|
||||
* @param publicApiAffected The set of symbols of which the public API has changed.
|
||||
*/
|
||||
isEmitAffected?(previousSymbol: SemanticSymbol, publicApiAffected: Set<SemanticSymbol>): boolean;
|
||||
|
||||
/**
|
||||
* Similar to `isPublicApiAffected`, but here equivalent symbol from a prior compilation needs
|
||||
* to be compared to see if the type-check block of components that use this symbol is affected.
|
||||
*
|
||||
* This method determines whether a change to _this_ symbol require the symbols that
|
||||
* use to this symbol to have their type-check block regenerated.
|
||||
*
|
||||
* Note: `previousSymbol` is obtained from the most recently succeeded compilation. Symbols of
|
||||
* failed compilations are never provided.
|
||||
*
|
||||
* @param previousSymbol The symbol from a prior compilation.
|
||||
*/
|
||||
abstract isTypeCheckApiAffected(previousSymbol: SemanticSymbol): boolean;
|
||||
|
||||
/**
|
||||
* Similar to `isEmitAffected`, but focused on the type-check block of this symbol. This method
|
||||
* determines whether a change to _other_ symbols, i.e. those present in `typeCheckApiAffected`,
|
||||
* should cause _this_ symbol's type-check block to be regenerated.
|
||||
*
|
||||
* @param previousSymbol The equivalent symbol from a prior compilation. Note that it may be a
|
||||
* different type of symbol, if e.g. a Component was changed into a Directive with the same name.
|
||||
* @param typeCheckApiAffected The set of symbols of which the type-check API has changed.
|
||||
*/
|
||||
isTypeCheckBlockAffected?
|
||||
(previousSymbol: SemanticSymbol, typeCheckApiAffected: Set<SemanticSymbol>): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a reference to a semantic symbol that has been emitted into a source file. The
|
||||
* reference may refer to the symbol using a different name than the semantic symbol's declared
|
||||
* name, e.g. in case a re-export under a different name was chosen by a reference emitter.
|
||||
* Consequently, to know that an emitted reference is still valid not only requires that the
|
||||
* semantic symbol is still valid, but also that the path by which the symbol is imported has not
|
||||
* changed.
|
||||
*/
|
||||
export interface SemanticReference {
|
||||
symbol: SemanticSymbol;
|
||||
|
||||
/**
|
||||
* The path by which the symbol has been referenced.
|
||||
*/
|
||||
importPath: string|null;
|
||||
}
|
||||
|
||||
function getSymbolIdentifier(decl: ClassDeclaration): string|null {
|
||||
if (!ts.isSourceFile(decl.parent)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this is a top-level class declaration, the class name is used as unique identifier.
|
||||
// Other scenarios are currently not supported and causes the symbol not to be identified
|
||||
// across rebuilds, unless the declaration node has not changed.
|
||||
return decl.name.text;
|
||||
}
|
|
@ -0,0 +1,281 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {Expression, ExternalExpr} from '@angular/compiler';
|
||||
import {AbsoluteFsPath} from '../../../file_system';
|
||||
import {ClassDeclaration} from '../../../reflection';
|
||||
import {SemanticReference, SemanticSymbol} from './api';
|
||||
|
||||
export interface SemanticDependencyResult {
|
||||
/**
|
||||
* The files that need to be re-emitted.
|
||||
*/
|
||||
needsEmit: Set<AbsoluteFsPath>;
|
||||
|
||||
/**
|
||||
* The files for which the type-check block should be regenerated.
|
||||
*/
|
||||
needsTypeCheckEmit: Set<AbsoluteFsPath>;
|
||||
|
||||
/**
|
||||
* The newly built graph that represents the current compilation.
|
||||
*/
|
||||
newGraph: SemanticDepGraph;
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a declaration for which no semantic symbol has been registered. For example,
|
||||
* declarations from external dependencies have not been explicitly registered and are represented
|
||||
* by this symbol. This allows the unresolved symbol to still be compared to a symbol from a prior
|
||||
* compilation.
|
||||
*/
|
||||
class OpaqueSymbol extends SemanticSymbol {
|
||||
isPublicApiAffected(): false {
|
||||
return false;
|
||||
}
|
||||
|
||||
isTypeCheckApiAffected(): false {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The semantic dependency graph of a single compilation.
|
||||
*/
|
||||
export class SemanticDepGraph {
|
||||
readonly files = new Map<AbsoluteFsPath, Map<string, SemanticSymbol>>();
|
||||
readonly symbolByDecl = new Map<ClassDeclaration, SemanticSymbol>();
|
||||
|
||||
/**
|
||||
* Registers a symbol in the graph. The symbol is given a unique identifier if possible, such that
|
||||
* its equivalent symbol can be obtained from a prior graph even if its declaration node has
|
||||
* changed across rebuilds. Symbols without an identifier are only able to find themselves in a
|
||||
* prior graph if their declaration node is identical.
|
||||
*/
|
||||
registerSymbol(symbol: SemanticSymbol): void {
|
||||
this.symbolByDecl.set(symbol.decl, symbol);
|
||||
|
||||
if (symbol.identifier !== null) {
|
||||
// If the symbol has a unique identifier, record it in the file that declares it. This enables
|
||||
// the symbol to be requested by its unique name.
|
||||
if (!this.files.has(symbol.path)) {
|
||||
this.files.set(symbol.path, new Map<string, SemanticSymbol>());
|
||||
}
|
||||
this.files.get(symbol.path)!.set(symbol.identifier, symbol);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve a symbol in this graph that represents the given symbol from another graph.
|
||||
* If no matching symbol could be found, null is returned.
|
||||
*
|
||||
* @param symbol The symbol from another graph for which its equivalent in this graph should be
|
||||
* found.
|
||||
*/
|
||||
getEquivalentSymbol(symbol: SemanticSymbol): SemanticSymbol|null {
|
||||
// First lookup the symbol by its declaration. It is typical for the declaration to not have
|
||||
// changed across rebuilds, so this is likely to find the symbol. Using the declaration also
|
||||
// allows to diff symbols for which no unique identifier could be determined.
|
||||
let previousSymbol = this.getSymbolByDecl(symbol.decl);
|
||||
if (previousSymbol === null && symbol.identifier !== null) {
|
||||
// The declaration could not be resolved to a symbol in a prior compilation, which may
|
||||
// happen because the file containing the declaration has changed. In that case we want to
|
||||
// lookup the symbol based on its unique identifier, as that allows us to still compare the
|
||||
// changed declaration to the prior compilation.
|
||||
previousSymbol = this.getSymbolByName(symbol.path, symbol.identifier);
|
||||
}
|
||||
|
||||
return previousSymbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to find the symbol by its identifier.
|
||||
*/
|
||||
private getSymbolByName(path: AbsoluteFsPath, identifier: string): SemanticSymbol|null {
|
||||
if (!this.files.has(path)) {
|
||||
return null;
|
||||
}
|
||||
const file = this.files.get(path)!;
|
||||
if (!file.has(identifier)) {
|
||||
return null;
|
||||
}
|
||||
return file.get(identifier)!;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to resolve the declaration to its semantic symbol.
|
||||
*/
|
||||
getSymbolByDecl(decl: ClassDeclaration): SemanticSymbol|null {
|
||||
if (!this.symbolByDecl.has(decl)) {
|
||||
return null;
|
||||
}
|
||||
return this.symbolByDecl.get(decl)!;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Implements the logic to go from a previous dependency graph to a new one, along with information
|
||||
* on which files have been affected.
|
||||
*/
|
||||
export class SemanticDepGraphUpdater {
|
||||
private readonly newGraph = new SemanticDepGraph();
|
||||
|
||||
/**
|
||||
* Contains opaque symbols that were created for declarations for which there was no symbol
|
||||
* registered, which happens for e.g. external declarations.
|
||||
*/
|
||||
private readonly opaqueSymbols = new Map<ClassDeclaration, OpaqueSymbol>();
|
||||
|
||||
constructor(
|
||||
/**
|
||||
* The semantic dependency graph of the most recently succeeded compilation, or null if this
|
||||
* is the initial build.
|
||||
*/
|
||||
private priorGraph: SemanticDepGraph|null) {}
|
||||
|
||||
/**
|
||||
* Registers the symbol in the new graph that is being created.
|
||||
*/
|
||||
registerSymbol(symbol: SemanticSymbol): void {
|
||||
this.newGraph.registerSymbol(symbol);
|
||||
}
|
||||
|
||||
/**
|
||||
* Takes all facts that have been gathered to create a new semantic dependency graph. In this
|
||||
* process, the semantic impact of the changes is determined which results in a set of files that
|
||||
* need to be emitted and/or type-checked.
|
||||
*/
|
||||
finalize(): SemanticDependencyResult {
|
||||
if (this.priorGraph === null) {
|
||||
// If no prior dependency graph is available then this was the initial build, in which case
|
||||
// we don't need to determine the semantic impact as everything is already considered
|
||||
// logically changed.
|
||||
return {
|
||||
needsEmit: new Set<AbsoluteFsPath>(),
|
||||
needsTypeCheckEmit: new Set<AbsoluteFsPath>(),
|
||||
newGraph: this.newGraph,
|
||||
};
|
||||
}
|
||||
|
||||
const needsEmit = this.determineInvalidatedFiles(this.priorGraph);
|
||||
const needsTypeCheckEmit = this.determineInvalidatedTypeCheckFiles(this.priorGraph);
|
||||
return {
|
||||
needsEmit,
|
||||
needsTypeCheckEmit,
|
||||
newGraph: this.newGraph,
|
||||
};
|
||||
}
|
||||
|
||||
private determineInvalidatedFiles(priorGraph: SemanticDepGraph): Set<AbsoluteFsPath> {
|
||||
const isPublicApiAffected = new Set<SemanticSymbol>();
|
||||
|
||||
// The first phase is to collect all symbols which have their public API affected. Any symbols
|
||||
// that cannot be matched up with a symbol from the prior graph are considered affected.
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null || symbol.isPublicApiAffected(previousSymbol)) {
|
||||
isPublicApiAffected.add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// The second phase is to find all symbols for which the emit result is affected, either because
|
||||
// their used declarations have changed or any of those used declarations has had its public API
|
||||
// affected as determined in the first phase.
|
||||
const needsEmit = new Set<AbsoluteFsPath>();
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
if (symbol.isEmitAffected === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null || symbol.isEmitAffected(previousSymbol, isPublicApiAffected)) {
|
||||
needsEmit.add(symbol.path);
|
||||
}
|
||||
}
|
||||
|
||||
return needsEmit;
|
||||
}
|
||||
|
||||
private determineInvalidatedTypeCheckFiles(priorGraph: SemanticDepGraph): Set<AbsoluteFsPath> {
|
||||
const isTypeCheckApiAffected = new Set<SemanticSymbol>();
|
||||
|
||||
// The first phase is to collect all symbols which have their public API affected. Any symbols
|
||||
// that cannot be matched up with a symbol from the prior graph are considered affected.
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null || symbol.isTypeCheckApiAffected(previousSymbol)) {
|
||||
isTypeCheckApiAffected.add(symbol);
|
||||
}
|
||||
}
|
||||
|
||||
// The second phase is to find all symbols for which the emit result is affected, either because
|
||||
// their used declarations have changed or any of those used declarations has had its public API
|
||||
// affected as determined in the first phase.
|
||||
const needsTypeCheckEmit = new Set<AbsoluteFsPath>();
|
||||
for (const symbol of this.newGraph.symbolByDecl.values()) {
|
||||
if (symbol.isTypeCheckBlockAffected === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const previousSymbol = priorGraph.getEquivalentSymbol(symbol);
|
||||
if (previousSymbol === null ||
|
||||
symbol.isTypeCheckBlockAffected(previousSymbol, isTypeCheckApiAffected)) {
|
||||
needsTypeCheckEmit.add(symbol.path);
|
||||
}
|
||||
}
|
||||
|
||||
return needsTypeCheckEmit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a `SemanticReference` for the reference to `decl` using the expression `expr`. See
|
||||
* the documentation of `SemanticReference` for details.
|
||||
*/
|
||||
getSemanticReference(decl: ClassDeclaration, expr: Expression): SemanticReference {
|
||||
return {
|
||||
symbol: this.getSymbol(decl),
|
||||
importPath: getImportPath(expr),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the `SemanticSymbol` that was registered for `decl` during the current compilation, or
|
||||
* returns an opaque symbol that represents `decl`.
|
||||
*/
|
||||
getSymbol(decl: ClassDeclaration): SemanticSymbol {
|
||||
const symbol = this.newGraph.getSymbolByDecl(decl);
|
||||
if (symbol === null) {
|
||||
// No symbol has been recorded for the provided declaration, which would be the case if the
|
||||
// declaration is external. Return an opaque symbol in that case, to allow the external
|
||||
// declaration to be compared to a prior compilation.
|
||||
return this.getOpaqueSymbol(decl);
|
||||
}
|
||||
return symbol;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates an `OpaqueSymbol` for the provided class declaration.
|
||||
*/
|
||||
private getOpaqueSymbol(decl: ClassDeclaration): OpaqueSymbol {
|
||||
if (this.opaqueSymbols.has(decl)) {
|
||||
return this.opaqueSymbols.get(decl)!;
|
||||
}
|
||||
|
||||
const symbol = new OpaqueSymbol(decl);
|
||||
this.opaqueSymbols.set(decl, symbol);
|
||||
return symbol;
|
||||
}
|
||||
}
|
||||
|
||||
function getImportPath(expr: Expression): string|null {
|
||||
if (expr instanceof ExternalExpr) {
|
||||
return `${expr.value.moduleName}\$${expr.value.name}`;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {ClassDeclaration} from '../../../reflection';
|
||||
import {isArrayEqual} from './util';
|
||||
|
||||
/**
|
||||
* Describes a generic type parameter of a semantic symbol. A class declaration with type parameters
|
||||
* needs special consideration in certain contexts. For example, template type-check blocks may
|
||||
* contain type constructors of used directives which include the type parameters of the directive.
|
||||
* As a consequence, if a change is made that affects the type parameters of said directive, any
|
||||
* template type-check blocks that use the directive need to be regenerated.
|
||||
*
|
||||
* This type represents a single generic type parameter. It currently only tracks whether the
|
||||
* type parameter has a constraint, i.e. has an `extends` clause. When a constraint is present, we
|
||||
* currently assume that the type parameter is affected in each incremental rebuild; proving that
|
||||
* a type parameter with constraint is not affected is non-trivial as it requires full semantic
|
||||
* understanding of the type constraint.
|
||||
*/
|
||||
export interface SemanticTypeParameter {
|
||||
/**
|
||||
* Whether a type constraint, i.e. an `extends` clause is present on the type parameter.
|
||||
*/
|
||||
hasGenericTypeBound: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the type parameters of the given class into their semantic representation. If the class
|
||||
* does not have any type parameters, then `null` is returned.
|
||||
*/
|
||||
export function extractSemanticTypeParameters(node: ClassDeclaration): SemanticTypeParameter[]|
|
||||
null {
|
||||
if (!ts.isClassDeclaration(node) || node.typeParameters === undefined) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return node.typeParameters.map(
|
||||
typeParam => ({hasGenericTypeBound: typeParam.constraint !== undefined}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares the list of type parameters to determine if they can be considered equal.
|
||||
*/
|
||||
export function areTypeParametersEqual(
|
||||
current: SemanticTypeParameter[]|null, previous: SemanticTypeParameter[]|null): boolean {
|
||||
// First compare all type parameters one-to-one; any differences mean that the list of type
|
||||
// parameters has changed.
|
||||
if (!isArrayEqual(current, previous, isTypeParameterEqual)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// If there is a current list of type parameters and if any of them has a generic type constraint,
|
||||
// then the meaning of that type parameter may have changed without us being aware; as such we
|
||||
// have to assume that the type parameters have in fact changed.
|
||||
if (current !== null && current.some(typeParam => typeParam.hasGenericTypeBound)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function isTypeParameterEqual(a: SemanticTypeParameter, b: SemanticTypeParameter): boolean {
|
||||
return a.hasGenericTypeBound === b.hasGenericTypeBound;
|
||||
}
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {SemanticReference, SemanticSymbol} from './api';
|
||||
|
||||
/**
|
||||
* Determines whether the provided symbols represent the same declaration.
|
||||
*/
|
||||
export function isSymbolEqual(a: SemanticSymbol, b: SemanticSymbol): boolean {
|
||||
if (a.decl === b.decl) {
|
||||
// If the declaration is identical then it must represent the same symbol.
|
||||
return true;
|
||||
}
|
||||
|
||||
if (a.identifier === null || b.identifier === null) {
|
||||
// Unidentifiable symbols are assumed to be different.
|
||||
return false;
|
||||
}
|
||||
|
||||
return a.path === b.path && a.identifier === b.identifier;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether the provided references to a semantic symbol are still equal, i.e. represent
|
||||
* the same symbol and are imported by the same path.
|
||||
*/
|
||||
export function isReferenceEqual(a: SemanticReference, b: SemanticReference): boolean {
|
||||
if (!isSymbolEqual(a.symbol, b.symbol)) {
|
||||
// If the reference's target symbols are different, the reference itself is different.
|
||||
return false;
|
||||
}
|
||||
|
||||
// The reference still corresponds with the same symbol, now check that the path by which it is
|
||||
// imported has not changed.
|
||||
return a.importPath === b.importPath;
|
||||
}
|
||||
|
||||
export function referenceEquality<T>(a: T, b: T): boolean {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided arrays are equal to each other, using the provided equality tester
|
||||
* that is called for all entries in the array.
|
||||
*/
|
||||
export function isArrayEqual<T>(
|
||||
a: readonly T[]|null, b: readonly T[]|null,
|
||||
equalityTester: (a: T, b: T) => boolean = referenceEquality): boolean {
|
||||
if (a === null || b === null) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
if (a.length !== b.length) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !a.some((item, index) => !equalityTester(item, b[index]));
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if the provided sets are equal to each other, using the provided equality tester.
|
||||
* Sets that only differ in ordering are considered equal.
|
||||
*/
|
||||
export function isSetEqual<T>(
|
||||
a: ReadonlySet<T>|null, b: ReadonlySet<T>|null,
|
||||
equalityTester: (a: T, b: T) => boolean = referenceEquality): boolean {
|
||||
if (a === null || b === null) {
|
||||
return a === b;
|
||||
}
|
||||
|
||||
if (a.size !== b.size) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const itemA of a) {
|
||||
let found = false;
|
||||
for (const itemB of b) {
|
||||
if (equalityTester(itemA, itemB)) {
|
||||
found = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
load("//tools:defaults.bzl", "jasmine_node_test", "ts_library")
|
||||
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
ts_library(
|
||||
name = "test_lib",
|
||||
testonly = True,
|
||||
srcs = glob([
|
||||
"**/*.ts",
|
||||
]),
|
||||
deps =
|
||||
[
|
||||
"//packages:types",
|
||||
"//packages/compiler",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system",
|
||||
"//packages/compiler-cli/src/ngtsc/file_system/testing",
|
||||
"//packages/compiler-cli/src/ngtsc/imports",
|
||||
"//packages/compiler-cli/src/ngtsc/metadata",
|
||||
"//packages/compiler-cli/src/ngtsc/reflection",
|
||||
"//packages/compiler-cli/src/ngtsc/testing",
|
||||
"@npm//typescript",
|
||||
],
|
||||
)
|
||||
|
||||
jasmine_node_test(
|
||||
name = "test",
|
||||
bootstrap = ["//tools/testing:node_no_angular_es5"],
|
||||
data = [
|
||||
"//packages/compiler-cli/src/ngtsc/testing/fake_core:npm_package",
|
||||
],
|
||||
deps =
|
||||
[
|
||||
":test_lib",
|
||||
],
|
||||
)
|
|
@ -0,0 +1,93 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {ExternalExpr} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {absoluteFrom, getFileSystem, getSourceFileOrError} from '../../file_system';
|
||||
import {runInEachFileSystem} from '../../file_system/testing';
|
||||
import {Reference} from '../../imports';
|
||||
import {isNamedClassDeclaration, TypeScriptReflectionHost} from '../../reflection';
|
||||
import {loadFakeCore, makeProgram} from '../../testing';
|
||||
|
||||
import {DtsMetadataReader} from '../src/dts';
|
||||
|
||||
runInEachFileSystem(() => {
|
||||
beforeEach(() => {
|
||||
loadFakeCore(getFileSystem());
|
||||
});
|
||||
|
||||
describe('DtsMetadataReader', () => {
|
||||
it('should not assume directives are structural', () => {
|
||||
const mainPath = absoluteFrom('/main.d.ts');
|
||||
const {program} = makeProgram(
|
||||
[{
|
||||
name: mainPath,
|
||||
contents: `
|
||||
import {ViewContainerRef} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
export declare class TestDir {
|
||||
constructor(p0: ViewContainerRef);
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<TestDir, "[test]", never, {}, {}, never>
|
||||
}
|
||||
`
|
||||
}],
|
||||
{
|
||||
skipLibCheck: true,
|
||||
lib: ['es6', 'dom'],
|
||||
});
|
||||
|
||||
const sf = getSourceFileOrError(program, mainPath);
|
||||
const clazz = sf.statements[2];
|
||||
if (!isNamedClassDeclaration(clazz)) {
|
||||
return fail('Expected class declaration');
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const dtsReader =
|
||||
new DtsMetadataReader(typeChecker, new TypeScriptReflectionHost(typeChecker));
|
||||
|
||||
const meta = dtsReader.getDirectiveMetadata(new Reference(clazz))!;
|
||||
expect(meta.isStructural).toBeFalse();
|
||||
});
|
||||
|
||||
it('should identify a structural directive by its constructor', () => {
|
||||
const mainPath = absoluteFrom('/main.d.ts');
|
||||
const {program} = makeProgram(
|
||||
[{
|
||||
name: mainPath,
|
||||
contents: `
|
||||
import {TemplateRef, ViewContainerRef} from '@angular/core';
|
||||
import * as i0 from '@angular/core';
|
||||
|
||||
export declare class TestDir {
|
||||
constructor(p0: ViewContainerRef, p1: TemplateRef);
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<TestDir, "[test]", never, {}, {}, never>
|
||||
}
|
||||
`
|
||||
}],
|
||||
{
|
||||
skipLibCheck: true,
|
||||
lib: ['es6', 'dom'],
|
||||
});
|
||||
|
||||
const sf = getSourceFileOrError(program, mainPath);
|
||||
const clazz = sf.statements[2];
|
||||
if (!isNamedClassDeclaration(clazz)) {
|
||||
return fail('Expected class declaration');
|
||||
}
|
||||
|
||||
const typeChecker = program.getTypeChecker();
|
||||
const dtsReader =
|
||||
new DtsMetadataReader(typeChecker, new TypeScriptReflectionHost(typeChecker));
|
||||
|
||||
const meta = dtsReader.getDirectiveMetadata(new Reference(clazz))!;
|
||||
expect(meta.isStructural).toBeTrue();
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,154 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
/// <reference types="node" />
|
||||
|
||||
import {PerfCheckpoint, PerfEvent, PerfPhase, PerfRecorder} from './api';
|
||||
import {HrTime, mark, timeSinceInMicros} from './clock';
|
||||
|
||||
/**
|
||||
* Serializable performance data for the compilation, using string names.
|
||||
*/
|
||||
export interface PerfResults {
|
||||
events: Record<string, number>;
|
||||
phases: Record<string, number>;
|
||||
memory: Record<string, number>;
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PerfRecorder` that actively tracks performance statistics.
|
||||
*/
|
||||
export class ActivePerfRecorder implements PerfRecorder {
|
||||
private counters: number[];
|
||||
private phaseTime: number[];
|
||||
private bytes: number[];
|
||||
|
||||
private currentPhase = PerfPhase.Unaccounted;
|
||||
private currentPhaseEntered = this.zeroTime;
|
||||
|
||||
/**
|
||||
* Creates an `ActivePerfRecoder` with its zero point set to the current time.
|
||||
*/
|
||||
static zeroedToNow(): ActivePerfRecorder {
|
||||
return new ActivePerfRecorder(mark());
|
||||
}
|
||||
|
||||
private constructor(private zeroTime: HrTime) {
|
||||
this.counters = Array(PerfEvent.LAST).fill(0);
|
||||
this.phaseTime = Array(PerfPhase.LAST).fill(0);
|
||||
this.bytes = Array(PerfCheckpoint.LAST).fill(0);
|
||||
|
||||
// Take an initial memory snapshot before any other compilation work begins.
|
||||
this.memory(PerfCheckpoint.Initial);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.counters = Array(PerfEvent.LAST).fill(0);
|
||||
this.phaseTime = Array(PerfPhase.LAST).fill(0);
|
||||
this.bytes = Array(PerfCheckpoint.LAST).fill(0);
|
||||
this.zeroTime = mark();
|
||||
this.currentPhase = PerfPhase.Unaccounted;
|
||||
this.currentPhaseEntered = this.zeroTime;
|
||||
}
|
||||
|
||||
memory(after: PerfCheckpoint): void {
|
||||
this.bytes[after] = process.memoryUsage().heapUsed;
|
||||
}
|
||||
|
||||
phase(phase: PerfPhase): PerfPhase {
|
||||
const previous = this.currentPhase;
|
||||
this.phaseTime[this.currentPhase] += timeSinceInMicros(this.currentPhaseEntered);
|
||||
this.currentPhase = phase;
|
||||
this.currentPhaseEntered = mark();
|
||||
return previous;
|
||||
}
|
||||
|
||||
inPhase<T>(phase: PerfPhase, fn: () => T): T {
|
||||
const previousPhase = this.phase(phase);
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
this.phase(previousPhase);
|
||||
}
|
||||
}
|
||||
|
||||
eventCount(counter: PerfEvent, incrementBy: number = 1): void {
|
||||
this.counters[counter] += incrementBy;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the current performance metrics as a serializable object.
|
||||
*/
|
||||
finalize(): PerfResults {
|
||||
// Track the last segment of time spent in `this.currentPhase` in the time array.
|
||||
this.phase(PerfPhase.Unaccounted);
|
||||
|
||||
const results: PerfResults = {
|
||||
events: {},
|
||||
phases: {},
|
||||
memory: {},
|
||||
};
|
||||
|
||||
for (let i = 0; i < this.phaseTime.length; i++) {
|
||||
if (this.phaseTime[i] > 0) {
|
||||
results.phases[PerfPhase[i]] = this.phaseTime[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.phaseTime.length; i++) {
|
||||
if (this.counters[i] > 0) {
|
||||
results.events[PerfEvent[i]] = this.counters[i];
|
||||
}
|
||||
}
|
||||
|
||||
for (let i = 0; i < this.bytes.length; i++) {
|
||||
if (this.bytes[i] > 0) {
|
||||
results.memory[PerfCheckpoint[i]] = this.bytes[i];
|
||||
}
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A `PerfRecorder` that delegates to a target `PerfRecorder` which can be updated later.
|
||||
*
|
||||
* `DelegatingPerfRecorder` is useful when a compiler class that needs a `PerfRecorder` can outlive
|
||||
* the current compilation. This is true for most compiler classes as resource-only changes reuse
|
||||
* the same `NgCompiler` for a new compilation.
|
||||
*/
|
||||
export class DelegatingPerfRecorder implements PerfRecorder {
|
||||
constructor(public target: PerfRecorder) {}
|
||||
|
||||
eventCount(counter: PerfEvent, incrementBy?: number): void {
|
||||
this.target.eventCount(counter, incrementBy);
|
||||
}
|
||||
|
||||
phase(phase: PerfPhase): PerfPhase {
|
||||
return this.target.phase(phase);
|
||||
}
|
||||
|
||||
inPhase<T>(phase: PerfPhase, fn: () => T): T {
|
||||
// Note: this doesn't delegate to `this.target.inPhase` but instead is implemented manually here
|
||||
// to avoid adding an additional frame of noise to the stack when debugging.
|
||||
const previousPhase = this.target.phase(phase);
|
||||
try {
|
||||
return fn();
|
||||
} finally {
|
||||
this.target.phase(previousPhase);
|
||||
}
|
||||
}
|
||||
|
||||
memory(after: PerfCheckpoint): void {
|
||||
this.target.memory(after);
|
||||
}
|
||||
|
||||
reset(): void {
|
||||
this.target.reset();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,129 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CssSelector, SchemaMetadata, SelectorMatcher} from '@angular/compiler';
|
||||
import * as ts from 'typescript';
|
||||
|
||||
import {Reference} from '../../imports';
|
||||
import {DirectiveMeta, flattenInheritedDirectiveMetadata, MetadataReader} from '../../metadata';
|
||||
import {ClassDeclaration} from '../../reflection';
|
||||
|
||||
import {ComponentScopeReader} from './component_scope';
|
||||
|
||||
/**
|
||||
* The scope that is used for type-check code generation of a component template.
|
||||
*/
|
||||
export interface TypeCheckScope {
|
||||
/**
|
||||
* A `SelectorMatcher` instance that contains the flattened directive metadata of all directives
|
||||
* that are in the compilation scope of the declaring NgModule.
|
||||
*/
|
||||
matcher: SelectorMatcher<DirectiveMeta>;
|
||||
|
||||
/**
|
||||
* All of the directives available in the compilation scope of the declaring NgModule.
|
||||
*/
|
||||
directives: DirectiveMeta[];
|
||||
|
||||
/**
|
||||
* The pipes that are available in the compilation scope.
|
||||
*/
|
||||
pipes: Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>;
|
||||
|
||||
/**
|
||||
* The schemas that are used in this scope.
|
||||
*/
|
||||
schemas: SchemaMetadata[];
|
||||
|
||||
/**
|
||||
* Whether the original compilation scope which produced this `TypeCheckScope` was itself poisoned
|
||||
* (contained semantic errors during its production).
|
||||
*/
|
||||
isPoisoned: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Computes scope information to be used in template type checking.
|
||||
*/
|
||||
export class TypeCheckScopeRegistry {
|
||||
/**
|
||||
* Cache of flattened directive metadata. Because flattened metadata is scope-invariant it's
|
||||
* cached individually, such that all scopes refer to the same flattened metadata.
|
||||
*/
|
||||
private flattenedDirectiveMetaCache = new Map<ClassDeclaration, DirectiveMeta>();
|
||||
|
||||
/**
|
||||
* Cache of the computed type check scope per NgModule declaration.
|
||||
*/
|
||||
private scopeCache = new Map<ClassDeclaration, TypeCheckScope>();
|
||||
|
||||
constructor(private scopeReader: ComponentScopeReader, private metaReader: MetadataReader) {}
|
||||
|
||||
/**
|
||||
* Computes the type-check scope information for the component declaration. If the NgModule
|
||||
* contains an error, then 'error' is returned. If the component is not declared in any NgModule,
|
||||
* an empty type-check scope is returned.
|
||||
*/
|
||||
getTypeCheckScope(node: ClassDeclaration): TypeCheckScope {
|
||||
const matcher = new SelectorMatcher<DirectiveMeta>();
|
||||
const directives: DirectiveMeta[] = [];
|
||||
const pipes = new Map<string, Reference<ClassDeclaration<ts.ClassDeclaration>>>();
|
||||
|
||||
const scope = this.scopeReader.getScopeForComponent(node);
|
||||
if (scope === null) {
|
||||
return {
|
||||
matcher,
|
||||
directives,
|
||||
pipes,
|
||||
schemas: [],
|
||||
isPoisoned: false,
|
||||
};
|
||||
}
|
||||
|
||||
if (this.scopeCache.has(scope.ngModule)) {
|
||||
return this.scopeCache.get(scope.ngModule)!;
|
||||
}
|
||||
|
||||
for (const meta of scope.compilation.directives) {
|
||||
if (meta.selector !== null) {
|
||||
const extMeta = this.getTypeCheckDirectiveMetadata(meta.ref);
|
||||
matcher.addSelectables(CssSelector.parse(meta.selector), extMeta);
|
||||
directives.push(extMeta);
|
||||
}
|
||||
}
|
||||
|
||||
for (const {name, ref} of scope.compilation.pipes) {
|
||||
if (!ts.isClassDeclaration(ref.node)) {
|
||||
throw new Error(`Unexpected non-class declaration ${
|
||||
ts.SyntaxKind[ref.node.kind]} for pipe ${ref.debugName}`);
|
||||
}
|
||||
pipes.set(name, ref as Reference<ClassDeclaration<ts.ClassDeclaration>>);
|
||||
}
|
||||
|
||||
const typeCheckScope: TypeCheckScope = {
|
||||
matcher,
|
||||
directives,
|
||||
pipes,
|
||||
schemas: scope.schemas,
|
||||
isPoisoned: scope.compilation.isPoisoned || scope.exported.isPoisoned,
|
||||
};
|
||||
this.scopeCache.set(scope.ngModule, typeCheckScope);
|
||||
return typeCheckScope;
|
||||
}
|
||||
|
||||
getTypeCheckDirectiveMetadata(ref: Reference<ClassDeclaration>): DirectiveMeta {
|
||||
const clazz = ref.node;
|
||||
if (this.flattenedDirectiveMetaCache.has(clazz)) {
|
||||
return this.flattenedDirectiveMetaCache.get(clazz)!;
|
||||
}
|
||||
|
||||
const meta = flattenInheritedDirectiveMetadata(this.metaReader, ref);
|
||||
this.flattenedDirectiveMetaCache.set(clazz, meta);
|
||||
return meta;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
/**
|
||||
* From where the content for a source file or source-map came.
|
||||
*
|
||||
* - Source files can be linked to source-maps by:
|
||||
* - providing the content inline via a base64 encoded data comment,
|
||||
* - providing a URL to the file path in a comment,
|
||||
* - the loader inferring the source-map path from the source file path.
|
||||
* - Source-maps can link to source files by:
|
||||
* - providing the content inline in the `sourcesContent` property
|
||||
* - providing the path to the file in the `sources` property
|
||||
*/
|
||||
export enum ContentOrigin {
|
||||
/**
|
||||
* The contents were provided programmatically when calling `loadSourceFile()`.
|
||||
*/
|
||||
Provided,
|
||||
/**
|
||||
* The contents were extracted directly form the contents of the referring file.
|
||||
*/
|
||||
Inline,
|
||||
/**
|
||||
* The contents were loaded from the file-system, after being explicitly referenced or inferred
|
||||
* from the referring file.
|
||||
*/
|
||||
FileSystem,
|
||||
}
|
|
@ -0,0 +1,25 @@
|
|||
/**
|
||||
* @license
|
||||
* Copyright Google LLC All Rights Reserved.
|
||||
*
|
||||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import * as ts from 'typescript';
|
||||
import {NgtscCompilerHost} from '../../file_system';
|
||||
import {getCachedSourceFile} from './cached_source_files';
|
||||
|
||||
/**
|
||||
* A compiler host intended to improve test performance by caching default library source files for
|
||||
* reuse across tests.
|
||||
*/
|
||||
export class NgtscTestCompilerHost extends NgtscCompilerHost {
|
||||
getSourceFile(fileName: string, languageVersion: ts.ScriptTarget): ts.SourceFile|undefined {
|
||||
const cachedSf = getCachedSourceFile(fileName, () => this.readFile(fileName));
|
||||
if (cachedSf !== null) {
|
||||
return cachedSf;
|
||||
}
|
||||
return super.getSourceFile(fileName, languageVersion);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,644 @@
|
|||
# The Template Type Checking Engine
|
||||
|
||||
The `typecheck` package is concerned with template type-checking, the process by which the compiler determines and understands the TypeScript types of constructs within component templates. It's used to perform actual type checking of templates (similarly to how TypeScript checks code for type errors). It also provides the `TemplateTypeChecker` API which is a conceptual analogue to TypeScript's own `ts.TypeChecker`, exposing various semantic details about template types to consumers such as the Angular Language Service.
|
||||
|
||||
The template type-checking engine is complex, as TypeScript itself is not very pluggable when it comes to the type system. The main algorithm for template type-checking is as follows:
|
||||
|
||||
1. The input `ts.Program` is analyzed, and information about directives/pipes as well as candidate templates is collected.
|
||||
2. Each candidate template is converted into a "type-check block", or TCB, a TypeScript function that semantically describes the operations in the template as well as their types.
|
||||
3. A derivative `ts.Program` is created from the user's input program from step 1, plus the newly generated TCBs.
|
||||
4. TypeScript is asked to produce diagnostics for the TCBs, which arise from type errors within the template.
|
||||
5. TCB diagnostics are converted to template diagnostics and reported to the user.
|
||||
|
||||
This algorithm relies extensively on TypeScript's ability to rapidly type check incremental changes in a `ts.Program` for its performance characteristics. Much of its design is optimized to ensure TypeScript has to do the minimum incremental work to check the new `ts.Program`.
|
||||
|
||||
## Type Check Blocks
|
||||
|
||||
To understand and check the types of various operations and structures within templates, the `typecheck` system maps them to TypeScript code, encoding them in such a way as to express the intent of the operation within the type system.
|
||||
|
||||
|
||||
TCBs are not ever emitted, nor are they referenced from any other code (they're unused code as far as TypeScript is concerned). Their _runtime_ effect is therefore unimportant. What matters is that they express to TypeScript the type relationships of directives, bindings, and other entities in the template. Type errors within TCBs translate directly to type errors in the original template.
|
||||
|
||||
### Theory
|
||||
|
||||
Given a component `SomeCmp`, its TCB takes the form of a function:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// TCB code
|
||||
}
|
||||
```
|
||||
|
||||
Encoding the TCB as a function serves two purposes:
|
||||
|
||||
1. It provides a lexical scope in which to declare variables without fear of collisions.
|
||||
2. It provides a convenient location (the parameter list) to declare the component context.
|
||||
|
||||
The component context is the theoretical component instance associated with the template. Expressions within the template often refer to properties of this component.
|
||||
|
||||
For example, if `SomeCmp`'s template has an interpolation expression `{{foo.bar}}`, this suggests that `SomeCmp` has a property `foo`, and that `foo` itself is an object with a property `bar` (or in a type sense, that the type of `SomeCmp.foo` has a property `bar`).
|
||||
|
||||
Such a binding is expressed in the TCB function, using the `ctx` parameter as the component instance:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
'' + ctx.foo.bar;
|
||||
}
|
||||
```
|
||||
|
||||
If `SomeCmp` does not have a `foo` property, then TypeScript will produce a type error/diagnostic for the expression `ctx.foo`. If `ctx.foo` does exist, but is not of a type that has a `bar` property, then TypeScript will catch that too. By mapping the template expression `{{foo.bar}}` to TypeScript code, the compiler has captured its _intent_ in the TCB in a way that TypeScript can validate.
|
||||
|
||||
#### Types of template declarations
|
||||
|
||||
Not only can a template consume properties declared from its component, but various structures within a template can also be considered "declarations" which have types of their own. For example, the template:
|
||||
|
||||
```html
|
||||
<input #name>
|
||||
{{name.value}}
|
||||
```
|
||||
|
||||
declares a single `<input>` element with a local ref `#name`, meaning that within this template `name` refers to the `<input>` element. The `{{name.value}}` interpolation is reading the `value` property of this element.
|
||||
|
||||
Within the TCB, the `<input>` element is treated as a declaration, and the compiler leverages the powerful type inference of `document.createElement`:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
var _t1 = document.createElement('input');
|
||||
'' + _t1.value;
|
||||
}
|
||||
```
|
||||
|
||||
The `_t1` variable represents the instance of the `<input>` element within the template. This statement will never be executed, but its initialization expression is used to infer a correct type of `_t1` (`HTMLInputElement`), using the `document.createElement` typings that TypeScript provides.
|
||||
|
||||
By knowing the type of this element, the expression involving the `name` local ref can be translated into the TCB as `_t1.value`. TypeScript will then validate that `_t1` has a `value` property (really, that the `HTMLInputElement` type has a `value` property).
|
||||
|
||||
#### Directive types
|
||||
|
||||
Just like with HTML elements, directives present on elements within the template (including those directives which are components) are treated as declarations as well. Consider the template:
|
||||
|
||||
```html
|
||||
<other-cmp [foo]="bar"></other-cmp>
|
||||
```
|
||||
|
||||
The TCB for this template looks like:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
var _t1: OtherCmp = null!;
|
||||
_t1.foo = ctx.bar;
|
||||
}
|
||||
```
|
||||
|
||||
Since `<other-cmp>` is a component, the TCB declares `_t1` to be of that component's type. This allows for the binding `[foo]="bar"` to be expressed in TypeScript as `_t1.foo = ctx.bar` - an assignment to `OtherCmp`'s `@Input` for `foo` of the `bar` property from the template's context. TypeScript can then type check this operation and produce diagnostics if the type of `ctx.bar` is not assignable to the `_t1.foo` property which backs the `@Input`.
|
||||
|
||||
#### Generic directives & type constructors
|
||||
|
||||
The above declaration of `_t1` using the component's type only works when the directive/component class is not generic. If `OtherCmp` were declared as:
|
||||
|
||||
```typescript
|
||||
export class OtherCmp<T> {
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
then the compiler could not write
|
||||
|
||||
```typescript
|
||||
var _t1: OtherCmp<?> = ...;
|
||||
```
|
||||
|
||||
without picking a value for the generic type parameter `T` of `OtherCmp`. How should the compiler know what type the user intended for this component instance?
|
||||
|
||||
Ordinarily, for a plain TypeScript class such as `Set<T>`, the generic type parameters of an instance are determined in one of two ways:
|
||||
|
||||
1. Directly, via construction: `new Set<string>()`
|
||||
2. Indirectly, via inference from constructor parameters: `new Set(['foo', 'bar']); // Set<string>`
|
||||
|
||||
For directives, neither of these options makes sense. Users do not write direct constructor calls to directives in a template, so there is no mechanism by which they can specify generic types directly. Directive constructors are also dependency-injected, and generic types cannot be used as DI tokens and so they cannot be inferred from DI.
|
||||
|
||||
Instead, conceptually, the generic type of a directive depends on the types bound to its _inputs_. This is immediately evident for a directive such as `NgFor`:
|
||||
|
||||
```typescript
|
||||
|
||||
@Directive({selector: '[ngFor]'})
|
||||
export class NgFor<T> {
|
||||
@Input() ngForOf!: Iterable<T>;
|
||||
}
|
||||
```
|
||||
|
||||
(Note: the real `NgFor` directive is more complex than the simplistic version examined here, but the same principles still apply)
|
||||
|
||||
In this case, the `T` type parameter of `NgFor` represents the value type of the `Iterable` over which we're iterating. This depends entirely on the `Iterable` passed to `NgFor` - if the user passes a `User[]` array, then this should conceptually create an `NgFor<User>` instance. If they pass a `string[]` array, it should be an `NgFor<string>` instead.
|
||||
|
||||
To infer a correct type for a generic directive, the TCB system generates a **type constructor** for the directive. The type constructor is a "fake" constructor which can be used to infer the directive type based on any provided input bindings.
|
||||
|
||||
A type constructor for the simplistic `NgFor` directive above would look like:
|
||||
|
||||
```typescript
|
||||
declare function ctor1<T>(inputs: {ngForOf?: Iterable<T>}): NgFor<T>;
|
||||
```
|
||||
|
||||
This type constructor can then be used to infer the instance type of a usage of `NgFor` based on provided bindings. For example, the template:
|
||||
|
||||
```html=
|
||||
<div *ngFor="let user of users">...</div>
|
||||
```
|
||||
|
||||
Would use the above type constructor in its TCB:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
var _t1 = ctor1({ngForOf: ctx.users});
|
||||
// Assuming ctx.users is User[], then _t1 is inferred as NgFor<User>.
|
||||
}
|
||||
```
|
||||
|
||||
A single type constructor for a directive can be used in multiple places, whenever an instance of the directive is present in the template.
|
||||
|
||||
#### Nested templates & structural directives
|
||||
|
||||
`NgFor` is a structural directive, meaning that it applies to a nested `<ng-template>`. That is, the template:
|
||||
|
||||
```html
|
||||
<div *ngFor="let user of users">
|
||||
{{user.name}}
|
||||
</div>
|
||||
```
|
||||
|
||||
is syntactic sugar for:
|
||||
|
||||
```html
|
||||
<ng-template ngFor let-user="$implicit" [ngForOf]="users">
|
||||
<div>{{user.name}}</div>
|
||||
</ng-template>
|
||||
```
|
||||
|
||||
The `NgFor` directive injects a `TemplateRef` for this _embedded view_, as well as a `ViewContainerRef`, and at runtime creates dynamic instances of this nested template (one per row). Each instance of the embedded view has its own "template context" object, from which any `let` bindings are read.
|
||||
|
||||
In the TCB, the template context of this nested template is itself a declaration with its own type. Since a structural directive can in theory create embedded views with any context object it wants, the template context type always starts as `any`:
|
||||
|
||||
```typescript
|
||||
declare function ctor1(inputs: {ngForOf?: Iterable<T>}): NgFor<T>;
|
||||
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// _t1 is the NgFor directive instance, inferred as NgFor<User>.
|
||||
var _t1 = ctor1({ngForOf: ctx.users});
|
||||
|
||||
// _t2 is the context type for the embedded views created by the NgFor structural directive.
|
||||
var _t2: any;
|
||||
|
||||
// _t3 is the let-user variable within the embedded view.
|
||||
var _t3 = _t2.$implicit;
|
||||
|
||||
// Represents the `{{user.name}}` interpolation within the embedded view.
|
||||
'' + _t3.name;
|
||||
}
|
||||
```
|
||||
|
||||
Note that the `any` type of the embedded view context `_t2` effectively disables type-checking of the `{{user.name}}` expression, because the compiler has no idea what type `user` will be when `NgFor` instantiates this embedded view. Since this instantiation is imperative code, the compiler cannot know what `NgFor` will do.
|
||||
|
||||
##### Template context hints
|
||||
|
||||
To solve this problem, the template type-checking engine allows structural directives to _declaratively_ narrow the context type of any embedded views they create. The example `NgFor` directive would do this by declaring a static `ngTemplateContextGuard` function:
|
||||
|
||||
```typescript
|
||||
@Directive({selector: '[ngFor]'})
|
||||
export class NgFor<T> {
|
||||
@Input() ngForOf!: Iterable<T>;
|
||||
|
||||
static ngTemplateContextGuard<T>(dir: NgFor<T>, ctx: any): ctx is NgForContext<T> {
|
||||
return true; // implementation is not important
|
||||
}
|
||||
}
|
||||
|
||||
export interface NgForContext<T> {
|
||||
$implicit: T;
|
||||
}
|
||||
```
|
||||
|
||||
The `typecheck` system is aware of the presence of this method on any structural directives used in templates, and uses it to narrow the type of its context declarations. So with this method on `NgFor`, the template from before would now generate a TCB of:
|
||||
|
||||
```typescript
|
||||
declare function ctor1(inputs: {ngForOf?: Iterable<T>}): NgFor<T>;
|
||||
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// _t1 is the NgFor directive instance, inferred as NgFor<User>.
|
||||
var _t1 = ctor1({ngForOf: ctx.users});
|
||||
|
||||
// _t2 is the context type for the embedded views created by the NgFor structural directive.
|
||||
var _t2: any;
|
||||
|
||||
if (NgFor.ngTemplateContextGuard(_t1, _t2)) {
|
||||
// NgFor's ngTemplateContextGuard has narrowed the type of _t2
|
||||
// based on the type of _t1 (the NgFor directive itself).
|
||||
// Within this `if` block, _t2 is now of type NgForContext<User>.
|
||||
|
||||
// _t3 is the let-user variable within the embedded view.
|
||||
// Because _t2 is narrowed, _t3 is now of type User.
|
||||
var _t3 = _t2.$implicit;
|
||||
|
||||
// Represents the `{{user.name}}` interpolation within the embedded view.
|
||||
'' + _t3.name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Because the `NgFor` directive _declared_ to the template type checking engine what type it intends to use for embedded views it creates, the TCB has full type information for expressions within the nested template for the `*ngFor` invocation, and the compiler is able to correctly type check the `{{user.name}}` expression.
|
||||
|
||||
##### 'binding' guard hints
|
||||
|
||||
`NgIf` requires a similar, albeit not identical, operation to perform type narrowing with its nested template. Instead of narrowing the template context, `NgIf` wants to narrow the actual type of the expression within its binding. Consider the template:
|
||||
|
||||
```html
|
||||
<div *ngIf="user !== null">
|
||||
{{user.name}}
|
||||
</div>
|
||||
```
|
||||
|
||||
Obviously, if `user` is potentially `null`, then this `NgIf` is intended to only show the `<div>` when `user` actually has a value. However, from a type-checking perspective, the expression `user.name` is not legal if `user` is potentially `null`. So if this template was rendered into a TCB as:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// Type of the NgIf directive instance.
|
||||
var _t1: NgIf;
|
||||
|
||||
// Binding *ngIf="user != null".
|
||||
_t1.ngIf = ctx.user !== null;
|
||||
|
||||
// Nested template interpolation `{{user.name}}`
|
||||
'' + ctx.user.name;
|
||||
}
|
||||
```
|
||||
|
||||
Then the `'' + user.name` line would produce a type error that `user` might be `null`. At runtime, though, the `NgIf` prevents this condition by only instantiating its embedded view if its bound `ngIf` expression is truthy.
|
||||
|
||||
Similarly to `ngTemplateContextGuard`, the template type checking engine allows `NgIf` to express this behavior by adding a static field:
|
||||
|
||||
```typescript
|
||||
@Directive({selector: '[ngIf]'})
|
||||
export class NgIf {
|
||||
@Input() ngIf!: boolean;
|
||||
|
||||
static ngTemplateGuard_ngIf: 'binding';
|
||||
}
|
||||
```
|
||||
|
||||
The presence and type of this static property tells the template type-checking engine to reflect the bound expression for its `ngIf` input as a guard for any embedded views created by the structural directive. This produces a TCB:
|
||||
|
||||
```typescript
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// Type of the NgIf directive instance.
|
||||
var _t1: NgIf;
|
||||
|
||||
// Binding *ngIf="user != null".
|
||||
_t1.ngIf = ctx.user !== null;
|
||||
|
||||
// Guard generated due to the `ngTemplateGuard_ngIf` declaration by the NgIf directive.
|
||||
if (user !== null) {
|
||||
// Nested template interpolation `{{user.name}}`.
|
||||
// `ctx.user` here is appropriately narrowed to be non-nullable.
|
||||
'' + ctx.user.name;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
The guard expression causes TypeScript to narrow the type of `ctx.user` within the `if` block and identify that `ctx.user` cannot be `null` within the embedded view context, just as `NgIf` itself does during rendering.
|
||||
|
||||
### Generation process
|
||||
|
||||
Angular templates allow forward references. For example, the template:
|
||||
|
||||
```html
|
||||
The value is: {{in.value}}
|
||||
<input #in>
|
||||
```
|
||||
|
||||
contains an expression which makes use of the `#in` local reference before the targeted `<input #in>` element is declared. Since such forward references are not legal in TypeScript code, the TCB may need to declare and check template structures in a different order than the template itself.
|
||||
|
||||
#### Two phase generation
|
||||
|
||||
To support this out-of-order generation, the template type checking engine processes templates using an abstraction of known as a `TcbOp`, or TCB operation. `TcbOp`s have two main behaviors:
|
||||
|
||||
1. Executing a `TcbOp` appends code to the TCB being generated.
|
||||
2. Executing a `TcbOp` optionally returns an identifier or expression, which can be used by other operations to refer to some aspect of the generated structure.
|
||||
|
||||
The main algorithm for TCB generation then makes use of this abstraction:
|
||||
|
||||
1. The template is processed in a depth-first manner, and `TcbOp`s representing the code to be generated for the structures and expressions within are enqueued into a `TcbOp` queue.
|
||||
2. Execution of operations begins from the start of the queue.
|
||||
3. As each `TcbOp` is executed, its result is recorded.
|
||||
4. An executing `TcbOp` may request the results of other `TcbOp`s for any dependencies, even `TcbOp`s which appear later in the queue and have not yet executed.
|
||||
5. Such dependency `TcbOp`s are executed "on demand", when requested.
|
||||
|
||||
This potential out-of-order execution of `TcbOp`s allows for the TCB ordering to support forward references within templates. The above forward reference example thus results in a `TcbOp` queue of two operations:
|
||||
|
||||
```typescript
|
||||
[
|
||||
TcbTextInterpolationOp(`in.value`),
|
||||
TcbElementOp('<input #in>'),
|
||||
]
|
||||
```
|
||||
|
||||
Execution of the first `TcbTextInterpolationOp` will attempt to generate code representing the expression. Doing this requires knowing the type of the `in` reference, which maps to the element node for the `<input>`. Therefore, as part of executing the `TcbTextInterpolationOp`, the execution of the `TcbElementOp` will be requested. This operation produces TCB code for the element:
|
||||
|
||||
```typescript
|
||||
var t1 = document.createElement('input');
|
||||
```
|
||||
|
||||
and returns the expression `t1` which represents the element type. The `TcbTextInterpolationOp` can then finish executing and produce its code:
|
||||
|
||||
```typescript
|
||||
'' + t1.value;
|
||||
```
|
||||
|
||||
resulting in a final TCB:
|
||||
|
||||
```typescript
|
||||
var t1 = document.createElement('input');
|
||||
'' + t1.value;
|
||||
```
|
||||
|
||||
This ordering resolves the forward reference from the original template.
|
||||
|
||||
|
||||
##### Tracking of `TcbOp`s
|
||||
|
||||
In practice, a `TcbOp` queue is maintained as an array, where each element begins as a `TcbOp` and is later replaced with the resulting `ts.Expression` once the operation is executed. As `TcbOp`s are generated for various template structures, the index of these operations is recorded. Future dependencies on those operations can then be satisfied by looking in the queue at the appropriate index. The contents will either be a `TcbOp` which has yet to be executed, or the result of the required operation.
|
||||
|
||||
#### `Scope`
|
||||
|
||||
Angular templates are nested structures, as the main template can contain embedded views, which can contain their own views. Much like in other programming languages, this leads to a scoped hierarchy of symbol visibility and name resolution.
|
||||
|
||||
This is reflected in the TCB generation system via the `Scope` class, which actually performs the TCB generation itself. Each embedded view is its own `Scope`, with its own `TcbOp` queue.
|
||||
|
||||
When a parent `Scope` processing a template encounters an `<ng-template>` node:
|
||||
|
||||
1. a new child `Scope` is created from the nodes of the embedded view.
|
||||
2. a `TcbTemplateBodyOp` is added to the parent scope's queue, which upon execution triggers generation of the child `Scope`'s TCB code and inserts it into the parent's code at the right position.
|
||||
|
||||
Resolution of names (such as local refs) within the template is also driven by the `Scope` hierarchy. Resolution of a name within a particular embedded view begins in that view's `Scope`. If the name is not defined there, resolution proceeds upwards to the parent `Scope`, all the way up to the root template `Scope`.
|
||||
|
||||
If the name resolves in any given `Scope`, the associated `TcbOp` can be executed and returned. If the name does not resolve even at the root scope, then it's treated as a reference to the component context for the template.
|
||||
|
||||
#### Breaking cycles
|
||||
|
||||
It's possible for a template to contain a referential cycle. As a contrived example, if a component is generic over one of its inputs:
|
||||
|
||||
```html
|
||||
<generic-cmp #ref [in]="ref.value"></generic-cmp>
|
||||
```
|
||||
|
||||
Here, type-checking the `[in]` binding requires knowing the type of `ref`, which is the `<generic-cmp>`. But the `<generic-cmp>` type is inferred using a type constructor for the component which requires the `[in]` binding expression:
|
||||
|
||||
```typescript
|
||||
declare function ctor1<T>(inputs: {in?: T}): GenericCmp<T>;
|
||||
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// Not legal: cannot refer to t1 (ref) before its declaration.
|
||||
var t1 = ctor1({in: t1.value});
|
||||
|
||||
t1.in = t1.value;
|
||||
}
|
||||
```
|
||||
|
||||
This is only a cycle in the _type_ sense. At runtime, the component is created before its inputs are set, so no cycle exists.
|
||||
|
||||
To get around this, `TcbOp`s may optionally provide a fallback value via a `circularFallback()` method, which will be used in the event that evaluation of a `TcbOp` attempts to re-enter its own evaluation. In the above example, the `TcbOp` for the directive type declares a fallback which infers a type for the directive _without_ using its bound inputs:
|
||||
|
||||
```typescript
|
||||
declare function ctor1<T>(inputs: {in?: T}): GenericCmp<T>;
|
||||
|
||||
function tcb(ctx: SomeCmp): void {
|
||||
// Generated to break the cycle for `ref` - infers a placeholder
|
||||
// type for the component without using any of its input bindings.
|
||||
var t1 = ctor1(null!);
|
||||
|
||||
// Infer the real type of the component using the `t1` placeholder
|
||||
// type for `ref`.
|
||||
var t2 = ctor1({in: t1.value});
|
||||
|
||||
// Check the binding to [in] using the real type of `ref`.
|
||||
t2.in = t2.value;
|
||||
}
|
||||
```
|
||||
|
||||
#### Optional operations
|
||||
|
||||
Some `TcbOp`s are marked as optional. Optional operations are never executed as part of processing the op queue, and are skipped when encountered directly. However, other ops may depend on the results of optional operations, and the optional ops are then executed when requested in this manner.
|
||||
|
||||
The TCB node generated to represent a DOM element type (`TcbElementOp`) is an example of such an optional operation. Such nodes are only useful if the type of the element is referenced in some other context (such as via a `#ref` local reference). If not, then including it in the TCB only serves to bloat the TCB and increase the time it takes TypeScript to process it.
|
||||
|
||||
Therefore, the `TcbElementOp` is optional. If nothing requires the element type, it won't be executed and no code will be generated for the element node.
|
||||
|
||||
### Source mapping and diagnostic translation
|
||||
|
||||
Once TCB code is generated, TypeScript is able to process it. One use case is to have TypeScript produce diagnostics related to type issues within the TCB code, which indicate type issues within the template itself. These diagnostics are of course expressed in terms of the TCB itself, but the compiler wants to report diagnostics to the user in terms of the original template text.
|
||||
|
||||
To do this, the template type checking system is capable of mapping from the TCB back to the original template. The algorithm to perform this mapping relies on the emission of source mapping information into the TCB itself, in the form of comments.
|
||||
|
||||
Consider a template expression of the form:
|
||||
|
||||
```html
|
||||
{{foo.bar}}
|
||||
```
|
||||
|
||||
The generated TCB code for this expression would look like:
|
||||
|
||||
```typescript
|
||||
'' + ctx.foo.bar;
|
||||
```
|
||||
|
||||
What actually gets generated for this expression looks more like:
|
||||
|
||||
```typescript
|
||||
'' + (ctx.foo /* 3,5 */).bar /* 3,9 */;
|
||||
```
|
||||
|
||||
The trailing comment for each node in the TCB indicates the template offsets for the corresponding template nodes. If for example TypeScript returns a diagnostic for the `ctx.foo` part of the expression (such as if `foo` is not a valid property on the component context), the attached comment can be used to map this diagnostic back to the original template's `foo` node.
|
||||
|
||||
#### `TemplateId`
|
||||
|
||||
As multiple TCBs can be present in a single typecheck file, an additional layer of mapping is necessary to determine the component and template for a given TCB diagnostic.
|
||||
|
||||
During TCB generation, a numeric `TemplateId` is assigned to each template declared within a given input file. These `TemplateId` are attached in a comment to the TCBs for each template within the corresponding typecheck files. So the full algorithm for mapping a diagnostic back to the original template involves two steps:
|
||||
|
||||
1. Locate the top level TCB function declaration that contains the TCB node in error, extract its `TemplateId`, and use that to locate the component and template in question.
|
||||
2. Locate the closest source map comment to the TCB node in error, and combine that with knowledge of the template to locate the template node in error to produce the template diagnostic.
|
||||
|
||||
#### Ignore markers
|
||||
|
||||
Occasionally, code needs to be generated in the TCB that should not produce diagnostics. For example, a safe property navigation operation `a?.b` is mapped to a TypeScript ternary operation `a ? a.b : undefined` (the actual code here may be more complex). If the original `a` expression is in error, then redundant diagnostics would be produced from both instances of `a` in the generated TCB code.
|
||||
|
||||
To avoid this, generated code can be marked with a special comment indicating that any diagnostics produced within should be ignored and not converted into template diagnostics.
|
||||
|
||||
#### Why not real sourcemaps?
|
||||
|
||||
TypeScript unfortunately cannot consume sourcemaps, only produce them. Therefore, it's impossible to generate a source map to go along with the TCB code and feed it to TypeScript.
|
||||
|
||||
### Generation diagnostics
|
||||
|
||||
Not all template errors will be caught by TypeScript from generated TCB code. The template type checking engine may also detect errors during the creation of the TCB itself. Several classes of errors are caught this way:
|
||||
|
||||
* DOM schema errors, like elements that don't exist or attributes that aren't correct.
|
||||
* Missing pipes.
|
||||
* Missing `#ref` targets.
|
||||
* Duplicate `let-variable`s.
|
||||
* Attempts to write to a `let-variable`.
|
||||
|
||||
These errors manifest as "generation diagnostics", diagnostics which are produced during TCB generation, before TCB code is fed to TypeScript. They're ultimately reported together with any converted TCB diagnostics, but are tracked separately by the type checking system.
|
||||
|
||||
### Inline operations
|
||||
|
||||
In certain cases, generation of TCBs as separate, independent structures may not be possible.
|
||||
|
||||
#### Inline type constructors
|
||||
|
||||
The mechanics of generic directive type constructors were described above. However, the example given was for a directive with an unbounded generic type. If the directive has a _bounded_ generic type, then the type bounds must be repeated as part of the type constructor. For example, consider the directive:
|
||||
|
||||
```typescript
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir<T extends string> {
|
||||
@Input() value: T;
|
||||
}
|
||||
```
|
||||
|
||||
In order to properly infer and check the type of this directve, the type constructor must include the generic bounds:
|
||||
|
||||
```typescript
|
||||
declare function ctor1<T extends string>(inputs: {value?: T}): MyDir<T>;
|
||||
```
|
||||
|
||||
A generic bound for a type parameter is an arbitrary type, which may contain references to other types. These other types may be imported, or declared locally in the same file as a directive. This means that copying the generic bounds into the type constructor for the directive is not always straightforward, as the TCB which requires this type constructor is usually not emitted into the same file as the directive itself.
|
||||
|
||||
For example, copying the generic bounds is not possible for the directive:
|
||||
|
||||
```typescript
|
||||
interface PrivateInterface {
|
||||
field: string;
|
||||
}
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir<T extends PrivateInterface> {
|
||||
@Input() value: T;
|
||||
}
|
||||
```
|
||||
|
||||
In such cases, the type checking system falls back to an alternative mechanism for declaring type constructors: adding them as static methods on the directive class itself. As part of the type checking phase, the above directive would be transformed to:
|
||||
|
||||
```typescript
|
||||
|
||||
interface PrivateInterface {
|
||||
field: string;
|
||||
}
|
||||
|
||||
@Directive({selector: '[dir]'})
|
||||
export class MyDir<T extends PrivateInterface> {
|
||||
@Input() value: T;
|
||||
|
||||
static ngTypeCtor<T extends PrivateInterface>(inputs: {value?: T}): MyDir<T> { return null!; }
|
||||
}
|
||||
```
|
||||
|
||||
Putting the type constructor declaration within the directive class itself allows the generic signature to be copied without issue, as any references to other types in the same file will still be valid. The type constructor can then be consumed from a TCB as `MyDir.ngTypeCtor`. This is known as an "inline" type constructor.
|
||||
|
||||
Additions of such inline type checking code have significant ramifications on the performance of template type checking, as discussed below.
|
||||
|
||||
#### Inline Type Check Blocks
|
||||
|
||||
A similar problem exists for generic components and the declaration of TCBs. A TCB function must also copy the generic bounds of its context component:
|
||||
|
||||
```typescript
|
||||
function tcb<T extends string>(ctx: SomeCmp<T>): void {
|
||||
/* tcb code */
|
||||
}
|
||||
```
|
||||
|
||||
If `SomeCmp`'s generic bounds are more complex and reference types that cannot be safely reproduced in the separate context of the TCB, then a similar workaround is employed: the compiler generates the TCB as an "inline" static method on the component.
|
||||
|
||||
This can also happen for components which aren't themselves importable:
|
||||
|
||||
```typescript
|
||||
it('should type-check components declared within functions', () => {
|
||||
@Component({
|
||||
selector: 'some-cmp',
|
||||
template: '{{foo.bar}}'})
|
||||
class SomeCmp {
|
||||
foo: Foo = {...};
|
||||
}
|
||||
});
|
||||
```
|
||||
|
||||
Such component declarations are still processed by the compiler and can still be type checked using inline TCBs.
|
||||
|
||||
### `Environment`
|
||||
|
||||
TCBs are not standalone, and require supporting code such as imports to be properly checked. Additonally, multiple TCBs can share declarations regarding common dependencies, such as type constructors for directives as well as pipe instances.
|
||||
|
||||
Each TCB is therefore generated in the context of an `Environment`, which loosely represents the file which will ultimately contain the TCB code.
|
||||
|
||||
During TCB generation, the `Environment` is used to obtain references to imported types, type constructors, and other shared structures.
|
||||
|
||||
|
||||
#### `TypeCheckingConfig`
|
||||
|
||||
`Environment` also carries the `TypeCheckingConfig`, an options interface which controls the specifics of TCB generation. Through the `TypeCheckingConfig`, a consumer can enable or disable various kinds of strictness checks and other TCB operations.
|
||||
|
||||
## The `TemplateTypeChecker`
|
||||
|
||||
The main interface used by consumers to interact with the template type checking system is the `TemplateTypeChecker`. Methods on this interface allow for various operations related to TCBs, such as:
|
||||
|
||||
* Generation of diagnostics.
|
||||
* Retrieving `Symbol`s (the template equivalent to TypeScript's `ts.Symbol`) for template nodes.
|
||||
* Retrieving TCB locations suitable for autocompletion operations.
|
||||
|
||||
### Symbols
|
||||
|
||||
The TCB structures representing specific template nodes and operations are highly useful for "code intelligence" purposes, not just for type checking. For example, consider the "Go to Definition" function within an IDE. Within TypeScript code, TypeScript's language service can access semantic information about an identifier under the user's cursor, and locate the referenced declaration for that identifier.
|
||||
|
||||
Because TCBs are TypeScript code, the TypeScript language service can be used within TCB code to locate definitions in a similar manner. Such a "template language service" works in the following manner:
|
||||
|
||||
1. Find the template node under the user's cursor.
|
||||
2. Locate its equivalent structure in the TCB.
|
||||
3. Ask TypeScript's language service to perform the requested operation on the TCB node.
|
||||
|
||||
Step 2 in this algorithm is made possible by the `TemplateTypeChecker`'s APIs for retrieving `Symbol`s for template nodes. A `Symbol` is a structure describing the TCB information associated with a given template node, including positions within the TCB where type information about the node in question can be found.
|
||||
|
||||
For example, an `ElementSymbol` contains a TCB location for the element type, as well as any directives which may be present on the element. Such information can be used by a consumer to query TypeScript for further information about the types present in the template for that element. This is used in the Angular Language Service to implement many of its features.
|
||||
|
||||
## The type checking workflow
|
||||
|
||||
Like TypeScript's `ts.TypeChecker`, the `TemplateTypeChecker`'s operations are lazy. TCB code for a given template is only generated on demand, to satisfy a query for information about that specific template. Once generated, future queries regarding that template can be answered using the existing TCB structure.
|
||||
|
||||
The lazy algorithm used by the `TemplateTypeChecker` is as follows.
|
||||
|
||||
1. Given a query, determine which (if any) components need TCBs generated.
|
||||
2. Request sufficient information about those components and any used directives/pipes from the main compiler.
|
||||
3. Generate TCB source text.
|
||||
4. Obtain a `ts.Program` containing the new, generated TCBs.
|
||||
5. Use TypeScript APIs against this program and the TCBs to answer the query.
|
||||
|
||||
To enable the most flexibility for integrating the template type checking engine into various compilation workflows, steps 2 and 4 are abstracted behind interfaces.
|
||||
|
||||
### Step 2: template information
|
||||
|
||||
To answer most queries, the `TemplateTypeChecker` will require the TCB for a specific component. If not already generated, this involves generating a TCB for that component, and incorporating that TCB somehow into a new `ts.Program`.
|
||||
|
||||
Each source file in the user's program has a corresponding synthetic "typecheck" file which holds the TCBs for any components it contains. Updating this typecheck file with new contents (a new TCB) has a large fixed cost, regardless of the scope of the textual change to the file. Thus, the `TemplateTypeChecker` will always generate _all_ TCBs for a given input file, instead of just the one it needs. The marginal cost of generating the extra TCBs is extremely low.
|
||||
|
||||
To perform this generation, the type checking system first constructs a "context" into which template information can be recorded. It then requests (via an interface, the `ProgramTypeCheckAdapter`) that this context be populated with information about any templates present in the input file(s) it needs to process. Once populated, the information in the context can be used to drive TCB generation.
|
||||
|
||||
This process is somewhat complicated by the bookkeeping required to track which TCBs have been generated as well as the source mapping information for components within those TCBs.
|
||||
|
||||
On the compiler side, this interface drives the `TraitCompiler` to call the `typeCheck()` method of the `ComponentDecoratorHandler` for each decorated component class.
|
||||
|
||||
### Step 4: `ts.Program`s
|
||||
|
||||
The main output of the TCB generation mechanism is a list of new text contents for various source files. Typically this contains new TCB text for "typecheck" source files, but inline operations may require changes to actual user input files.
|
||||
|
||||
Before diagnostics can be produced or the `ts.TypeChecker` API can be used to interrogate types within the TCBs, these changes need to be parsed and incorporated into a `ts.Program`.
|
||||
|
||||
Creating such a `ts.Program` is not the responsibility of the `TemplateTypeChecker`. Instead, this creation is abstracted behind the `TypeCheckingProgramStrategy` interface. A strategy which implements this interface allows the type checking system to request textual changes be applied to the current `ts.Program`, resulting in an updated `ts.Program`.
|
||||
|
||||
As a convenience, the type-checking system provides an implementation of this abstraction, the `ReusedProgramStrategy`, which can be used by consumers that manage `ts.Program`s via TypeScript's `ts.createProgram` API. The main compiler uses this strategy to support template type checking.
|
||||
|
||||
`ts.Program`s can also be created via Language Service APIs, which would require a different strategy implementation.
|
|
@ -0,0 +1,17 @@
|
|||
import {exec} from 'shelljs';
|
||||
|
||||
const {BUILD_WORKSPACE_DIRECTORY} = process.env;
|
||||
|
||||
const rulesResult = exec(
|
||||
'yarn --silent bazel query \'filter("\\.update$", kind(rule, //packages/compiler-cli/test/compliance/test_cases:*))\' --output label',
|
||||
{cwd: BUILD_WORKSPACE_DIRECTORY, env: process.env, silent: true});
|
||||
|
||||
if (rulesResult.code !== 0) {
|
||||
throw new Error('Failed to query Bazel for the update rules:\n' + rulesResult.stderr);
|
||||
}
|
||||
|
||||
for (const rule of rulesResult.split('\n')) {
|
||||
if (rule.trim() !== '') {
|
||||
console.log('yarn bazel run ' + rule);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,495 @@
|
|||
/****************************************************************************************************
|
||||
* PARTIAL FILE: forward_referenced_directive.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Directive, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class HostBindingComp {
|
||||
}
|
||||
HostBindingComp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
HostBindingComp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: HostBindingComp, selector: "host-binding-comp", ngImport: i0, template: `
|
||||
<my-forward-directive></my-forward-directive>
|
||||
`, isInline: true, directives: [{ type: i0.forwardRef(function () { return MyForwardDirective; }), selector: "my-forward-directive" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(HostBindingComp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'host-binding-comp',
|
||||
template: `
|
||||
<my-forward-directive></my-forward-directive>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
class MyForwardDirective {
|
||||
}
|
||||
MyForwardDirective.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyForwardDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
MyForwardDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: "0.0.0-PLACEHOLDER", type: MyForwardDirective, selector: "my-forward-directive", ngImport: i0 });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyForwardDirective, [{
|
||||
type: Directive,
|
||||
args: [{ selector: 'my-forward-directive' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [HostBindingComp, MyForwardDirective] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [HostBindingComp, MyForwardDirective] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: forward_referenced_directive.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class HostBindingComp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<HostBindingComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<HostBindingComp, "host-binding-comp", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof HostBindingComp, typeof MyForwardDirective], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: forward_referenced_pipe.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule, Pipe } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class HostBindingComp {
|
||||
}
|
||||
HostBindingComp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: HostBindingComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
HostBindingComp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: HostBindingComp, selector: "host-binding-comp", ngImport: i0, template: `
|
||||
<div [attr.style]="{} | my_forward_pipe">...</div>
|
||||
`, isInline: true, pipes: { "my_forward_pipe": i0.forwardRef(function () { return MyForwardPipe; }) } });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(HostBindingComp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'host-binding-comp',
|
||||
template: `
|
||||
<div [attr.style]="{} | my_forward_pipe">...</div>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
class MyForwardPipe {
|
||||
}
|
||||
MyForwardPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyForwardPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
||||
MyForwardPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyForwardPipe, name: "my_forward_pipe" });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyForwardPipe, [{
|
||||
type: Pipe,
|
||||
args: [{ name: 'my_forward_pipe' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [HostBindingComp, MyForwardPipe] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [HostBindingComp, MyForwardPipe] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: forward_referenced_pipe.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class HostBindingComp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<HostBindingComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<HostBindingComp, "host-binding-comp", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof HostBindingComp, typeof MyForwardPipe], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: export_as.js
|
||||
****************************************************************************************************/
|
||||
import { Directive, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SomeDirective {
|
||||
}
|
||||
SomeDirective.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
SomeDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: "0.0.0-PLACEHOLDER", type: SomeDirective, selector: "[some-directive]", exportAs: ["someDir", "otherDir"], ngImport: i0 });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeDirective, [{
|
||||
type: Directive,
|
||||
args: [{ selector: '[some-directive]', exportAs: 'someDir, otherDir' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [SomeDirective] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [SomeDirective] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: export_as.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SomeDirective {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SomeDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<SomeDirective, "[some-directive]", ["someDir", "otherDir"], {}, {}, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof SomeDirective], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: no_selector.js
|
||||
****************************************************************************************************/
|
||||
import { Directive } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class AbstractDirective {
|
||||
}
|
||||
AbstractDirective.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: AbstractDirective, deps: [], target: i0.ɵɵFactoryTarget.Directive });
|
||||
AbstractDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: "0.0.0-PLACEHOLDER", type: AbstractDirective, ngImport: i0 });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(AbstractDirective, [{
|
||||
type: Directive
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: no_selector.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class AbstractDirective {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<AbstractDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<AbstractDirective, never, never, {}, {}, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: constant_object_literals.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Input, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SomeComp {
|
||||
}
|
||||
SomeComp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SomeComp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SomeComp, selector: "some-comp", inputs: { prop: "prop", otherProp: "otherProp" }, ngImport: i0, template: '', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeComp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'some-comp', template: '' }]
|
||||
}], null, { prop: [{
|
||||
type: Input
|
||||
}], otherProp: [{
|
||||
type: Input
|
||||
}] }); })();
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: '<some-comp [prop]="{}" [otherProp]="{a: 1, b: 2}"></some-comp>', isInline: true, components: [{ type: SomeComp, selector: "some-comp", inputs: ["prop", "otherProp"] }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ template: '<some-comp [prop]="{}" [otherProp]="{a: 1, b: 2}"></some-comp>' }]
|
||||
}], null, null); })();
|
||||
export class MyMod {
|
||||
}
|
||||
MyMod.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyMod.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, declarations: [SomeComp, MyApp] });
|
||||
MyMod.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyMod, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [SomeComp, MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: constant_object_literals.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SomeComp {
|
||||
prop: any;
|
||||
otherProp: any;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SomeComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SomeComp, "some-comp", never, { "prop": "prop"; "otherProp": "otherProp"; }, {}, never, never>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyMod {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyMod, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyMod, [typeof SomeComp, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyMod>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: constant_array_literals.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Input, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SomeComp {
|
||||
}
|
||||
SomeComp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SomeComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SomeComp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SomeComp, selector: "some-comp", inputs: { prop: "prop", otherProp: "otherProp" }, ngImport: i0, template: '', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SomeComp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'some-comp', template: '' }]
|
||||
}], null, { prop: [{
|
||||
type: Input
|
||||
}], otherProp: [{
|
||||
type: Input
|
||||
}] }); })();
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: '<some-comp [prop]="[]" [otherProp]="[0, 1, 2]"></some-comp>', isInline: true, components: [{ type: SomeComp, selector: "some-comp", inputs: ["prop", "otherProp"] }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ template: '<some-comp [prop]="[]" [otherProp]="[0, 1, 2]"></some-comp>' }]
|
||||
}], null, null); })();
|
||||
export class MyMod {
|
||||
}
|
||||
MyMod.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyMod.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod, declarations: [MyApp, SomeComp] });
|
||||
MyMod.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyMod });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyMod, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyApp, SomeComp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: constant_array_literals.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SomeComp {
|
||||
prop: any;
|
||||
otherProp: any;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SomeComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SomeComp, "some-comp", never, { "prop": "prop"; "otherProp": "otherProp"; }, {}, never, never>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyMod {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyMod, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyMod, [typeof MyApp, typeof SomeComp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyMod>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: object_literals_null_vs_empty.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: {}}"></div>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: {}}"></div>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: object_literals_null_vs_empty.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: array_literals_null_vs_empty.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: []}"></div>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: []}"></div>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: array_literals_null_vs_empty.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: object_literals_null_vs_function.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
getFoo() {
|
||||
return 'foo!';
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "ng-component", ngImport: i0, template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: getFoo()}"></div>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: getFoo()}"></div>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: object_literals_null_vs_function.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
getFoo(): string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "ng-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: custom_decorator_es5.js
|
||||
****************************************************************************************************/
|
||||
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
|
||||
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
||||
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
||||
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
||||
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
||||
};
|
||||
import { Component, InjectionToken } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
var token = new InjectionToken('token');
|
||||
export function Custom() {
|
||||
return function (target) { };
|
||||
}
|
||||
var Comp = /** @class */ (function () {
|
||||
function Comp() {
|
||||
}
|
||||
Comp_1 = Comp;
|
||||
var Comp_1;
|
||||
Comp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Comp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
Comp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: Comp, selector: "ng-component", providers: [{ provide: token, useExisting: Comp_1 }], ngImport: i0, template: '', isInline: true });
|
||||
Comp = Comp_1 = __decorate([
|
||||
Custom()
|
||||
], Comp);
|
||||
return Comp;
|
||||
}());
|
||||
export { Comp };
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Comp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: '',
|
||||
providers: [{ provide: token, useExisting: Comp }],
|
||||
}]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: custom_decorator_es5.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare function Custom(): (target: any) => void;
|
||||
export declare class Comp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<Comp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<Comp, "ng-component", never, {}, {}, never, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_template_empty_binding.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyComponent {
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-app", ngImport: i0, template: '<ng-template [id]=""></ng-template>', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyComponent, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-app', template: '<ng-template [id]=""></ng-template>' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyComponent] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_template_empty_binding.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
{
|
||||
"$schema": "../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"description": "should instantiate directives in a closure when they are forward referenced",
|
||||
"inputFiles": [
|
||||
"forward_referenced_directive.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"forward_referenced_directive.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should instantiate pipes in a closure when they are forward referenced",
|
||||
"inputFiles": [
|
||||
"forward_referenced_pipe.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"forward_referenced_pipe.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should split multiple `exportAs` values into an array",
|
||||
"inputFiles": [
|
||||
"export_as.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect SomeDirective.ɵdir",
|
||||
"files": [
|
||||
"export_as.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should not generate a selectors array if the directive does not have a selector",
|
||||
"inputFiles": [
|
||||
"no_selector.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid directive definition",
|
||||
"files": [
|
||||
"no_selector.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate a pure function for constant object literals",
|
||||
"inputFiles": [
|
||||
"constant_object_literals.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"constant_object_literals.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate a pure function for constant array literals",
|
||||
"inputFiles": [
|
||||
"constant_array_literals.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"constant_array_literals.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should not share pure functions between null and object literals",
|
||||
"inputFiles": [
|
||||
"object_literals_null_vs_empty.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"object_literals_null_vs_empty.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should not share pure functions between null and array literals",
|
||||
"inputFiles": [
|
||||
"array_literals_null_vs_empty.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"array_literals_null_vs_empty.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should not share pure functions between null and function calls",
|
||||
"inputFiles": [
|
||||
"object_literals_null_vs_function.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid component definition",
|
||||
"files": [
|
||||
"object_literals_null_vs_function.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should emit a valid setClassMetadata call in ES5 if a class with a custom decorator is referencing itself inside its own metadata",
|
||||
"inputFiles": [
|
||||
"custom_decorator_es5.ts"
|
||||
],
|
||||
"compilerOptions": {
|
||||
"target": "ES5"
|
||||
},
|
||||
"compilationModeFilter": [
|
||||
"full compile"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect setClassMetadata call",
|
||||
"files": [
|
||||
"custom_decorator_es5.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support empty property bindings on ng-template",
|
||||
"inputFiles": [
|
||||
"ng_template_empty_binding.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect template",
|
||||
"files": [
|
||||
"ng_template_empty_binding.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
const $c0$ = function () { return { foo: null }; };
|
||||
const $c1$ = function () { return []; };
|
||||
const $c2$ = function (a0) { return { foo: a0 }; };
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["ng-component"]],
|
||||
decls: 2,
|
||||
vars: 6,
|
||||
consts: [[__AttributeMarker.Bindings__, "dir"]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "div", 0);
|
||||
$r3$.ɵɵelement(1, "div", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$));
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$)));
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: []}"></div>
|
||||
`
|
||||
})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
const $c0$ = function () { return []; };
|
||||
const $c1$ = function () { return [0, 1, 2]; };
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["ng-component"]],
|
||||
decls: 1,
|
||||
vars: 4,
|
||||
consts: [[__AttributeMarker.Bindings__, "prop", "otherProp"]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "some-comp", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$));
|
||||
}
|
||||
},
|
||||
directives: [SomeComp],
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'some-comp', template: ''})
|
||||
export class SomeComp {
|
||||
@Input() prop!: any;
|
||||
@Input() otherProp!: any;
|
||||
}
|
||||
|
||||
@Component({template: '<some-comp [prop]="[]" [otherProp]="[0, 1, 2]"></some-comp>'})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp, SomeComp]})
|
||||
export class MyMod {
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
const $c0$ = function () { return {}; };
|
||||
const $c1$ = function () { return { a: 1, b: 2 }; };
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["ng-component"]],
|
||||
decls: 1,
|
||||
vars: 4,
|
||||
consts: [[__AttributeMarker.Bindings__, "prop", "otherProp"]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "some-comp", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("prop", $r3$.ɵɵpureFunction0(2, $c0$))("otherProp", $r3$.ɵɵpureFunction0(3, $c1$));
|
||||
}
|
||||
},
|
||||
directives: [SomeComp],
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'some-comp', template: ''})
|
||||
export class SomeComp {
|
||||
@Input() prop!: any;
|
||||
@Input() otherProp!: any;
|
||||
}
|
||||
|
||||
@Component({template: '<some-comp [prop]="{}" [otherProp]="{a: 1, b: 2}"></some-comp>'})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeComp, MyApp]})
|
||||
export class MyMod {
|
||||
}
|
|
@ -0,0 +1,358 @@
|
|||
/****************************************************************************************************
|
||||
* PARTIAL FILE: root_template.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleComponent {
|
||||
}
|
||||
SimpleComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SimpleComponent, selector: "simple", ngImport: i0, template: '<div><ng-content></ng-content></div>', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SimpleComponent, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'simple', template: '<div><ng-content></ng-content></div>' }]
|
||||
}], null, null); })();
|
||||
export class ComplexComponent {
|
||||
}
|
||||
ComplexComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: ComplexComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
ComplexComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: ComplexComponent, selector: "complex", ngImport: i0, template: `
|
||||
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
||||
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(ComplexComponent, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'complex',
|
||||
template: `
|
||||
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
||||
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '<simple>content</simple> <complex></complex>', isInline: true, components: [{ type: SimpleComponent, selector: "simple" }, { type: ComplexComponent, selector: "complex" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-app', template: '<simple>content</simple> <complex></complex>' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [SimpleComponent, ComplexComponent, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [SimpleComponent, ComplexComponent, MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: root_template.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleComponent, "simple", never, {}, {}, never, ["*"]>;
|
||||
}
|
||||
export declare class ComplexComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<ComplexComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<ComplexComponent, "complex", never, {}, {}, never, ["span[title=toFirst]", "span[title=toSecond]"]>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof SimpleComponent, typeof ComplexComponent, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: multiple_wildcards.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
class Cmp {
|
||||
}
|
||||
Cmp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Cmp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
Cmp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: Cmp, selector: "ng-component", ngImport: i0, template: `
|
||||
<ng-content></ng-content>
|
||||
<ng-content select="[spacer]"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Cmp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
<ng-content select="[spacer]"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
}]
|
||||
}], null, null); })();
|
||||
class Module {
|
||||
}
|
||||
Module.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
Module.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module, declarations: [Cmp] });
|
||||
Module.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Module, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [Cmp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: multiple_wildcards.d.ts
|
||||
****************************************************************************************************/
|
||||
export {};
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: nested_template.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
class Cmp {
|
||||
}
|
||||
Cmp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Cmp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
Cmp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: Cmp, selector: "ng-component", ngImport: i0, template: `
|
||||
<div id="second" *ngIf="visible">
|
||||
<ng-content SELECT="span[title=toFirst]"></ng-content>
|
||||
</div>
|
||||
<div id="third" *ngIf="visible">
|
||||
No ng-content, no instructions generated.
|
||||
</div>
|
||||
<ng-template>
|
||||
'*' selector: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Cmp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
<div id="second" *ngIf="visible">
|
||||
<ng-content SELECT="span[title=toFirst]"></ng-content>
|
||||
</div>
|
||||
<div id="third" *ngIf="visible">
|
||||
No ng-content, no instructions generated.
|
||||
</div>
|
||||
<ng-template>
|
||||
'*' selector: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
`,
|
||||
}]
|
||||
}], null, null); })();
|
||||
class Module {
|
||||
}
|
||||
Module.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
Module.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module, declarations: [Cmp] });
|
||||
Module.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Module, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [Cmp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: nested_template.d.ts
|
||||
****************************************************************************************************/
|
||||
export {};
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: root_and_nested.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
class Cmp {
|
||||
}
|
||||
Cmp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Cmp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
Cmp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: Cmp, selector: "ng-component", ngImport: i0, template: `
|
||||
<ng-content select="[id=toMainBefore]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toTemplate]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toNestedTemplate]"></ng-content>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template>
|
||||
'*' selector in a template: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
<ng-content select="[id=toMainAfter]"></ng-content>
|
||||
`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Cmp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: `
|
||||
<ng-content select="[id=toMainBefore]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toTemplate]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toNestedTemplate]"></ng-content>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template>
|
||||
'*' selector in a template: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
<ng-content select="[id=toMainAfter]"></ng-content>
|
||||
`,
|
||||
}]
|
||||
}], null, null); })();
|
||||
class Module {
|
||||
}
|
||||
Module.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
Module.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module, declarations: [Cmp] });
|
||||
Module.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: Module });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Module, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [Cmp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: root_and_nested.d.ts
|
||||
****************************************************************************************************/
|
||||
export {};
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_project_as_selector.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleComponent {
|
||||
}
|
||||
SimpleComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SimpleComponent, selector: "simple", ngImport: i0, template: '<div><ng-content select="[title]"></ng-content></div>', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SimpleComponent, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'simple', template: '<div><ng-content select="[title]"></ng-content></div>' }]
|
||||
}], null, null); })();
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '<simple><h1 ngProjectAs="[title]"></h1></simple>', isInline: true, components: [{ type: SimpleComponent, selector: "simple" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-app', template: '<simple><h1 ngProjectAs="[title]"></h1></simple>' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyApp, SimpleComponent] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyApp, SimpleComponent] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_project_as_selector.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleComponent, "simple", never, {}, {}, never, ["[title]"]>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyApp, typeof SimpleComponent], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_project_as_compound_selector.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleComponent {
|
||||
}
|
||||
SimpleComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SimpleComponent, selector: "simple", ngImport: i0, template: '<div><ng-content select="[title]"></ng-content></div>', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SimpleComponent, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'simple', template: '<div><ng-content select="[title]"></ng-content></div>' }]
|
||||
}], null, null); })();
|
||||
export class MyApp {
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '<simple><h1 ngProjectAs="[title],[header]"></h1></simple>', isInline: true, components: [{ type: SimpleComponent, selector: "simple" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-app', template: '<simple><h1 ngProjectAs="[title],[header]"></h1></simple>' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [SimpleComponent, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [SimpleComponent, MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_project_as_compound_selector.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleComponent, "simple", never, {}, {}, never, ["[title]"]>;
|
||||
}
|
||||
export declare class MyApp {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof SimpleComponent, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_project_as_attribute.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.show = true;
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '<div *ngIf="show" ngProjectAs=".someclass"></div>', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-app', template: '<div *ngIf="show" ngProjectAs=".someclass"></div>' }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_project_as_attribute.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyApp {
|
||||
show: boolean;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_content_with_structural_dir.js
|
||||
****************************************************************************************************/
|
||||
import { Component } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class SimpleComponent {
|
||||
}
|
||||
SimpleComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SimpleComponent, selector: "simple", ngImport: i0, template: '<ng-content *ngIf="showContent"></ng-content>', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SimpleComponent, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'simple', template: '<ng-content *ngIf="showContent"></ng-content>' }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: ng_content_with_structural_dir.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class SimpleComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleComponent, "simple", never, {}, {}, never, ["*"]>;
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
{
|
||||
"$schema": "../../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"description": "should support content projection in root template",
|
||||
"inputFiles": [
|
||||
"root_template.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect SimpleComponent definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "root_template_simple_def.js",
|
||||
"generated": "root_template.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Incorrect ComplexComponent definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "root_template_complex_def.js",
|
||||
"generated": "root_template.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support multi-slot content projection with multiple wildcard slots",
|
||||
"inputFiles": [
|
||||
"multiple_wildcards.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid content projection instructions generated",
|
||||
"files": [
|
||||
"multiple_wildcards.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support content projection in nested templates",
|
||||
"inputFiles": [
|
||||
"nested_template.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid content projection instructions generated",
|
||||
"files": [
|
||||
"nested_template.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support content projection in both the root and nested templates",
|
||||
"inputFiles": [
|
||||
"root_and_nested.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid content projection instructions generated",
|
||||
"files": [
|
||||
"root_and_nested.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should parse the selector that is passed into ngProjectAs",
|
||||
"inputFiles": [
|
||||
"ng_project_as_selector.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect SimpleComponent definition",
|
||||
"files": [
|
||||
"ng_project_as_selector.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should take the first selector if multiple values are passed into ngProjectAs",
|
||||
"inputFiles": [
|
||||
"ng_project_as_compound_selector.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect SimpleComponent definition",
|
||||
"files": [
|
||||
"ng_project_as_compound_selector.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should include parsed ngProjectAs selectors into template attrs",
|
||||
"inputFiles": [
|
||||
"ng_project_as_attribute.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect MyApp definition",
|
||||
"files": [
|
||||
"ng_project_as_attribute.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should capture the node name of ng-content with a structural directive",
|
||||
"inputFiles": [
|
||||
"ng_content_with_structural_dir.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect SimpleComponent definition",
|
||||
"files": [
|
||||
"ng_content_with_structural_dir.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const $c0$ = ["*", [["", "spacer", ""]], "*"];
|
||||
const $c1$ = ["*", "[spacer]", "*"];
|
||||
// ...
|
||||
Cmp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: Cmp,
|
||||
selectors: [["ng-component"]],
|
||||
ngContentSelectors: $c1$,
|
||||
decls: 3,
|
||||
vars: 0,
|
||||
template: function Cmp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵprojectionDef($c0$);
|
||||
i0.ɵɵprojection(0);
|
||||
i0.ɵɵprojection(1, 1);
|
||||
i0.ɵɵprojection(2, 2);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-content></ng-content>
|
||||
<ng-content select="[spacer]"></ng-content>
|
||||
<ng-content></ng-content>
|
||||
`,
|
||||
})
|
||||
class Cmp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [Cmp]})
|
||||
class Module {
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
function Cmp_div_0_Template(rf, ctx) { if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", 2);
|
||||
$r3$.ɵɵprojection(1);
|
||||
$r3$.ɵɵelementEnd();
|
||||
} }
|
||||
function Cmp_div_1_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div", 3);
|
||||
$r3$.ɵɵtext(1, " No ng-content, no instructions generated. ");
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
}
|
||||
function Cmp_ng_template_2_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtext(0, " '*' selector: ");
|
||||
$r3$.ɵɵprojection(1, 1);
|
||||
}
|
||||
}
|
||||
const $_c4$ = [[["span", "title", "tofirst"]], "*"];
|
||||
// ...
|
||||
consts: [["id", "second", __AttributeMarker.Template__, "ngIf"], ["id", "third", __AttributeMarker.Template__, "ngIf"], ["id", "second"], ["id", "third"]],
|
||||
template: function Cmp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵprojectionDef($_c4$);
|
||||
$r3$.ɵɵtemplate(0, Cmp_div_0_Template, 2, 0, "div", 0);
|
||||
$r3$.ɵɵtemplate(1, Cmp_div_1_Template, 2, 0, "div", 1);
|
||||
$r3$.ɵɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("ngIf", ctx.visible);
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵproperty("ngIf", ctx.visible);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div id="second" *ngIf="visible">
|
||||
<ng-content SELECT="span[title=toFirst]"></ng-content>
|
||||
</div>
|
||||
<div id="third" *ngIf="visible">
|
||||
No ng-content, no instructions generated.
|
||||
</div>
|
||||
<ng-template>
|
||||
'*' selector: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
`,
|
||||
})
|
||||
class Cmp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [Cmp]})
|
||||
class Module {
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
SimpleComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
selectors: [["simple"]],
|
||||
ngContentSelectors: $c0$,
|
||||
decls: 1,
|
||||
vars: 1,
|
||||
consts: [[__AttributeMarker.Template__, "ngIf"]],
|
||||
template: function SimpleComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵprojectionDef();
|
||||
i0.ɵɵtemplate(0, SimpleComponent_ng_content_0_Template, 1, 0, "ng-content", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
i0.ɵɵproperty("ngIf", ctx.showContent);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
|
||||
@Component({selector: 'simple', template: '<ng-content *ngIf="showContent"></ng-content>'})
|
||||
export class SimpleComponent {
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
MyApp.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [
|
||||
["my-app"]
|
||||
],
|
||||
decls: 1,
|
||||
vars: 1,
|
||||
consts: [
|
||||
["ngProjectAs", ".someclass", __AttributeMarker.ProjectAs__, ["", 8, "someclass"], __AttributeMarker.Template__, "ngIf"],
|
||||
["ngProjectAs", ".someclass", __AttributeMarker.ProjectAs__, ["", 8, "someclass"]]
|
||||
],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
i0.ɵɵtemplate(0, MyApp_div_0_Template, 1, 0, "div", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
i0.ɵɵproperty("ngIf", ctx.show);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,6 @@
|
|||
import {Component} from '@angular/core';
|
||||
|
||||
@Component({selector: 'my-app', template: '<div *ngIf="show" ngProjectAs=".someclass"></div>'})
|
||||
export class MyApp {
|
||||
show = true;
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
// NOTE: the c0 and c1 constants aren't being used in this particular test,
|
||||
// NOTE: but they are used in some of the logic that is folded under the ellipsis.
|
||||
const $_c0$ = [[["", "title", ""]]];
|
||||
const $_c1$ = ["[title]"];
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["my-app"]],
|
||||
decls: 2,
|
||||
vars: 0,
|
||||
consts: [["ngProjectAs", "[title],[header]", 5, ["", "title", ""]]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "simple");
|
||||
$r3$.ɵɵelement(1, "h1", 0);
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
},
|
||||
directives: [SimpleComponent],
|
||||
encapsulation: 2
|
||||
})
|
|
@ -0,0 +1,14 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'simple', template: '<div><ng-content select="[title]"></ng-content></div>'})
|
||||
export class SimpleComponent {
|
||||
}
|
||||
|
||||
@Component(
|
||||
{selector: 'my-app', template: '<simple><h1 ngProjectAs="[title],[header]"></h1></simple>'})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SimpleComponent, MyApp]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
const $_c0$ = [[["", "title", ""]]];
|
||||
const $_c1$ = ["[title]"];
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["my-app"]],
|
||||
decls: 2,
|
||||
vars: 0,
|
||||
consts: [["ngProjectAs", "[title]", 5, ["", "title", ""]]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "simple");
|
||||
$r3$.ɵɵelement(1, "h1", 0);
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
},
|
||||
directives: [SimpleComponent],
|
||||
encapsulation: 2
|
||||
})
|
|
@ -0,0 +1,13 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'simple', template: '<div><ng-content select="[title]"></ng-content></div>'})
|
||||
export class SimpleComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'my-app', template: '<simple><h1 ngProjectAs="[title]"></h1></simple>'})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp, SimpleComponent]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,29 @@
|
|||
function Cmp_ng_template_1_ng_template_1_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵprojection(0, 3);
|
||||
}
|
||||
}
|
||||
function Cmp_ng_template_1_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵprojection(0, 2);
|
||||
$r3$.ɵɵtemplate(1, Cmp_ng_template_1_ng_template_1_Template, 1, 0, "ng-template");
|
||||
}
|
||||
}
|
||||
function Cmp_ng_template_2_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtext(0, " '*' selector in a template: ");
|
||||
$r3$.ɵɵprojection(1, 4);
|
||||
}
|
||||
}
|
||||
const $_c0$ = [[["", "id", "tomainbefore"]], [["", "id", "tomainafter"]], [["", "id", "totemplate"]], [["", "id", "tonestedtemplate"]], "*"];
|
||||
const $_c1$ = ["[id=toMainBefore]", "[id=toMainAfter]", "[id=toTemplate]", "[id=toNestedTemplate]", "*"];
|
||||
// ...
|
||||
template: function Cmp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵprojectionDef($_c0$);
|
||||
$r3$.ɵɵprojection(0);
|
||||
$r3$.ɵɵtemplate(1, Cmp_ng_template_1_Template, 2, 0, "ng-template");
|
||||
$r3$.ɵɵtemplate(2, Cmp_ng_template_2_Template, 2, 0, "ng-template");
|
||||
$r3$.ɵɵprojection(3, 1);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<ng-content select="[id=toMainBefore]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toTemplate]"></ng-content>
|
||||
<ng-template>
|
||||
<ng-content select="[id=toNestedTemplate]"></ng-content>
|
||||
</ng-template>
|
||||
</ng-template>
|
||||
<ng-template>
|
||||
'*' selector in a template: <ng-content></ng-content>
|
||||
</ng-template>
|
||||
<ng-content select="[id=toMainAfter]"></ng-content>
|
||||
`,
|
||||
})
|
||||
class Cmp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [Cmp]})
|
||||
class Module {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
|
||||
@Component({selector: 'simple', template: '<div><ng-content></ng-content></div>'})
|
||||
export class SimpleComponent {
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'complex',
|
||||
template: `
|
||||
<div id="first"><ng-content select="span[title=toFirst]"></ng-content></div>
|
||||
<div id="second"><ng-content SELECT="span[title=toSecond]"></ng-content></div>`
|
||||
})
|
||||
export class ComplexComponent {
|
||||
}
|
||||
|
||||
@Component({selector: 'my-app', template: '<simple>content</simple> <complex></complex>'})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SimpleComponent, ComplexComponent, MyApp]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const $c1$ = [[["span", "title", "tofirst"]], [["span", "title", "tosecond"]]];
|
||||
// ...
|
||||
ComplexComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: ComplexComponent,
|
||||
selectors: [["complex"]],
|
||||
ngContentSelectors: $c2$,
|
||||
decls: 4,
|
||||
vars: 0,
|
||||
consts: [["id","first"], ["id","second"]],
|
||||
template: function ComplexComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵprojectionDef($c1$);
|
||||
$r3$.ɵɵelementStart(0, "div", 0);
|
||||
$r3$.ɵɵprojection(1);
|
||||
$r3$.ɵɵelementEnd();
|
||||
$r3$.ɵɵelementStart(2, "div", 1);
|
||||
$r3$.ɵɵprojection(3, 1);
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,16 @@
|
|||
SimpleComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: SimpleComponent,
|
||||
selectors: [["simple"]],
|
||||
ngContentSelectors: $c0$,
|
||||
decls: 2,
|
||||
vars: 0,
|
||||
template: function SimpleComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵprojectionDef();
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵprojection(1);
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,7 @@
|
|||
(function () {(typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(Comp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
template: '',
|
||||
providers: [{provide: token, useExisting: Comp}],
|
||||
}]
|
||||
}], null, null); })();
|
|
@ -0,0 +1,15 @@
|
|||
import {Component, InjectionToken} from '@angular/core';
|
||||
|
||||
const token = new InjectionToken('token');
|
||||
|
||||
export function Custom() {
|
||||
return function(target: any) {};
|
||||
}
|
||||
|
||||
@Custom()
|
||||
@Component({
|
||||
template: '',
|
||||
providers: [{provide: token, useExisting: Comp}],
|
||||
})
|
||||
export class Comp {
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
SomeDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({
|
||||
type: SomeDirective,
|
||||
selectors: [["", "some-directive", ""]],
|
||||
exportAs: ["someDir", "otherDir"]
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import {Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[some-directive]', exportAs: 'someDir, otherDir'})
|
||||
export class SomeDirective {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [SomeDirective]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// ...
|
||||
directives: function () { return [MyForwardDirective]; }
|
||||
// ...
|
|
@ -0,0 +1,18 @@
|
|||
import {Component, Directive, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'host-binding-comp',
|
||||
template: `
|
||||
<my-forward-directive></my-forward-directive>
|
||||
`
|
||||
})
|
||||
export class HostBindingComp {
|
||||
}
|
||||
|
||||
@Directive({selector: 'my-forward-directive'})
|
||||
class MyForwardDirective {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [HostBindingComp, MyForwardDirective]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
// ...
|
||||
pipes: function () { return [MyForwardPipe]; }
|
||||
// ...
|
|
@ -0,0 +1,18 @@
|
|||
import {Component, NgModule, Pipe} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'host-binding-comp',
|
||||
template: `
|
||||
<div [attr.style]="{} | my_forward_pipe">...</div>
|
||||
`
|
||||
})
|
||||
export class HostBindingComp {
|
||||
}
|
||||
|
||||
@Pipe({name: 'my_forward_pipe'})
|
||||
class MyForwardPipe {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [HostBindingComp, MyForwardPipe]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
/****************************************************************************************************
|
||||
* PARTIAL FILE: local_reference.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyComponent {
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: '<input #user>Hello {{user.value}}!', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyComponent, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-component', template: '<input #user>Hello {{user.value}}!' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyComponent] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: local_reference.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: local_reference_nested.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Directive, NgModule, TemplateRef } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class IfDirective {
|
||||
constructor(template) { }
|
||||
}
|
||||
IfDirective.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: IfDirective, deps: [{ token: i0.TemplateRef }], target: i0.ɵɵFactoryTarget.Directive });
|
||||
IfDirective.ɵdir = i0.ɵɵngDeclareDirective({ version: "0.0.0-PLACEHOLDER", type: IfDirective, selector: "[if]", ngImport: i0 });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(IfDirective, [{
|
||||
type: Directive,
|
||||
args: [{ selector: '[if]' }]
|
||||
}], function () { return [{ type: i0.TemplateRef }]; }, null); })();
|
||||
export class MyComponent {
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
|
||||
<div #foo></div>
|
||||
{{foo}}
|
||||
<div *if>
|
||||
{{foo}}-{{bar}}
|
||||
<span *if>{{foo}}-{{bar}}-{{baz}}</span>
|
||||
<span #bar></span>
|
||||
</div>
|
||||
<div #baz></div>
|
||||
`, isInline: true, directives: [{ type: IfDirective, selector: "[if]" }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyComponent, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-component',
|
||||
template: `
|
||||
<div #foo></div>
|
||||
{{foo}}
|
||||
<div *if>
|
||||
{{foo}}-{{bar}}
|
||||
<span *if>{{foo}}-{{bar}}-{{baz}}</span>
|
||||
<span #bar></span>
|
||||
</div>
|
||||
<div #baz></div>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [IfDirective, MyComponent] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [IfDirective, MyComponent] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: local_reference_nested.d.ts
|
||||
****************************************************************************************************/
|
||||
import { TemplateRef } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class IfDirective {
|
||||
constructor(template: TemplateRef<any>);
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<IfDirective, never>;
|
||||
static ɵdir: i0.ɵɵDirectiveDeclaration<IfDirective, "[if]", never, {}, {}, never>;
|
||||
}
|
||||
export declare class MyComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof IfDirective, typeof MyComponent], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: local_reference_and_context_variables.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyComponent {
|
||||
}
|
||||
MyComponent.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyComponent.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyComponent, selector: "my-component", ngImport: i0, template: `
|
||||
<div *ngFor="let item of items">
|
||||
<div #foo></div>
|
||||
<span *ngIf="showing">
|
||||
{{ foo }} - {{ item }}
|
||||
</span>
|
||||
</div>`, isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyComponent, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-component',
|
||||
template: `
|
||||
<div *ngFor="let item of items">
|
||||
<div #foo></div>
|
||||
<span *ngIf="showing">
|
||||
{{ foo }} - {{ item }}
|
||||
</span>
|
||||
</div>`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyComponent] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyComponent] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: local_reference_and_context_variables.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyComponent {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyComponent, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyComponent, "my-component", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyComponent], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: lifecycle_hooks.js
|
||||
****************************************************************************************************/
|
||||
import { Component, Input, NgModule } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
let events = [];
|
||||
export class LifecycleComp {
|
||||
ngOnChanges() {
|
||||
events.push('changes' + this.nameMin);
|
||||
}
|
||||
ngOnInit() {
|
||||
events.push('init' + this.nameMin);
|
||||
}
|
||||
ngDoCheck() {
|
||||
events.push('check' + this.nameMin);
|
||||
}
|
||||
ngAfterContentInit() {
|
||||
events.push('content init' + this.nameMin);
|
||||
}
|
||||
ngAfterContentChecked() {
|
||||
events.push('content check' + this.nameMin);
|
||||
}
|
||||
ngAfterViewInit() {
|
||||
events.push('view init' + this.nameMin);
|
||||
}
|
||||
ngAfterViewChecked() {
|
||||
events.push('view check' + this.nameMin);
|
||||
}
|
||||
ngOnDestroy() {
|
||||
events.push(this.nameMin);
|
||||
}
|
||||
}
|
||||
LifecycleComp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LifecycleComp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
LifecycleComp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: LifecycleComp, selector: "lifecycle-comp", inputs: { nameMin: ["name", "nameMin"] }, usesOnChanges: true, ngImport: i0, template: '', isInline: true });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(LifecycleComp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'lifecycle-comp', template: '' }]
|
||||
}], null, { nameMin: [{
|
||||
type: Input,
|
||||
args: ['name']
|
||||
}] }); })();
|
||||
export class SimpleLayout {
|
||||
constructor() {
|
||||
this.name1 = '1';
|
||||
this.name2 = '2';
|
||||
}
|
||||
}
|
||||
SimpleLayout.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: SimpleLayout, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
SimpleLayout.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: SimpleLayout, selector: "simple-layout", ngImport: i0, template: `
|
||||
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||
`, isInline: true, components: [{ type: LifecycleComp, selector: "lifecycle-comp", inputs: ["name"] }] });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(SimpleLayout, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'simple-layout',
|
||||
template: `
|
||||
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||
`
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class LifecycleModule {
|
||||
}
|
||||
LifecycleModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LifecycleModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
LifecycleModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LifecycleModule, declarations: [LifecycleComp, SimpleLayout] });
|
||||
LifecycleModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: LifecycleModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(LifecycleModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [LifecycleComp, SimpleLayout] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: lifecycle_hooks.d.ts
|
||||
****************************************************************************************************/
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class LifecycleComp {
|
||||
nameMin: string;
|
||||
ngOnChanges(): void;
|
||||
ngOnInit(): void;
|
||||
ngDoCheck(): void;
|
||||
ngAfterContentInit(): void;
|
||||
ngAfterContentChecked(): void;
|
||||
ngAfterViewInit(): void;
|
||||
ngAfterViewChecked(): void;
|
||||
ngOnDestroy(): void;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LifecycleComp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<LifecycleComp, "lifecycle-comp", never, { "nameMin": "name"; }, {}, never, never>;
|
||||
}
|
||||
export declare class SimpleLayout {
|
||||
name1: string;
|
||||
name2: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<SimpleLayout, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<SimpleLayout, "simple-layout", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class LifecycleModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<LifecycleModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<LifecycleModule, [typeof LifecycleComp, typeof SimpleLayout], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<LifecycleModule>;
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
{
|
||||
"$schema": "../../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"description": "local reference",
|
||||
"inputFiles": [
|
||||
"local_reference.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect MyComponent.ɵcmp",
|
||||
"files": [
|
||||
"local_reference.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "local references in nested views",
|
||||
"inputFiles": [
|
||||
"local_reference_nested.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect MyComponent.ɵcmp",
|
||||
"files": [
|
||||
"local_reference_nested.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should support local refs mixed with context assignments",
|
||||
"inputFiles": [
|
||||
"local_reference_and_context_variables.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Incorrect template",
|
||||
"files": [
|
||||
"local_reference_and_context_variables.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should gen hooks with a few simple components",
|
||||
"inputFiles": [
|
||||
"lifecycle_hooks.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid LifecycleComp definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "lifecycle_hooks_lifecycle_comp_def.js",
|
||||
"generated": "lifecycle_hooks.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid SimpleLayout definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "lifecycle_hooks_simple_layout_def.js",
|
||||
"generated": "lifecycle_hooks.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
import {Component, Input, NgModule} from '@angular/core';
|
||||
|
||||
let events: string[] = [];
|
||||
|
||||
@Component({selector: 'lifecycle-comp', template: ''})
|
||||
export class LifecycleComp {
|
||||
@Input('name') nameMin!: string;
|
||||
|
||||
ngOnChanges() {
|
||||
events.push('changes' + this.nameMin);
|
||||
}
|
||||
|
||||
ngOnInit() {
|
||||
events.push('init' + this.nameMin);
|
||||
}
|
||||
ngDoCheck() {
|
||||
events.push('check' + this.nameMin);
|
||||
}
|
||||
|
||||
ngAfterContentInit() {
|
||||
events.push('content init' + this.nameMin);
|
||||
}
|
||||
ngAfterContentChecked() {
|
||||
events.push('content check' + this.nameMin);
|
||||
}
|
||||
|
||||
ngAfterViewInit() {
|
||||
events.push('view init' + this.nameMin);
|
||||
}
|
||||
ngAfterViewChecked() {
|
||||
events.push('view check' + this.nameMin);
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
events.push(this.nameMin);
|
||||
}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'simple-layout',
|
||||
template: `
|
||||
<lifecycle-comp [name]="name1"></lifecycle-comp>
|
||||
<lifecycle-comp [name]="name2"></lifecycle-comp>
|
||||
`
|
||||
})
|
||||
export class SimpleLayout {
|
||||
name1 = '1';
|
||||
name2 = '2';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [LifecycleComp, SimpleLayout]})
|
||||
export class LifecycleModule {
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
LifecycleComp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: LifecycleComp,
|
||||
selectors: [["lifecycle-comp"]],
|
||||
inputs: {nameMin: ["name", "nameMin"]},
|
||||
features: [$r3$.ɵɵNgOnChangesFeature],
|
||||
decls: 0,
|
||||
vars: 0,
|
||||
template: function LifecycleComp_Template(rf, ctx) {},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
SimpleLayout.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: SimpleLayout,
|
||||
selectors: [["simple-layout"]],
|
||||
decls: 2,
|
||||
vars: 2,
|
||||
consts: [[__AttributeMarker.Bindings__, "name"]],
|
||||
template: function SimpleLayout_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "lifecycle-comp", 0);
|
||||
$r3$.ɵɵelement(1, "lifecycle-comp", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("name", ctx.name1);
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵproperty("name", ctx.name2);
|
||||
}
|
||||
},
|
||||
directives: [LifecycleComp],
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,20 @@
|
|||
// ...
|
||||
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors: [["my-component"]],
|
||||
decls: 3,
|
||||
vars: 1,
|
||||
consts: [["user", ""]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "input", null, 0);
|
||||
$r3$.ɵɵtext(2);
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $user$ = $r3$.ɵɵreference(1);
|
||||
$r3$.ɵɵadvance(2);
|
||||
$r3$.ɵɵtextInterpolate1("Hello ", $user$.value, "!");
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'my-component', template: '<input #user>Hello {{user.value}}!'})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
function MyComponent_div_0_span_3_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵelementStart(0, "span");
|
||||
$i0$.ɵɵtext(1);
|
||||
$i0$.ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $item$ = $i0$.ɵɵnextContext().$implicit;
|
||||
const $foo$ = $i0$.ɵɵreference(2);
|
||||
$r3$.ɵɵadvance(1);
|
||||
$i0$.ɵɵtextInterpolate2(" ", $foo$, " - ", $item$, " ");
|
||||
}
|
||||
}
|
||||
|
||||
function MyComponent_div_0_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵelementStart(0, "div");
|
||||
$i0$.ɵɵelement(1, "div", null, 1);
|
||||
$i0$.ɵɵtemplate(3, MyComponent_div_0_span_3_Template, 2, 2, "span", 2);
|
||||
$i0$.ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $app$ = $i0$.ɵɵnextContext();
|
||||
$r3$.ɵɵadvance(3);
|
||||
$i0$.ɵɵproperty("ngIf", $app$.showing);
|
||||
}
|
||||
}
|
||||
|
||||
// ...
|
||||
consts: [[__AttributeMarker.Template__, "ngFor", "ngForOf"], ["foo", ""], [__AttributeMarker.Template__, "ngIf"]],
|
||||
template:function MyComponent_Template(rf, ctx){
|
||||
if (rf & 1) {
|
||||
$i0$.ɵɵtemplate(0, MyComponent_div_0_Template, 4, 1, "div", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$i0$.ɵɵproperty("ngForOf", ctx.items);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: `
|
||||
<div *ngFor="let item of items">
|
||||
<div #foo></div>
|
||||
<span *ngIf="showing">
|
||||
{{ foo }} - {{ item }}
|
||||
</span>
|
||||
</div>`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
function MyComponent_div_3_span_2_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "span");
|
||||
$r3$.ɵɵtext(1);
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵnextContext();
|
||||
const $bar$ = $r3$.ɵɵreference(4);
|
||||
$r3$.ɵɵnextContext();
|
||||
const $foo$ = $r3$.ɵɵreference(1);
|
||||
const $baz$ = $r3$.ɵɵreference(5);
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵtextInterpolate3("", $foo$, "-", $bar$, "-", $baz$, "");
|
||||
}
|
||||
}
|
||||
function MyComponent_div_3_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelementStart(0, "div");
|
||||
$r3$.ɵɵtext(1);
|
||||
$r3$.ɵɵtemplate(2, MyComponent_div_3_span_2_Template, 2, 3, "span", 1);
|
||||
$r3$.ɵɵelement(3, "span", null, 3);
|
||||
$r3$.ɵɵelementEnd();
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $bar$ = $r3$.ɵɵreference(4);
|
||||
$r3$.ɵɵnextContext();
|
||||
const $foo$ = $r3$.ɵɵreference(1);
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵtextInterpolate2(" ", $foo$, "-", $bar$, " ");
|
||||
}
|
||||
}
|
||||
// ...
|
||||
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors: [["my-component"]],
|
||||
decls: 6,
|
||||
vars: 1,
|
||||
consts: [["foo", ""], [__AttributeMarker.Template__, "if"], ["baz", ""], ["bar", ""]],
|
||||
template: function MyComponent_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "div", null, 0);
|
||||
$r3$.ɵɵtext(2);
|
||||
$r3$.ɵɵtemplate(3, MyComponent_div_3_Template, 5, 2, "div", 1);
|
||||
$r3$.ɵɵelement(4, "div", null, 2);
|
||||
}
|
||||
if (rf & 2) {
|
||||
const $foo$ = $r3$.ɵɵreference(1);
|
||||
$r3$.ɵɵadvance(2);
|
||||
$r3$.ɵɵtextInterpolate1(" ", $foo$, " ");
|
||||
}
|
||||
},
|
||||
directives:[IfDirective],
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,26 @@
|
|||
import {Component, Directive, NgModule, TemplateRef} from '@angular/core';
|
||||
|
||||
@Directive({selector: '[if]'})
|
||||
export class IfDirective {
|
||||
constructor(template: TemplateRef<any>) {}
|
||||
}
|
||||
|
||||
@Component({
|
||||
selector: 'my-component',
|
||||
template: `
|
||||
<div #foo></div>
|
||||
{{foo}}
|
||||
<div *if>
|
||||
{{foo}}-{{bar}}
|
||||
<span *if>{{foo}}-{{bar}}-{{baz}}</span>
|
||||
<span #bar></span>
|
||||
</div>
|
||||
<div #baz></div>
|
||||
`
|
||||
})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [IfDirective, MyComponent]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
function MyComponent_ng_template_0_Template(rf, ctx) {}
|
||||
// ...
|
||||
MyComponent.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyComponent,
|
||||
selectors: [["my-app"]],
|
||||
decls: 1,
|
||||
vars: 0,
|
||||
consts: [[__AttributeMarker.Bindings__, "id"]],
|
||||
template: function MyComponent_Template(rf, $ctx$) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtemplate(0, MyComponent_ng_template_0_Template, 0, 0, "ng-template", 0);
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,9 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({selector: 'my-app', template: '<ng-template [id]=""></ng-template>'})
|
||||
export class MyComponent {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyComponent]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
AbstractDirective.ɵdir = /*@__PURE__*/ $r3$.ɵɵdefineDirective({
|
||||
type: AbstractDirective
|
||||
});
|
|
@ -0,0 +1,5 @@
|
|||
import {Directive} from '@angular/core';
|
||||
|
||||
@Directive()
|
||||
export class AbstractDirective {
|
||||
}
|
|
@ -0,0 +1,23 @@
|
|||
const $c0$ = function () { return { foo: null }; };
|
||||
const $c1$ = function () { return {}; };
|
||||
const $c2$ = function (a0) { return { foo: a0 }; };
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["ng-component"]],
|
||||
decls: 2,
|
||||
vars: 6,
|
||||
consts: [[__AttributeMarker.Bindings__, "dir"]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "div", 0);
|
||||
$r3$.ɵɵelement(1, "div", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$));
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(4, $c2$, $r3$.ɵɵpureFunction0(3, $c1$)));
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,14 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: {}}"></div>
|
||||
`
|
||||
})
|
||||
export class MyApp {
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
const $c0$ = function () { return { foo: null }; };
|
||||
const $c1$ = function (a0) { return { foo: a0 }; };
|
||||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["ng-component"]],
|
||||
decls: 2,
|
||||
vars: 5,
|
||||
consts: [[__AttributeMarker.Bindings__, "dir"]],
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵelement(0, "div", 0);
|
||||
$r3$.ɵɵelement(1, "div", 0);
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction0(2, $c0$));
|
||||
$r3$.ɵɵadvance(1);
|
||||
$r3$.ɵɵproperty("dir", $r3$.ɵɵpureFunction1(3, $c1$, ctx.getFoo()));
|
||||
}
|
||||
},
|
||||
encapsulation: 2
|
||||
});
|
|
@ -0,0 +1,17 @@
|
|||
import {Component, NgModule} from '@angular/core';
|
||||
|
||||
@Component({
|
||||
template: `
|
||||
<div [dir]="{foo: null}"></div>
|
||||
<div [dir]="{foo: getFoo()}"></div>
|
||||
`
|
||||
})
|
||||
export class MyApp {
|
||||
getFoo() {
|
||||
return 'foo!';
|
||||
}
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyApp]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,227 @@
|
|||
/****************************************************************************************************
|
||||
* PARTIAL FILE: pipes.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule, Pipe } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyPipe {
|
||||
transform(value, ...args) {
|
||||
return value;
|
||||
}
|
||||
ngOnDestroy() { }
|
||||
}
|
||||
MyPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
||||
MyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, name: "myPipe", pure: false });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyPipe, [{
|
||||
type: Pipe,
|
||||
args: [{ name: 'myPipe', pure: false }]
|
||||
}], null, null); })();
|
||||
export class MyPurePipe {
|
||||
transform(value, ...args) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
MyPurePipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPurePipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
||||
MyPurePipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPurePipe, name: "myPurePipe" });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyPurePipe, [{
|
||||
type: Pipe,
|
||||
args: [{
|
||||
name: 'myPurePipe',
|
||||
pure: true,
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.name = 'World';
|
||||
this.size = 0;
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '{{name | myPipe:size | myPurePipe:size }}<p>{{ name | myPipe:1:2:3:4:5 }} {{ name ? 1 : 2 | myPipe }}</p>', isInline: true, pipes: { "myPurePipe": MyPurePipe, "myPipe": MyPipe } });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-app',
|
||||
template: '{{name | myPipe:size | myPurePipe:size }}<p>{{ name | myPipe:1:2:3:4:5 }} {{ name ? 1 : 2 | myPipe }}</p>'
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyPipe, MyPurePipe, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyPipe, MyPurePipe, MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: pipes.d.ts
|
||||
****************************************************************************************************/
|
||||
import { OnDestroy, PipeTransform } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyPipe implements PipeTransform, OnDestroy {
|
||||
transform(value: any, ...args: any[]): any;
|
||||
ngOnDestroy(): void;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyPipe, never>;
|
||||
static ɵpipe: i0.ɵɵPipeDeclaration<MyPipe, "myPipe">;
|
||||
}
|
||||
export declare class MyPurePipe implements PipeTransform {
|
||||
transform(value: any, ...args: any[]): any;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyPurePipe, never>;
|
||||
static ɵpipe: i0.ɵɵPipeDeclaration<MyPurePipe, "myPurePipe">;
|
||||
}
|
||||
export declare class MyApp {
|
||||
name: string;
|
||||
size: number;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyPipe, typeof MyPurePipe, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: pipe_invocation.js
|
||||
****************************************************************************************************/
|
||||
import { Component, NgModule, Pipe } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyPipe {
|
||||
transform(value, ...args) {
|
||||
return value;
|
||||
}
|
||||
ngOnDestroy() { }
|
||||
}
|
||||
MyPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe });
|
||||
MyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, name: "myPipe", pure: false });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyPipe, [{
|
||||
type: Pipe,
|
||||
args: [{ name: 'myPipe', pure: false }]
|
||||
}], null, null); })();
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.name = '';
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '0:{{name | myPipe}}1:{{name | myPipe:1}}2:{{name | myPipe:1:2}}3:{{name | myPipe:1:2:3}}4:{{name | myPipe:1:2:3:4}}', isInline: true, pipes: { "myPipe": MyPipe } });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{
|
||||
selector: 'my-app',
|
||||
template: '0:{{name | myPipe}}1:{{name | myPipe:1}}2:{{name | myPipe:1:2}}3:{{name | myPipe:1:2:3}}4:{{name | myPipe:1:2:3:4}}'
|
||||
}]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyPipe, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyPipe, MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: pipe_invocation.d.ts
|
||||
****************************************************************************************************/
|
||||
import { OnDestroy, PipeTransform } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyPipe implements PipeTransform, OnDestroy {
|
||||
transform(value: any, ...args: any[]): any;
|
||||
ngOnDestroy(): void;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyPipe, never>;
|
||||
static ɵpipe: i0.ɵɵPipeDeclaration<MyPipe, "myPipe">;
|
||||
}
|
||||
export declare class MyApp {
|
||||
name: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyPipe, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: pipe_di_change_detector_ref.js
|
||||
****************************************************************************************************/
|
||||
import { ChangeDetectorRef, Component, NgModule, Optional, Pipe } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export class MyPipe {
|
||||
constructor(changeDetectorRef) { }
|
||||
transform(value, ...args) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
MyPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, deps: [{ token: i0.ChangeDetectorRef }], target: i0.ɵɵFactoryTarget.Pipe });
|
||||
MyPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyPipe, name: "myPipe" });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyPipe, [{
|
||||
type: Pipe,
|
||||
args: [{ name: 'myPipe' }]
|
||||
}], function () { return [{ type: i0.ChangeDetectorRef }]; }, null); })();
|
||||
export class MyOtherPipe {
|
||||
constructor(changeDetectorRef) { }
|
||||
transform(value, ...args) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
MyOtherPipe.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyOtherPipe, deps: [{ token: i0.ChangeDetectorRef, optional: true }], target: i0.ɵɵFactoryTarget.Pipe });
|
||||
MyOtherPipe.ɵpipe = i0.ɵɵngDeclarePipe({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyOtherPipe, name: "myOtherPipe" });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyOtherPipe, [{
|
||||
type: Pipe,
|
||||
args: [{ name: 'myOtherPipe' }]
|
||||
}], function () { return [{ type: i0.ChangeDetectorRef, decorators: [{
|
||||
type: Optional
|
||||
}] }]; }, null); })();
|
||||
export class MyApp {
|
||||
constructor() {
|
||||
this.name = 'World';
|
||||
}
|
||||
}
|
||||
MyApp.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyApp, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
||||
MyApp.ɵcmp = i0.ɵɵngDeclareComponent({ version: "0.0.0-PLACEHOLDER", type: MyApp, selector: "my-app", ngImport: i0, template: '{{name | myPipe }}<p>{{ name | myOtherPipe }}</p>', isInline: true, pipes: { "myPipe": MyPipe, "myOtherPipe": MyOtherPipe } });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyApp, [{
|
||||
type: Component,
|
||||
args: [{ selector: 'my-app', template: '{{name | myPipe }}<p>{{ name | myOtherPipe }}</p>' }]
|
||||
}], null, null); })();
|
||||
export class MyModule {
|
||||
}
|
||||
MyModule.ɵfac = i0.ɵɵngDeclareFactory({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule });
|
||||
MyModule.ɵmod = i0.ɵɵngDeclareNgModule({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule, declarations: [MyPipe, MyOtherPipe, MyApp] });
|
||||
MyModule.ɵinj = i0.ɵɵngDeclareInjector({ version: "0.0.0-PLACEHOLDER", ngImport: i0, type: MyModule });
|
||||
(function () { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MyModule, [{
|
||||
type: NgModule,
|
||||
args: [{ declarations: [MyPipe, MyOtherPipe, MyApp] }]
|
||||
}], null, null); })();
|
||||
|
||||
/****************************************************************************************************
|
||||
* PARTIAL FILE: pipe_di_change_detector_ref.d.ts
|
||||
****************************************************************************************************/
|
||||
import { ChangeDetectorRef, PipeTransform } from '@angular/core';
|
||||
import * as i0 from "@angular/core";
|
||||
export declare class MyPipe implements PipeTransform {
|
||||
constructor(changeDetectorRef: ChangeDetectorRef);
|
||||
transform(value: any, ...args: any[]): any;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyPipe, never>;
|
||||
static ɵpipe: i0.ɵɵPipeDeclaration<MyPipe, "myPipe">;
|
||||
}
|
||||
export declare class MyOtherPipe implements PipeTransform {
|
||||
constructor(changeDetectorRef: ChangeDetectorRef);
|
||||
transform(value: any, ...args: any[]): any;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyOtherPipe, [{ optional: true; }]>;
|
||||
static ɵpipe: i0.ɵɵPipeDeclaration<MyOtherPipe, "myOtherPipe">;
|
||||
}
|
||||
export declare class MyApp {
|
||||
name: string;
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyApp, never>;
|
||||
static ɵcmp: i0.ɵɵComponentDeclaration<MyApp, "my-app", never, {}, {}, never, never>;
|
||||
}
|
||||
export declare class MyModule {
|
||||
static ɵfac: i0.ɵɵFactoryDeclaration<MyModule, never>;
|
||||
static ɵmod: i0.ɵɵNgModuleDeclaration<MyModule, [typeof MyPipe, typeof MyOtherPipe, typeof MyApp], never, never>;
|
||||
static ɵinj: i0.ɵɵInjectorDeclaration<MyModule>;
|
||||
}
|
||||
|
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
"$schema": "../../../test_case_schema.json",
|
||||
"cases": [
|
||||
{
|
||||
"description": "should render pipes",
|
||||
"inputFiles": [
|
||||
"pipes.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid pipe definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipes_my_pipe_def.js",
|
||||
"generated": "pipes.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid pipe factory function",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipes_my_pipe_fac.js",
|
||||
"generated": "pipes.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid pure pipe definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipes_my_pure_pipe_def.js",
|
||||
"generated": "pipes.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid pure pipe factory function",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipes_my_pure_pipe_fac.js",
|
||||
"generated": "pipes.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid MyApp definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipes_my_app_def.js",
|
||||
"generated": "pipes.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should use appropriate function for a given no of pipe arguments",
|
||||
"inputFiles": [
|
||||
"pipe_invocation.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid MyApp definition",
|
||||
"files": [
|
||||
"pipe_invocation.js"
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"description": "should generate the proper instruction when injecting ChangeDetectorRef into a pipe",
|
||||
"inputFiles": [
|
||||
"pipe_di_change_detector_ref.ts"
|
||||
],
|
||||
"expectations": [
|
||||
{
|
||||
"failureMessage": "Invalid pipe definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipe_di_change_detector_ref_my_pipe_def.js",
|
||||
"generated": "pipe_di_change_detector_ref.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid pipe factory function",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipe_di_change_detector_ref_my_pipe_fac.js",
|
||||
"generated": "pipe_di_change_detector_ref.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid alternate pipe definition",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipe_di_change_detector_ref_my_other_pipe_def.js",
|
||||
"generated": "pipe_di_change_detector_ref.js"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"failureMessage": "Invalid alternate pipe factory function",
|
||||
"files": [
|
||||
{
|
||||
"expected": "pipe_di_change_detector_ref_my_other_pipe_fac.js",
|
||||
"generated": "pipe_di_change_detector_ref.js"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
import {ChangeDetectorRef, Component, NgModule, Optional, Pipe, PipeTransform} from '@angular/core';
|
||||
|
||||
@Pipe({name: 'myPipe'})
|
||||
export class MyPipe implements PipeTransform {
|
||||
constructor(changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
transform(value: any, ...args: any[]) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Pipe({name: 'myOtherPipe'})
|
||||
export class MyOtherPipe implements PipeTransform {
|
||||
constructor(@Optional() changeDetectorRef: ChangeDetectorRef) {}
|
||||
|
||||
transform(value: any, ...args: any[]) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@Component({selector: 'my-app', template: '{{name | myPipe }}<p>{{ name | myOtherPipe }}</p>'})
|
||||
export class MyApp {
|
||||
name = 'World';
|
||||
}
|
||||
|
||||
@NgModule({declarations: [MyPipe, MyOtherPipe, MyApp]})
|
||||
export class MyModule {
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
MyOtherPipe.ɵpipe = /*@__PURE__*/ $r3$.ɵɵdefinePipe({
|
||||
name: "myOtherPipe",
|
||||
type: MyOtherPipe,
|
||||
pure: true
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
MyOtherPipe.ɵfac = function MyOtherPipe_Factory(t) { return new (t || MyOtherPipe)($i0$.ɵɵdirectiveInject($i0$.ChangeDetectorRef, 24)); };
|
|
@ -0,0 +1,5 @@
|
|||
MyPipe.ɵpipe = /*@__PURE__*/ $r3$.ɵɵdefinePipe({
|
||||
name: "myPipe",
|
||||
type: MyPipe,
|
||||
pure: true
|
||||
});
|
|
@ -0,0 +1 @@
|
|||
MyPipe.ɵfac = function MyPipe_Factory(t) { return new (t || MyPipe)($i0$.ɵɵdirectiveInject($i0$.ChangeDetectorRef, 16)); };
|
|
@ -0,0 +1,29 @@
|
|||
// ...
|
||||
MyApp.ɵcmp = /*@__PURE__*/ $r3$.ɵɵdefineComponent({
|
||||
type: MyApp,
|
||||
selectors: [["my-app"]],
|
||||
decls: 6,
|
||||
vars: 27,
|
||||
template: function MyApp_Template(rf, ctx) {
|
||||
if (rf & 1) {
|
||||
$r3$.ɵɵtext(0);
|
||||
$r3$.ɵɵpipe(1, "myPipe");
|
||||
$r3$.ɵɵpipe(2, "myPipe");
|
||||
$r3$.ɵɵpipe(3, "myPipe");
|
||||
$r3$.ɵɵpipe(4, "myPipe");
|
||||
$r3$.ɵɵpipe(5, "myPipe");
|
||||
}
|
||||
if (rf & 2) {
|
||||
$r3$.ɵɵtextInterpolate5(
|
||||
"0:", i0.ɵɵpipeBind1(1, 5, ctx.name),
|
||||
"1:", i0.ɵɵpipeBind2(2, 7, ctx.name, 1),
|
||||
"2:", i0.ɵɵpipeBind3(3, 10, ctx.name, 1, 2),
|
||||
"3:", i0.ɵɵpipeBind4(4, 14, ctx.name, 1, 2, 3),
|
||||
"4:", i0.ɵɵpipeBindV(5, 19, $r3$.ɵɵpureFunction1(25, $c0$, ctx.name)),
|
||||
""
|
||||
);
|
||||
}
|
||||
},
|
||||
pipes: [MyPipe],
|
||||
encapsulation: 2
|
||||
});
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue