refactor(language-service): clean up and exports and consolidate types (#36533)

PR Close #36533
This commit is contained in:
Ayaz Hafiz 2020-04-08 20:05:32 -07:00 committed by atscott
parent d50cb30443
commit 5e80e7e216
24 changed files with 251 additions and 433 deletions

View File

@ -1956,10 +1956,6 @@
"packages/forms/src/directives/validators.ts", "packages/forms/src/directives/validators.ts",
"packages/forms/src/validators.ts" "packages/forms/src/validators.ts"
], ],
[
"packages/language-service/src/common.ts",
"packages/language-service/src/types.ts"
],
[ [
"packages/language-service/src/completions.ts", "packages/language-service/src/completions.ts",
"packages/language-service/src/template.ts", "packages/language-service/src/template.ts",

View File

@ -15,6 +15,5 @@
*/ */
export {createLanguageService} from './src/language_service'; export {createLanguageService} from './src/language_service';
export * from './src/ts_plugin'; export * from './src/ts_plugin';
export {Completion, Completions, Declaration, Declarations, Definition, Diagnostic, Diagnostics, Hover, HoverTextSection, LanguageService, LanguageServiceHost, Location, Span, TemplateSource, TemplateSources} from './src/types'; export {Declaration, Definition, Diagnostic, LanguageService, LanguageServiceHost, Span, TemplateSource} from './src/types';
export {TypeScriptServiceHost, createLanguageServiceFromTypescript} from './src/typescript_host'; export {TypeScriptServiceHost, createLanguageServiceFromTypescript} from './src/typescript_host';
export {VERSION} from './src/version';

View File

@ -1,27 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CssSelector, Node as HtmlAst, ParseError, Parser, TemplateAst} from '@angular/compiler';
import {TemplateSource} from './types';
export interface AstResult {
htmlAst: HtmlAst[];
templateAst: TemplateAst[];
directive: CompileDirectiveMetadata;
directives: CompileDirectiveSummary[];
pipes: CompilePipeSummary[];
parseErrors?: ParseError[];
expressionParser: Parser;
template: TemplateSource;
}
export type SelectorInfo = {
selectors: CssSelector[],
map: Map<CssSelector, CompileDirectiveSummary>
};

View File

@ -10,13 +10,12 @@ import {AbsoluteSourceSpan, AST, AstPath, AttrAst, Attribute, BoundDirectiveProp
import {$$, $_, isAsciiLetter, isDigit} from '@angular/compiler/src/chars'; import {$$, $_, isAsciiLetter, isDigit} from '@angular/compiler/src/chars';
import {ATTR, getBindingDescriptor} from './binding_utils'; import {ATTR, getBindingDescriptor} from './binding_utils';
import {AstResult} from './common'; import {getExpressionScope} from './expression_diagnostics';
import {diagnosticInfoFromTemplateInfo, getExpressionScope} from './expression_diagnostics';
import {getExpressionCompletions} from './expressions'; import {getExpressionCompletions} from './expressions';
import {attributeNames, elementNames, eventNames, propertyNames} from './html_info'; import {attributeNames, elementNames, eventNames, propertyNames} from './html_info';
import {InlineTemplate} from './template'; import {InlineTemplate} from './template';
import * as ng from './types'; import * as ng from './types';
import {findTemplateAstAt, getPathToNodeAtPosition, getSelectors, inSpan, isStructuralDirective, spanOf} from './utils'; import {diagnosticInfoFromTemplateInfo, findTemplateAstAt, getPathToNodeAtPosition, getSelectors, inSpan, isStructuralDirective, spanOf} from './utils';
const HIDDEN_HTML_ELEMENTS: ReadonlySet<string> = const HIDDEN_HTML_ELEMENTS: ReadonlySet<string> =
new Set(['html', 'script', 'noscript', 'base', 'body', 'title', 'head', 'link']); new Set(['html', 'script', 'noscript', 'base', 'body', 'title', 'head', 'link']);
@ -56,7 +55,7 @@ function isIdentifierPart(code: number) {
* `position`, nothing is returned. * `position`, nothing is returned.
*/ */
function getBoundedWordSpan( function getBoundedWordSpan(
templateInfo: AstResult, position: number, ast: HtmlAst|undefined): ts.TextSpan|undefined { templateInfo: ng.AstResult, position: number, ast: HtmlAst|undefined): ts.TextSpan|undefined {
const {template} = templateInfo; const {template} = templateInfo;
const templateSrc = template.source; const templateSrc = template.source;
@ -127,7 +126,7 @@ function getBoundedWordSpan(
} }
export function getTemplateCompletions( export function getTemplateCompletions(
templateInfo: AstResult, position: number): ng.CompletionEntry[] { templateInfo: ng.AstResult, position: number): ng.CompletionEntry[] {
let result: ng.CompletionEntry[] = []; let result: ng.CompletionEntry[] = [];
const {htmlAst, template} = templateInfo; const {htmlAst, template} = templateInfo;
// The templateNode starts at the delimiter character so we add 1 to skip it. // The templateNode starts at the delimiter character so we add 1 to skip it.
@ -204,7 +203,7 @@ export function getTemplateCompletions(
}); });
} }
function attributeCompletions(info: AstResult, path: AstPath<HtmlAst>): ng.CompletionEntry[] { function attributeCompletions(info: ng.AstResult, path: AstPath<HtmlAst>): ng.CompletionEntry[] {
const attr = path.tail; const attr = path.tail;
const elem = path.parentOf(attr); const elem = path.parentOf(attr);
if (!(attr instanceof Attribute) || !(elem instanceof Element)) { if (!(attr instanceof Attribute) || !(elem instanceof Element)) {
@ -258,7 +257,7 @@ function attributeCompletions(info: AstResult, path: AstPath<HtmlAst>): ng.Compl
} }
function attributeCompletionsForElement( function attributeCompletionsForElement(
info: AstResult, elementName: string): ng.CompletionEntry[] { info: ng.AstResult, elementName: string): ng.CompletionEntry[] {
const results: ng.CompletionEntry[] = []; const results: ng.CompletionEntry[] = [];
if (info.template instanceof InlineTemplate) { if (info.template instanceof InlineTemplate) {
@ -292,7 +291,8 @@ function attributeCompletionsForElement(
* @param info Object that contains the template AST * @param info Object that contains the template AST
* @param htmlPath Path to the HTML node * @param htmlPath Path to the HTML node
*/ */
function attributeValueCompletions(info: AstResult, htmlPath: HtmlAstPath): ng.CompletionEntry[] { function attributeValueCompletions(
info: ng.AstResult, htmlPath: HtmlAstPath): ng.CompletionEntry[] {
// Find the corresponding Template AST path. // Find the corresponding Template AST path.
const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position); const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position);
const visitor = new ExpressionVisitor(info, htmlPath.position, () => { const visitor = new ExpressionVisitor(info, htmlPath.position, () => {
@ -334,7 +334,7 @@ function attributeValueCompletions(info: AstResult, htmlPath: HtmlAstPath): ng.C
return visitor.results; return visitor.results;
} }
function elementCompletions(info: AstResult): ng.CompletionEntry[] { function elementCompletions(info: ng.AstResult): ng.CompletionEntry[] {
const results: ng.CompletionEntry[] = [...ANGULAR_ELEMENTS]; const results: ng.CompletionEntry[] = [...ANGULAR_ELEMENTS];
if (info.template instanceof InlineTemplate) { if (info.template instanceof InlineTemplate) {
@ -380,7 +380,7 @@ function entityCompletions(value: string, position: number): ng.CompletionEntry[
return result; return result;
} }
function interpolationCompletions(info: AstResult, position: number): ng.CompletionEntry[] { function interpolationCompletions(info: ng.AstResult, position: number): ng.CompletionEntry[] {
// Look for an interpolation in at the position. // Look for an interpolation in at the position.
const templatePath = findTemplateAstAt(info.templateAst, position); const templatePath = findTemplateAstAt(info.templateAst, position);
if (!templatePath.tail) { if (!templatePath.tail) {
@ -399,7 +399,7 @@ function interpolationCompletions(info: AstResult, position: number): ng.Complet
// code checks for this case and returns element completions if it is detected or undefined // code checks for this case and returns element completions if it is detected or undefined
// if it is not. // if it is not.
function voidElementAttributeCompletions( function voidElementAttributeCompletions(
info: AstResult, path: AstPath<HtmlAst>): ng.CompletionEntry[] { info: ng.AstResult, path: AstPath<HtmlAst>): ng.CompletionEntry[] {
const tail = path.tail; const tail = path.tail;
if (tail instanceof Text) { if (tail instanceof Text) {
const match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/); const match = tail.value.match(/<(\w(\w|\d|-)*:)?(\w(\w|\d|-)*)\s/);
@ -417,7 +417,7 @@ class ExpressionVisitor extends NullTemplateVisitor {
private readonly completions = new Map<string, ng.CompletionEntry>(); private readonly completions = new Map<string, ng.CompletionEntry>();
constructor( constructor(
private readonly info: AstResult, private readonly position: number, private readonly info: ng.AstResult, private readonly position: number,
private readonly getExpressionScope: () => ng.SymbolTable) { private readonly getExpressionScope: () => ng.SymbolTable) {
super(); super();
} }
@ -619,7 +619,7 @@ interface AngularAttributes {
* @param info * @param info
* @param elementName * @param elementName
*/ */
function angularAttributes(info: AstResult, elementName: string): AngularAttributes { function angularAttributes(info: ng.AstResult, elementName: string): AngularAttributes {
const {selectors, map: selectorMap} = getSelectors(info); const {selectors, map: selectorMap} = getSelectors(info);
const templateRefs = new Set<string>(); const templateRefs = new Set<string>();
const inputs = new Set<string>(); const inputs = new Set<string>();

View File

@ -8,11 +8,10 @@
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; // used as value and is provided at runtime import * as ts from 'typescript'; // used as value and is provided at runtime
import {AstResult} from './common';
import {locateSymbols} from './locate_symbol'; import {locateSymbols} from './locate_symbol';
import {getPropertyAssignmentFromValue, isClassDecoratorProperty} from './template'; import {AstResult, Span} from './types';
import {Span} from './types'; import {findTightestNode, getPropertyAssignmentFromValue, isClassDecoratorProperty} from './utils';
import {findTightestNode} from './utils';
/** /**
* Convert Angular Span to TypeScript TextSpan. Angular Span has 'start' and * Convert Angular Span to TypeScript TextSpan. Angular Span has 'start' and

View File

@ -10,20 +10,17 @@ import {NgAnalyzedModules} from '@angular/compiler';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AstResult} from './common';
import {createDiagnostic, Diagnostic} from './diagnostic_messages'; import {createDiagnostic, Diagnostic} from './diagnostic_messages';
import {getTemplateExpressionDiagnostics} from './expression_diagnostics'; import {getTemplateExpressionDiagnostics} from './expression_diagnostics';
import * as ng from './types'; import * as ng from './types';
import {TypeScriptServiceHost} from './typescript_host'; import {TypeScriptServiceHost} from './typescript_host';
import {findPropertyValueOfType, findTightestNode, offsetSpan, spanOf} from './utils'; import {findPropertyValueOfType, findTightestNode, offsetSpan, spanOf} from './utils';
/** /**
* Return diagnostic information for the parsed AST of the template. * Return diagnostic information for the parsed AST of the template.
* @param ast contains HTML and template AST * @param ast contains HTML and template AST
*/ */
export function getTemplateDiagnostics(ast: AstResult): ng.Diagnostic[] { export function getTemplateDiagnostics(ast: ng.AstResult): ng.Diagnostic[] {
const {parseErrors, templateAst, htmlAst, template} = ast; const {parseErrors, templateAst, htmlAst, template} = ast;
if (parseErrors && parseErrors.length) { if (parseErrors && parseErrors.length) {
return parseErrors.map(e => { return parseErrors.map(e => {

View File

@ -6,33 +6,22 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {AST, AstPath, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, CompileDirectiveSummary, CompileTypeMetadata, DirectiveAst, ElementAst, EmbeddedTemplateAst, identifierName, Node, ParseSourceSpan, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference, VariableAst} from '@angular/compiler'; import {AST, AstPath, Attribute, BoundDirectivePropertyAst, BoundElementPropertyAst, BoundEventAst, BoundTextAst, CompileDirectiveSummary, CompileTypeMetadata, DirectiveAst, ElementAst, EmbeddedTemplateAst, identifierName, ParseSourceSpan, RecursiveTemplateAstVisitor, ReferenceAst, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference, VariableAst} from '@angular/compiler';
import {AstResult} from './common';
import {createDiagnostic, Diagnostic} from './diagnostic_messages'; import {createDiagnostic, Diagnostic} from './diagnostic_messages';
import {AstType} from './expression_type'; import {AstType} from './expression_type';
import {BuiltinType, Definition, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols'; import {BuiltinType, Definition, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols';
import * as ng from './types'; import * as ng from './types';
import {findOutputBinding, getPathToNodeAtPosition} from './utils'; import {findOutputBinding, getPathToNodeAtPosition} from './utils';
export interface DiagnosticTemplateInfo { export function getTemplateExpressionDiagnostics(info: ng.DiagnosticTemplateInfo): ng.Diagnostic[] {
fileName?: string;
offset: number;
query: SymbolQuery;
members: SymbolTable;
htmlAst: Node[];
templateAst: TemplateAst[];
source: string;
}
export function getTemplateExpressionDiagnostics(info: DiagnosticTemplateInfo): ng.Diagnostic[] {
const visitor = new ExpressionDiagnosticsVisitor( const visitor = new ExpressionDiagnosticsVisitor(
info, (path: TemplateAstPath) => getExpressionScope(info, path)); info, (path: TemplateAstPath) => getExpressionScope(info, path));
templateVisitAll(visitor, info.templateAst); templateVisitAll(visitor, info.templateAst);
return visitor.diagnostics; return visitor.diagnostics;
} }
function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] { function getReferences(info: ng.DiagnosticTemplateInfo): SymbolDeclaration[] {
const result: SymbolDeclaration[] = []; const result: SymbolDeclaration[] = [];
function processReferences(references: ReferenceAst[]) { function processReferences(references: ReferenceAst[]) {
@ -68,7 +57,7 @@ function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] {
return result; return result;
} }
function getDefinitionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined { function getDefinitionOf(info: ng.DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined {
if (info.fileName) { if (info.fileName) {
const templateOffset = info.offset; const templateOffset = info.offset;
return [{ return [{
@ -88,7 +77,7 @@ function getDefinitionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Defini
* @param path template AST path * @param path template AST path
*/ */
function getVarDeclarations( function getVarDeclarations(
info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration[] { info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration[] {
const results: SymbolDeclaration[] = []; const results: SymbolDeclaration[] = [];
for (let current = path.head; current; current = path.childOf(current)) { for (let current = path.head; current; current = path.childOf(current)) {
if (!(current instanceof EmbeddedTemplateAst)) { if (!(current instanceof EmbeddedTemplateAst)) {
@ -154,7 +143,7 @@ function getVariableTypeFromDirectiveContext(
* @param templateElement * @param templateElement
*/ */
function refinedVariableType( function refinedVariableType(
value: string, mergedTable: SymbolTable, info: DiagnosticTemplateInfo, value: string, mergedTable: SymbolTable, info: ng.DiagnosticTemplateInfo,
templateElement: EmbeddedTemplateAst): Symbol { templateElement: EmbeddedTemplateAst): Symbol {
if (value === '$implicit') { if (value === '$implicit') {
// Special case: ngFor directive // Special case: ngFor directive
@ -206,7 +195,7 @@ function refinedVariableType(
} }
function getEventDeclaration( function getEventDeclaration(
info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration|undefined { info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration|undefined {
const event = path.tail; const event = path.tail;
if (!(event instanceof BoundEventAst)) { if (!(event instanceof BoundEventAst)) {
// No event available in this context. // No event available in this context.
@ -241,7 +230,7 @@ function getEventDeclaration(
* derived for. * derived for.
*/ */
export function getExpressionScope( export function getExpressionScope(
info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable { info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable {
let result = info.members; let result = info.members;
const references = getReferences(info); const references = getReferences(info);
const variables = getVarDeclarations(info, path); const variables = getVarDeclarations(info, path);
@ -262,7 +251,7 @@ class ExpressionDiagnosticsVisitor extends RecursiveTemplateAstVisitor {
diagnostics: ng.Diagnostic[] = []; diagnostics: ng.Diagnostic[] = [];
constructor( constructor(
private info: DiagnosticTemplateInfo, private info: ng.DiagnosticTemplateInfo,
private getExpressionScope: (path: TemplateAstPath, includeEvent: boolean) => SymbolTable) { private getExpressionScope: (path: TemplateAstPath, includeEvent: boolean) => SymbolTable) {
super(); super();
this.path = new AstPath<TemplateAst>([]); this.path = new AstPath<TemplateAst>([]);
@ -386,16 +375,3 @@ function hasTemplateReference(type: CompileTypeMetadata): boolean {
function spanOf(sourceSpan: ParseSourceSpan): Span { function spanOf(sourceSpan: ParseSourceSpan): Span {
return {start: sourceSpan.start.offset, end: sourceSpan.end.offset}; return {start: sourceSpan.start.offset, end: sourceSpan.end.offset};
} }
export function diagnosticInfoFromTemplateInfo(info: AstResult): DiagnosticTemplateInfo {
return {
fileName: info.template.fileName,
offset: info.template.span.start,
query: info.template.query,
members: info.template.members,
htmlAst: info.htmlAst,
templateAst: info.templateAst,
source: info.template.source,
};
}

View File

@ -12,7 +12,7 @@ import {createDiagnostic, Diagnostic} from './diagnostic_messages';
import {BuiltinType, Signature, Symbol, SymbolQuery, SymbolTable} from './symbols'; import {BuiltinType, Signature, Symbol, SymbolQuery, SymbolTable} from './symbols';
import * as ng from './types'; import * as ng from './types';
export interface ExpressionDiagnosticsContext { interface ExpressionDiagnosticsContext {
inEvent?: boolean; inEvent?: boolean;
} }
@ -225,7 +225,7 @@ export class AstType implements AstVisitor {
return this.anyType; return this.anyType;
} }
visitImplicitReceiver(ast: ImplicitReceiver): Symbol { visitImplicitReceiver(_ast: ImplicitReceiver): Symbol {
const _this = this; const _this = this;
// Return a pseudo-symbol for the implicit receiver. // Return a pseudo-symbol for the implicit receiver.
// The members of the implicit receiver are what is defined by the // The members of the implicit receiver are what is defined by the
@ -247,11 +247,11 @@ export class AstType implements AstVisitor {
signatures(): Signature[] { signatures(): Signature[] {
return []; return [];
}, },
selectSignature(types): Signature | selectSignature(_types): Signature |
undefined { undefined {
return undefined; return undefined;
}, },
indexed(argument): Symbol | indexed(_argument): Symbol |
undefined { undefined {
return undefined; return undefined;
}, },
@ -366,7 +366,7 @@ export class AstType implements AstVisitor {
return this.getType(ast.value); return this.getType(ast.value);
} }
visitQuote(ast: Quote) { visitQuote(_ast: Quote) {
// The type of a quoted expression is any. // The type of a quoted expression is any.
return this.query.getBuiltinType(BuiltinType.Any); return this.query.getBuiltinType(BuiltinType.Any);
} }

View File

@ -53,20 +53,20 @@ export function getExpressionCompletions(
// (that is the scope of the implicit receiver) is the right scope as the user is typing the // (that is the scope of the implicit receiver) is the right scope as the user is typing the
// beginning of an expression. // beginning of an expression.
tail.visit({ tail.visit({
visitBinary(ast) {}, visitBinary(_ast) {},
visitChain(ast) {}, visitChain(_ast) {},
visitConditional(ast) {}, visitConditional(_ast) {},
visitFunctionCall(ast) {}, visitFunctionCall(_ast) {},
visitImplicitReceiver(ast) {}, visitImplicitReceiver(_ast) {},
visitInterpolation(ast) { visitInterpolation(_ast) {
result = undefined; result = undefined;
}, },
visitKeyedRead(ast) {}, visitKeyedRead(_ast) {},
visitKeyedWrite(ast) {}, visitKeyedWrite(_ast) {},
visitLiteralArray(ast) {}, visitLiteralArray(_ast) {},
visitLiteralMap(ast) {}, visitLiteralMap(_ast) {},
visitLiteralPrimitive(ast) {}, visitLiteralPrimitive(_ast) {},
visitMethodCall(ast) {}, visitMethodCall(_ast) {},
visitPipe(ast) { visitPipe(ast) {
if (position >= ast.exp.span.end && if (position >= ast.exp.span.end &&
(!ast.args || !ast.args.length || position < (<AST>ast.args[0]).span.start)) { (!ast.args || !ast.args.length || position < (<AST>ast.args[0]).span.start)) {
@ -74,8 +74,8 @@ export function getExpressionCompletions(
result = templateInfo.query.getPipes(); result = templateInfo.query.getPipes();
} }
}, },
visitPrefixNot(ast) {}, visitPrefixNot(_ast) {},
visitNonNullAssert(ast) {}, visitNonNullAssert(_ast) {},
visitPropertyRead(ast) { visitPropertyRead(ast) {
const receiverType = getType(ast.receiver); const receiverType = getType(ast.receiver);
result = receiverType ? receiverType.members() : scope; result = receiverType ? receiverType.members() : scope;
@ -84,7 +84,7 @@ export function getExpressionCompletions(
const receiverType = getType(ast.receiver); const receiverType = getType(ast.receiver);
result = receiverType ? receiverType.members() : scope; result = receiverType ? receiverType.members() : scope;
}, },
visitQuote(ast) { visitQuote(_ast) {
// For a quote, return the members of any (if there are any). // For a quote, return the members of any (if there are any).
result = templateInfo.query.getBuiltinType(BuiltinType.Any).members(); result = templateInfo.query.getBuiltinType(BuiltinType.Any).members();
}, },
@ -127,17 +127,17 @@ export function getExpressionSymbol(
// (that is the scope of the implicit receiver) is the right scope as the user is typing the // (that is the scope of the implicit receiver) is the right scope as the user is typing the
// beginning of an expression. // beginning of an expression.
tail.visit({ tail.visit({
visitBinary(ast) {}, visitBinary(_ast) {},
visitChain(ast) {}, visitChain(_ast) {},
visitConditional(ast) {}, visitConditional(_ast) {},
visitFunctionCall(ast) {}, visitFunctionCall(_ast) {},
visitImplicitReceiver(ast) {}, visitImplicitReceiver(_ast) {},
visitInterpolation(ast) {}, visitInterpolation(_ast) {},
visitKeyedRead(ast) {}, visitKeyedRead(_ast) {},
visitKeyedWrite(ast) {}, visitKeyedWrite(_ast) {},
visitLiteralArray(ast) {}, visitLiteralArray(_ast) {},
visitLiteralMap(ast) {}, visitLiteralMap(_ast) {},
visitLiteralPrimitive(ast) {}, visitLiteralPrimitive(_ast) {},
visitMethodCall(ast) { visitMethodCall(ast) {
const receiverType = getType(ast.receiver); const receiverType = getType(ast.receiver);
symbol = receiverType && receiverType.members().get(ast.name); symbol = receiverType && receiverType.members().get(ast.name);
@ -159,8 +159,8 @@ export function getExpressionSymbol(
}; };
} }
}, },
visitPrefixNot(ast) {}, visitPrefixNot(_ast) {},
visitNonNullAssert(ast) {}, visitNonNullAssert(_ast) {},
visitPropertyRead(ast) { visitPropertyRead(ast) {
const receiverType = getType(ast.receiver); const receiverType = getType(ast.receiver);
symbol = receiverType && receiverType.members().get(ast.name); symbol = receiverType && receiverType.members().get(ast.name);
@ -177,7 +177,7 @@ export function getExpressionSymbol(
// ^^^^^^ value; visited separately as a nested AST // ^^^^^^ value; visited separately as a nested AST
span = {start, end: start + ast.name.length}; span = {start, end: start + ast.name.length};
}, },
visitQuote(ast) {}, visitQuote(_ast) {},
visitSafeMethodCall(ast) { visitSafeMethodCall(ast) {
const receiverType = getType(ast.receiver); const receiverType = getType(ast.receiver);
symbol = receiverType && receiverType.members().get(ast.name); symbol = receiverType && receiverType.members().get(ast.name);

View File

@ -8,7 +8,6 @@
import {NgAnalyzedModules} from '@angular/compiler'; import {NgAnalyzedModules} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AstResult} from './common';
import {locateSymbols} from './locate_symbol'; import {locateSymbols} from './locate_symbol';
import * as ng from './types'; import * as ng from './types';
import {inSpan} from './utils'; import {inSpan} from './utils';
@ -27,7 +26,8 @@ const SYMBOL_INTERFACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.inter
* @param analyzedModules all NgModules in the program. * @param analyzedModules all NgModules in the program.
*/ */
export function getTemplateHover( export function getTemplateHover(
info: AstResult, position: number, analyzedModules: NgAnalyzedModules): ts.QuickInfo|undefined { info: ng.AstResult, position: number, analyzedModules: NgAnalyzedModules): ts.QuickInfo|
undefined {
const symbolInfo = locateSymbols(info, position)[0]; const symbolInfo = locateSymbols(info, position)[0];
if (!symbolInfo) { if (!symbolInfo) {
return; return;

View File

@ -453,7 +453,3 @@ export function eventNames(elementName: string): string[] {
export function propertyNames(elementName: string): string[] { export function propertyNames(elementName: string): string[] {
return SchemaInformation.instance.propertiesOf(elementName); return SchemaInformation.instance.propertiesOf(elementName);
} }
export function propertyType(elementName: string, propertyName: string): string {
return SchemaInformation.instance.typeOf(elementName, propertyName);
}

View File

@ -49,7 +49,7 @@ class LanguageServiceImpl implements ng.LanguageService {
getCompletionsAtPosition( getCompletionsAtPosition(
fileName: string, position: number, fileName: string, position: number,
options?: tss.GetCompletionsAtPositionOptions): tss.CompletionInfo|undefined { _options?: tss.GetCompletionsAtPositionOptions): tss.CompletionInfo|undefined {
this.host.getAnalyzedModules(); // same role as 'synchronizeHostData' this.host.getAnalyzedModules(); // same role as 'synchronizeHostData'
const ast = this.host.getTemplateAstAtPosition(fileName, position); const ast = this.host.getTemplateAstAtPosition(fileName, position);
if (!ast) { if (!ast) {

View File

@ -9,17 +9,10 @@
import {AST, Attribute, BoundDirectivePropertyAst, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, RecursiveTemplateAstVisitor, SelectorMatcher, StaticSymbol, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference, VariableBinding} from '@angular/compiler'; import {AST, Attribute, BoundDirectivePropertyAst, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, RecursiveTemplateAstVisitor, SelectorMatcher, StaticSymbol, TemplateAst, TemplateAstPath, templateVisitAll, tokenReference, VariableBinding} from '@angular/compiler';
import * as tss from 'typescript/lib/tsserverlibrary'; import * as tss from 'typescript/lib/tsserverlibrary';
import {AstResult} from './common'; import {getExpressionScope} from './expression_diagnostics';
import {diagnosticInfoFromTemplateInfo, getExpressionScope} from './expression_diagnostics';
import {getExpressionSymbol} from './expressions'; import {getExpressionSymbol} from './expressions';
import {Definition, DirectiveKind, Span, Symbol} from './types'; import {AstResult, Definition, DirectiveKind, Span, Symbol, SymbolInfo} from './types';
import {findOutputBinding, findTemplateAstAt, getPathToNodeAtPosition, inSpan, invertMap, isNarrower, offsetSpan, spanOf} from './utils'; import {diagnosticInfoFromTemplateInfo, findOutputBinding, findTemplateAstAt, getPathToNodeAtPosition, inSpan, invertMap, isNarrower, offsetSpan, spanOf} from './utils';
export interface SymbolInfo {
symbol: Symbol;
span: tss.TextSpan;
staticSymbol?: StaticSymbol;
}
/** /**
* Traverses a template AST and locates symbol(s) at a specified position. * Traverses a template AST and locates symbol(s) at a specified position.
@ -86,8 +79,8 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult):
}; };
ast.visit( ast.visit(
{ {
visitNgContent(ast) {}, visitNgContent(_ast) {},
visitEmbeddedTemplate(ast) {}, visitEmbeddedTemplate(_ast) {},
visitElement(ast) { visitElement(ast) {
const component = ast.directives.find(d => d.directive.isComponent); const component = ast.directives.find(d => d.directive.isComponent);
if (component) { if (component) {
@ -113,7 +106,7 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult):
symbol = ast.value && info.template.query.getTypeSymbol(tokenReference(ast.value)); symbol = ast.value && info.template.query.getTypeSymbol(tokenReference(ast.value));
span = spanOf(ast); span = spanOf(ast);
}, },
visitVariable(ast) {}, visitVariable(_ast) {},
visitEvent(ast) { visitEvent(ast) {
if (!attributeValueSymbol(ast.handler)) { if (!attributeValueSymbol(ast.handler)) {
symbol = findOutputBinding(ast, path, info.template.query); symbol = findOutputBinding(ast, path, info.template.query);
@ -158,7 +151,7 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult):
} }
} }
}, },
visitText(ast) {}, visitText(_ast) {},
visitDirective(ast) { visitDirective(ast) {
// Need to cast because 'reference' is typed as any // Need to cast because 'reference' is typed as any
staticSymbol = ast.directive.type.reference as StaticSymbol; staticSymbol = ast.directive.type.reference as StaticSymbol;

View File

@ -132,62 +132,3 @@ export class ExternalTemplate extends BaseTemplate {
}; };
} }
} }
/**
* Returns a property assignment from the assignment value, or `undefined` if there is no
* assignment.
*/
export function getPropertyAssignmentFromValue(value: ts.Node): ts.PropertyAssignment|undefined {
if (!value.parent || !ts.isPropertyAssignment(value.parent)) {
return;
}
return value.parent;
}
/**
* Given a decorator property assignment, return the ClassDeclaration node that corresponds to the
* directive class the property applies to.
* If the property assignment is not on a class decorator, no declaration is returned.
*
* For example,
*
* @Component({
* template: '<div></div>'
* ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment
* })
* class AppComponent {}
* ^---- class declaration node
*
* @param propAsgn property assignment
*/
export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment):
ts.ClassDeclaration|undefined {
if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) {
return;
}
const objLitExprNode = propAsgnNode.parent;
if (!objLitExprNode.parent || !ts.isCallExpression(objLitExprNode.parent)) {
return;
}
const callExprNode = objLitExprNode.parent;
if (!callExprNode.parent || !ts.isDecorator(callExprNode.parent)) {
return;
}
const decorator = callExprNode.parent;
if (!decorator.parent || !ts.isClassDeclaration(decorator.parent)) {
return;
}
const classDeclNode = decorator.parent;
return classDeclNode;
}
/**
* Determines if a property assignment is on a class decorator.
* See `getClassDeclFromDecoratorProperty`, which gets the class the decorator is applied to, for
* more details.
*
* @param prop property assignment
*/
export function isClassDecoratorProperty(propAsgn: ts.PropertyAssignment): boolean {
return !!getClassDeclFromDecoratorProp(propAsgn);
}

View File

@ -6,26 +6,13 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {CompileDirectiveMetadata, NgAnalyzedModules, StaticSymbol} from '@angular/compiler'; import {CompileDirectiveMetadata, CompileDirectiveSummary, CompilePipeSummary, CssSelector, NgAnalyzedModules, Node as HtmlAst, ParseError, Parser, StaticSymbol, TemplateAst} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AstResult} from './common';
import {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols';
export { import {Span, Symbol, SymbolQuery, SymbolTable} from './symbols';
BuiltinType,
DeclarationKind,
Definition,
PipeInfo,
Pipes,
Signature,
Span,
StaticSymbol,
Symbol,
SymbolDeclaration,
SymbolQuery,
SymbolTable
};
export {StaticSymbol} from '@angular/compiler';
export {BuiltinType, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols';
/** /**
* The information `LanguageService` needs from the `LanguageServiceHost` to describe the content of * The information `LanguageService` needs from the `LanguageServiceHost` to describe the content of
@ -67,15 +54,6 @@ export interface TemplateSource {
readonly fileName: string; readonly fileName: string;
} }
/**
* A sequence of template sources.
*
* A host type; see `LanguageServiceHost`.
*
* @publicApi
*/
export type TemplateSources = TemplateSource[]|undefined;
/** /**
* Error information found getting declaration information * Error information found getting declaration information
* *
@ -132,15 +110,6 @@ export interface Declaration {
readonly errors: DeclarationError[]; readonly errors: DeclarationError[];
} }
/**
* A sequence of declarations.
*
* A host type; see `LanguageServiceHost`.
*
* @publicApi
*/
export type Declarations = Declaration[];
/** /**
* The host for a `LanguageService`. This provides all the `LanguageService` requires to respond * The host for a `LanguageService`. This provides all the `LanguageService` requires to respond
* to the `LanguageService` requests. * to the `LanguageService` requests.
@ -178,7 +147,7 @@ export interface LanguageServiceHost {
/** /**
* Returns the Angular declarations in the given file. * Returns the Angular declarations in the given file.
*/ */
getDeclarations(fileName: string): Declarations; getDeclarations(fileName: string): Declaration[];
/** /**
* Return a summary of all Angular modules in the project. * Return a summary of all Angular modules in the project.
@ -196,45 +165,6 @@ export interface LanguageServiceHost {
getTemplateAstAtPosition(fileName: string, position: number): AstResult|undefined; getTemplateAstAtPosition(fileName: string, position: number): AstResult|undefined;
} }
/**
* An item of the completion result to be displayed by an editor.
*
* A `LanguageService` interface.
*
* @publicApi
*/
export interface Completion {
/**
* The kind of completion.
*/
kind: DeclarationKind;
/**
* The name of the completion to be displayed
*/
name: string;
/**
* The key to use to sort the completions for display.
*/
sort: string;
}
/**
* A sequence of completions.
*
* @deprecated
*/
export type Completions = Completion[];
/**
* A file and span.
*/
export interface Location {
fileName: string;
span: Span;
}
/** /**
* The type of Angular directive. Used for QuickInfo in template. * The type of Angular directive. Used for QuickInfo in template.
*/ */
@ -312,45 +242,6 @@ export interface Diagnostic {
message: string|DiagnosticMessageChain; message: string|DiagnosticMessageChain;
} }
/**
* A sequence of diagnostic message.
*
* @deprecated
*/
export type Diagnostics = Diagnostic[];
/**
* A section of hover text. If the text is code then language should be provided.
* Otherwise the text is assumed to be Markdown text that will be sanitized.
*/
export interface HoverTextSection {
/**
* Source code or markdown text describing the symbol a the hover location.
*/
readonly text: string;
/**
* The language of the source if `text` is a source code fragment.
*/
readonly language?: string;
}
/**
* Hover information for a symbol at the hover location.
*/
export interface Hover {
/**
* The hover text to display for the symbol at the hover location. If the text includes
* source code, the section will specify which language it should be interpreted as.
*/
readonly text: HoverTextSection[];
/**
* The span of source the hover covers.
*/
readonly span: Span;
}
/** /**
* An instance of an Angular language service created by `createLanguageService()`. * An instance of an Angular language service created by `createLanguageService()`.
* *
@ -364,3 +255,38 @@ export type LanguageService = Pick<
ts.LanguageService, ts.LanguageService,
'getCompletionsAtPosition'|'getDefinitionAndBoundSpan'|'getQuickInfoAtPosition'| 'getCompletionsAtPosition'|'getDefinitionAndBoundSpan'|'getQuickInfoAtPosition'|
'getSemanticDiagnostics'>; 'getSemanticDiagnostics'>;
/** Information about an Angular template AST. */
export interface AstResult {
htmlAst: HtmlAst[];
templateAst: TemplateAst[];
directive: CompileDirectiveMetadata;
directives: CompileDirectiveSummary[];
pipes: CompilePipeSummary[];
parseErrors?: ParseError[];
expressionParser: Parser;
template: TemplateSource;
}
/** Information about a directive's selectors. */
export type SelectorInfo = {
selectors: CssSelector[],
map: Map<CssSelector, CompileDirectiveSummary>
};
export interface SymbolInfo {
symbol: Symbol;
span: ts.TextSpan;
staticSymbol?: StaticSymbol;
}
/** TODO: this should probably be merged with AstResult */
export interface DiagnosticTemplateInfo {
fileName?: string;
offset: number;
query: SymbolQuery;
members: SymbolTable;
htmlAst: HtmlAst[];
templateAst: TemplateAst[];
source: string;
}

View File

@ -6,16 +6,15 @@
* found in the LICENSE file at https://angular.io/license * found in the LICENSE file at https://angular.io/license
*/ */
import {analyzeNgModules, AotSummaryResolver, CompileDirectiveSummary, CompileMetadataResolver, CompileNgModuleMetadata, CompilePipeSummary, CompilerConfig, createOfflineCompileUrlResolver, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, FormattedError, FormattedMessageChain, HtmlParser, I18NHtmlParser, isFormattedError, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, Parser, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser} from '@angular/compiler'; import {analyzeNgModules, AotSummaryResolver, CompileDirectiveSummary, CompileMetadataResolver, CompileNgModuleMetadata, CompilePipeSummary, CompilerConfig, createOfflineCompileUrlResolver, DirectiveNormalizer, DirectiveResolver, DomElementSchemaRegistry, FormattedError, FormattedMessageChain, HtmlParser, isFormattedError, JitSummaryResolver, Lexer, NgAnalyzedModules, NgModuleResolver, Parser, ParseTreeResult, PipeResolver, ResourceLoader, StaticReflector, StaticSymbol, StaticSymbolCache, StaticSymbolResolver, TemplateParser} from '@angular/compiler';
import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/core'; import {SchemaMetadata, ViewEncapsulation, ɵConsole as Console} from '@angular/core';
import * as tss from 'typescript/lib/tsserverlibrary'; import * as tss from 'typescript/lib/tsserverlibrary';
import {AstResult} from './common';
import {createLanguageService} from './language_service'; import {createLanguageService} from './language_service';
import {ReflectorHost} from './reflector_host'; import {ReflectorHost} from './reflector_host';
import {ExternalTemplate, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue, InlineTemplate} from './template'; import {ExternalTemplate, InlineTemplate} from './template';
import {Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types'; import {AstResult, Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
import {findTightestNode, getDirectiveClassLike} from './utils'; import {findTightestNode, getClassDeclFromDecoratorProp, getDirectiveClassLike, getPropertyAssignmentFromValue} from './utils';
/** /**
@ -44,7 +43,7 @@ export class DummyHtmlParser extends HtmlParser {
* Avoid loading resources in the language servcie by using a dummy loader. * Avoid loading resources in the language servcie by using a dummy loader.
*/ */
export class DummyResourceLoader extends ResourceLoader { export class DummyResourceLoader extends ResourceLoader {
get(url: string): Promise<string> { get(_url: string): Promise<string> {
return Promise.resolve(''); return Promise.resolve('');
} }
} }
@ -78,10 +77,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
readonly tsLsHost: tss.LanguageServiceHost, private readonly tsLS: tss.LanguageService) { readonly tsLsHost: tss.LanguageServiceHost, private readonly tsLS: tss.LanguageService) {
this.summaryResolver = new AotSummaryResolver( this.summaryResolver = new AotSummaryResolver(
{ {
loadSummary(filePath: string) { loadSummary(_filePath: string) {
return null; return null;
}, },
isSourceFile(sourceFilePath: string) { isSourceFile(_sourceFilePath: string) {
return true; return true;
}, },
toSummaryFileName(sourceFilePath: string) { toSummaryFileName(sourceFilePath: string) {
@ -172,7 +171,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
this.resolver.clearCache(); this.resolver.clearCache();
const analyzeHost = { const analyzeHost = {
isSourceFile(filePath: string) { isSourceFile(_filePath: string) {
return true; return true;
} }
}; };

View File

@ -57,8 +57,14 @@ export function getClassMembersFromDeclaration(
return new TypeWrapper(type, {node: source, program, checker}).members(); return new TypeWrapper(type, {node: source, program, checker}).members();
} }
export function getClassFromStaticSymbol( export function getPipesTable(
program: ts.Program, type: StaticSymbol): ts.ClassDeclaration|undefined { source: ts.SourceFile, program: ts.Program, checker: ts.TypeChecker,
pipes: CompilePipeSummary[]): SymbolTable {
return new PipesTable(pipes, {program, checker, node: source});
}
function getClassFromStaticSymbol(program: ts.Program, type: StaticSymbol): ts.ClassDeclaration|
undefined {
const source = program.getSourceFile(type.filePath); const source = program.getSourceFile(type.filePath);
if (source) { if (source) {
return ts.forEachChild(source, child => { return ts.forEachChild(source, child => {
@ -74,12 +80,6 @@ export function getClassFromStaticSymbol(
return undefined; return undefined;
} }
export function getPipesTable(
source: ts.SourceFile, program: ts.Program, checker: ts.TypeChecker,
pipes: CompilePipeSummary[]): SymbolTable {
return new PipesTable(pipes, {program, checker, node: source});
}
class TypeScriptSymbolQuery implements SymbolQuery { class TypeScriptSymbolQuery implements SymbolQuery {
private typeCache = new Map<BuiltinType, Symbol>(); private typeCache = new Map<BuiltinType, Symbol>();
private pipesCache: SymbolTable|undefined; private pipesCache: SymbolTable|undefined;
@ -123,7 +123,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
return result || this.getBuiltinType(BuiltinType.Any); return result || this.getBuiltinType(BuiltinType.Any);
} }
getArrayType(type: Symbol): Symbol { getArrayType(_type: Symbol): Symbol {
return this.getBuiltinType(BuiltinType.Any); return this.getBuiltinType(BuiltinType.Any);
} }
@ -222,7 +222,7 @@ function signaturesOf(type: ts.Type, context: TypeContext): Signature[] {
return type.getCallSignatures().map(s => new SignatureWrapper(s, context)); return type.getCallSignatures().map(s => new SignatureWrapper(s, context));
} }
function selectSignature(type: ts.Type, context: TypeContext, types: Symbol[]): Signature| function selectSignature(type: ts.Type, context: TypeContext, _types: Symbol[]): Signature|
undefined { undefined {
// TODO: Do a better job of selecting the right signature. TypeScript does not currently support a // TODO: Do a better job of selecting the right signature. TypeScript does not currently support a
// Type Relationship API (see https://github.com/angular/vscode-ng-language-service/issues/143). // Type Relationship API (see https://github.com/angular/vscode-ng-language-service/issues/143).
@ -404,7 +404,7 @@ class SymbolWrapper implements Symbol {
return selectSignature(this.tsType, this.context, types); return selectSignature(this.tsType, this.context, types);
} }
indexed(argument: Symbol): Symbol|undefined { indexed(_argument: Symbol): Symbol|undefined {
return undefined; return undefined;
} }
@ -475,7 +475,7 @@ class DeclaredSymbol implements Symbol {
return this.type.typeArguments(); return this.type.typeArguments();
} }
indexed(argument: Symbol): Symbol|undefined { indexed(_argument: Symbol): Symbol|undefined {
return undefined; return undefined;
} }
} }
@ -504,7 +504,7 @@ class SignatureResultOverride implements Signature {
} }
} }
export function toSymbolTableFactory(symbols: ts.Symbol[]): ts.SymbolTable { function toSymbolTableFactory(symbols: ts.Symbol[]): ts.SymbolTable {
// ∀ Typescript version >= 2.2, `SymbolTable` is implemented as an ES6 `Map` // ∀ Typescript version >= 2.2, `SymbolTable` is implemented as an ES6 `Map`
const result = new Map<string, ts.Symbol>(); const result = new Map<string, ts.Symbol>();
for (const symbol of symbols) { for (const symbol of symbols) {
@ -548,8 +548,7 @@ class SymbolTableWrapper implements SymbolTable {
* @param context program context * @param context program context
* @param type original TypeScript type of entity owning the symbols, if known * @param type original TypeScript type of entity owning the symbols, if known
*/ */
constructor( constructor(symbols: ts.SymbolTable|ts.Symbol[], private context: TypeContext, type?: ts.Type) {
symbols: ts.SymbolTable|ts.Symbol[], private context: TypeContext, private type?: ts.Type) {
symbols = symbols || []; symbols = symbols || [];
if (Array.isArray(symbols)) { if (Array.isArray(symbols)) {
@ -727,7 +726,7 @@ class PipeSymbol implements Symbol {
return signature; return signature;
} }
indexed(argument: Symbol): Symbol|undefined { indexed(_argument: Symbol): Symbol|undefined {
return undefined; return undefined;
} }
@ -786,10 +785,10 @@ function findClassSymbolInContext(type: StaticSymbol, context: TypeContext): ts.
class EmptyTable implements SymbolTable { class EmptyTable implements SymbolTable {
public readonly size: number = 0; public readonly size: number = 0;
get(key: string): Symbol|undefined { get(_key: string): Symbol|undefined {
return undefined; return undefined;
} }
has(key: string): boolean { has(_key: string): boolean {
return false; return false;
} }
values(): Symbol[] { values(): Symbol[] {

View File

@ -9,16 +9,15 @@
import {AstPath, BoundEventAst, CompileDirectiveSummary, CompileTypeMetadata, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, HtmlAstPath, identifierName, Identifiers, Node, ParseSourceSpan, RecursiveTemplateAstVisitor, RecursiveVisitor, TemplateAst, TemplateAstPath, templateVisitAll, visitAll} from '@angular/compiler'; import {AstPath, BoundEventAst, CompileDirectiveSummary, CompileTypeMetadata, CssSelector, DirectiveAst, ElementAst, EmbeddedTemplateAst, HtmlAstPath, identifierName, Identifiers, Node, ParseSourceSpan, RecursiveTemplateAstVisitor, RecursiveVisitor, TemplateAst, TemplateAstPath, templateVisitAll, visitAll} from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {AstResult, SelectorInfo} from './common'; import {AstResult, DiagnosticTemplateInfo, SelectorInfo, Span, Symbol, SymbolQuery} from './types';
import {Span, Symbol, SymbolQuery} from './types';
export interface SpanHolder { interface SpanHolder {
sourceSpan: ParseSourceSpan; sourceSpan: ParseSourceSpan;
endSourceSpan?: ParseSourceSpan|null; endSourceSpan?: ParseSourceSpan|null;
children?: SpanHolder[]; children?: SpanHolder[];
} }
export function isParseSourceSpan(value: any): value is ParseSourceSpan { function isParseSourceSpan(value: any): value is ParseSourceSpan {
return value && !!value.start; return value && !!value.start;
} }
@ -80,14 +79,16 @@ export function getSelectors(info: AstResult): SelectorInfo {
return {selectors: results, map}; return {selectors: results, map};
} }
export function isTypescriptVersion(low: string, high?: string) { export function diagnosticInfoFromTemplateInfo(info: AstResult): DiagnosticTemplateInfo {
const version = ts.version; return {
fileName: info.template.fileName,
if (version.substring(0, low.length) < low) return false; offset: info.template.span.start,
query: info.template.query,
if (high && (version.substring(0, high.length) > high)) return false; members: info.template.members,
htmlAst: info.htmlAst,
return true; templateAst: info.templateAst,
source: info.template.source,
};
} }
export function findTemplateAstAt(ast: TemplateAst[], position: number): TemplateAstPath { export function findTemplateAstAt(ast: TemplateAst[], position: number): TemplateAstPath {
@ -276,3 +277,62 @@ export function findOutputBinding(
} }
} }
} }
/**
* Returns a property assignment from the assignment value, or `undefined` if there is no
* assignment.
*/
export function getPropertyAssignmentFromValue(value: ts.Node): ts.PropertyAssignment|undefined {
if (!value.parent || !ts.isPropertyAssignment(value.parent)) {
return;
}
return value.parent;
}
/**
* Given a decorator property assignment, return the ClassDeclaration node that corresponds to the
* directive class the property applies to.
* If the property assignment is not on a class decorator, no declaration is returned.
*
* For example,
*
* @Component({
* template: '<div></div>'
* ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment
* })
* class AppComponent {}
* ^---- class declaration node
*
* @param propAsgn property assignment
*/
export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment):
ts.ClassDeclaration|undefined {
if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) {
return;
}
const objLitExprNode = propAsgnNode.parent;
if (!objLitExprNode.parent || !ts.isCallExpression(objLitExprNode.parent)) {
return;
}
const callExprNode = objLitExprNode.parent;
if (!callExprNode.parent || !ts.isDecorator(callExprNode.parent)) {
return;
}
const decorator = callExprNode.parent;
if (!decorator.parent || !ts.isClassDeclaration(decorator.parent)) {
return;
}
const classDeclNode = decorator.parent;
return classDeclNode;
}
/**
* Determines if a property assignment is on a class decorator.
* See `getClassDeclFromDecoratorProperty`, which gets the class the decorator is applied to, for
* more details.
*
* @param prop property assignment
*/
export function isClassDecoratorProperty(propAsgn: ts.PropertyAssignment): boolean {
return !!getClassDeclFromDecoratorProp(propAsgn);
}

View File

@ -1,17 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
/**
* @module
* @description
* Entry point for all public APIs of the common package.
*/
import {Version} from '@angular/core';
export const VERSION = new Version('0.0.0-PLACEHOLDER');

View File

@ -47,7 +47,6 @@ ts_library(
"html_info_spec.ts", "html_info_spec.ts",
"language_service_spec.ts", "language_service_spec.ts",
"reflector_host_spec.ts", "reflector_host_spec.ts",
"template_spec.ts",
"ts_plugin_spec.ts", "ts_plugin_spec.ts",
"typescript_host_spec.ts", "typescript_host_spec.ts",
"utils_spec.ts", "utils_spec.ts",

View File

@ -14,7 +14,7 @@ import * as fs from 'fs';
import * as path from 'path'; import * as path from 'path';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {DiagnosticTemplateInfo} from '../src/expression_diagnostics'; import {DiagnosticTemplateInfo} from '../src/types';
import {getClassMembers, getPipesTable, getSymbolQuery} from '../src/typescript_symbols'; import {getClassMembers, getPipesTable, getSymbolQuery} from '../src/typescript_symbols';
const realFiles = new Map<string, string>(); const realFiles = new Map<string, string>();

View File

@ -1,50 +0,0 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import * as ts from 'typescript';
import {getClassDeclFromDecoratorProp} from '../src/template';
import {MockTypescriptHost} from './test_utils';
describe('getClassDeclFromTemplateNode', () => {
it('should find class declaration in syntax-only mode', () => {
const sourceFile = ts.createSourceFile(
'foo.ts', `
@Component({
template: '<div></div>'
})
class MyComponent {}`,
ts.ScriptTarget.ES2015, true /* setParentNodes */);
function visit(node: ts.Node): ts.ClassDeclaration|undefined {
if (ts.isPropertyAssignment(node)) {
return getClassDeclFromDecoratorProp(node);
}
return node.forEachChild(visit);
}
const classDecl = sourceFile.forEachChild(visit);
expect(classDecl).toBeTruthy();
expect(classDecl!.kind).toBe(ts.SyntaxKind.ClassDeclaration);
expect((classDecl as ts.ClassDeclaration).name!.text).toBe('MyComponent');
});
it('should return class declaration for AppComponent', () => {
const host = new MockTypescriptHost(['/app/app.component.ts']);
const tsLS = ts.createLanguageService(host);
const sourceFile = tsLS.getProgram()!.getSourceFile('/app/app.component.ts');
expect(sourceFile).toBeTruthy();
const classDecl = sourceFile!.forEachChild(function visit(node): ts.Node|undefined {
if (ts.isPropertyAssignment(node)) {
return getClassDeclFromDecoratorProp(node);
}
return node.forEachChild(visit);
});
expect(classDecl).toBeTruthy();
expect(ts.isClassDeclaration(classDecl!)).toBe(true);
expect((classDecl as ts.ClassDeclaration).name!.text).toBe('AppComponent');
});
});

View File

@ -11,7 +11,7 @@ import {ReflectorHost} from '@angular/language-service/src/reflector_host';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {BuiltinType, Symbol, SymbolQuery, SymbolTable} from '../src/symbols'; import {BuiltinType, Symbol, SymbolQuery, SymbolTable} from '../src/symbols';
import {getSymbolQuery, toSymbolTableFactory} from '../src/typescript_symbols'; import {getSymbolQuery} from '../src/typescript_symbols';
import {DiagnosticContext, MockLanguageServiceHost} from './mocks'; import {DiagnosticContext, MockLanguageServiceHost} from './mocks';
@ -79,14 +79,6 @@ describe('symbol query', () => {
}); });
}); });
describe('toSymbolTableFactory(tsVersion)', () => {
it('should return a Map for versions of TypeScript >= 2.2', () => {
const a = {name: 'a'} as ts.Symbol;
const b = {name: 'b'} as ts.Symbol;
expect(toSymbolTableFactory([a, b]) instanceof Map).toEqual(true);
});
});
function appComponentSource(template: string): string { function appComponentSource(template: string): string {
return ` return `
import {Component} from '@angular/core'; import {Component} from '@angular/core';

View File

@ -9,7 +9,8 @@
import * as ng from '@angular/compiler'; import * as ng from '@angular/compiler';
import * as ts from 'typescript'; import * as ts from 'typescript';
import {getDirectiveClassLike, getPathToNodeAtPosition} from '../src/utils'; import {getClassDeclFromDecoratorProp, getDirectiveClassLike, getPathToNodeAtPosition} from '../src/utils';
import {MockTypescriptHost} from './test_utils';
describe('getDirectiveClassLike', () => { describe('getDirectiveClassLike', () => {
it('should return a directive class', () => { it('should return a directive class', () => {
@ -80,3 +81,42 @@ describe('getPathToNodeAtPosition', () => {
expect(path.tail instanceof ng.Attribute).toBe(true); expect(path.tail instanceof ng.Attribute).toBe(true);
}); });
}); });
describe('getClassDeclFromTemplateNode', () => {
it('should find class declaration in syntax-only mode', () => {
const sourceFile = ts.createSourceFile(
'foo.ts', `
@Component({
template: '<div></div>'
})
class MyComponent {}`,
ts.ScriptTarget.ES2015, true /* setParentNodes */);
function visit(node: ts.Node): ts.ClassDeclaration|undefined {
if (ts.isPropertyAssignment(node)) {
return getClassDeclFromDecoratorProp(node);
}
return node.forEachChild(visit);
}
const classDecl = sourceFile.forEachChild(visit);
expect(classDecl).toBeTruthy();
expect(classDecl!.kind).toBe(ts.SyntaxKind.ClassDeclaration);
expect((classDecl as ts.ClassDeclaration).name!.text).toBe('MyComponent');
});
it('should return class declaration for AppComponent', () => {
const host = new MockTypescriptHost(['/app/app.component.ts']);
const tsLS = ts.createLanguageService(host);
const sourceFile = tsLS.getProgram()!.getSourceFile('/app/app.component.ts');
expect(sourceFile).toBeTruthy();
const classDecl = sourceFile!.forEachChild(function visit(node): ts.Node|undefined {
if (ts.isPropertyAssignment(node)) {
return getClassDeclFromDecoratorProp(node);
}
return node.forEachChild(visit);
});
expect(classDecl).toBeTruthy();
expect(ts.isClassDeclaration(classDecl!)).toBe(true);
expect((classDecl as ts.ClassDeclaration).name!.text).toBe('AppComponent');
});
});