fix(compiler): Update types for TypeScript nullability support

This commit is contained in:
Miško Hevery
2017-03-24 09:59:58 -07:00
committed by Hans
parent d8b73e4223
commit 09d9f5fe54
118 changed files with 2086 additions and 1859 deletions

View File

@ -64,7 +64,7 @@ export class BindingParser {
createDirectiveHostPropertyAsts(
dirMeta: CompileDirectiveSummary, elementSelector: string,
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[] {
sourceSpan: ParseSourceSpan): BoundElementPropertyAst[]|null {
if (dirMeta.hostProperties) {
const boundProps: BoundProperty[] = [];
Object.keys(dirMeta.hostProperties).forEach(propName => {
@ -79,10 +79,11 @@ export class BindingParser {
});
return boundProps.map((prop) => this.createElementPropertyAst(elementSelector, prop));
}
return null;
}
createDirectiveHostEventAsts(dirMeta: CompileDirectiveSummary, sourceSpan: ParseSourceSpan):
BoundEventAst[] {
BoundEventAst[]|null {
if (dirMeta.hostListeners) {
const targetEventAsts: BoundEventAst[] = [];
Object.keys(dirMeta.hostListeners).forEach(propName => {
@ -97,13 +98,15 @@ export class BindingParser {
});
return targetEventAsts;
}
return null;
}
parseInterpolation(value: string, sourceSpan: ParseSourceSpan): ASTWithSource {
const sourceInfo = sourceSpan.start.toString();
try {
const ast = this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig);
const ast =
this._exprParser.parseInterpolation(value, sourceInfo, this._interpolationConfig) !;
if (ast) this._reportExpressionParserErrors(ast.errors, sourceSpan);
this._checkPipes(ast, sourceSpan);
return ast;
@ -153,8 +156,8 @@ export class BindingParser {
}
parseLiteralAttr(
name: string, value: string, sourceSpan: ParseSourceSpan, targetMatchableAttrs: string[][],
targetProps: BoundProperty[]) {
name: string, value: string|null, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
if (_isAnimationLabel(name)) {
name = name.substring(1);
if (value) {
@ -206,18 +209,18 @@ export class BindingParser {
private _parsePropertyAst(
name: string, ast: ASTWithSource, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
targetMatchableAttrs.push([name, ast.source]);
targetMatchableAttrs.push([name, ast.source !]);
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.DEFAULT, sourceSpan));
}
private _parseAnimation(
name: string, expression: string, sourceSpan: ParseSourceSpan,
name: string, expression: string|null, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetProps: BoundProperty[]) {
// 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 || 'null', false, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetMatchableAttrs.push([name, ast.source !]);
targetProps.push(new BoundProperty(name, ast, BoundPropertyType.ANIMATION, sourceSpan));
}
@ -246,11 +249,11 @@ export class BindingParser {
null, boundProp.sourceSpan);
}
let unit: string = null;
let bindingType: PropertyBindingType;
let boundPropertyName: string = null;
let unit: string|null = null;
let bindingType: PropertyBindingType = undefined !;
let boundPropertyName: string|null = null;
const parts = boundProp.name.split(PROPERTY_PARTS_SEPARATOR);
let securityContexts: SecurityContext[];
let securityContexts: SecurityContext[] = undefined !;
// Check check for special cases (prefix style, attr, class)
if (parts.length > 1) {
@ -336,9 +339,9 @@ export class BindingParser {
name: string, expression: string, sourceSpan: ParseSourceSpan,
targetMatchableAttrs: string[][], targetEvents: BoundEventAst[]) {
// long format: 'target: eventName'
const [target, eventName] = splitAtColon(name, [null, name]);
const [target, eventName] = splitAtColon(name, [null !, name]);
const ast = this._parseAction(expression, sourceSpan);
targetMatchableAttrs.push([name, ast.source]);
targetMatchableAttrs.push([name !, ast.source !]);
targetEvents.push(new BoundEventAst(eventName, target, null, ast, sourceSpan));
// Don't detect directives for event names for now,
// so don't add the event name to the matchableAttrs
@ -405,7 +408,7 @@ export class BindingParser {
const report = isAttr ? this._schemaRegistry.validateAttribute(propName) :
this._schemaRegistry.validateProperty(propName);
if (report.error) {
this._reportError(report.msg, sourceSpan, ParseErrorLevel.ERROR);
this._reportError(report.msg !, sourceSpan, ParseErrorLevel.ERROR);
}
}
}

View File

@ -19,7 +19,7 @@ export interface TemplateAst {
/**
* The source span from which this node was parsed.
*/
sourceSpan: ParseSourceSpan;
sourceSpan: ParseSourceSpan|null;
/**
* Visit this node and possibly transform it.
@ -62,7 +62,7 @@ export class AttrAst implements TemplateAst {
export class BoundElementPropertyAst implements TemplateAst {
constructor(
public name: string, public type: PropertyBindingType,
public securityContext: SecurityContext, public value: AST, public unit: string,
public securityContext: SecurityContext, public value: AST, public unit: string|null,
public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElementProperty(this, context);
@ -75,7 +75,7 @@ export class BoundElementPropertyAst implements TemplateAst {
* `(@trigger.phase)="callback($event)"`).
*/
export class BoundEventAst implements TemplateAst {
static calcFullName(name: string, target: string, phase: string): string {
static calcFullName(name: string, target: string|null, phase: string|null): string {
if (target) {
return `${target}:${name}`;
} else if (phase) {
@ -86,8 +86,8 @@ export class BoundEventAst implements TemplateAst {
}
constructor(
public name: string, public target: string, public phase: string, public handler: AST,
public sourceSpan: ParseSourceSpan) {}
public name: string, public target: string|null, public phase: string|null,
public handler: AST, public sourceSpan: ParseSourceSpan) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitEvent(this, context);
}
@ -126,8 +126,8 @@ export class ElementAst implements TemplateAst {
public outputs: BoundEventAst[], public references: ReferenceAst[],
public directives: DirectiveAst[], public providers: ProviderAst[],
public hasViewContainer: boolean, public queryMatches: QueryMatch[],
public children: TemplateAst[], public ngContentIndex: number,
public sourceSpan: ParseSourceSpan, public endSourceSpan: ParseSourceSpan) {}
public children: TemplateAst[], public ngContentIndex: number|null,
public sourceSpan: ParseSourceSpan|null, public endSourceSpan: ParseSourceSpan|null) {}
visit(visitor: TemplateAstVisitor, context: any): any {
return visitor.visitElement(this, context);
@ -275,7 +275,7 @@ export function templateVisitAll(
visitor: TemplateAstVisitor, asts: TemplateAst[], context: any = null): any[] {
const result: any[] = [];
const visit = visitor.visit ?
(ast: TemplateAst) => visitor.visit(ast, context) || ast.visit(visitor, context) :
(ast: TemplateAst) => visitor.visit !(ast, context) || ast.visit(visitor, context) :
(ast: TemplateAst) => ast.visit(visitor, context);
asts.forEach(ast => {
const astResult = visit(ast);

View File

@ -115,11 +115,11 @@ export class TemplateParser {
templateUrl: string): {template: TemplateAst[], pipes: CompilePipeSummary[]} {
const result = this.tryParse(component, template, directives, pipes, schemas, templateUrl);
const warnings =
result.errors.filter(error => error.level === ParseErrorLevel.WARNING).filter(warnOnlyOnce([
TEMPLATE_ATTR_DEPRECATION_WARNING, TEMPLATE_ELEMENT_DEPRECATION_WARNING
]));
result.errors !.filter(error => error.level === ParseErrorLevel.WARNING)
.filter(warnOnlyOnce(
[TEMPLATE_ATTR_DEPRECATION_WARNING, TEMPLATE_ELEMENT_DEPRECATION_WARNING]));
const errors = result.errors.filter(error => error.level === ParseErrorLevel.ERROR);
const errors = result.errors !.filter(error => error.level === ParseErrorLevel.ERROR);
if (warnings.length > 0) {
this._console.warn(`Template parse warnings:\n${warnings.join('\n')}`);
@ -130,7 +130,7 @@ export class TemplateParser {
throw syntaxError(`Template parse errors:\n${errorString}`);
}
return {template: result.templateAst, pipes: result.usedPipes};
return {template: result.templateAst !, pipes: result.usedPipes !};
}
tryParse(
@ -138,7 +138,7 @@ export class TemplateParser {
pipes: CompilePipeSummary[], schemas: SchemaMetadata[],
templateUrl: string): TemplateParseResult {
return this.tryParseHtml(
this.expandHtml(this._htmlParser.parse(
this.expandHtml(this._htmlParser !.parse(
template, templateUrl, true, this.getInterpolationConfig(component))),
component, template, directives, pipes, schemas, templateUrl);
}
@ -154,7 +154,7 @@ export class TemplateParser {
const uniqDirectives = removeSummaryDuplicates(directives);
const uniqPipes = removeSummaryDuplicates(pipes);
const providerViewContext = new ProviderViewContext(component);
let interpolationConfig: InterpolationConfig;
let interpolationConfig: InterpolationConfig = undefined !;
if (component.template && component.template.interpolation) {
interpolationConfig = {
start: component.template.interpolation[0],
@ -162,7 +162,7 @@ export class TemplateParser {
};
}
const bindingParser = new BindingParser(
this._exprParser, interpolationConfig, this._schemaRegistry, uniqPipes, errors);
this._exprParser, interpolationConfig !, this._schemaRegistry, uniqPipes, errors);
const parseVisitor = new TemplateParseVisitor(
this._config, providerViewContext, uniqDirectives, bindingParser, this._schemaRegistry,
schemas, errors);
@ -198,10 +198,11 @@ export class TemplateParser {
return htmlAstWithErrors;
}
getInterpolationConfig(component: CompileDirectiveMetadata): InterpolationConfig {
getInterpolationConfig(component: CompileDirectiveMetadata): InterpolationConfig|undefined {
if (component.template) {
return InterpolationConfig.fromArray(component.template.interpolation);
}
return undefined;
}
/** @internal */
@ -238,7 +239,7 @@ class TemplateParseVisitor implements html.Visitor {
// Note: queries start with id 1 so we can use the number in a Bloom filter!
this.contentQueryStartId = providerViewContext.component.viewQueries.length + 1;
directives.forEach((directive, index) => {
const selector = CssSelector.parse(directive.selector);
const selector = CssSelector.parse(directive.selector !);
this.selectorMatcher.addSelectables(selector, directive);
this.directivesIndex.set(directive, index);
});
@ -249,10 +250,10 @@ class TemplateParseVisitor implements html.Visitor {
visitExpansionCase(expansionCase: html.ExpansionCase, context: any): any { return null; }
visitText(text: html.Text, parent: ElementContext): any {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan);
return expr ? new BoundTextAst(expr, ngContentIndex, text.sourceSpan) :
new TextAst(text.value, ngContentIndex, text.sourceSpan);
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !;
const expr = this._bindingParser.parseInterpolation(text.value, text.sourceSpan !);
return expr ? new BoundTextAst(expr, ngContentIndex, text.sourceSpan !) :
new TextAst(text.value, ngContentIndex, text.sourceSpan !);
}
visitAttribute(attribute: html.Attribute, context: any): any {
@ -322,7 +323,7 @@ class TemplateParseVisitor implements html.Visitor {
}
hasInlineTemplates = true;
this._bindingParser.parseInlineTemplateBinding(
prefixToken, templateBindingsSource, attr.sourceSpan, templateMatchableAttrs,
prefixToken !, templateBindingsSource !, attr.sourceSpan, templateMatchableAttrs,
templateElementOrDirectiveProps, templateElementVars);
}
@ -340,48 +341,49 @@ class TemplateParseVisitor implements html.Visitor {
const boundDirectivePropNames = new Set<string>();
const directiveAsts = this._createDirectiveAsts(
isTemplateElement, element.name, directiveMetas, elementOrDirectiveProps,
elementOrDirectiveRefs, element.sourceSpan, references, boundDirectivePropNames);
elementOrDirectiveRefs, element.sourceSpan !, references, boundDirectivePropNames);
const elementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, elementOrDirectiveProps, boundDirectivePropNames);
const isViewRoot = parent.isTemplateElement || hasInlineTemplates;
const providerContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, isViewRoot, directiveAsts, attrs,
references, isTemplateElement, queryStartIndex, element.sourceSpan);
this.providerViewContext, parent.providerContext !, isViewRoot, directiveAsts, attrs,
references, isTemplateElement, queryStartIndex, element.sourceSpan !);
const children: TemplateAst[] = html.visitAll(
preparsedElement.nonBindable ? NON_BINDABLE_VISITOR : this, element.children,
ElementContext.create(
isTemplateElement, directiveAsts,
isTemplateElement ? parent.providerContext : providerContext));
isTemplateElement ? parent.providerContext ! : providerContext));
providerContext.afterElement();
// Override the actual selector when the `ngProjectAs` attribute is provided
const projectionSelector = preparsedElement.projectAs != null ?
CssSelector.parse(preparsedElement.projectAs)[0] :
elementCssSelector;
const ngContentIndex = parent.findNgContentIndex(projectionSelector);
const ngContentIndex = parent.findNgContentIndex(projectionSelector) !;
let parsedElement: TemplateAst;
if (preparsedElement.type === PreparsedElementType.NG_CONTENT) {
if (element.children && !element.children.every(_isEmptyTextNode)) {
this._reportError(`<ng-content> element cannot have content.`, element.sourceSpan);
this._reportError(`<ng-content> element cannot have content.`, element.sourceSpan !);
}
parsedElement = new NgContentAst(
this.ngContentCount++, hasInlineTemplates ? null : ngContentIndex, element.sourceSpan);
this.ngContentCount++, hasInlineTemplates ? null ! : ngContentIndex,
element.sourceSpan !);
} else if (isTemplateElement) {
this._assertAllEventsPublishedByDirectives(directiveAsts, events);
this._assertNoComponentsNorElementBindingsOnTemplate(
directiveAsts, elementProps, element.sourceSpan);
directiveAsts, elementProps, element.sourceSpan !);
parsedElement = new EmbeddedTemplateAst(
attrs, events, references, elementVars, providerContext.transformedDirectiveAsts,
providerContext.transformProviders, providerContext.transformedHasViewContainer,
providerContext.queryMatches, children, hasInlineTemplates ? null : ngContentIndex,
element.sourceSpan);
providerContext.queryMatches, children, hasInlineTemplates ? null ! : ngContentIndex,
element.sourceSpan !);
} else {
this._assertElementExists(matchElement, element);
this._assertOnlyOneComponent(directiveAsts, element.sourceSpan);
this._assertOnlyOneComponent(directiveAsts, element.sourceSpan !);
const ngContentIndex =
hasInlineTemplates ? null : parent.findNgContentIndex(projectionSelector);
@ -389,7 +391,8 @@ class TemplateParseVisitor implements html.Visitor {
nodeName, attrs, elementProps, events, references,
providerContext.transformedDirectiveAsts, providerContext.transformProviders,
providerContext.transformedHasViewContainer, providerContext.queryMatches, children,
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan, element.endSourceSpan);
hasInlineTemplates ? null : ngContentIndex, element.sourceSpan || null,
element.endSourceSpan || null);
}
if (hasInlineTemplates) {
@ -400,21 +403,21 @@ class TemplateParseVisitor implements html.Visitor {
const templateBoundDirectivePropNames = new Set<string>();
const templateDirectiveAsts = this._createDirectiveAsts(
true, element.name, templateDirectiveMetas, templateElementOrDirectiveProps, [],
element.sourceSpan, [], templateBoundDirectivePropNames);
element.sourceSpan !, [], templateBoundDirectivePropNames);
const templateElementProps: BoundElementPropertyAst[] = this._createElementPropertyAsts(
element.name, templateElementOrDirectiveProps, templateBoundDirectivePropNames);
this._assertNoComponentsNorElementBindingsOnTemplate(
templateDirectiveAsts, templateElementProps, element.sourceSpan);
templateDirectiveAsts, templateElementProps, element.sourceSpan !);
const templateProviderContext = new ProviderElementContext(
this.providerViewContext, parent.providerContext, parent.isTemplateElement,
templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan);
this.providerViewContext, parent.providerContext !, parent.isTemplateElement,
templateDirectiveAsts, [], [], true, templateQueryStartIndex, element.sourceSpan !);
templateProviderContext.afterElement();
parsedElement = new EmbeddedTemplateAst(
[], [], [], templateElementVars, templateProviderContext.transformedDirectiveAsts,
templateProviderContext.transformProviders,
templateProviderContext.transformedHasViewContainer, templateProviderContext.queryMatches,
[parsedElement], ngContentIndex, element.sourceSpan);
[parsedElement], ngContentIndex, element.sourceSpan !);
}
return parsedElement;
@ -531,7 +534,7 @@ class TemplateParseVisitor implements html.Visitor {
let matchElement = false;
selectorMatcher.match(elementCssSelector, (selector, directive) => {
directives[this.directivesIndex.get(directive)] = directive;
directives[this.directivesIndex.get(directive) !] = directive;
matchElement = matchElement || selector.hasElementSelector();
});
@ -547,7 +550,7 @@ class TemplateParseVisitor implements html.Visitor {
elementSourceSpan: ParseSourceSpan, targetReferences: ReferenceAst[],
targetBoundDirectivePropNames: Set<string>): DirectiveAst[] {
const matchedReferences = new Set<string>();
let component: CompileDirectiveSummary = null;
let component: CompileDirectiveSummary = null !;
const directiveAsts = directives.map((directive) => {
const sourceSpan = new ParseSourceSpan(
@ -559,11 +562,11 @@ class TemplateParseVisitor implements html.Visitor {
}
const directiveProperties: BoundDirectivePropertyAst[] = [];
let hostProperties =
this._bindingParser.createDirectiveHostPropertyAsts(directive, elementName, sourceSpan);
this._bindingParser.createDirectiveHostPropertyAsts(directive, elementName, sourceSpan) !;
// 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 hostEvents = this._bindingParser.createDirectiveHostEventAsts(directive, sourceSpan) !;
this._createDirectivePropertyAsts(
directive.inputs, props, directiveProperties, targetBoundDirectivePropNames);
elementOrDirectiveRefs.forEach((elOrDirRef) => {
@ -589,7 +592,7 @@ class TemplateParseVisitor implements html.Visitor {
elOrDirRef.sourceSpan);
}
} else if (!component) {
let refToken: CompileTokenMetadata = null;
let refToken: CompileTokenMetadata = null !;
if (isTemplateElement) {
refToken = createIdentifierToken(Identifiers.TemplateRef);
}
@ -648,7 +651,7 @@ class TemplateParseVisitor implements html.Visitor {
private _findComponentDirectiveNames(directives: DirectiveAst[]): string[] {
return this._findComponentDirectives(directives)
.map(directive => identifierName(directive.directive.type));
.map(directive => identifierName(directive.directive.type) !);
}
private _assertOnlyOneComponent(directives: DirectiveAst[], sourceSpan: ParseSourceSpan) {
@ -685,7 +688,7 @@ class TemplateParseVisitor implements html.Visitor {
errorMsg +=
`2. To allow any element add 'NO_ERRORS_SCHEMA' to the '@NgModule.schemas' of this component.`;
}
this._reportError(errorMsg, element.sourceSpan);
this._reportError(errorMsg, element.sourceSpan !);
}
}
@ -757,7 +760,7 @@ class TemplateParseVisitor implements html.Visitor {
}
class NonBindableVisitor implements html.Visitor {
visitElement(ast: html.Element, parent: ElementContext): ElementAst {
visitElement(ast: html.Element, parent: ElementContext): ElementAst|null {
const preparsedElement = preparseElement(ast);
if (preparsedElement.type === PreparsedElementType.SCRIPT ||
preparsedElement.type === PreparsedElementType.STYLE ||
@ -783,8 +786,8 @@ class NonBindableVisitor implements html.Visitor {
}
visitText(text: html.Text, parent: ElementContext): TextAst {
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR);
return new TextAst(text.value, ngContentIndex, text.sourceSpan);
const ngContentIndex = parent.findNgContentIndex(TEXT_CSS_SELECTOR) !;
return new TextAst(text.value, ngContentIndex, text.sourceSpan !);
}
visitExpansion(expansion: html.Expansion, context: any): any { return expansion; }
@ -805,10 +808,10 @@ class ElementContext {
isTemplateElement: boolean, directives: DirectiveAst[],
providerContext: ProviderElementContext): ElementContext {
const matcher = new SelectorMatcher();
let wildcardNgContentIndex: number = null;
let wildcardNgContentIndex: number = null !;
const component = directives.find(directive => directive.directive.isComponent);
if (component) {
const ngContentSelectors = component.directive.template.ngContentSelectors;
const ngContentSelectors = component.directive.template !.ngContentSelectors;
for (let i = 0; i < ngContentSelectors.length; i++) {
const selector = ngContentSelectors[i];
if (selector === '*') {
@ -822,9 +825,10 @@ class ElementContext {
}
constructor(
public isTemplateElement: boolean, private _ngContentIndexMatcher: SelectorMatcher,
private _wildcardNgContentIndex: number, public providerContext: ProviderElementContext) {}
private _wildcardNgContentIndex: number|null,
public providerContext: ProviderElementContext|null) {}
findNgContentIndex(selector: CssSelector): number {
findNgContentIndex(selector: CssSelector): number|null {
const ngContentIndices: number[] = [];
this._ngContentIndexMatcher.match(
selector, (selector, ngContentIndex) => { ngContentIndices.push(ngContentIndex); });
@ -893,10 +897,9 @@ function isTemplate(
// `<template>` is HTML and case insensitive
if (tagNoNs.toLowerCase() === TEMPLATE_ELEMENT) {
if (enableLegacyTemplate && tagNoNs.toLowerCase() === TEMPLATE_ELEMENT) {
reportDeprecation(TEMPLATE_ELEMENT_DEPRECATION_WARNING, el.sourceSpan);
reportDeprecation(TEMPLATE_ELEMENT_DEPRECATION_WARNING, el.sourceSpan !);
return true;
}
return false;
}
return false;
}

View File

@ -21,11 +21,11 @@ const NG_NON_BINDABLE_ATTR = 'ngNonBindable';
const NG_PROJECT_AS = 'ngProjectAs';
export function preparseElement(ast: html.Element): PreparsedElement {
let selectAttr: string = null;
let hrefAttr: string = null;
let relAttr: string = null;
let selectAttr: string = null !;
let hrefAttr: string = null !;
let relAttr: string = null !;
let nonBindable = false;
let projectAs: string = null;
let projectAs: string = null !;
ast.attrs.forEach(attr => {
const lcAttrName = attr.name.toLowerCase();
if (lcAttrName == NG_CONTENT_SELECT_ATTR) {