Compare commits
11 Commits
mprobst-ng
...
test_publi
Author | SHA1 | Date | |
---|---|---|---|
6fcb3f8673 | |||
ace6440460 | |||
b26ac1c22f | |||
60e5507076 | |||
4cfa571258 | |||
999ab0a690 | |||
ba47997715 | |||
a35bf114eb | |||
6761a64522 | |||
0b47902ad7 | |||
4662878a1f |
@ -63,7 +63,7 @@ jobs:
|
||||
- run: yarn install --frozen-lockfile --non-interactive
|
||||
- run: ./node_modules/.bin/gulp lint
|
||||
|
||||
build:
|
||||
test:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
@ -87,10 +87,6 @@ jobs:
|
||||
# NOTE: Angular developers should typically just bazel build //packages/... or bazel test //packages/...
|
||||
- run: bazel query --output=label //... | xargs bazel test
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# See comments inside this script.
|
||||
- run: xvfb-run --auto-servernum ./integration/run_tests.sh
|
||||
|
||||
# CircleCI will allow us to go back and view/download these artifacts from past builds.
|
||||
# Also we can use a service like https://buildsize.org/ to automatically track binary size of these artifacts.
|
||||
- store_artifacts:
|
||||
@ -112,6 +108,66 @@ jobs:
|
||||
- "node_modules"
|
||||
- "~/bazel_repository_cache"
|
||||
|
||||
# This job exists only for backwards-compatibility with old scripts and tests
|
||||
# that rely on the pre-Bazel dist/packages-dist layout.
|
||||
# It duplicates some work with the job above: we build the bazel packages
|
||||
# twice. Even though we have a remote cache, these jobs will typically run in
|
||||
# parallel so up-to-date outputs will not be available at the time the build
|
||||
# starts.
|
||||
# No new jobs should depend on this one.
|
||||
build-packages-dist:
|
||||
<<: *job_defaults
|
||||
resource_class: xlarge
|
||||
steps:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
# See remote cache documentation in /docs/BAZEL.md
|
||||
- run: .circleci/setup_cache.sh
|
||||
- run: sudo cp .circleci/bazel.rc /etc/bazel.bazelrc
|
||||
- *setup-bazel-remote-cache
|
||||
|
||||
- run: bazel run @yarn//:yarn
|
||||
- run: scripts/build-packages-dist.sh
|
||||
|
||||
# Save the npm packages from //packages/... for other workflow jobs to read
|
||||
# https://circleci.com/docs/2.0/workflows/#using-workspaces-to-share-data-among-jobs
|
||||
- persist_to_workspace:
|
||||
root: dist
|
||||
paths:
|
||||
- packages-dist
|
||||
|
||||
# We run the integration tests outside of Bazel for now.
|
||||
# They are a separate workflow job so that they can be easily re-run.
|
||||
# When the tests are ported to bazel test targets, they should move to the "test"
|
||||
# job above, as part of the bazel test command. That has flaky_test_attempts so the
|
||||
# need to re-run manually should be alleviated.
|
||||
# See comments inside the integration/run_tests.sh script.
|
||||
integration_test:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- *define_env_vars
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
- run: xvfb-run --auto-servernum ./integration/run_tests.sh
|
||||
|
||||
# This job updates the content of repos like github.com/angular/core-builds
|
||||
# for every green build on angular/angular.
|
||||
publish_snapshot:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
- checkout:
|
||||
<<: *post_checkout
|
||||
- attach_workspace:
|
||||
at: dist
|
||||
# CircleCI has a config setting to force SSH for all github connections
|
||||
# This is not compatible with our mechanism of using a Personal Access Token
|
||||
# Clear the global setting
|
||||
- run: git config --global --unset "url.ssh://git@github.com.insteadof"
|
||||
- run: ./scripts/ci/publish-build-artifacts.sh
|
||||
|
||||
aio_monitoring:
|
||||
<<: *job_defaults
|
||||
steps:
|
||||
@ -126,7 +182,25 @@ workflows:
|
||||
default_workflow:
|
||||
jobs:
|
||||
- lint
|
||||
- build
|
||||
- test
|
||||
- build-packages-dist
|
||||
- integration_test:
|
||||
requires:
|
||||
- build-packages-dist
|
||||
- publish_snapshot:
|
||||
# Note: no filters on this job because we want it to run for all upstream branches
|
||||
# We'd really like to filter out pull requests here, but not yet available:
|
||||
# https://discuss.circleci.com/t/workflows-pull-request-filter/14396/4
|
||||
# Instead, the publish-build-artifacts.sh script just terminates when
|
||||
# CIRCLE_PR_NUMBER is set.
|
||||
requires:
|
||||
# Only publish if tests and integration tests pass
|
||||
- test
|
||||
- integration_test
|
||||
# Get the artifacts to publish from the build-packages-dist job
|
||||
# since the publishing script expects the legacy outputs layout.
|
||||
- build-packages-dist
|
||||
|
||||
aio_monitoring:
|
||||
jobs:
|
||||
- aio_monitoring
|
||||
|
1
.circleci/github_token
Normal file
1
.circleci/github_token
Normal file
@ -0,0 +1 @@
|
||||
Salted__<EFBFBD><EFBFBD><EFBFBD><EFBFBD>˓]<5D><><EFBFBD>O<>ʤu'<27><>Uzh<7A><68><EFBFBD>bE<62>]+<2B>xC<78>Y-<2D>?<3F>c"q<>;ƲK@l#<23>xހ<78>I<EFBFBD>1&w0<77>+<2B>\p/O<>;<3B>
|
@ -153,7 +153,7 @@ groups:
|
||||
- "packages/compiler/src/i18n/*"
|
||||
users:
|
||||
- vicb #primary
|
||||
- chuckjaz
|
||||
- alxhub
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
||||
@ -187,7 +187,7 @@ groups:
|
||||
- "packages/compiler-cli/src/ngtools*"
|
||||
users:
|
||||
- alexeagle
|
||||
- chuckjaz
|
||||
- alxhub
|
||||
- vicb
|
||||
- IgorMinar #fallback
|
||||
- mhevery #fallback
|
||||
|
@ -574,6 +574,12 @@
|
||||
"title": "Learn Angular (francais)",
|
||||
"url": "http://www.learn-angular.fr/"
|
||||
},
|
||||
"upgrading-ajs": {
|
||||
"desc": "The world's most comprehensive, step-by-step course on using best practices and avoiding pitfalls while migrating from AngularJS to Angular.",
|
||||
"rev": true,
|
||||
"title": "Upgrading AngularJS",
|
||||
"url": "https://www.upgradingangularjs.com"
|
||||
},
|
||||
"toddmotto-ultimateangular": {
|
||||
"desc": "Online courses providing in-depth coverage of the Angular ecosystem, AngularJS, Angular and TypeScript, with functional code samples and a full-featured seed environment. Get a deep understanding of Angular and TypeScript from foundation to functional application, then move on to advanced topics with Todd Motto and collaborators.",
|
||||
"rev": true,
|
||||
|
@ -133,11 +133,10 @@ $ gulp lint
|
||||
|
||||
## Publishing snapshot builds
|
||||
|
||||
When the `master` branch successfully builds on Travis, it automatically publishes build artifacts
|
||||
When a build of any branch on the upstream fork angular/angular is green on CircleCI,
|
||||
it automatically publishes build artifacts
|
||||
to repositories in the Angular org, eg. the `@angular/core` package is published to
|
||||
http://github.com/angular/core-builds.
|
||||
The ES2015 version of Angular is published to a different branch in these repos, for example
|
||||
http://github.com/angular/core-builds#master-es2015
|
||||
|
||||
You may find that your un-merged change needs some validation from external participants.
|
||||
Rather than requiring them to pull your Pull Request and build Angular locally, you can
|
||||
|
@ -5,7 +5,7 @@
|
||||
"scripts": {
|
||||
"ng": "ng",
|
||||
"start": "ng serve",
|
||||
"build": "ng build --prod",
|
||||
"build": "ng build --prod --progress false",
|
||||
"test": "ng test",
|
||||
"lint": "ng lint",
|
||||
"e2e": "ng e2e"
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -e -o pipefail
|
||||
set -u -e -o pipefail
|
||||
|
||||
# see https://circleci.com/docs/2.0/env-vars/#circleci-built-in-environment-variables
|
||||
CI=${CI:-false}
|
||||
@ -9,41 +9,21 @@ cd "$(dirname "$0")"
|
||||
|
||||
# basedir is the workspace root
|
||||
readonly basedir=$(pwd)/..
|
||||
readonly bin=$(bazel info bazel-bin)
|
||||
|
||||
echo "#################################"
|
||||
echo "Building @angular/* npm packages "
|
||||
echo "#################################"
|
||||
|
||||
# Ideally these integration tests should run under bazel, and just list the npm
|
||||
# packages in their deps[].
|
||||
# Until then, we have to manually run bazel first to create the npm packages we
|
||||
# want to test.
|
||||
bazel query --output=label 'kind(.*_package, //packages/...)' \
|
||||
| xargs bazel build
|
||||
|
||||
# Allow this test to run even if dist/ doesn't exist yet.
|
||||
# Under Bazel we don't need to create the dist folder to run the integration tests
|
||||
[ -d "${basedir}/dist/packages-dist" ] || mkdir -p $basedir/dist/packages-dist
|
||||
# Each package is a subdirectory of bazel-bin/packages/
|
||||
for pkg in $(ls ${bin}/packages); do
|
||||
# Skip any that don't have an "npm_package" target
|
||||
if [ -d "${bin}/packages/${pkg}/npm_package" ]; then
|
||||
echo "# Copy artifacts to dist/packages-dist/${pkg}"
|
||||
rm -rf ${basedir}/dist/packages-dist/${pkg}
|
||||
cp -R ${bin}/packages/${pkg}/npm_package ${basedir}/dist/packages-dist/${pkg}
|
||||
fi
|
||||
done
|
||||
chmod -R u+w ${basedir}/dist/packages-dist/
|
||||
|
||||
# Track payload size functions
|
||||
# TODO(alexeagle): finish migrating these to buildsize.org
|
||||
if $CI; then
|
||||
# We don't install this by default because it contains some broken Bazel setup
|
||||
# and also it's a very big dependency that we never use except when publishing
|
||||
# payload sizes on CI.
|
||||
yarn add -D firebase-tools@3.12.0
|
||||
yarn add --silent -D firebase-tools@3.12.0
|
||||
source ${basedir}/scripts/ci/payload-size.sh
|
||||
|
||||
# NB: we don't run build-packages-dist.sh because we expect that it was done
|
||||
# by an earlier job in the CircleCI workflow.
|
||||
else
|
||||
# Not on CircleCI so let's build the packages-dist directory.
|
||||
# This should be fast on incremental re-build.
|
||||
${basedir}/scripts/build-packages-dist.sh
|
||||
fi
|
||||
|
||||
# Workaround https://github.com/yarnpkg/yarn/issues/2165
|
||||
@ -64,7 +44,7 @@ for testDir in $(ls | grep -v node_modules) ; do
|
||||
(
|
||||
cd $testDir
|
||||
rm -rf dist
|
||||
pwd
|
||||
|
||||
yarn install --cache-folder ../$cache
|
||||
yarn test || exit 1
|
||||
# Track payload size for cli-hello-world and hello_world__closure and the render3 tests
|
||||
|
@ -6,8 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {CommonModule, NgForOf} from '@angular/common';
|
||||
import {Component, Directive} from '@angular/core';
|
||||
import {CommonModule} from '@angular/common';
|
||||
import {Component} from '@angular/core';
|
||||
import {ComponentFixture, TestBed, async} from '@angular/core/testing';
|
||||
import {By} from '@angular/platform-browser/src/dom/debug/by';
|
||||
import {expect} from '@angular/platform-browser/testing/src/matchers';
|
||||
|
@ -12,6 +12,8 @@
|
||||
// This is important to prevent a build cycle, as @angular/core needs to
|
||||
// be compiled with the compiler.
|
||||
|
||||
import {CssSelector} from './selector';
|
||||
|
||||
export interface Inject { token: any; }
|
||||
export const createInject = makeMetadataFactory<Inject>('Inject', (token: any) => ({token}));
|
||||
export const createInjectionToken = makeMetadataFactory<object>(
|
||||
@ -295,3 +297,67 @@ export interface Route {
|
||||
children?: Route[];
|
||||
loadChildren?: string|Type|any;
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags used to generate R3-style CSS Selectors. They are pasted from
|
||||
* core/src/render3/projection.ts because they cannot be referenced directly.
|
||||
*/
|
||||
export const enum SelectorFlags {
|
||||
/** Indicates this is the beginning of a new negative selector */
|
||||
NOT = 0b0001,
|
||||
|
||||
/** Mode for matching attributes */
|
||||
ATTRIBUTE = 0b0010,
|
||||
|
||||
/** Mode for matching tag names */
|
||||
ELEMENT = 0b0100,
|
||||
|
||||
/** Mode for matching class names */
|
||||
CLASS = 0b1000,
|
||||
}
|
||||
|
||||
// These are a copy the CSS types from core/src/render3/interfaces/projection.ts
|
||||
// They are duplicated here as they cannot be directly referenced from core.
|
||||
export type R3CssSelector = (string | SelectorFlags)[];
|
||||
export type R3CssSelectorList = R3CssSelector[];
|
||||
|
||||
function parserSelectorToSimpleSelector(selector: CssSelector): R3CssSelector {
|
||||
const classes = selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
const elementName = selector.element && selector.element !== '*' ? selector.element : '';
|
||||
return [elementName, ...selector.attrs, ...classes];
|
||||
}
|
||||
|
||||
function parserSelectorToNegativeSelector(selector: CssSelector): R3CssSelector {
|
||||
const classes = selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
|
||||
if (selector.element) {
|
||||
return [
|
||||
SelectorFlags.NOT | SelectorFlags.ELEMENT, selector.element, ...selector.attrs, ...classes
|
||||
];
|
||||
} else if (selector.attrs.length) {
|
||||
return [SelectorFlags.NOT | SelectorFlags.ATTRIBUTE, ...selector.attrs, ...classes];
|
||||
} else {
|
||||
return selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.NOT | SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
}
|
||||
}
|
||||
|
||||
function parserSelectorToR3Selector(selector: CssSelector): R3CssSelector {
|
||||
const positive = parserSelectorToSimpleSelector(selector);
|
||||
|
||||
const negative: R3CssSelectorList = selector.notSelectors && selector.notSelectors.length ?
|
||||
selector.notSelectors.map(notSelector => parserSelectorToNegativeSelector(notSelector)) :
|
||||
[];
|
||||
|
||||
return positive.concat(...negative);
|
||||
}
|
||||
|
||||
export function parseSelectorToR3Selector(selector: string): R3CssSelectorList {
|
||||
const selectors = CssSelector.parse(selector);
|
||||
return selectors.map(parserSelectorToR3Selector);
|
||||
}
|
||||
|
@ -6,7 +6,8 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
|
||||
import {SecurityContext} from '../core';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
export class ParserError {
|
||||
public message: string;
|
||||
@ -215,7 +216,7 @@ export class ASTWithSource extends AST {
|
||||
export class TemplateBinding {
|
||||
constructor(
|
||||
public span: ParseSpan, public key: string, public keyIsVar: boolean, public name: string,
|
||||
public expression: ASTWithSource) {}
|
||||
public expression: ASTWithSource|null) {}
|
||||
}
|
||||
|
||||
export interface AstVisitor {
|
||||
@ -663,3 +664,63 @@ export function visitAstChildren(ast: AST, visitor: AstVisitor, context?: any) {
|
||||
visitSafePropertyRead(ast) { visit(ast.receiver); },
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Bindings
|
||||
|
||||
export class ParsedProperty {
|
||||
public readonly isLiteral: boolean;
|
||||
public readonly isAnimation: boolean;
|
||||
|
||||
constructor(
|
||||
public name: string, public expression: ASTWithSource, public type: ParsedPropertyType,
|
||||
public sourceSpan: ParseSourceSpan) {
|
||||
this.isLiteral = this.type === ParsedPropertyType.LITERAL_ATTR;
|
||||
this.isAnimation = this.type === ParsedPropertyType.ANIMATION;
|
||||
}
|
||||
}
|
||||
|
||||
export enum ParsedPropertyType {
|
||||
DEFAULT,
|
||||
LITERAL_ATTR,
|
||||
ANIMATION
|
||||
}
|
||||
|
||||
export const enum ParsedEventType {
|
||||
// DOM or Directive event
|
||||
Regular,
|
||||
// Animation specific event
|
||||
Animation,
|
||||
}
|
||||
|
||||
export class ParsedEvent {
|
||||
// Regular events have a target
|
||||
// Animation events have a phase
|
||||
constructor(
|
||||
public name: string, public targetOrPhase: string, public type: ParsedEventType,
|
||||
public handler: AST, public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
||||
export class ParsedVariable {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
||||
export const enum BoundElementBindingType {
|
||||
// A regular binding to a property (e.g. `[property]="expression"`).
|
||||
Property,
|
||||
// A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||
Attribute,
|
||||
// A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||
Class,
|
||||
// A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
Style,
|
||||
// A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
Animation,
|
||||
}
|
||||
|
||||
export class BoundElementProperty {
|
||||
constructor(
|
||||
public name: string, public type: BoundElementBindingType,
|
||||
public securityContext: SecurityContext, public value: AST, public unit: string|null,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
}
|
||||
|
@ -98,19 +98,11 @@ export class Parser {
|
||||
return new Quote(new ParseSpan(0, input.length), prefix, uninterpretedExpression, location);
|
||||
}
|
||||
|
||||
parseTemplateBindings(prefixToken: string|null, input: string, location: any):
|
||||
parseTemplateBindings(tplKey: string, tplValue: string, location: any):
|
||||
TemplateBindingParseResult {
|
||||
const tokens = this._lexer.tokenize(input);
|
||||
if (prefixToken) {
|
||||
// Prefix the tokens with the tokens from prefixToken but have them take no space (0 index).
|
||||
const prefixTokens = this._lexer.tokenize(prefixToken).map(t => {
|
||||
t.index = 0;
|
||||
return t;
|
||||
});
|
||||
tokens.unshift(...prefixTokens);
|
||||
}
|
||||
return new _ParseAST(input, location, tokens, input.length, false, this.errors, 0)
|
||||
.parseTemplateBindings();
|
||||
const tokens = this._lexer.tokenize(tplValue);
|
||||
return new _ParseAST(tplValue, location, tokens, tplValue.length, false, this.errors, 0)
|
||||
.parseTemplateBindings(tplKey);
|
||||
}
|
||||
|
||||
parseInterpolation(
|
||||
@ -686,48 +678,49 @@ export class _ParseAST {
|
||||
return result.toString();
|
||||
}
|
||||
|
||||
parseTemplateBindings(): TemplateBindingParseResult {
|
||||
// Parses the AST for `<some-tag *tplKey=AST>`
|
||||
parseTemplateBindings(tplKey: string): TemplateBindingParseResult {
|
||||
let firstBinding = true;
|
||||
const bindings: TemplateBinding[] = [];
|
||||
let prefix: string = null !;
|
||||
const warnings: string[] = [];
|
||||
while (this.index < this.tokens.length) {
|
||||
do {
|
||||
const start = this.inputIndex;
|
||||
let keyIsVar: boolean = this.peekKeywordLet();
|
||||
if (keyIsVar) {
|
||||
this.advance();
|
||||
let rawKey: string;
|
||||
let key: string;
|
||||
let isVar: boolean = false;
|
||||
if (firstBinding) {
|
||||
rawKey = key = tplKey;
|
||||
firstBinding = false;
|
||||
} else {
|
||||
isVar = this.peekKeywordLet();
|
||||
if (isVar) this.advance();
|
||||
rawKey = this.expectTemplateBindingKey();
|
||||
key = isVar ? rawKey : tplKey + rawKey[0].toUpperCase() + rawKey.substring(1);
|
||||
this.optionalCharacter(chars.$COLON);
|
||||
}
|
||||
let rawKey = this.expectTemplateBindingKey();
|
||||
let key = rawKey;
|
||||
if (!keyIsVar) {
|
||||
if (prefix == null) {
|
||||
prefix = key;
|
||||
} else {
|
||||
key = prefix + key[0].toUpperCase() + key.substring(1);
|
||||
}
|
||||
}
|
||||
this.optionalCharacter(chars.$COLON);
|
||||
|
||||
let name: string = null !;
|
||||
let expression: ASTWithSource = null !;
|
||||
if (keyIsVar) {
|
||||
let expression: ASTWithSource|null = null;
|
||||
if (isVar) {
|
||||
if (this.optionalOperator('=')) {
|
||||
name = this.expectTemplateBindingKey();
|
||||
} else {
|
||||
name = '\$implicit';
|
||||
}
|
||||
} else if (this.peekKeywordAs()) {
|
||||
const letStart = this.inputIndex;
|
||||
this.advance(); // consume `as`
|
||||
name = rawKey;
|
||||
key = this.expectTemplateBindingKey(); // read local var name
|
||||
keyIsVar = true;
|
||||
isVar = true;
|
||||
} else if (this.next !== EOF && !this.peekKeywordLet()) {
|
||||
const start = this.inputIndex;
|
||||
const ast = this.parsePipe();
|
||||
const source = this.input.substring(start - this.offset, this.inputIndex - this.offset);
|
||||
expression = new ASTWithSource(ast, source, this.location, this.errors);
|
||||
}
|
||||
bindings.push(new TemplateBinding(this.span(start), key, keyIsVar, name, expression));
|
||||
if (this.peekKeywordAs() && !keyIsVar) {
|
||||
|
||||
bindings.push(new TemplateBinding(this.span(start), key, isVar, name, expression));
|
||||
if (this.peekKeywordAs() && !isVar) {
|
||||
const letStart = this.inputIndex;
|
||||
this.advance(); // consume `as`
|
||||
const letName = this.expectTemplateBindingKey(); // read local var name
|
||||
@ -736,7 +729,8 @@ export class _ParseAST {
|
||||
if (!this.optionalCharacter(chars.$SEMICOLON)) {
|
||||
this.optionalCharacter(chars.$COMMA);
|
||||
}
|
||||
}
|
||||
} while (this.index < this.tokens.length);
|
||||
|
||||
return new TemplateBindingParseResult(bindings, warnings, this.errors);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@
|
||||
*/
|
||||
|
||||
import {SecurityContext} from '../core';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType} from '../expression_parser/ast';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
export interface Node {
|
||||
@ -32,42 +32,17 @@ export class TextAttribute implements Node {
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitAttribute(this); }
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of types of property bindings.
|
||||
*/
|
||||
export enum PropertyBindingType {
|
||||
|
||||
/**
|
||||
* A normal binding to a property (e.g. `[property]="expression"`).
|
||||
*/
|
||||
Property,
|
||||
|
||||
/**
|
||||
* A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||
*/
|
||||
Attribute,
|
||||
|
||||
/**
|
||||
* A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||
*/
|
||||
Class,
|
||||
|
||||
/**
|
||||
* A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
*/
|
||||
Style,
|
||||
|
||||
/**
|
||||
* A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
*/
|
||||
Animation
|
||||
}
|
||||
|
||||
export class BoundAttribute implements Node {
|
||||
constructor(
|
||||
public name: string, public type: PropertyBindingType,
|
||||
public name: string, public type: BoundElementBindingType,
|
||||
public securityContext: SecurityContext, public value: AST, public unit: string|null,
|
||||
public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
static fromBoundElementProperty(prop: BoundElementProperty) {
|
||||
return new BoundAttribute(
|
||||
prop.name, prop.type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan);
|
||||
}
|
||||
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundAttribute(this); }
|
||||
}
|
||||
|
||||
@ -75,6 +50,14 @@ export class BoundEvent implements Node {
|
||||
constructor(
|
||||
public name: string, public handler: AST, public target: string|null,
|
||||
public phase: string|null, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
static fromParsedEvent(event: ParsedEvent) {
|
||||
const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null;
|
||||
const phase: string|null =
|
||||
event.type === ParsedEventType.Animation ? event.targetOrPhase : null;
|
||||
return new BoundEvent(event.name, event.handler, target, phase, event.sourceSpan);
|
||||
}
|
||||
|
||||
visit<Result>(visitor: Visitor<Result>): Result { return visitor.visitBoundEvent(this); }
|
||||
}
|
||||
|
||||
|
@ -6,14 +6,13 @@
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
|
||||
import {ParsedEvent, ParsedProperty, ParsedVariable} from '../expression_parser/ast';
|
||||
import * as html from '../ml_parser/ast';
|
||||
import {replaceNgsp} from '../ml_parser/html_whitespaces';
|
||||
import {isNgTemplate} from '../ml_parser/tags';
|
||||
import {ParseError, ParseErrorLevel, ParseSourceSpan} from '../parse_util';
|
||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||
import {BindingParser, BoundProperty} from '../template_parser/binding_parser';
|
||||
// TODO(chuckj): Refactor binding parser to not have a dependency on template_ast.
|
||||
import {BoundEventAst, VariableAst} from '../template_parser/template_ast';
|
||||
import {BindingParser} from '../template_parser/binding_parser';
|
||||
import {PreparsedElementType, preparseElement} from '../template_parser/template_preparser';
|
||||
|
||||
import * as t from './r3_ast';
|
||||
@ -78,7 +77,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
const isTemplateElement = isNgTemplate(element.name);
|
||||
|
||||
const matchableAttributes: [string, string][] = [];
|
||||
const boundProperties: BoundProperty[] = [];
|
||||
const parsedProperties: ParsedProperty[] = [];
|
||||
const boundEvents: t.BoundEvent[] = [];
|
||||
const variables: t.Variable[] = [];
|
||||
const references: t.Reference[] = [];
|
||||
@ -86,7 +85,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
|
||||
const templateMatchableAttributes: [string, string][] = [];
|
||||
let inlineTemplateSourceSpan: ParseSourceSpan;
|
||||
const templateBoundProperties: BoundProperty[] = [];
|
||||
const templateParsedProperties: ParsedProperty[] = [];
|
||||
const templateVariables: t.Variable[] = [];
|
||||
|
||||
// Whether the element has any *-attribute
|
||||
@ -107,23 +106,18 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
}
|
||||
isTemplateBinding = true;
|
||||
elementHasInlineTemplate = true;
|
||||
const templateBindingsSource = attribute.value;
|
||||
const prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
||||
|
||||
const oldVariables: VariableAst[] = [];
|
||||
const templateValue = attribute.value;
|
||||
const templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||
|
||||
inlineTemplateSourceSpan = attribute.valueSpan || attribute.sourceSpan;
|
||||
|
||||
this.bindingParser.parseInlineTemplateBinding(
|
||||
prefixToken !, templateBindingsSource !, attribute.sourceSpan,
|
||||
templateMatchableAttributes, templateBoundProperties, oldVariables);
|
||||
|
||||
templateVariables.push(
|
||||
...oldVariables.map(v => new t.Variable(v.name, v.value, v.sourceSpan)));
|
||||
templateKey, templateValue, attribute.sourceSpan, templateMatchableAttributes,
|
||||
templateParsedProperties, templateVariables);
|
||||
} else {
|
||||
// Check for variables, events, property bindings, interpolation
|
||||
hasBinding = this.parseAttribute(
|
||||
isTemplateElement, attribute, matchableAttributes, boundProperties, boundEvents,
|
||||
isTemplateElement, attribute, matchableAttributes, parsedProperties, boundEvents,
|
||||
variables, references);
|
||||
}
|
||||
|
||||
@ -158,12 +152,12 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
parsedElement = new t.Content(selectorIndex, attributes, element.sourceSpan);
|
||||
} else if (isTemplateElement) {
|
||||
// `<ng-template>`
|
||||
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
|
||||
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
|
||||
parsedElement = new t.Template(
|
||||
attributes, boundAttributes, children, references, variables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan);
|
||||
} else {
|
||||
const boundAttributes = this.createBoundAttributes(element.name, boundProperties);
|
||||
const boundAttributes = this.createBoundAttributes(element.name, parsedProperties);
|
||||
|
||||
parsedElement = new t.Element(
|
||||
element.name, attributes, boundAttributes, boundEvents, children, references,
|
||||
@ -177,7 +171,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
([name, value]) =>
|
||||
attributes.push(new t.TextAttribute(name, value, inlineTemplateSourceSpan)));
|
||||
|
||||
const boundAttributes = this.createBoundAttributes('ng-template', templateBoundProperties);
|
||||
const boundAttributes = this.createBoundAttributes('ng-template', templateParsedProperties);
|
||||
parsedElement = new t.Template(
|
||||
attributes, boundAttributes, [parsedElement], [], templateVariables, element.sourceSpan,
|
||||
element.startSourceSpan, element.endSourceSpan);
|
||||
@ -202,22 +196,16 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
|
||||
visitExpansionCase(expansionCase: html.ExpansionCase): null { return null; }
|
||||
|
||||
private createBoundAttributes(elementName: string, boundProperties: BoundProperty[]):
|
||||
private createBoundAttributes(elementName: string, properties: ParsedProperty[]):
|
||||
t.BoundAttribute[] {
|
||||
const literalProperties = boundProperties.filter(prop => !prop.isLiteral);
|
||||
|
||||
return literalProperties.map(property => {
|
||||
// TODO(vicb): get ride of the boundProperty (from TemplateAst)
|
||||
const boundProp = this.bindingParser.createElementPropertyAst(elementName, property);
|
||||
return new t.BoundAttribute(
|
||||
boundProp.name, boundProp.type as any as t.PropertyBindingType, boundProp.securityContext,
|
||||
boundProp.value, boundProp.unit, boundProp.sourceSpan);
|
||||
});
|
||||
return properties.filter(prop => !prop.isLiteral)
|
||||
.map(prop => this.bindingParser.createBoundElementProperty(elementName, prop))
|
||||
.map(prop => t.BoundAttribute.fromBoundElementProperty(prop));
|
||||
}
|
||||
|
||||
private parseAttribute(
|
||||
isTemplateElement: boolean, attribute: html.Attribute, matchableAttributes: string[][],
|
||||
boundProperties: BoundProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
|
||||
parsedProperties: ParsedProperty[], boundEvents: t.BoundEvent[], variables: t.Variable[],
|
||||
references: t.Reference[]) {
|
||||
const name = normalizeAttributeName(attribute.name);
|
||||
const value = attribute.value;
|
||||
@ -230,7 +218,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
hasBinding = true;
|
||||
if (bindParts[KW_BIND_IDX] != null) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
|
||||
|
||||
} else if (bindParts[KW_LET_IDX]) {
|
||||
if (isTemplateElement) {
|
||||
@ -245,40 +233,40 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
this.parseReference(identifier, value, srcSpan, references);
|
||||
|
||||
} else if (bindParts[KW_ON_IDX]) {
|
||||
const events: BoundEventAst[] = [];
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
} else if (bindParts[KW_BINDON_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, boundProperties);
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, matchableAttributes, parsedProperties);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||
} else if (bindParts[KW_AT_IDX]) {
|
||||
this.bindingParser.parseLiteralAttr(
|
||||
name, value, srcSpan, matchableAttributes, boundProperties);
|
||||
name, value, srcSpan, matchableAttributes, parsedProperties);
|
||||
|
||||
} else if (bindParts[IDENT_BANANA_BOX_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, matchableAttributes,
|
||||
boundProperties);
|
||||
parsedProperties);
|
||||
this.parseAssignmentEvent(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, matchableAttributes, boundEvents);
|
||||
|
||||
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||
this.bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_PROPERTY_IDX], value, false, srcSpan, matchableAttributes,
|
||||
boundProperties);
|
||||
parsedProperties);
|
||||
|
||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||
const events: BoundEventAst[] = [];
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, matchableAttributes, events);
|
||||
addEvents(events, boundEvents);
|
||||
}
|
||||
} else {
|
||||
hasBinding = this.bindingParser.parsePropertyInterpolation(
|
||||
name, value, srcSpan, matchableAttributes, boundProperties);
|
||||
name, value, srcSpan, matchableAttributes, parsedProperties);
|
||||
}
|
||||
|
||||
return hasBinding;
|
||||
@ -305,7 +293,7 @@ export class HtmlToTemplateTransform implements html.Visitor {
|
||||
private parseAssignmentEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], boundEvents: t.BoundEvent[]) {
|
||||
const events: BoundEventAst[] = [];
|
||||
const events: ParsedEvent[] = [];
|
||||
this.bindingParser.parseEvent(
|
||||
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, events);
|
||||
addEvents(events, boundEvents);
|
||||
@ -356,9 +344,8 @@ function normalizeAttributeName(attrName: string): string {
|
||||
return /^data-/i.test(attrName) ? attrName.substring(5) : attrName;
|
||||
}
|
||||
|
||||
function addEvents(events: BoundEventAst[], boundEvents: t.BoundEvent[]) {
|
||||
boundEvents.push(
|
||||
...events.map(e => new t.BoundEvent(e.name, e.handler, e.target, e.phase, e.sourceSpan)));
|
||||
function addEvents(events: ParsedEvent[], boundEvents: t.BoundEvent[]) {
|
||||
boundEvents.push(...events.map(e => t.BoundEvent.fromParsedEvent(e)));
|
||||
}
|
||||
|
||||
function isEmptyTextNode(node: html.Node): boolean {
|
||||
|
@ -10,8 +10,8 @@ import {CompileDiDependencyMetadata, CompileDirectiveMetadata, CompileQueryMetad
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {BindingForm, BuiltinFunctionCall, LocalResolver, convertActionBinding, convertPropertyBinding} from '../compiler_util/expression_converter';
|
||||
import {ConstantPool, DefinitionKind} from '../constant_pool';
|
||||
import {InjectFlags} from '../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../expression_parser/ast';
|
||||
import * as core from '../core';
|
||||
import {AST, AstMemoryEfficientTransformer, BindingPipe, BoundElementBindingType, FunctionCall, ImplicitReceiver, LiteralArray, LiteralMap, LiteralPrimitive, PropertyRead} from '../expression_parser/ast';
|
||||
import {Identifiers} from '../identifiers';
|
||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||
import * as o from '../output/output_ast';
|
||||
@ -24,6 +24,7 @@ import * as t from './r3_ast';
|
||||
import {Identifiers as R3} from './r3_identifiers';
|
||||
|
||||
|
||||
|
||||
/** Name of the context parameter passed into a template function */
|
||||
const CONTEXT_NAME = 'ctx';
|
||||
|
||||
@ -213,10 +214,10 @@ function unsupported(feature: string): never {
|
||||
}
|
||||
|
||||
const BINDING_INSTRUCTION_MAP: {[type: number]: o.ExternalReference} = {
|
||||
[t.PropertyBindingType.Property]: R3.elementProperty,
|
||||
[t.PropertyBindingType.Attribute]: R3.elementAttribute,
|
||||
[t.PropertyBindingType.Class]: R3.elementClassNamed,
|
||||
[t.PropertyBindingType.Style]: R3.elementStyleNamed,
|
||||
[BoundElementBindingType.Property]: R3.elementProperty,
|
||||
[BoundElementBindingType.Attribute]: R3.elementAttribute,
|
||||
[BoundElementBindingType.Class]: R3.elementClassNamed,
|
||||
[BoundElementBindingType.Style]: R3.elementStyleNamed,
|
||||
};
|
||||
|
||||
function interpolate(args: o.Expression[]): o.Expression {
|
||||
@ -440,7 +441,7 @@ class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||
|
||||
// Only selectors with a non-default value are generated
|
||||
if (ngContentSelectors.length > 1) {
|
||||
const r3Selectors = ngContentSelectors.map(s => parseSelectorToR3Selector(s));
|
||||
const r3Selectors = ngContentSelectors.map(s => core.parseSelectorToR3Selector(s));
|
||||
// `projectionDef` needs both the parsed and raw value of the selectors
|
||||
const parsed = this.outputCtx.constantPool.getConstLiteral(asLiteral(r3Selectors), true);
|
||||
const unParsed =
|
||||
@ -679,7 +680,7 @@ class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||
|
||||
// Generate element input bindings
|
||||
element.inputs.forEach((input: t.BoundAttribute) => {
|
||||
if (input.type === t.PropertyBindingType.Animation) {
|
||||
if (input.type === BoundElementBindingType.Animation) {
|
||||
this._unsupported('animations');
|
||||
}
|
||||
const convertedBinding = this.convertPropertyBinding(implicit, input.value);
|
||||
@ -691,7 +692,7 @@ class TemplateDefinitionBuilder implements t.Visitor<void>, LocalResolver {
|
||||
this._bindingCode, input.sourceSpan, instruction, o.literal(elementIndex),
|
||||
o.literal(input.name), value);
|
||||
} else {
|
||||
this._unsupported(`binding ${t.PropertyBindingType[input.type]}`);
|
||||
this._unsupported(`binding type ${input.type}`);
|
||||
}
|
||||
});
|
||||
|
||||
@ -883,7 +884,7 @@ export function createFactory(
|
||||
token.identifier != null ? outputCtx.importExpr(tokenRef) : o.literal(tokenRef);
|
||||
const directiveInjectArgs = [tokenValue];
|
||||
const flags = extractFlags(dependency);
|
||||
if (flags != InjectFlags.Default) {
|
||||
if (flags != core.InjectFlags.Default) {
|
||||
// Append flag information if other than default.
|
||||
directiveInjectArgs.push(o.literal(flags));
|
||||
}
|
||||
@ -921,19 +922,19 @@ export function createFactory(
|
||||
type.reference.name ? `${type.reference.name}_Factory` : null);
|
||||
}
|
||||
|
||||
function extractFlags(dependency: CompileDiDependencyMetadata): InjectFlags {
|
||||
let flags = InjectFlags.Default;
|
||||
function extractFlags(dependency: CompileDiDependencyMetadata): core.InjectFlags {
|
||||
let flags = core.InjectFlags.Default;
|
||||
if (dependency.isHost) {
|
||||
flags |= InjectFlags.Host;
|
||||
flags |= core.InjectFlags.Host;
|
||||
}
|
||||
if (dependency.isOptional) {
|
||||
flags |= InjectFlags.Optional;
|
||||
flags |= core.InjectFlags.Optional;
|
||||
}
|
||||
if (dependency.isSelf) {
|
||||
flags |= InjectFlags.Self;
|
||||
flags |= core.InjectFlags.Self;
|
||||
}
|
||||
if (dependency.isSkipSelf) {
|
||||
flags |= InjectFlags.SkipSelf;
|
||||
flags |= core.InjectFlags.SkipSelf;
|
||||
}
|
||||
if (dependency.isValue) {
|
||||
unsupported('value dependencies');
|
||||
@ -953,7 +954,7 @@ function trimTrailingNulls(parameters: o.Expression[]): o.Expression[] {
|
||||
|
||||
// Turn a directive selector into an R3-compatible selector for directive def
|
||||
function createDirectiveSelector(selector: string): o.Expression {
|
||||
return asLiteral(parseSelectorToR3Selector(selector));
|
||||
return asLiteral(core.parseSelectorToR3Selector(selector));
|
||||
}
|
||||
|
||||
function createHostAttributesArray(
|
||||
@ -1106,72 +1107,6 @@ function invalid<T>(arg: o.Expression | o.Statement | t.Node): never {
|
||||
`Invalid state: Visitor ${this.constructor.name} doesn't handle ${o.constructor.name}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flags used to generate R3-style CSS Selectors. They are pasted from
|
||||
* core/src/render3/projection.ts because they cannot be referenced directly.
|
||||
*/
|
||||
// TODO(vicb): move to ../core
|
||||
const enum SelectorFlags {
|
||||
/** Indicates this is the beginning of a new negative selector */
|
||||
NOT = 0b0001,
|
||||
|
||||
/** Mode for matching attributes */
|
||||
ATTRIBUTE = 0b0010,
|
||||
|
||||
/** Mode for matching tag names */
|
||||
ELEMENT = 0b0100,
|
||||
|
||||
/** Mode for matching class names */
|
||||
CLASS = 0b1000,
|
||||
}
|
||||
|
||||
// These are a copy the CSS types from core/src/render3/interfaces/projection.ts
|
||||
// They are duplicated here as they cannot be directly referenced from core.
|
||||
// TODO(vicb): move to ../core
|
||||
type R3CssSelector = (string | SelectorFlags)[];
|
||||
type R3CssSelectorList = R3CssSelector[];
|
||||
|
||||
function parserSelectorToSimpleSelector(selector: CssSelector): R3CssSelector {
|
||||
const classes = selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
const elementName = selector.element && selector.element !== '*' ? selector.element : '';
|
||||
return [elementName, ...selector.attrs, ...classes];
|
||||
}
|
||||
|
||||
function parserSelectorToNegativeSelector(selector: CssSelector): R3CssSelector {
|
||||
const classes = selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
|
||||
if (selector.element) {
|
||||
return [
|
||||
SelectorFlags.NOT | SelectorFlags.ELEMENT, selector.element, ...selector.attrs, ...classes
|
||||
];
|
||||
} else if (selector.attrs.length) {
|
||||
return [SelectorFlags.NOT | SelectorFlags.ATTRIBUTE, ...selector.attrs, ...classes];
|
||||
} else {
|
||||
return selector.classNames && selector.classNames.length ?
|
||||
[SelectorFlags.NOT | SelectorFlags.CLASS, ...selector.classNames] :
|
||||
[];
|
||||
}
|
||||
}
|
||||
|
||||
function parserSelectorToR3Selector(selector: CssSelector): R3CssSelector {
|
||||
const positive = parserSelectorToSimpleSelector(selector);
|
||||
|
||||
const negative: R3CssSelectorList = selector.notSelectors && selector.notSelectors.length ?
|
||||
selector.notSelectors.map(notSelector => parserSelectorToNegativeSelector(notSelector)) :
|
||||
[];
|
||||
|
||||
return positive.concat(...negative);
|
||||
}
|
||||
|
||||
function parseSelectorToR3Selector(selector: string): R3CssSelectorList {
|
||||
const selectors = CssSelector.parse(selector);
|
||||
return selectors.map(parserSelectorToR3Selector);
|
||||
}
|
||||
|
||||
function asLiteral(value: any): o.Expression {
|
||||
if (Array.isArray(value)) {
|
||||
return o.literalArr(value.map(asLiteral));
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import {CompileDirectiveSummary, CompilePipeSummary} from '../compile_metadata';
|
||||
import {SecurityContext} from '../core';
|
||||
import {ASTWithSource, BindingPipe, EmptyExpr, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {ASTWithSource, BindingPipe, BoundElementBindingType, BoundElementProperty, EmptyExpr, ParsedEvent, ParsedEventType, ParsedProperty, ParsedPropertyType, ParsedVariable, ParserError, RecursiveAstVisitor, TemplateBinding} from '../expression_parser/ast';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {InterpolationConfig} from '../ml_parser/interpolation_config';
|
||||
import {mergeNsAndName} from '../ml_parser/tags';
|
||||
@ -17,7 +17,7 @@ import {ElementSchemaRegistry} from '../schema/element_schema_registry';
|
||||
import {CssSelector} from '../selector';
|
||||
import {splitAtColon, splitAtPeriod} from '../util';
|
||||
|
||||
import {BoundElementPropertyAst, BoundEventAst, PropertyBindingType, VariableAst} from './template_ast';
|
||||
import {BoundElementPropertyAst, PropertyBindingType} from './template_ast';
|
||||
|
||||
const PROPERTY_PARTS_SEPARATOR = '.';
|
||||
const ATTRIBUTE_PREFIX = 'attr';
|
||||
@ -26,27 +26,6 @@ const STYLE_PREFIX = 'style';
|
||||
|
||||
const ANIMATE_PROP_PREFIX = 'animate-';
|
||||
|
||||
export enum BoundPropertyType {
|
||||
DEFAULT,
|
||||
LITERAL_ATTR,
|
||||
ANIMATION
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a parsed property.
|
||||
*/
|
||||
export class BoundProperty {
|
||||
public readonly isLiteral: boolean;
|
||||
public readonly isAnimation: boolean;
|
||||
|
||||
constructor(
|
||||
public name: string, public expression: ASTWithSource, public type: BoundPropertyType,
|
||||
public sourceSpan: ParseSourceSpan) {
|
||||
this.isLiteral = this.type === BoundPropertyType.LITERAL_ATTR;
|
||||
this.isAnimation = this.type === BoundPropertyType.ANIMATION;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses bindings in templates and in the directive host area.
|
||||
*/
|
||||
@ -70,9 +49,9 @@ export class BindingParser {
|
||||
getUsedPipes(): CompilePipeSummary[] { return Array.from(this._usedPipes.values()); }
|
||||
|
||||
createBoundHostProperties(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||
BoundProperty[]|null {
|
||||
ParsedProperty[]|null {
|
||||
if (dirMeta.hostProperties) {
|
||||
const boundProps: BoundProperty[] = [];
|
||||
const boundProps: ParsedProperty[] = [];
|
||||
Object.keys(dirMeta.hostProperties).forEach(propName => {
|
||||
const expression = dirMeta.hostProperties[propName];
|
||||
if (typeof expression === 'string') {
|
||||
@ -90,27 +69,27 @@ export class BindingParser {
|
||||
|
||||
createDirectiveHostPropertyAsts(
|
||||
dirMeta: CompileDirectiveSummary, elementSelector: string,
|
||||
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[]|null {
|
||||
sourceSpan: ParseSourceSpan): BoundElementProperty[]|null {
|
||||
const boundProps = this.createBoundHostProperties(dirMeta, sourceSpan);
|
||||
return boundProps &&
|
||||
boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop));
|
||||
boundProps.map((prop) => this.createBoundElementProperty(elementSelector, prop));
|
||||
}
|
||||
|
||||
createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
|
||||
BoundEventAst[]|null {
|
||||
ParsedEvent[]|null {
|
||||
if (dirMeta.hostListeners) {
|
||||
const targetEventAsts: BoundEventAst[] = [];
|
||||
const targetEvents: ParsedEvent[] = [];
|
||||
Object.keys(dirMeta.hostListeners).forEach(propName => {
|
||||
const expression = dirMeta.hostListeners[propName];
|
||||
if (typeof expression === 'string') {
|
||||
this.parseEvent(propName, expression, sourceSpan, [], targetEventAsts);
|
||||
this.parseEvent(propName, expression, sourceSpan, [], targetEvents);
|
||||
} else {
|
||||
this._reportError(
|
||||
`Value of the host listener "${propName}" needs to be a string representing an expression but got "${expression}" (${typeof expression})`,
|
||||
sourceSpan);
|
||||
}
|
||||
});
|
||||
return targetEventAsts;
|
||||
return targetEvents;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@ -130,15 +109,17 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
// Parse an inline template binding. ie `<tag *prefixToken="<value>">`
|
||||
// Parse an inline template binding. ie `<tag *tplKey="<tplValue>">`
|
||||
parseInlineTemplateBinding(
|
||||
prefixToken: string, value: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[], targetVars: VariableAst[]) {
|
||||
const bindings = this._parseTemplateBindings(prefixToken, value, sourceSpan);
|
||||
tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[],
|
||||
targetVars: ParsedVariable[]) {
|
||||
const bindings = this._parseTemplateBindings(tplKey, tplValue, sourceSpan);
|
||||
|
||||
for (let i = 0; i < bindings.length; i++) {
|
||||
const binding = bindings[i];
|
||||
if (binding.keyIsVar) {
|
||||
targetVars.push(new VariableAst(binding.key, binding.name, sourceSpan));
|
||||
targetVars.push(new ParsedVariable(binding.key, binding.name, sourceSpan));
|
||||
} else if (binding.expression) {
|
||||
this._parsePropertyAst(
|
||||
binding.key, binding.expression, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
@ -149,12 +130,12 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
private _parseTemplateBindings(prefixToken: string, value: string, sourceSpan: ParseSourceSpan):
|
||||
private _parseTemplateBindings(tplKey: string, tplValue: string, sourceSpan: ParseSourceSpan):
|
||||
TemplateBinding[] {
|
||||
const sourceInfo = sourceSpan.start.toString();
|
||||
|
||||
try {
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(prefixToken, value, sourceInfo);
|
||||
const bindingsResult = this._exprParser.parseTemplateBindings(tplKey, tplValue, sourceInfo);
|
||||
this._reportExpressionParserErrors(bindingsResult.errors, sourceSpan);
|
||||
bindingsResult.templateBindings.forEach((binding) => {
|
||||
if (binding.expression) {
|
||||
@ -172,8 +153,8 @@ export class BindingParser {
|
||||
|
||||
parseLiteralAttr(
|
||||
name: string, value: string|null, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
if (isAnimationLabel(name)) {
|
||||
name = name.substring(1);
|
||||
if (value) {
|
||||
this._reportError(
|
||||
@ -183,20 +164,20 @@ export class BindingParser {
|
||||
}
|
||||
this._parseAnimation(name, value, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
} else {
|
||||
targetProps.push(new BoundProperty(
|
||||
name, this._exprParser.wrapLiteralPrimitive(value, ''), BoundPropertyType.LITERAL_ATTR,
|
||||
targetProps.push(new ParsedProperty(
|
||||
name, this._exprParser.wrapLiteralPrimitive(value, ''), ParsedPropertyType.LITERAL_ATTR,
|
||||
sourceSpan));
|
||||
}
|
||||
}
|
||||
|
||||
parsePropertyBinding(
|
||||
name: string, expression: string, isHost: boolean, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
let isAnimationProp = false;
|
||||
if (name.startsWith(ANIMATE_PROP_PREFIX)) {
|
||||
isAnimationProp = true;
|
||||
name = name.substring(ANIMATE_PROP_PREFIX.length);
|
||||
} else if (_isAnimationLabel(name)) {
|
||||
} else if (isAnimationLabel(name)) {
|
||||
isAnimationProp = true;
|
||||
name = name.substring(1);
|
||||
}
|
||||
@ -212,7 +193,7 @@ export class BindingParser {
|
||||
|
||||
parsePropertyInterpolation(
|
||||
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[]): boolean {
|
||||
targetProps: ParsedProperty[]): boolean {
|
||||
const expr = this.parseInterpolation(value, sourceSpan);
|
||||
if (expr) {
|
||||
this._parsePropertyAst(name, expr, sourceSpan, targetMatchableAttrs, targetProps);
|
||||
@ -223,20 +204,20 @@ export class BindingParser {
|
||||
|
||||
private _parsePropertyAst(
|
||||
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
targetMatchableAttrs.push([name, ast.source !]);
|
||||
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.DEFAULT, sourceSpan));
|
||||
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.DEFAULT, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseAnimation(
|
||||
name: string, expression: string|null, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
|
||||
targetMatchableAttrs: string[][], targetProps: ParsedProperty[]) {
|
||||
// This will occur when a @trigger is not paired with an expression.
|
||||
// For animations it is valid to not have an expression since */void
|
||||
// states will be applied by angular when the element is attached/detached
|
||||
const ast = this._parseBinding(expression || 'undefined', false, sourceSpan);
|
||||
targetMatchableAttrs.push([name, ast.source !]);
|
||||
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan));
|
||||
targetProps.push(new ParsedProperty(name, ast, ParsedPropertyType.ANIMATION, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseBinding(value: string, isHostBinding: boolean, sourceSpan: ParseSourceSpan):
|
||||
@ -256,16 +237,16 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
createElementPropertyAst(elementSelector: string, boundProp: BoundProperty):
|
||||
BoundElementPropertyAst {
|
||||
createBoundElementProperty(elementSelector: string, boundProp: ParsedProperty):
|
||||
BoundElementProperty {
|
||||
if (boundProp.isAnimation) {
|
||||
return new BoundElementPropertyAst(
|
||||
boundProp.name, PropertyBindingType.Animation, SecurityContext.NONE, boundProp.expression,
|
||||
null, boundProp.sourceSpan);
|
||||
return new BoundElementProperty(
|
||||
boundProp.name, BoundElementBindingType.Animation, SecurityContext.NONE,
|
||||
boundProp.expression, null, boundProp.sourceSpan);
|
||||
}
|
||||
|
||||
let unit: string|null = null;
|
||||
let bindingType: PropertyBindingType = undefined !;
|
||||
let bindingType: BoundElementBindingType = undefined !;
|
||||
let boundPropertyName: string|null = null;
|
||||
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
|
||||
let securityContexts: SecurityContext[] = undefined !;
|
||||
@ -285,15 +266,15 @@ export class BindingParser {
|
||||
boundPropertyName = mergeNsAndName(ns, name);
|
||||
}
|
||||
|
||||
bindingType = PropertyBindingType.Attribute;
|
||||
bindingType = BoundElementBindingType.Attribute;
|
||||
} else if (parts[0] == CLASS_PREFIX) {
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Class;
|
||||
bindingType = BoundElementBindingType.Class;
|
||||
securityContexts = [SecurityContext.NONE];
|
||||
} else if (parts[0] == STYLE_PREFIX) {
|
||||
unit = parts.length > 2 ? parts[2] : null;
|
||||
boundPropertyName = parts[1];
|
||||
bindingType = PropertyBindingType.Style;
|
||||
bindingType = BoundElementBindingType.Style;
|
||||
securityContexts = [SecurityContext.STYLE];
|
||||
}
|
||||
}
|
||||
@ -303,29 +284,28 @@ export class BindingParser {
|
||||
boundPropertyName = this._schemaRegistry.getMappedPropName(boundProp.name);
|
||||
securityContexts = calcPossibleSecurityContexts(
|
||||
this._schemaRegistry, elementSelector, boundPropertyName, false);
|
||||
bindingType = PropertyBindingType.Property;
|
||||
bindingType = BoundElementBindingType.Property;
|
||||
this._validatePropertyOrAttributeName(boundPropertyName, boundProp.sourceSpan, false);
|
||||
}
|
||||
|
||||
return new BoundElementPropertyAst(
|
||||
return new BoundElementProperty(
|
||||
boundPropertyName, bindingType, securityContexts[0], boundProp.expression, unit,
|
||||
boundProp.sourceSpan);
|
||||
}
|
||||
|
||||
parseEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
if (_isAnimationLabel(name)) {
|
||||
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
|
||||
if (isAnimationLabel(name)) {
|
||||
name = name.substr(1);
|
||||
this._parseAnimationEvent(name, expression, sourceSpan, targetEvents);
|
||||
} else {
|
||||
this._parseEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
this._parseRegularEvent(name, expression, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
}
|
||||
|
||||
private _parseAnimationEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetEvents: BoundEventAst[]) {
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan, targetEvents: ParsedEvent[]) {
|
||||
const matches = splitAtPeriod(name, [name, '']);
|
||||
const eventName = matches[0];
|
||||
const phase = matches[1].toLowerCase();
|
||||
@ -334,7 +314,8 @@ export class BindingParser {
|
||||
case 'start':
|
||||
case 'done':
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetEvents.push(new BoundEventAst(eventName, null, phase, ast, sourceSpan));
|
||||
targetEvents.push(
|
||||
new ParsedEvent(eventName, phase, ParsedEventType.Animation, ast, sourceSpan));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -350,14 +331,14 @@ export class BindingParser {
|
||||
}
|
||||
}
|
||||
|
||||
private _parseEvent(
|
||||
private _parseRegularEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
|
||||
// long format: 'target: eventName'
|
||||
const [target, eventName] = splitAtColon(name, [null !, name]);
|
||||
const ast = this._parseAction(expression, sourceSpan);
|
||||
targetMatchableAttrs.push([name !, ast.source !]);
|
||||
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
|
||||
targetEvents.push(new ParsedEvent(eventName, target, ParsedEventType.Regular, ast, sourceSpan));
|
||||
// Don't detect directives for event names for now,
|
||||
// so don't add the event name to the matchableAttrs
|
||||
}
|
||||
@ -438,7 +419,7 @@ export class PipeCollector extends RecursiveAstVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
function _isAnimationLabel(name: string): boolean {
|
||||
function isAnimationLabel(name: string): boolean {
|
||||
return name[0] == '@';
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@
|
||||
import {AstPath} from '../ast_path';
|
||||
import {CompileDirectiveSummary, CompileProviderMetadata, CompileTokenMetadata} from '../compile_metadata';
|
||||
import {SecurityContext} from '../core';
|
||||
import {AST} from '../expression_parser/ast';
|
||||
import {AST, BoundElementBindingType, BoundElementProperty, ParsedEvent, ParsedEventType, ParsedVariable} from '../expression_parser/ast';
|
||||
import {LifecycleHooks} from '../lifecycle_reflector';
|
||||
import {ParseSourceSpan} from '../parse_util';
|
||||
|
||||
@ -58,12 +58,33 @@ export class AttrAst implements TemplateAst {
|
||||
visit(visitor: TemplateAstVisitor, context: any): any { return visitor.visitAttr(this, context); }
|
||||
}
|
||||
|
||||
export enum PropertyBindingType {
|
||||
// A normal binding to a property (e.g. `[property]="expression"`).
|
||||
Property,
|
||||
// A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||
Attribute,
|
||||
// A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||
Class,
|
||||
// A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
Style,
|
||||
// A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
Animation,
|
||||
}
|
||||
|
||||
const BoundPropertyMapping = {
|
||||
[BoundElementBindingType.Animation]: PropertyBindingType.Animation,
|
||||
[BoundElementBindingType.Attribute]: PropertyBindingType.Attribute,
|
||||
[BoundElementBindingType.Class]: PropertyBindingType.Class,
|
||||
[BoundElementBindingType.Property]: PropertyBindingType.Property,
|
||||
[BoundElementBindingType.Style]: PropertyBindingType.Style,
|
||||
};
|
||||
|
||||
/**
|
||||
* A binding for an element property (e.g. `[property]="expression"`) or an animation trigger (e.g.
|
||||
* `[@trigger]="stateExp"`)
|
||||
*/
|
||||
export class BoundElementPropertyAst implements TemplateAst {
|
||||
public readonly isAnimation: boolean;
|
||||
readonly isAnimation: boolean;
|
||||
|
||||
constructor(
|
||||
public name: string, public type: PropertyBindingType,
|
||||
@ -71,6 +92,13 @@ export class BoundElementPropertyAst implements TemplateAst {
|
||||
public sourceSpan: ParseSourceSpan) {
|
||||
this.isAnimation = this.type === PropertyBindingType.Animation;
|
||||
}
|
||||
|
||||
static fromBoundProperty(prop: BoundElementProperty) {
|
||||
const type = BoundPropertyMapping[prop.type];
|
||||
return new BoundElementPropertyAst(
|
||||
prop.name, type, prop.securityContext, prop.value, prop.unit, prop.sourceSpan);
|
||||
}
|
||||
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitElementProperty(this, context);
|
||||
}
|
||||
@ -81,18 +109,8 @@ export class BoundElementPropertyAst implements TemplateAst {
|
||||
* `(@trigger.phase)="callback($event)"`).
|
||||
*/
|
||||
export class BoundEventAst implements TemplateAst {
|
||||
static calcFullName(name: string, target: string|null, phase: string|null): string {
|
||||
if (target) {
|
||||
return `${target}:${name}`;
|
||||
} else if (phase) {
|
||||
return `@${name}.${phase}`;
|
||||
} else {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
|
||||
public readonly fullName: string;
|
||||
public readonly isAnimation: boolean;
|
||||
readonly fullName: string;
|
||||
readonly isAnimation: boolean;
|
||||
|
||||
constructor(
|
||||
public name: string, public target: string|null, public phase: string|null,
|
||||
@ -100,6 +118,25 @@ export class BoundEventAst implements TemplateAst {
|
||||
this.fullName = BoundEventAst.calcFullName(this.name, this.target, this.phase);
|
||||
this.isAnimation = !!this.phase;
|
||||
}
|
||||
|
||||
static calcFullName(name: string, target: string|null, phase: string|null): string {
|
||||
if (target) {
|
||||
return `${target}:${name}`;
|
||||
}
|
||||
if (phase) {
|
||||
return `@${name}.${phase}`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
static fromParsedEvent(event: ParsedEvent) {
|
||||
const target: string|null = event.type === ParsedEventType.Regular ? event.targetOrPhase : null;
|
||||
const phase: string|null =
|
||||
event.type === ParsedEventType.Animation ? event.targetOrPhase : null;
|
||||
return new BoundEventAst(event.name, target, phase, event.handler, event.sourceSpan);
|
||||
}
|
||||
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitEvent(this, context);
|
||||
}
|
||||
@ -122,6 +159,11 @@ export class ReferenceAst implements TemplateAst {
|
||||
*/
|
||||
export class VariableAst implements TemplateAst {
|
||||
constructor(public name: string, public value: string, public sourceSpan: ParseSourceSpan) {}
|
||||
|
||||
static fromParsedVariable(v: ParsedVariable) {
|
||||
return new VariableAst(v.name, v.value, v.sourceSpan);
|
||||
}
|
||||
|
||||
visit(visitor: TemplateAstVisitor, context: any): any {
|
||||
return visitor.visitVariable(this, context);
|
||||
}
|
||||
@ -220,37 +262,6 @@ export class NgContentAst implements TemplateAst {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enumeration of types of property bindings.
|
||||
*/
|
||||
export enum PropertyBindingType {
|
||||
|
||||
/**
|
||||
* A normal binding to a property (e.g. `[property]="expression"`).
|
||||
*/
|
||||
Property,
|
||||
|
||||
/**
|
||||
* A binding to an element attribute (e.g. `[attr.name]="expression"`).
|
||||
*/
|
||||
Attribute,
|
||||
|
||||
/**
|
||||
* A binding to a CSS class (e.g. `[class.name]="condition"`).
|
||||
*/
|
||||
Class,
|
||||
|
||||
/**
|
||||
* A binding to a style rule (e.g. `[style.rule]="expression"`).
|
||||
*/
|
||||
Style,
|
||||
|
||||
/**
|
||||
* A binding to an animation reference (e.g. `[animate.key]="expression"`).
|
||||
*/
|
||||
Animation
|
||||
}
|
||||
|
||||
export interface QueryMatch {
|
||||
queryId: number;
|
||||
value: CompileTokenMetadata;
|
||||
|
@ -10,7 +10,7 @@ import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, C
|
||||
import {CompileReflector} from '../compile_reflector';
|
||||
import {CompilerConfig} from '../config';
|
||||
import {SchemaMetadata} from '../core';
|
||||
import {AST, ASTWithSource, EmptyExpr} from '../expression_parser/ast';
|
||||
import {AST, ASTWithSource, EmptyExpr, ParsedEvent, ParsedProperty, ParsedVariable} from '../expression_parser/ast';
|
||||
import {Parser} from '../expression_parser/parser';
|
||||
import {Identifiers, createTokenForExternalReference, createTokenForReference} from '../identifiers';
|
||||
import * as html from '../ml_parser/ast';
|
||||
@ -26,8 +26,8 @@ import {CssSelector, SelectorMatcher} from '../selector';
|
||||
import {isStyleUrlResolvable} from '../style_url_resolver';
|
||||
import {Console, syntaxError} from '../util';
|
||||
|
||||
import {BindingParser, BoundProperty} from './binding_parser';
|
||||
import {AttrAst, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, DirectiveAst, ElementAst, EmbeddedTemplateAst, NgContentAst, PropertyBindingType, ReferenceAst, TemplateAst, TemplateAstVisitor, TextAst, VariableAst, templateVisitAll} from './template_ast';
|
||||
import {BindingParser} from './binding_parser';
|
||||
import * as t from './template_ast';
|
||||
import {PreparsedElementType, preparseElement} from './template_preparser';
|
||||
|
||||
const BIND_NAME_REGEXP =
|
||||
@ -79,7 +79,7 @@ export class TemplateParseError extends ParseError {
|
||||
|
||||
export class TemplateParseResult {
|
||||
constructor(
|
||||
public templateAst?: TemplateAst[], public usedPipes?: CompilePipeSummary[],
|
||||
public templateAst?: t.TemplateAst[], public usedPipes?: CompilePipeSummary[],
|
||||
public errors?: ParseError[]) {}
|
||||
}
|
||||
|
||||
@ -88,7 +88,7 @@ export class TemplateParser {
|
||||
private _config: CompilerConfig, private _reflector: CompileReflector,
|
||||
private _exprParser: Parser, private _schemaRegistry: ElementSchemaRegistry,
|
||||
private _htmlParser: HtmlParser, private _console: Console,
|
||||
public transforms: TemplateAstVisitor[]) {}
|
||||
public transforms: t.TemplateAstVisitor[]) {}
|
||||
|
||||
public get expressionParser() { return this._exprParser; }
|
||||
|
||||
@ -96,7 +96,7 @@ export class TemplateParser {
|
||||
component: CompileDirectiveMetadata, template: string|ParseTreeResult,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
|
||||
templateUrl: string,
|
||||
preserveWhitespaces: boolean): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||
preserveWhitespaces: boolean): {template: t.TemplateAst[], pipes: CompilePipeSummary[]} {
|
||||
const result = this.tryParse(
|
||||
component, template, directives, pipes, schemas, templateUrl, preserveWhitespaces);
|
||||
const warnings = result.errors !.filter(error => error.level === ParseErrorLevel.WARNING);
|
||||
@ -136,7 +136,7 @@ export class TemplateParser {
|
||||
htmlAstWithErrors: ParseTreeResult, component: CompileDirectiveMetadata,
|
||||
directives: CompileDirectiveSummary[], pipes: CompilePipeSummary[],
|
||||
schemas: SchemaMetadata[]): TemplateParseResult {
|
||||
let result: TemplateAst[];
|
||||
let result: t.TemplateAst[];
|
||||
const errors = htmlAstWithErrors.errors;
|
||||
const usedPipes: CompilePipeSummary[] = [];
|
||||
if (htmlAstWithErrors.rootNodes.length > 0) {
|
||||
@ -169,7 +169,7 @@ export class TemplateParser {
|
||||
|
||||
if (this.transforms) {
|
||||
this.transforms.forEach(
|
||||
(transform: TemplateAstVisitor) => { result = templateVisitAll(transform, result); });
|
||||
(transform: t.TemplateAstVisitor) => { result = t.templateVisitAll(transform, result); });
|
||||
}
|
||||
|
||||
return new TemplateParseResult(result, usedPipes, errors);
|
||||
@ -195,12 +195,12 @@ export class TemplateParser {
|
||||
}
|
||||
|
||||
/** @internal */
|
||||
_assertNoReferenceDuplicationOnTemplate(result: TemplateAst[], errors: TemplateParseError[]):
|
||||
_assertNoReferenceDuplicationOnTemplate(result: t.TemplateAst[], errors: TemplateParseError[]):
|
||||
void {
|
||||
const existingReferences: string[] = [];
|
||||
|
||||
result.filter(element => !!(<any>element).references)
|
||||
.forEach(element => (<any>element).references.forEach((reference: ReferenceAst) => {
|
||||
.forEach(element => (<any>element).references.forEach((reference: t.ReferenceAst) => {
|
||||
const name = reference.name;
|
||||
if (existingReferences.indexOf(name) < 0) {
|
||||
existingReferences.push(name);
|
||||
@ -242,12 +242,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !;
|
||||
const valueNoNgsp = replaceNgsp(text.value);
|
||||
const expr = this._bindingParser.parseInterpolation(valueNoNgsp, text.sourceSpan !);
|
||||
return expr ? new BoundTextAst(expr, ngContentIndex, text.sourceSpan !) :
|
||||
new TextAst(valueNoNgsp, ngContentIndex, text.sourceSpan !);
|
||||
return expr ? new t.BoundTextAst(expr, ngContentIndex, text.sourceSpan !) :
|
||||
new t.TextAst(valueNoNgsp, ngContentIndex, text.sourceSpan !);
|
||||
}
|
||||
|
||||
visitAttribute(attribute: html.Attribute, context: any): any {
|
||||
return new AttrAst(attribute.name, attribute.value, attribute.sourceSpan);
|
||||
return new t.AttrAst(attribute.name, attribute.value, attribute.sourceSpan);
|
||||
}
|
||||
|
||||
visitComment(comment: html.Comment, context: any): any { return null; }
|
||||
@ -271,34 +271,36 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
const matchableAttrs: [string, string][] = [];
|
||||
const elementOrDirectiveProps: BoundProperty[] = [];
|
||||
const elementOrDirectiveProps: ParsedProperty[] = [];
|
||||
const elementOrDirectiveRefs: ElementOrDirectiveRef[] = [];
|
||||
const elementVars: VariableAst[] = [];
|
||||
const events: BoundEventAst[] = [];
|
||||
const elementVars: t.VariableAst[] = [];
|
||||
const events: t.BoundEventAst[] = [];
|
||||
|
||||
const templateElementOrDirectiveProps: BoundProperty[] = [];
|
||||
const templateElementOrDirectiveProps: ParsedProperty[] = [];
|
||||
const templateMatchableAttrs: [string, string][] = [];
|
||||
const templateElementVars: VariableAst[] = [];
|
||||
const templateElementVars: t.VariableAst[] = [];
|
||||
|
||||
let hasInlineTemplates = false;
|
||||
const attrs: AttrAst[] = [];
|
||||
const attrs: t.AttrAst[] = [];
|
||||
const isTemplateElement = isNgTemplate(element.name);
|
||||
|
||||
element.attrs.forEach(attr => {
|
||||
const parsedVariables: ParsedVariable[] = [];
|
||||
const hasBinding = this._parseAttr(
|
||||
isTemplateElement, attr, matchableAttrs, elementOrDirectiveProps, events,
|
||||
elementOrDirectiveRefs, elementVars);
|
||||
elementVars.push(...parsedVariables.map(v => t.VariableAst.fromParsedVariable(v)));
|
||||
|
||||
let templateBindingsSource: string|undefined;
|
||||
let prefixToken: string|undefined;
|
||||
let templateValue: string|undefined;
|
||||
let templateKey: string|undefined;
|
||||
const normalizedName = this._normalizeAttributeName(attr.name);
|
||||
|
||||
if (normalizedName.startsWith(TEMPLATE_ATTR_PREFIX)) {
|
||||
templateBindingsSource = attr.value;
|
||||
prefixToken = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length) + ':';
|
||||
templateValue = attr.value;
|
||||
templateKey = normalizedName.substring(TEMPLATE_ATTR_PREFIX.length);
|
||||
}
|
||||
|
||||
const hasTemplateBinding = templateBindingsSource != null;
|
||||
const hasTemplateBinding = templateValue != null;
|
||||
if (hasTemplateBinding) {
|
||||
if (hasInlineTemplates) {
|
||||
this._reportError(
|
||||
@ -306,9 +308,11 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
attr.sourceSpan);
|
||||
}
|
||||
hasInlineTemplates = true;
|
||||
const parsedVariables: ParsedVariable[] = [];
|
||||
this._bindingParser.parseInlineTemplateBinding(
|
||||
prefixToken !, templateBindingsSource !, attr.sourceSpan, templateMatchableAttrs,
|
||||
templateElementOrDirectiveProps, templateElementVars);
|
||||
templateKey !, templateValue !, attr.sourceSpan, templateMatchableAttrs,
|
||||
templateElementOrDirectiveProps, parsedVariables);
|
||||
templateElementVars.push(...parsedVariables.map(v => t.VariableAst.fromParsedVariable(v)));
|
||||
}
|
||||
|
||||
if (!hasBinding && !hasTemplateBinding) {
|
||||
@ -321,12 +325,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
const elementCssSelector = createElementCssSelector(elName, matchableAttrs);
|
||||
const {directives: directiveMetas, matchElement} =
|
||||
this._parseDirectives(this.selectorMatcher, elementCssSelector);
|
||||
const references: ReferenceAst[] = [];
|
||||
const references: t.ReferenceAst[] = [];
|
||||
const boundDirectivePropNames = new Set<string>();
|
||||
const directiveAsts = this._createDirectiveAsts(
|
||||
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
|
||||
elementOrDirectiveRefs, element.sourceSpan !, references, boundDirectivePropNames);
|
||||
const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||
const elementProps: t.BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||
element.name, elementOrDirectiveProps, boundDirectivePropNames);
|
||||
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
|
||||
|
||||
@ -334,7 +338,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
this.providerViewContext, parent.providerContext !, isViewRoot, directiveAsts, attrs,
|
||||
references, isTemplateElement, queryStartIndex, element.sourceSpan !);
|
||||
|
||||
const children: TemplateAst[] = html.visitAll(
|
||||
const children: t.TemplateAst[] = html.visitAll(
|
||||
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
|
||||
ElementContext.create(
|
||||
isTemplateElement, directiveAsts,
|
||||
@ -345,7 +349,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
CssSelector.parse(preparsedElement.projectAs)[0] :
|
||||
elementCssSelector;
|
||||
const ngContentIndex = parent.findNgContentIndex(projectionSelector) !;
|
||||
let parsedElement: TemplateAst;
|
||||
let parsedElement: t.TemplateAst;
|
||||
|
||||
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
|
||||
// `<ng-content>` element
|
||||
@ -353,7 +357,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
this._reportError(`<ng-content> element cannot have content.`, element.sourceSpan !);
|
||||
}
|
||||
|
||||
parsedElement = new NgContentAst(
|
||||
parsedElement = new t.NgContentAst(
|
||||
this.ngContentCount++, hasInlineTemplates ? null ! : ngContentIndex,
|
||||
element.sourceSpan !);
|
||||
} else if (isTemplateElement) {
|
||||
@ -362,7 +366,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(
|
||||
directiveAsts, elementProps, element.sourceSpan !);
|
||||
|
||||
parsedElement = new EmbeddedTemplateAst(
|
||||
parsedElement = new t.EmbeddedTemplateAst(
|
||||
attrs, events, references, elementVars, providerContext.transformedDirectiveAsts,
|
||||
providerContext.transformProviders, providerContext.transformedHasViewContainer,
|
||||
providerContext.queryMatches, children, hasInlineTemplates ? null ! : ngContentIndex,
|
||||
@ -374,7 +378,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
const ngContentIndex =
|
||||
hasInlineTemplates ? null : parent.findNgContentIndex(projectionSelector);
|
||||
parsedElement = new ElementAst(
|
||||
parsedElement = new t.ElementAst(
|
||||
elName, attrs, elementProps, events, references, providerContext.transformedDirectiveAsts,
|
||||
providerContext.transformProviders, providerContext.transformedHasViewContainer,
|
||||
providerContext.queryMatches, children, hasInlineTemplates ? null : ngContentIndex,
|
||||
@ -390,7 +394,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
const templateDirectiveAsts = this._createDirectiveAsts(
|
||||
true, elName, directives, templateElementOrDirectiveProps, [], element.sourceSpan !, [],
|
||||
templateBoundDirectivePropNames);
|
||||
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||
const templateElementProps: t.BoundElementPropertyAst[] = this._createElementPropertyAsts(
|
||||
elName, templateElementOrDirectiveProps, templateBoundDirectivePropNames);
|
||||
this._assertNoComponentsNorElementBindingsOnTemplate(
|
||||
templateDirectiveAsts, templateElementProps, element.sourceSpan !);
|
||||
@ -399,7 +403,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan !);
|
||||
templateProviderContext.afterElement();
|
||||
|
||||
parsedElement = new EmbeddedTemplateAst(
|
||||
parsedElement = new t.EmbeddedTemplateAst(
|
||||
[], [], [], templateElementVars, templateProviderContext.transformedDirectiveAsts,
|
||||
templateProviderContext.transformProviders,
|
||||
templateProviderContext.transformedHasViewContainer, templateProviderContext.queryMatches,
|
||||
@ -411,15 +415,15 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
private _parseAttr(
|
||||
isTemplateElement: boolean, attr: html.Attribute, targetMatchableAttrs: string[][],
|
||||
targetProps: BoundProperty[], targetEvents: BoundEventAst[],
|
||||
targetRefs: ElementOrDirectiveRef[], targetVars: VariableAst[]): boolean {
|
||||
targetProps: ParsedProperty[], targetEvents: t.BoundEventAst[],
|
||||
targetRefs: ElementOrDirectiveRef[], targetVars: t.VariableAst[]): boolean {
|
||||
const name = this._normalizeAttributeName(attr.name);
|
||||
const value = attr.value;
|
||||
const srcSpan = attr.sourceSpan;
|
||||
|
||||
const boundEvents: ParsedEvent[] = [];
|
||||
const bindParts = name.match(BIND_NAME_REGEXP);
|
||||
let hasBinding = false;
|
||||
const boundEvents: BoundEventAst[] = [];
|
||||
|
||||
if (bindParts !== null) {
|
||||
hasBinding = true;
|
||||
@ -441,13 +445,13 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
} else if (bindParts[KW_ON_IDX]) {
|
||||
this._bindingParser.parseEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
|
||||
|
||||
} else if (bindParts[KW_BINDON_IDX]) {
|
||||
this._bindingParser.parsePropertyBinding(
|
||||
bindParts[IDENT_KW_IDX], value, false, srcSpan, targetMatchableAttrs, targetProps);
|
||||
this._parseAssignmentEvent(
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
bindParts[IDENT_KW_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
|
||||
|
||||
} else if (bindParts[KW_AT_IDX]) {
|
||||
this._bindingParser.parseLiteralAttr(
|
||||
@ -458,7 +462,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, false, srcSpan, targetMatchableAttrs,
|
||||
targetProps);
|
||||
this._parseAssignmentEvent(
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
bindParts[IDENT_BANANA_BOX_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
|
||||
|
||||
} else if (bindParts[IDENT_PROPERTY_IDX]) {
|
||||
this._bindingParser.parsePropertyBinding(
|
||||
@ -467,7 +471,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
} else if (bindParts[IDENT_EVENT_IDX]) {
|
||||
this._bindingParser.parseEvent(
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, targetEvents);
|
||||
bindParts[IDENT_EVENT_IDX], value, srcSpan, targetMatchableAttrs, boundEvents);
|
||||
}
|
||||
} else {
|
||||
hasBinding = this._bindingParser.parsePropertyInterpolation(
|
||||
@ -478,6 +482,8 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
this._bindingParser.parseLiteralAttr(name, value, srcSpan, targetMatchableAttrs, targetProps);
|
||||
}
|
||||
|
||||
targetEvents.push(...boundEvents.map(e => t.BoundEventAst.fromParsedEvent(e)));
|
||||
|
||||
return hasBinding;
|
||||
}
|
||||
|
||||
@ -486,12 +492,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
private _parseVariable(
|
||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, targetVars: VariableAst[]) {
|
||||
identifier: string, value: string, sourceSpan: ParseSourceSpan, targetVars: t.VariableAst[]) {
|
||||
if (identifier.indexOf('-') > -1) {
|
||||
this._reportError(`"-" is not allowed in variable names`, sourceSpan);
|
||||
}
|
||||
|
||||
targetVars.push(new VariableAst(identifier, value, sourceSpan));
|
||||
targetVars.push(new t.VariableAst(identifier, value, sourceSpan));
|
||||
}
|
||||
|
||||
private _parseReference(
|
||||
@ -506,7 +512,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
private _parseAssignmentEvent(
|
||||
name: string, expression: string, sourceSpan: ParseSourceSpan,
|
||||
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
|
||||
targetMatchableAttrs: string[][], targetEvents: ParsedEvent[]) {
|
||||
this._bindingParser.parseEvent(
|
||||
`${name}Change`, `${expression}=$event`, sourceSpan, targetMatchableAttrs, targetEvents);
|
||||
}
|
||||
@ -533,9 +539,9 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
|
||||
private _createDirectiveAsts(
|
||||
isTemplateElement: boolean, elementName: string, directives: CompileDirectiveSummary[],
|
||||
props: BoundProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
|
||||
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[],
|
||||
targetBoundDirectivePropNames: Set<string>): DirectiveAst[] {
|
||||
props: ParsedProperty[], elementOrDirectiveRefs: ElementOrDirectiveRef[],
|
||||
elementSourceSpan: ParseSourceSpan, targetReferences: t.ReferenceAst[],
|
||||
targetBoundDirectivePropNames: Set<string>): t.DirectiveAst[] {
|
||||
const matchedReferences = new Set<string>();
|
||||
let component: CompileDirectiveSummary = null !;
|
||||
|
||||
@ -547,27 +553,32 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
if (directive.isComponent) {
|
||||
component = directive;
|
||||
}
|
||||
const directiveProperties: BoundDirectivePropertyAst[] = [];
|
||||
let hostProperties =
|
||||
const directiveProperties: t.BoundDirectivePropertyAst[] = [];
|
||||
const boundProperties =
|
||||
this._bindingParser.createDirectiveHostPropertyAsts(directive, elementName, sourceSpan) !;
|
||||
|
||||
let hostProperties =
|
||||
boundProperties.map(prop => t.BoundElementPropertyAst.fromBoundProperty(prop));
|
||||
// Note: We need to check the host properties here as well,
|
||||
// as we don't know the element name in the DirectiveWrapperCompiler yet.
|
||||
hostProperties = this._checkPropertiesInSchema(elementName, hostProperties);
|
||||
const hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan) !;
|
||||
const parsedEvents =
|
||||
this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan) !;
|
||||
this._createDirectivePropertyAsts(
|
||||
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
|
||||
elementOrDirectiveRefs.forEach((elOrDirRef) => {
|
||||
if ((elOrDirRef.value.length === 0 && directive.isComponent) ||
|
||||
(elOrDirRef.isReferenceToDirective(directive))) {
|
||||
targetReferences.push(new ReferenceAst(
|
||||
targetReferences.push(new t.ReferenceAst(
|
||||
elOrDirRef.name, createTokenForReference(directive.type.reference), elOrDirRef.value,
|
||||
elOrDirRef.sourceSpan));
|
||||
matchedReferences.add(elOrDirRef.name);
|
||||
}
|
||||
});
|
||||
const hostEvents = parsedEvents.map(e => t.BoundEventAst.fromParsedEvent(e));
|
||||
const contentQueryStartId = this.contentQueryStartId;
|
||||
this.contentQueryStartId += directive.queries.length;
|
||||
return new DirectiveAst(
|
||||
return new t.DirectiveAst(
|
||||
directive, directiveProperties, hostProperties, hostEvents, contentQueryStartId,
|
||||
sourceSpan);
|
||||
});
|
||||
@ -585,18 +596,18 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
refToken = createTokenForExternalReference(this.reflector, Identifiers.TemplateRef);
|
||||
}
|
||||
targetReferences.push(
|
||||
new ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.value, elOrDirRef.sourceSpan));
|
||||
new t.ReferenceAst(elOrDirRef.name, refToken, elOrDirRef.value, elOrDirRef.sourceSpan));
|
||||
}
|
||||
});
|
||||
return directiveAsts;
|
||||
}
|
||||
|
||||
private _createDirectivePropertyAsts(
|
||||
directiveProperties: {[key: string]: string}, boundProps: BoundProperty[],
|
||||
targetBoundDirectiveProps: BoundDirectivePropertyAst[],
|
||||
directiveProperties: {[key: string]: string}, boundProps: ParsedProperty[],
|
||||
targetBoundDirectiveProps: t.BoundDirectivePropertyAst[],
|
||||
targetBoundDirectivePropNames: Set<string>) {
|
||||
if (directiveProperties) {
|
||||
const boundPropsByName = new Map<string, BoundProperty>();
|
||||
const boundPropsByName = new Map<string, ParsedProperty>();
|
||||
boundProps.forEach(boundProp => {
|
||||
const prevValue = boundPropsByName.get(boundProp.name);
|
||||
if (!prevValue || prevValue.isLiteral) {
|
||||
@ -613,7 +624,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
if (boundProp) {
|
||||
targetBoundDirectivePropNames.add(boundProp.name);
|
||||
if (!isEmptyExpression(boundProp.expression)) {
|
||||
targetBoundDirectiveProps.push(new BoundDirectivePropertyAst(
|
||||
targetBoundDirectiveProps.push(new t.BoundDirectivePropertyAst(
|
||||
dirProp, boundProp.name, boundProp.expression, boundProp.sourceSpan));
|
||||
}
|
||||
}
|
||||
@ -622,28 +633,29 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
private _createElementPropertyAsts(
|
||||
elementName: string, props: BoundProperty[],
|
||||
boundDirectivePropNames: Set<string>): BoundElementPropertyAst[] {
|
||||
const boundElementProps: BoundElementPropertyAst[] = [];
|
||||
elementName: string, props: ParsedProperty[],
|
||||
boundDirectivePropNames: Set<string>): t.BoundElementPropertyAst[] {
|
||||
const boundElementProps: t.BoundElementPropertyAst[] = [];
|
||||
|
||||
props.forEach((prop: BoundProperty) => {
|
||||
props.forEach((prop: ParsedProperty) => {
|
||||
if (!prop.isLiteral && !boundDirectivePropNames.has(prop.name)) {
|
||||
boundElementProps.push(this._bindingParser.createElementPropertyAst(elementName, prop));
|
||||
const boundProp = this._bindingParser.createBoundElementProperty(elementName, prop);
|
||||
boundElementProps.push(t.BoundElementPropertyAst.fromBoundProperty(boundProp));
|
||||
}
|
||||
});
|
||||
return this._checkPropertiesInSchema(elementName, boundElementProps);
|
||||
}
|
||||
|
||||
private _findComponentDirectives(directives: DirectiveAst[]): DirectiveAst[] {
|
||||
private _findComponentDirectives(directives: t.DirectiveAst[]): t.DirectiveAst[] {
|
||||
return directives.filter(directive => directive.directive.isComponent);
|
||||
}
|
||||
|
||||
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
|
||||
private _findComponentDirectiveNames(directives: t.DirectiveAst[]): string[] {
|
||||
return this._findComponentDirectives(directives)
|
||||
.map(directive => identifierName(directive.directive.type) !);
|
||||
}
|
||||
|
||||
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
|
||||
private _assertOnlyOneComponent(directives: t.DirectiveAst[], sourceSpan: ParseSourceSpan) {
|
||||
const componentTypeNames = this._findComponentDirectiveNames(directives);
|
||||
if (componentTypeNames.length > 1) {
|
||||
this._reportError(
|
||||
@ -682,7 +694,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
private _assertNoComponentsNorElementBindingsOnTemplate(
|
||||
directives: DirectiveAst[], elementProps: BoundElementPropertyAst[],
|
||||
directives: t.DirectiveAst[], elementProps: t.BoundElementPropertyAst[],
|
||||
sourceSpan: ParseSourceSpan) {
|
||||
const componentTypeNames: string[] = this._findComponentDirectiveNames(directives);
|
||||
if (componentTypeNames.length > 0) {
|
||||
@ -697,7 +709,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
private _assertAllEventsPublishedByDirectives(
|
||||
directives: DirectiveAst[], events: BoundEventAst[]) {
|
||||
directives: t.DirectiveAst[], events: t.BoundEventAst[]) {
|
||||
const allDirectiveEvents = new Set<string>();
|
||||
|
||||
directives.forEach(directive => {
|
||||
@ -716,12 +728,12 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
});
|
||||
}
|
||||
|
||||
private _checkPropertiesInSchema(elementName: string, boundProps: BoundElementPropertyAst[]):
|
||||
BoundElementPropertyAst[] {
|
||||
private _checkPropertiesInSchema(elementName: string, boundProps: t.BoundElementPropertyAst[]):
|
||||
t.BoundElementPropertyAst[] {
|
||||
// Note: We can't filter out empty expressions before this method,
|
||||
// as we still want to validate them!
|
||||
return boundProps.filter((boundProp) => {
|
||||
if (boundProp.type === PropertyBindingType.Property &&
|
||||
if (boundProp.type === t.PropertyBindingType.Property &&
|
||||
!this._schemaRegistry.hasProperty(elementName, boundProp.name, this._schemas)) {
|
||||
let errorMsg =
|
||||
`Can't bind to '${boundProp.name}' since it isn't a known property of '${elementName}'.`;
|
||||
@ -749,7 +761,7 @@ class TemplateParseVisitor implements html.Visitor {
|
||||
}
|
||||
|
||||
class NonBindableVisitor implements html.Visitor {
|
||||
visitElement(ast: html.Element, parent: ElementContext): ElementAst|null {
|
||||
visitElement(ast: html.Element, parent: ElementContext): t.ElementAst|null {
|
||||
const preparsedElement = preparseElement(ast);
|
||||
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
|
||||
preparsedElement.type === PreparsedElementType.STYLE ||
|
||||
@ -763,20 +775,20 @@ class NonBindableVisitor implements html.Visitor {
|
||||
const attrNameAndValues = ast.attrs.map((attr): [string, string] => [attr.name, attr.value]);
|
||||
const selector = createElementCssSelector(ast.name, attrNameAndValues);
|
||||
const ngContentIndex = parent.findNgContentIndex(selector);
|
||||
const children: TemplateAst[] = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
return new ElementAst(
|
||||
const children: t.TemplateAst[] = html.visitAll(this, ast.children, EMPTY_ELEMENT_CONTEXT);
|
||||
return new t.ElementAst(
|
||||
ast.name, html.visitAll(this, ast.attrs), [], [], [], [], [], false, [], children,
|
||||
ngContentIndex, ast.sourceSpan, ast.endSourceSpan);
|
||||
}
|
||||
visitComment(comment: html.Comment, context: any): any { return null; }
|
||||
|
||||
visitAttribute(attribute: html.Attribute, context: any): AttrAst {
|
||||
return new AttrAst(attribute.name, attribute.value, attribute.sourceSpan);
|
||||
visitAttribute(attribute: html.Attribute, context: any): t.AttrAst {
|
||||
return new t.AttrAst(attribute.name, attribute.value, attribute.sourceSpan);
|
||||
}
|
||||
|
||||
visitText(text: html.Text, parent: ElementContext): TextAst {
|
||||
visitText(text: html.Text, parent: ElementContext): t.TextAst {
|
||||
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !;
|
||||
return new TextAst(text.value, ngContentIndex, text.sourceSpan !);
|
||||
return new t.TextAst(text.value, ngContentIndex, text.sourceSpan !);
|
||||
}
|
||||
|
||||
visitExpansion(expansion: html.Expansion, context: any): any { return expansion; }
|
||||
@ -811,7 +823,7 @@ export function splitClasses(classAttrValue: string): string[] {
|
||||
|
||||
class ElementContext {
|
||||
static create(
|
||||
isTemplateElement: boolean, directives: DirectiveAst[],
|
||||
isTemplateElement: boolean, directives: t.DirectiveAst[],
|
||||
providerContext: ProviderElementContext): ElementContext {
|
||||
const matcher = new SelectorMatcher();
|
||||
let wildcardNgContentIndex: number = null !;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -278,6 +278,9 @@
|
||||
{
|
||||
"name": "BoundDirectivePropertyAst"
|
||||
},
|
||||
{
|
||||
"name": "BoundElementProperty"
|
||||
},
|
||||
{
|
||||
"name": "BoundElementPropertyAst"
|
||||
},
|
||||
@ -285,10 +288,7 @@
|
||||
"name": "BoundEventAst"
|
||||
},
|
||||
{
|
||||
"name": "BoundProperty"
|
||||
},
|
||||
{
|
||||
"name": "BoundPropertyType"
|
||||
"name": "BoundPropertyMapping"
|
||||
},
|
||||
{
|
||||
"name": "BoundTextAst"
|
||||
@ -1235,6 +1235,18 @@
|
||||
{
|
||||
"name": "ParseTreeResult"
|
||||
},
|
||||
{
|
||||
"name": "ParsedEvent"
|
||||
},
|
||||
{
|
||||
"name": "ParsedProperty"
|
||||
},
|
||||
{
|
||||
"name": "ParsedPropertyType"
|
||||
},
|
||||
{
|
||||
"name": "ParsedVariable"
|
||||
},
|
||||
{
|
||||
"name": "Parser"
|
||||
},
|
||||
@ -2195,9 +2207,6 @@
|
||||
{
|
||||
"name": "_global"
|
||||
},
|
||||
{
|
||||
"name": "_isAnimationLabel"
|
||||
},
|
||||
{
|
||||
"name": "_isClosingComment"
|
||||
},
|
||||
@ -3221,6 +3230,9 @@
|
||||
{
|
||||
"name": "invalid"
|
||||
},
|
||||
{
|
||||
"name": "isAnimationLabel"
|
||||
},
|
||||
{
|
||||
"name": "isArray"
|
||||
},
|
||||
|
@ -30,16 +30,19 @@ function createNode(
|
||||
return new TreeNode<ActivatedRoute>(value, children);
|
||||
|
||||
// retrieve an activated route that is used to be displayed, but is not currently displayed
|
||||
} else if (routeReuseStrategy.retrieve(curr.value)) {
|
||||
const tree: TreeNode<ActivatedRoute> =
|
||||
(<DetachedRouteHandleInternal>routeReuseStrategy.retrieve(curr.value)).route;
|
||||
setFutureSnapshotsOfActivatedRoutes(curr, tree);
|
||||
return tree;
|
||||
|
||||
} else {
|
||||
const value = createActivatedRoute(curr.value);
|
||||
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
||||
return new TreeNode<ActivatedRoute>(value, children);
|
||||
const detachedRouteHandle =
|
||||
<DetachedRouteHandleInternal>routeReuseStrategy.retrieve(curr.value);
|
||||
if (detachedRouteHandle) {
|
||||
const tree: TreeNode<ActivatedRoute> = detachedRouteHandle.route;
|
||||
setFutureSnapshotsOfActivatedRoutes(curr, tree);
|
||||
return tree;
|
||||
|
||||
} else {
|
||||
const value = createActivatedRoute(curr.value);
|
||||
const children = curr.children.map(c => createNode(routeReuseStrategy, c));
|
||||
return new TreeNode<ActivatedRoute>(value, children);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -88,6 +88,27 @@ describe('create router state', () => {
|
||||
checkActivatedRoute(currC[0], ComponentA);
|
||||
checkActivatedRoute(currC[1], ComponentB, 'right');
|
||||
});
|
||||
|
||||
it('should cache the retrieved routeReuseStrategy', () => {
|
||||
const config = [
|
||||
{path: 'a', component: ComponentA}, {path: 'b', component: ComponentB, outlet: 'left'},
|
||||
{path: 'c', component: ComponentC, outlet: 'left'}
|
||||
];
|
||||
spyOn(reuseStrategy, 'retrieve').and.callThrough();
|
||||
|
||||
const prevState =
|
||||
createRouterState(reuseStrategy, createState(config, 'a(left:b)'), emptyState());
|
||||
advanceState(prevState);
|
||||
|
||||
// Expect 2 calls as the baseline setup
|
||||
expect(reuseStrategy.retrieve).toHaveBeenCalledTimes(2);
|
||||
|
||||
// This call should produce a reused activated route
|
||||
const state = createRouterState(reuseStrategy, createState(config, 'a(left:c)'), prevState);
|
||||
|
||||
// Verify the retrieve method has been called one more time
|
||||
expect(reuseStrategy.retrieve).toHaveBeenCalledTimes(3);
|
||||
});
|
||||
});
|
||||
|
||||
function advanceState(state: RouterState): void {
|
||||
|
41
scripts/build-packages-dist.sh
Executable file
41
scripts/build-packages-dist.sh
Executable file
@ -0,0 +1,41 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build the dist/packages-dist directory in the same fashion as the legacy
|
||||
# /build.sh script, by building the npm packages with Bazel and copying files.
|
||||
# This is needed for scripts and tests which are not updated to the Bazel output
|
||||
# layout (which always matches the input layout).
|
||||
# Do not add new dependencies on this script, instead adapt scripts to use the
|
||||
# new layout, and write new tests as Bazel targets.
|
||||
|
||||
set -u -e -o pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
# basedir is the workspace root
|
||||
readonly basedir=$(pwd)/..
|
||||
|
||||
echo "##################################"
|
||||
echo "scripts/build-packages-dist.sh:"
|
||||
echo " building @angular/* npm packages"
|
||||
echo "##################################"
|
||||
# Ideally these integration tests should run under bazel, and just list the npm
|
||||
# packages in their deps[].
|
||||
# Until then, we have to manually run bazel first to create the npm packages we
|
||||
# want to test.
|
||||
bazel query --output=label 'kind(.*_package, //packages/...)' \
|
||||
| xargs bazel build
|
||||
readonly bin=$(bazel info bazel-bin)
|
||||
|
||||
# Create the legacy dist/packages-dist folder
|
||||
[ -d "${basedir}/dist/packages-dist" ] || mkdir -p $basedir/dist/packages-dist
|
||||
# Each package is a subdirectory of bazel-bin/packages/
|
||||
for pkg in $(ls ${bin}/packages); do
|
||||
# Skip any that don't have an "npm_package" target
|
||||
srcDir="${bin}/packages/${pkg}/npm_package"
|
||||
destDir="${basedir}/dist/packages-dist/${pkg}"
|
||||
if [ -d $srcDir ]; then
|
||||
echo "# Copy artifacts to ${destDir}"
|
||||
rm -rf $destDir
|
||||
cp -R $srcDir $destDir
|
||||
chmod -R u+w $destDir
|
||||
fi
|
||||
done
|
@ -15,12 +15,7 @@ if [[ ${TRAVIS_TEST_RESULT=0} == 1 ]]; then
|
||||
fi
|
||||
|
||||
|
||||
# Don't deploy if not running against angular/angular
|
||||
# TODO(i): because we don't let deploy to run outside of angular/angular folks can't use their
|
||||
# private travis build to deploy anywhere. This is likely ok, but this means that @alexeagle's
|
||||
# fancy setup to publish ES2015 packages to github -build repos no longer works. This is ok
|
||||
# since with flat modules we'll have this feature built-in. We should still go and remove
|
||||
# stuff that Alex put in for this from publish-build-artifacts.sh
|
||||
# Don't deploy Angular.io if we are running in a fork
|
||||
if [[ ${TRAVIS_REPO_SLUG} != "angular/angular" ]]; then
|
||||
echo "Skipping deploy because this is not angular/angular."
|
||||
exit 0
|
||||
@ -28,19 +23,6 @@ fi
|
||||
|
||||
|
||||
case ${CI_MODE} in
|
||||
|
||||
e2e)
|
||||
# Don't deploy if this is a PR build
|
||||
if [[ ${TRAVIS_PULL_REQUEST} != "false" ]]; then
|
||||
echo "Skipping deploy because this is a PR build."
|
||||
exit 0
|
||||
fi
|
||||
|
||||
travisFoldStart "deploy.packages"
|
||||
${thisDir}/publish-build-artifacts.sh
|
||||
travisFoldEnd "deploy.packages"
|
||||
;;
|
||||
|
||||
aio)
|
||||
travisFoldStart "deploy.aio"
|
||||
(
|
||||
|
@ -4,8 +4,6 @@ set -x -u -e -o pipefail
|
||||
|
||||
# Setup environment
|
||||
readonly thisDir=$(cd $(dirname $0); pwd)
|
||||
source ${thisDir}/_travis-fold.sh
|
||||
|
||||
|
||||
# Find the most recent tag that is reachable from the current commit.
|
||||
# This is shallow clone of the repo, so we might need to fetch more commits to
|
||||
@ -63,22 +61,12 @@ function publishRepo {
|
||||
rm -rf $REPO_DIR/*
|
||||
cp -R $ARTIFACTS_DIR/* $REPO_DIR/
|
||||
|
||||
# Replace $$ANGULAR_VERSION$$ with the build version.
|
||||
BUILD_VER="${LATEST_TAG}+${SHORT_SHA}"
|
||||
if [[ ${TRAVIS} ]]; then
|
||||
find $REPO_DIR/ -type f -name package.json -print0 | xargs -0 sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g"
|
||||
|
||||
# Find umd.js and umd.min.js
|
||||
UMD_FILES=$(find $REPO_DIR/ -type f -name "*.umd*.js" -print)
|
||||
for UMD_FILE in ${UMD_FILES}; do
|
||||
sed -i "s/\\\$\\\$ANGULAR_VERSION\\\$\\\$/${BUILD_VER}/g" ${UMD_FILE}
|
||||
done
|
||||
|
||||
if [[ ${CI} ]]; then
|
||||
(
|
||||
# The file ~/.git_credentials is created below
|
||||
cd $REPO_DIR && \
|
||||
git config credential.helper "store --file=.git/credentials" && \
|
||||
# SECURITY CRITICAL: DO NOT use shell to expand vars since it could be logged and leaked.
|
||||
node -e "console.log('https://'+process.env.GITHUB_TOKEN_ANGULAR+':@github.com')" > .git/credentials
|
||||
git config credential.helper "store --file=$HOME/.git_credentials"
|
||||
)
|
||||
fi
|
||||
echo `date` > $REPO_DIR/BUILD_INFO
|
||||
@ -130,23 +118,22 @@ function publishPackages {
|
||||
}
|
||||
|
||||
# See docs/DEVELOPER.md for help
|
||||
CUR_BRANCH=${TRAVIS_BRANCH:-$(git symbolic-ref --short HEAD)}
|
||||
CUR_BRANCH=${CIRCLE_BRANCH:-$(git symbolic-ref --short HEAD)}
|
||||
if [ $# -gt 0 ]; then
|
||||
ORG=$1
|
||||
publishPackages "ssh" dist/packages-dist $CUR_BRANCH
|
||||
if [[ -e dist/packages-dist-es2015 ]]; then
|
||||
publishPackages "ssh" dist/packages-dist-es2015 ${CUR_BRANCH}-es2015
|
||||
fi
|
||||
|
||||
elif [[ \
|
||||
"$TRAVIS_REPO_SLUG" == "angular/angular" && \
|
||||
"$TRAVIS_PULL_REQUEST" == "false" && \
|
||||
"$CI_MODE" == "e2e" ]]; then
|
||||
"$CIRCLE_PROJECT_USERNAME" == "angular" && \
|
||||
"$CIRCLE_PROJECT_REPONAME" == "angular" && \
|
||||
! -v CIRCLE_PR_NUMBER ]]; then
|
||||
ORG="angular"
|
||||
# $KEY is set on CI only for non-PR builds. See /.circleci/README.md
|
||||
openssl aes-256-cbc -d -in .circleci/github_token -k "${KEY}" -out "${HOME}/.git_credentials"
|
||||
|
||||
publishPackages "http" dist/packages-dist $CUR_BRANCH
|
||||
if [[ -e dist/packages-dist-es2015 ]]; then
|
||||
publishPackages "http" dist/packages-dist-es2015 ${CUR_BRANCH}-es2015
|
||||
fi
|
||||
# Clean up the credentials file out of caution
|
||||
rm "${HOME}/.git_credentials"
|
||||
|
||||
else
|
||||
echo "Not building the upstream/${CUR_BRANCH} branch, build artifacts won't be published."
|
||||
|
@ -22,18 +22,21 @@ const configPath = path.resolve(__dirname, './commit-message.json');
|
||||
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||
const PATTERN = /^(\w+)(?:\(([^)]+)\))?\: (.+)$/;
|
||||
const FIXUP_SQUASH = /^(fixup|squash)\! /i;
|
||||
const REVERT = /^revert:? (\"(.*)\"|(.*))?$/i;
|
||||
const REVERT = /^revert:? /i;
|
||||
|
||||
module.exports = function(commitSubject) {
|
||||
commitSubject = commitSubject.replace(FIXUP_SQUASH, '');
|
||||
commitSubject = commitSubject.replace(REVERT, function(m, g1, g2, g3) { return g2 || g3; });
|
||||
const subject = commitSubject.replace(FIXUP_SQUASH, '');
|
||||
|
||||
if (commitSubject.length > config['maxLength']) {
|
||||
if (subject.match(REVERT)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (subject.length > config['maxLength']) {
|
||||
error(`The commit message is longer than ${config['maxLength']} characters`, commitSubject);
|
||||
return false;
|
||||
}
|
||||
|
||||
const match = PATTERN.exec(commitSubject);
|
||||
const match = PATTERN.exec(subject);
|
||||
if (!match) {
|
||||
error(
|
||||
`The commit message does not match the format of '<type>(<scope>): <subject>' OR 'Revert: "type(<scope>): <subject>"'`,
|
||||
|
@ -98,20 +98,26 @@ describe('validate-commit-message.js', function() {
|
||||
});
|
||||
|
||||
|
||||
it('should support "revert: type(scope):" syntax and reject "revert(scope):" syntax',
|
||||
function() {
|
||||
let correctMsg = 'revert: fix(compiler): reduce generated code payload size by 65%';
|
||||
expect(validateMessage(correctMsg)).toBe(VALID);
|
||||
it('should support "revert: type(scope):" syntax', function() {
|
||||
const correctMsg = 'revert: fix(compiler): reduce generated code payload size by 65%';
|
||||
expect(validateMessage(correctMsg)).toBe(VALID);
|
||||
});
|
||||
|
||||
let incorretMsg = 'revert(compiler): reduce generated code payload size by 65%';
|
||||
expect(validateMessage(incorretMsg)).toBe(INVALID);
|
||||
expect(errors).toEqual([
|
||||
'INVALID COMMIT MSG: "revert(compiler): reduce generated code payload size by 65%"\n' +
|
||||
it('should support reject "revert(scope):" syntax', function() {
|
||||
const incorretMsg = 'revert(compiler): reduce generated code payload size by 65%';
|
||||
expect(validateMessage(incorretMsg)).toBe(INVALID);
|
||||
expect(errors).toEqual(
|
||||
['INVALID COMMIT MSG: "revert(compiler): reduce generated code payload size by 65%"\n' +
|
||||
' => ERROR: revert is not an allowed type.\n' +
|
||||
' => TYPES: ' + TYPES
|
||||
]);
|
||||
});
|
||||
' => TYPES: ' + TYPES]);
|
||||
});
|
||||
|
||||
// https://github.com/angular/angular/issues/23479
|
||||
it('should support typical Angular messages generated by git', function() {
|
||||
const correctMsg =
|
||||
'Revert "fix(compiler): Pretty print object instead of [Object object] (#22689)" (#23442)';
|
||||
expect(validateMessage(correctMsg)).toBe(VALID);
|
||||
});
|
||||
|
||||
it('should validate type', function() {
|
||||
expect(validateMessage('weird($filter): something')).toBe(INVALID);
|
||||
|
Reference in New Issue
Block a user