fix(language-service): remove asserts for non-null expressions (#16422)

Reworked some of the code so asserts are no longer necessary.
Added additional and potentially redundant checks
Added checks where the null checker found real problems.

PR Close #16422
This commit is contained in:
Chuck Jazdzewski 2017-04-28 15:10:30 -07:00 committed by Miško Hevery
parent 270d694ed2
commit 253345c0c0
12 changed files with 241 additions and 195 deletions

View File

@ -13,7 +13,9 @@ export class AstPath<T> {
get head(): T|undefined { return this.path[0]; }
get tail(): T|undefined { return this.path[this.path.length - 1]; }
parentOf(node: T): T|undefined { return this.path[this.path.indexOf(node) - 1]; }
parentOf(node: T|undefined): T|undefined {
return node && this.path[this.path.indexOf(node) - 1];
}
childOf(node: T): T|undefined { return this.path[this.path.indexOf(node) + 1]; }
first<N extends T>(ctor: {new (...args: any[]): N}): N|undefined {

View File

@ -33,23 +33,24 @@ export function getTemplateCompletions(templateInfo: TemplateInfo): Completions|
let result: Completions|undefined = undefined;
let {htmlAst, templateAst, template} = templateInfo;
// The templateNode starts at the delimiter character so we add 1 to skip it.
let templatePosition = templateInfo.position ! - template.span.start;
if (templateInfo.position != null) {
let templatePosition = templateInfo.position - template.span.start;
let path = new HtmlAstPath(htmlAst, templatePosition);
let mostSpecific = path.tail;
if (path.empty) {
if (path.empty || !mostSpecific) {
result = elementCompletions(templateInfo, path);
} else {
let astPosition = templatePosition - mostSpecific !.sourceSpan !.start.offset;
mostSpecific !.visit(
let astPosition = templatePosition - mostSpecific.sourceSpan.start.offset;
mostSpecific.visit(
{
visitElement(ast) {
let startTagSpan = spanOf(ast.sourceSpan);
let tagLen = ast.name.length;
if (templatePosition <=
startTagSpan !.start + tagLen + 1 /* 1 for the opening angle bracked */) {
startTagSpan.start + tagLen + 1 /* 1 for the opening angle bracked */) {
// If we are in the tag then return the element completions.
result = elementCompletions(templateInfo, path);
} else if (templatePosition < startTagSpan !.end) {
} else if (templatePosition < startTagSpan.end) {
// We are in the attribute section of the element (but not in an attribute).
// Return the attribute completions.
result = attributeCompletions(templateInfo, path);
@ -65,7 +66,7 @@ export function getTemplateCompletions(templateInfo: TemplateInfo): Completions|
},
visitText(ast) {
// Check if we are in a entity.
result = entityCompletions(getSourceText(template, spanOf(ast) !), astPosition);
result = entityCompletions(getSourceText(template, spanOf(ast)), astPosition);
if (result) return result;
result = interpolationCompletions(templateInfo, templatePosition);
if (result) return result;
@ -93,11 +94,12 @@ export function getTemplateCompletions(templateInfo: TemplateInfo): Completions|
},
null);
}
}
return result;
}
function attributeCompletions(info: TemplateInfo, path: HtmlAstPath): Completions|undefined {
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail !);
let item = path.tail instanceof Element ? path.tail : path.parentOf(path.tail);
if (item instanceof Element) {
return attributeCompletionsForElement(info, item.name, item);
}
@ -213,11 +215,12 @@ function elementCompletions(info: TemplateInfo, path: HtmlAstPath): Completions|
let htmlNames = elementNames().filter(name => !(name in hiddenHtmlElements));
// Collect the elements referenced by the selectors
let directiveElements =
getSelectors(info).selectors.map(selector => selector.element).filter(name => !!name);
let directiveElements = getSelectors(info)
.selectors.map(selector => selector.element)
.filter(name => !!name) as string[];
let components =
directiveElements.map<Completion>(name => ({kind: 'component', name: name !, sort: name !}));
directiveElements.map<Completion>(name => ({kind: 'component', name, sort: name}));
let htmlElements = htmlNames.map<Completion>(name => ({kind: 'element', name: name, sort: name}));
// Return components and html elements
@ -262,25 +265,25 @@ function voidElementAttributeCompletions(info: TemplateInfo, path: HtmlAstPath):
undefined {
let tail = path.tail;
if (tail instanceof Text) {
let match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/) !;
let match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/);
// The position must be after the match, otherwise we are still in a place where elements
// are expected (such as `<|a` or `<a|`; we only want attributes for `<a |` or after).
if (match && path.position >= match.index ! + match[0].length + tail.sourceSpan.start.offset) {
if (match &&
path.position >= (match.index || 0) + match[0].length + tail.sourceSpan.start.offset) {
return attributeCompletionsForElement(info, match[3]);
}
}
}
class ExpressionVisitor extends NullTemplateVisitor {
private getExpressionScope: () => SymbolTable;
result: Completions;
constructor(
private info: TemplateInfo, private position: number, private attr?: Attribute,
private getExpressionScope?: () => SymbolTable) {
getExpressionScope?: () => SymbolTable) {
super();
if (!getExpressionScope) {
this.getExpressionScope = () => info.template.members;
}
this.getExpressionScope = getExpressionScope || (() => info.template.members);
}
visitDirectiveProperty(ast: BoundDirectivePropertyAst): void {
@ -311,7 +314,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
this.info.expressionParser.parseTemplateBindings(key, this.attr.value, null);
// find the template binding that contains the position
const valueRelativePosition = this.position - this.attr.valueSpan !.start.offset - 1;
if (!this.attr.valueSpan) return;
const valueRelativePosition = this.position - this.attr.valueSpan.start.offset - 1;
const bindings = templateBindingResult.templateBindings;
const binding =
bindings.find(
@ -340,11 +344,13 @@ class ExpressionVisitor extends NullTemplateVisitor {
// We are after the '=' in a let clause. The valid values here are the members of the
// template reference's type parameter.
const directiveMetadata = selectorInfo.map.get(selector);
if (directiveMetadata) {
const contextTable =
this.info.template.query.getTemplateContext(directiveMetadata !.type.reference);
this.info.template.query.getTemplateContext(directiveMetadata.type.reference);
if (contextTable) {
this.result = this.symbolsToCompletions(contextTable.values());
}
}
} else if (binding.key && valueRelativePosition <= (binding.key.length - key.length)) {
keyCompletions();
}
@ -371,7 +377,7 @@ class ExpressionVisitor extends NullTemplateVisitor {
const expressionPosition = this.position - ast.sourceSpan.start.offset;
if (inSpan(expressionPosition, ast.value.span)) {
const completions = getExpressionCompletions(
this.getExpressionScope !(), ast.value, expressionPosition, this.info.template.query);
this.getExpressionScope(), ast.value, expressionPosition, this.info.template.query);
if (completions) {
this.result = this.symbolsToCompletions(completions);
}
@ -380,8 +386,8 @@ class ExpressionVisitor extends NullTemplateVisitor {
private attributeValueCompletions(value: AST, position?: number) {
const symbols = getExpressionCompletions(
this.getExpressionScope !(), value,
position == null ? this.attributeValuePosition : position, this.info.template.query);
this.getExpressionScope(), value, position == null ? this.attributeValuePosition : position,
this.info.template.query);
if (symbols) {
this.result = this.symbolsToCompletions(symbols);
}
@ -393,12 +399,13 @@ class ExpressionVisitor extends NullTemplateVisitor {
}
private get attributeValuePosition() {
return this.position - this.attr !.valueSpan !.start.offset - 1;
if (this.attr && this.attr.valueSpan) {
return this.position - this.attr.valueSpan.start.offset - 1;
}
return 0;
}
}
function getSourceText(template: TemplateSource, span: Span): string {
return template.source.substring(span.start, span.end);
}

View File

@ -29,7 +29,7 @@ export function getTemplateDiagnostics(
results.push(...ast.parseErrors.map<Diagnostic>(
e => ({
kind: DiagnosticKind.Error,
span: offsetSpan(spanOf(e.span) !, template.span.start),
span: offsetSpan(spanOf(e.span), template.span.start),
message: e.msg
})));
} else if (ast.templateAst) {
@ -91,14 +91,16 @@ export function getDeclarationDiagnostics(
function getTemplateExpressionDiagnostics(
template: TemplateSource, astResult: AstResult): Diagnostics {
if (astResult.htmlAst && astResult.directive && astResult.directives && astResult.pipes &&
astResult.templateAst && astResult.expressionParser) {
const info: TemplateInfo = {
template,
htmlAst: astResult.htmlAst !,
directive: astResult.directive !,
directives: astResult.directives !,
pipes: astResult.pipes !,
templateAst: astResult.templateAst !,
expressionParser: astResult.expressionParser !
htmlAst: astResult.htmlAst,
directive: astResult.directive,
directives: astResult.directives,
pipes: astResult.pipes,
templateAst: astResult.templateAst,
expressionParser: astResult.expressionParser
};
const visitor = new ExpressionDiagnosticsVisitor(
info, (path: TemplateAstPath, includeEvent: boolean) =>
@ -106,6 +108,8 @@ function getTemplateExpressionDiagnostics(
templateVisitAll(visitor, astResult.templateAst !);
return visitor.diagnostics;
}
return [];
}
class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
private path: TemplateAstPath;
@ -158,11 +162,11 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
if (context && !context.has(ast.value)) {
if (ast.value === '$implicit') {
this.reportError(
'The template context does not have an implicit value', spanOf(ast.sourceSpan) !);
'The template context does not have an implicit value', spanOf(ast.sourceSpan));
} else {
this.reportError(
`The template context does not defined a member called '${ast.value}'`,
spanOf(ast.sourceSpan) !);
spanOf(ast.sourceSpan));
}
}
}
@ -233,12 +237,14 @@ class ExpressionDiagnosticsVisitor extends TemplateAstChildVisitor {
}
}
private reportError(message: string, span: Span) {
private reportError(message: string, span: Span|undefined) {
if (span) {
this.diagnostics.push({
span: offsetSpan(span, this.info.template.span.start),
kind: DiagnosticKind.Error, message
});
}
}
private reportWarning(message: string, span: Span) {
this.diagnostics.push({

View File

@ -495,8 +495,9 @@ class AstType implements ExpressionVisitor {
// The type of a method is the selected methods result type.
const method = receiverType.members().get(ast.name);
if (!method) return this.reportError(`Unknown method ${ast.name}`, ast);
if (!method.type !.callable) return this.reportError(`Member ${ast.name} is not callable`, ast);
const signature = method.type !.selectSignature(ast.args.map(arg => this.getType(arg)));
if (!method.type) return this.reportError(`Could not find a type for ${ast.name}`, ast);
if (!method.type.callable) return this.reportError(`Member ${ast.name} is not callable`, ast);
const signature = method.type.selectSignature(ast.args.map(arg => this.getType(arg)));
if (!signature)
return this.reportError(`Unable to resolve signature for call of method ${ast.name}`, ast);
return signature.result;
@ -601,7 +602,9 @@ function visitChildren(ast: AST, visitor: ExpressionVisitor) {
visit(ast.falseExp);
},
visitFunctionCall(ast) {
visit(ast.target !);
if (ast.target) {
visit(ast.target);
}
visitAll(ast.args);
},
visitImplicitReceiver(ast) {},
@ -676,7 +679,7 @@ function getReferences(info: TemplateInfo): SymbolDeclaration[] {
function processReferences(references: ReferenceAst[]) {
for (const reference of references) {
let type: Symbol = undefined !;
let type: Symbol|undefined = undefined;
if (reference.value) {
type = info.template.query.getTypeSymbol(tokenReference(reference.value));
}
@ -721,7 +724,7 @@ function getVarDeclarations(info: TemplateInfo, path: TemplateAstPath): SymbolDe
.find(c => !!c);
// Determine the type of the context field referenced by variable.value.
let type: Symbol = undefined !;
let type: Symbol|undefined = undefined;
if (context) {
const value = context.get(variable.value);
if (value) {
@ -762,7 +765,10 @@ function refinedVariableType(
const bindingType =
new AstType(info.template.members, info.template.query, {}).getType(ngForOfBinding.value);
if (bindingType) {
return info.template.query.getElementType(bindingType) !;
const result = info.template.query.getElementType(bindingType);
if (result) {
return result;
}
}
}
}

View File

@ -81,24 +81,25 @@ class LanguageServiceImpl implements LanguageService {
let template = this.host.getTemplateAt(fileName, position);
if (template) {
let astResult = this.getTemplateAst(template, fileName);
if (astResult && astResult.htmlAst && astResult.templateAst)
if (astResult && astResult.htmlAst && astResult.templateAst && astResult.directive &&
astResult.directives && astResult.pipes && astResult.expressionParser)
return {
position,
fileName,
template,
htmlAst: astResult.htmlAst,
directive: astResult.directive !,
directives: astResult.directives !,
pipes: astResult.pipes !,
directive: astResult.directive,
directives: astResult.directives,
pipes: astResult.pipes,
templateAst: astResult.templateAst,
expressionParser: astResult.expressionParser !
expressionParser: astResult.expressionParser
};
}
return undefined;
}
getTemplateAst(template: TemplateSource, contextFile: string): AstResult {
let result: AstResult = undefined !;
let result: AstResult|undefined = undefined;
try {
const resolvedMetadata =
this.metadataResolver.getNonNormalizedDirectiveMetadata(template.type as any);
@ -112,7 +113,7 @@ class LanguageServiceImpl implements LanguageService {
config, expressionParser, new DomElementSchemaRegistry(), htmlParser, null !, []);
const htmlResult = htmlParser.parse(template.source, '', true);
const analyzedModules = this.host.getAnalyzedModules();
let errors: Diagnostic[] = undefined !;
let errors: Diagnostic[]|undefined = undefined;
let ngModule = analyzedModules.ngModuleByPipeOrDirective.get(template.type);
if (!ngModule) {
// Reported by the the declaration diagnostics.
@ -121,8 +122,7 @@ class LanguageServiceImpl implements LanguageService {
if (ngModule) {
const resolvedDirectives = ngModule.transitiveModule.directives.map(
d => this.host.resolver.getNonNormalizedDirectiveMetadata(d.reference));
const directives =
resolvedDirectives.filter(d => d !== null).map(d => d !.metadata.toSummary());
const directives = removeMissing(resolvedDirectives).map(d => d.metadata.toSummary());
const pipes = ngModule.transitiveModule.pipes.map(
p => this.host.resolver.getOrLoadPipeMetadata(p.reference).toSummary());
const schemas = ngModule.schemas;
@ -142,10 +142,14 @@ class LanguageServiceImpl implements LanguageService {
}
result = {errors: [{kind: DiagnosticKind.Error, message: e.message, span}]};
}
return result;
return result || {};
}
}
function removeMissing<T>(values: (T | null | undefined)[]): T[] {
return values.filter(e => !!e) as T[];
}
function uniqueBySpan < T extends {
span: Span;
}
@ -169,8 +173,8 @@ function uniqueBySpan < T extends {
}
}
function findSuitableDefaultModule(modules: NgAnalyzedModules): CompileNgModuleMetadata {
let result: CompileNgModuleMetadata = undefined !;
function findSuitableDefaultModule(modules: NgAnalyzedModules): CompileNgModuleMetadata|undefined {
let result: CompileNgModuleMetadata|undefined = undefined;
let resultSize = 0;
for (const module of modules.ngModules) {
const moduleSize = module.transitiveModule.directives.length;

View File

@ -21,23 +21,26 @@ export interface SymbolInfo {
}
export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
const templatePosition = info.position ! - info.template.span.start;
if (!info.position) return undefined;
const templatePosition = info.position - info.template.span.start;
const path = new TemplateAstPath(info.templateAst, templatePosition);
if (path.tail) {
let symbol: Symbol = undefined !;
let span: Span = undefined !;
let symbol: Symbol|undefined = undefined;
let span: Span|undefined = undefined;
const attributeValueSymbol = (ast: AST, inEvent: boolean = false): boolean => {
const attribute = findAttribute(info);
if (attribute) {
if (inSpan(templatePosition, spanOf(attribute.valueSpan))) {
const scope = getExpressionScope(info, path, inEvent);
const expressionOffset = attribute.valueSpan !.start.offset + 1;
if (attribute.valueSpan) {
const expressionOffset = attribute.valueSpan.start.offset + 1;
const result = getExpressionSymbol(
scope, ast, templatePosition - expressionOffset, info.template.query);
if (result) {
symbol = result.symbol;
span = offsetSpan(result.span, expressionOffset);
}
}
return true;
}
}
@ -52,28 +55,28 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
if (component) {
symbol = info.template.query.getTypeSymbol(component.directive.type.reference);
symbol = symbol && new OverrideKindSymbol(symbol, 'component');
span = spanOf(ast) !;
span = spanOf(ast);
} else {
// Find a directive that matches the element name
const directive =
ast.directives.find(d => d.directive.selector !.indexOf(ast.name) >= 0);
const directive = ast.directives.find(
d => d.directive.selector != null && d.directive.selector.indexOf(ast.name) >= 0);
if (directive) {
symbol = info.template.query.getTypeSymbol(directive.directive.type.reference);
symbol = symbol && new OverrideKindSymbol(symbol, 'directive');
span = spanOf(ast) !;
span = spanOf(ast);
}
}
},
visitReference(ast) {
symbol = info.template.query.getTypeSymbol(tokenReference(ast.value));
span = spanOf(ast) !;
span = spanOf(ast);
},
visitVariable(ast) {},
visitEvent(ast) {
if (!attributeValueSymbol(ast.handler, /* inEvent */ true)) {
symbol = findOutputBinding(info, path, ast) !;
symbol = findOutputBinding(info, path, ast);
symbol = symbol && new OverrideKindSymbol(symbol, 'event');
span = spanOf(ast) !;
span = spanOf(ast);
}
},
visitElementProperty(ast) { attributeValueSymbol(ast.value); },
@ -93,12 +96,12 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
visitText(ast) {},
visitDirective(ast) {
symbol = info.template.query.getTypeSymbol(ast.directive.type.reference);
span = spanOf(ast) !;
span = spanOf(ast);
},
visitDirectiveProperty(ast) {
if (!attributeValueSymbol(ast.value)) {
symbol = findInputBinding(info, path, ast) !;
span = spanOf(ast) !;
symbol = findInputBinding(info, path, ast);
span = spanOf(ast);
}
}
},
@ -110,10 +113,12 @@ export function locateSymbol(info: TemplateInfo): SymbolInfo|undefined {
}
function findAttribute(info: TemplateInfo): Attribute|undefined {
const templatePosition = info.position ! - info.template.span.start;
if (info.position) {
const templatePosition = info.position - info.template.span.start;
const path = new HtmlAstPath(info.htmlAst, templatePosition);
return path.first(Attribute);
}
}
function findInputBinding(
info: TemplateInfo, path: TemplateAstPath, binding: BoundDirectivePropertyAst): Symbol|

View File

@ -22,18 +22,24 @@ class ReflectorModuleModuleResolutionHost implements ts.ModuleResolutionHost {
if (snapshot) {
return snapshot.getText(0, snapshot.getLength());
}
// Typescript readFile() declaration should be `readFile(fileName: string): string | undefined
return undefined !;
}
directoryExists: (directoryName: string) => boolean;
}
// This reflector host's purpose is to first set verboseInvalidExpressions to true so the
// reflector will collect errors instead of throwing, and second to all deferring the creation
// of the program until it is actually needed.
export class ReflectorHost extends CompilerHost {
constructor(
private getProgram: () => ts.Program, serviceHost: ts.LanguageServiceHost,
options: AngularCompilerOptions) {
super(
null !, options,
// The ancestor value for program is overridden below so passing null here is safe.
/* program */ null !, options,
new ModuleResolutionHostAdapter(new ReflectorModuleModuleResolutionHost(serviceHost)),
{verboseInvalidExpression: true});
}

View File

@ -104,10 +104,10 @@ class TemplateAstPathBuilder extends TemplateAstChildVisitor {
constructor(private position: number, private allowWidening: boolean) { super(); }
visit(ast: TemplateAst, context: any): any {
let span = spanOf(ast) !;
let span = spanOf(ast);
if (inSpan(this.position, span)) {
const len = this.path.length;
if (!len || this.allowWidening || isNarrower(span, spanOf(this.path[len - 1]) !)) {
if (!len || this.allowWidening || isNarrower(span, spanOf(this.path[len - 1]))) {
this.path.push(ast);
}
} else {

View File

@ -170,14 +170,16 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
}
function diagnosticToDiagnostic(d: Diagnostic, file: ts.SourceFile): ts.Diagnostic {
return {
const result = {
file,
start: d.span.start,
length: d.span.end - d.span.start,
messageText: d.message,
category: ts.DiagnosticCategory.Error,
code: 0
code: 0,
source: 'ng'
};
return result;
}
function tryOperation<T>(attempting: string, callback: () => T): T|null {
@ -229,7 +231,7 @@ export function create(info: any /* ts.server.PluginCreateInfo */): ts.LanguageS
if (ours) {
const displayParts: ts.SymbolDisplayPart[] = [];
for (const part of ours.text) {
displayParts.push({kind: part.language !, text: part.text});
displayParts.push({kind: part.language || 'angular', text: part.text});
}
const tags = base && (<any>base).tags;
base = <any>{

View File

@ -8,8 +8,6 @@
import {CompileDirectiveMetadata, CompileMetadataResolver, NgAnalyzedModules, StaticSymbol} from '@angular/compiler';
/**
* The range of a span of text in a source file.
*
@ -455,7 +453,7 @@ export interface LanguageServiceHost {
* refers to a template file then the `position` should be ignored. If the `position` is not in a
* template literal string then this method should return `undefined`.
*/
getTemplateAt(fileName: string, position: number): TemplateSource /* |undefined */;
getTemplateAt(fileName: string, position: number): TemplateSource|undefined;
/**
* Return the template source information for all templates in `fileName` or for `fileName` if it

View File

@ -74,22 +74,22 @@ export class DummyResourceLoader extends ResourceLoader {
* @experimental
*/
export class TypeScriptServiceHost implements LanguageServiceHost {
private _resolver: CompileMetadataResolver;
private _resolver: CompileMetadataResolver|null;
private _staticSymbolCache = new StaticSymbolCache();
private _summaryResolver: AotSummaryResolver;
private _staticSymbolResolver: StaticSymbolResolver;
private _reflector: StaticReflector;
private _reflector: StaticReflector|null;
private _reflectorHost: ReflectorHost;
private _checker: ts.TypeChecker;
private _checker: ts.TypeChecker|null;
private _typeCache: Symbol[] = [];
private context: string|undefined;
private lastProgram: ts.Program|undefined;
private modulesOutOfDate: boolean = true;
private analyzedModules: NgAnalyzedModules;
private analyzedModules: NgAnalyzedModules|null;
private service: LanguageService;
private fileToComponent: Map<string, StaticSymbol>;
private templateReferences: string[];
private collectedErrors: Map<string, any[]>;
private fileToComponent: Map<string, StaticSymbol>|null;
private templateReferences: string[]|null;
private collectedErrors: Map<string, any[]>|null;
private fileVersions = new Map<string, string>();
constructor(private host: ts.LanguageServiceHost, private tsService: ts.LanguageService) {}
@ -127,28 +127,28 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
getTemplateReferences(): string[] {
this.ensureTemplateMap();
return this.templateReferences;
return this.templateReferences || [];
}
getTemplateAt(fileName: string, position: number): TemplateSource {
getTemplateAt(fileName: string, position: number): TemplateSource|undefined {
let sourceFile = this.getSourceFile(fileName);
if (sourceFile) {
this.context = sourceFile.fileName;
let node = this.findNode(sourceFile, position);
if (node) {
return this.getSourceFromNode(
fileName, this.host.getScriptVersion(sourceFile.fileName), node) !;
fileName, this.host.getScriptVersion(sourceFile.fileName), node);
}
} else {
this.ensureTemplateMap();
// TODO: Cannocalize the file?
const componentType = this.fileToComponent.get(fileName);
const componentType = this.fileToComponent !.get(fileName);
if (componentType) {
return this.getSourceFromType(
fileName, this.host.getScriptVersion(fileName), componentType) !;
fileName, this.host.getScriptVersion(fileName), componentType);
}
}
return null !;
return undefined;
}
getAnalyzedModules(): NgAnalyzedModules {
@ -172,7 +172,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
getTemplates(fileName: string): TemplateSources {
this.ensureTemplateMap();
const componentType = this.fileToComponent.get(fileName);
const componentType = this.fileToComponent !.get(fileName);
if (componentType) {
const templateSource = this.getTemplateAt(fileName, 0);
if (templateSource) {
@ -225,10 +225,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
updateAnalyzedModules() {
this.validate();
if (this.modulesOutOfDate) {
this.analyzedModules = null !;
this._reflector = null !;
this.templateReferences = null !;
this.fileToComponent = null !;
this.analyzedModules = null;
this._reflector = null;
this.templateReferences = null;
this.fileToComponent = null;
this.ensureAnalyzedModules();
this.modulesOutOfDate = false;
}
@ -273,10 +273,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
}
private clearCaches() {
this._checker = null !;
this._checker = null;
this._typeCache = [];
this._resolver = null !;
this.collectedErrors = null !;
this._resolver = null;
this.collectedErrors = null;
this.modulesOutOfDate = true;
}
@ -345,7 +345,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
if (declaration && declaration.name) {
const sourceFile = this.getSourceFile(fileName);
return this.getSourceFromDeclaration(
fileName, version, this.stringOf(node) !, shrink(spanOf(node)),
fileName, version, this.stringOf(node) || '', shrink(spanOf(node)),
this.reflector.getStaticSymbol(sourceFile.fileName, declaration.name.text),
declaration, node, sourceFile);
}
@ -359,11 +359,13 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
let result: TemplateSource|undefined = undefined;
const declaration = this.getTemplateClassFromStaticSymbol(type);
if (declaration) {
const snapshot = this.host.getScriptSnapshot(fileName) !;
const snapshot = this.host.getScriptSnapshot(fileName);
if (snapshot) {
const source = snapshot.getText(0, snapshot.getLength());
result = this.getSourceFromDeclaration(
fileName, version, source, {start: 0, end: source.length}, type, declaration, declaration,
declaration.getSourceFile());
fileName, version, source, {start: 0, end: source.length}, type, declaration,
declaration, declaration.getSourceFile());
}
}
return result;
}
@ -398,9 +400,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
return result;
}
private collectError(error: any, filePath: string) {
private collectError(error: any, filePath: string|null) {
if (filePath) {
let errorMap = this.collectedErrors;
if (!errorMap) {
if (!errorMap || !this.collectedErrors) {
errorMap = this.collectedErrors = new Map();
}
let errors = errorMap.get(filePath);
@ -410,15 +413,16 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
}
errors.push(error);
}
}
private get staticSymbolResolver(): StaticSymbolResolver {
let result = this._staticSymbolResolver;
if (!result) {
this._summaryResolver = new AotSummaryResolver(
{
loadSummary(filePath: string) { return null !; },
isSourceFile(sourceFilePath: string) { return true !; },
getOutputFileName(sourceFilePath: string) { return null !; }
loadSummary(filePath: string) { return null; },
isSourceFile(sourceFilePath: string) { return true; },
getOutputFileName(sourceFilePath: string) { return sourceFilePath; }
},
this._staticSymbolCache);
result = this._staticSymbolResolver = new StaticSymbolResolver(
@ -445,7 +449,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
const declarationNode = ts.forEachChild(source, child => {
if (child.kind === ts.SyntaxKind.ClassDeclaration) {
const classDeclaration = child as ts.ClassDeclaration;
if (classDeclaration.name !.text === type.name) {
if (classDeclaration.name != null && classDeclaration.name.text === type.name) {
return classDeclaration;
}
}
@ -614,7 +618,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
private program: ts.Program, private checker: ts.TypeChecker, private source: ts.SourceFile,
private fetchPipes: () => SymbolTable) {}
getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol) !); }
getTypeKind(symbol: Symbol): BuiltinType { return typeKindOf(this.getTsTypeOf(symbol)); }
getBuiltinType(kind: BuiltinType): Symbol {
// TODO: Replace with typeChecker API when available.
@ -1303,7 +1307,7 @@ function getTypeParameterOf(type: ts.Type, name: string): ts.Type|undefined {
}
}
function typeKindOf(type: ts.Type): BuiltinType {
function typeKindOf(type: ts.Type | undefined): BuiltinType {
if (type) {
if (type.flags & ts.TypeFlags.Any) {
return BuiltinType.Any;
@ -1318,17 +1322,19 @@ function typeKindOf(type: ts.Type): BuiltinType {
return BuiltinType.Null;
} else if (type.flags & ts.TypeFlags.Union) {
// If all the constituent types of a union are the same kind, it is also that kind.
let candidate: BuiltinType = undefined !;
let candidate: BuiltinType|null = null;
const unionType = type as ts.UnionType;
if (unionType.types.length > 0) {
candidate = typeKindOf(unionType.types[0]) !;
candidate = typeKindOf(unionType.types[0]);
for (const subType of unionType.types) {
if (candidate != typeKindOf(subType)) {
return BuiltinType.Other;
}
}
}
if (candidate != null) {
return candidate;
}
} else if (type.flags & ts.TypeFlags.TypeParameter) {
return BuiltinType.Unbound;
}

View File

@ -21,6 +21,9 @@ export function isParseSourceSpan(value: any): value is ParseSourceSpan {
return value && !!value.start;
}
export function spanOf(span: SpanHolder): Span;
export function spanOf(span: ParseSourceSpan): Span;
export function spanOf(span: SpanHolder | ParseSourceSpan | undefined): Span|undefined;
export function spanOf(span?: SpanHolder | ParseSourceSpan): Span|undefined {
if (!span) return undefined;
if (isParseSourceSpan(span)) {
@ -39,8 +42,8 @@ export function spanOf(span?: SpanHolder | ParseSourceSpan): Span|undefined {
}
export function inSpan(position: number, span?: Span, exclusive?: boolean): boolean {
return span && exclusive ? position >= span.start && position < span.end :
position >= span !.start && position <= span !.end;
return span != null && (exclusive ? position >= span.start && position < span.end :
position >= span.start && position <= span.end);
}
export function offsetSpan(span: Span, amount: number): Span {
@ -54,7 +57,8 @@ export function isNarrower(spanA: Span, spanB: Span): boolean {
export function hasTemplateReference(type: CompileTypeMetadata): boolean {
if (type.diDeps) {
for (let diDep of type.diDeps) {
if (diDep.token !.identifier && identifierName(diDep.token !.identifier !) == 'TemplateRef')
if (diDep.token && diDep.token.identifier &&
identifierName(diDep.token !.identifier !) == 'TemplateRef')
return true;
}
}