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/validators.ts"
],
[
"packages/language-service/src/common.ts",
"packages/language-service/src/types.ts"
],
[
"packages/language-service/src/completions.ts",
"packages/language-service/src/template.ts",

View File

@ -15,6 +15,5 @@
*/
export {createLanguageService} from './src/language_service';
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 {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 {ATTR, getBindingDescriptor} from './binding_utils';
import {AstResult} from './common';
import {diagnosticInfoFromTemplateInfo, getExpressionScope} from './expression_diagnostics';
import {getExpressionScope} from './expression_diagnostics';
import {getExpressionCompletions} from './expressions';
import {attributeNames, elementNames, eventNames, propertyNames} from './html_info';
import {InlineTemplate} from './template';
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> =
new Set(['html', 'script', 'noscript', 'base', 'body', 'title', 'head', 'link']);
@ -56,7 +55,7 @@ function isIdentifierPart(code: number) {
* `position`, nothing is returned.
*/
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 templateSrc = template.source;
@ -127,7 +126,7 @@ function getBoundedWordSpan(
}
export function getTemplateCompletions(
templateInfo: AstResult, position: number): ng.CompletionEntry[] {
templateInfo: ng.AstResult, position: number): ng.CompletionEntry[] {
let result: ng.CompletionEntry[] = [];
const {htmlAst, template} = templateInfo;
// 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 elem = path.parentOf(attr);
if (!(attr instanceof Attribute) || !(elem instanceof Element)) {
@ -258,7 +257,7 @@ function attributeCompletions(info: AstResult, path: AstPath<HtmlAst>): ng.Compl
}
function attributeCompletionsForElement(
info: AstResult, elementName: string): ng.CompletionEntry[] {
info: ng.AstResult, elementName: string): ng.CompletionEntry[] {
const results: ng.CompletionEntry[] = [];
if (info.template instanceof InlineTemplate) {
@ -292,7 +291,8 @@ function attributeCompletionsForElement(
* @param info Object that contains the template AST
* @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.
const templatePath = findTemplateAstAt(info.templateAst, htmlPath.position);
const visitor = new ExpressionVisitor(info, htmlPath.position, () => {
@ -334,7 +334,7 @@ function attributeValueCompletions(info: AstResult, htmlPath: HtmlAstPath): ng.C
return visitor.results;
}
function elementCompletions(info: AstResult): ng.CompletionEntry[] {
function elementCompletions(info: ng.AstResult): ng.CompletionEntry[] {
const results: ng.CompletionEntry[] = [...ANGULAR_ELEMENTS];
if (info.template instanceof InlineTemplate) {
@ -380,7 +380,7 @@ function entityCompletions(value: string, position: number): ng.CompletionEntry[
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.
const templatePath = findTemplateAstAt(info.templateAst, position);
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
// if it is not.
function voidElementAttributeCompletions(
info: AstResult, path: AstPath<HtmlAst>): ng.CompletionEntry[] {
info: ng.AstResult, path: AstPath<HtmlAst>): ng.CompletionEntry[] {
const tail = path.tail;
if (tail instanceof Text) {
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>();
constructor(
private readonly info: AstResult, private readonly position: number,
private readonly info: ng.AstResult, private readonly position: number,
private readonly getExpressionScope: () => ng.SymbolTable) {
super();
}
@ -619,7 +619,7 @@ interface AngularAttributes {
* @param info
* @param elementName
*/
function angularAttributes(info: AstResult, elementName: string): AngularAttributes {
function angularAttributes(info: ng.AstResult, elementName: string): AngularAttributes {
const {selectors, map: selectorMap} = getSelectors(info);
const templateRefs = new Set<string>();
const inputs = new Set<string>();

View File

@ -8,11 +8,10 @@
import * as path from 'path';
import * as ts from 'typescript'; // used as value and is provided at runtime
import {AstResult} from './common';
import {locateSymbols} from './locate_symbol';
import {getPropertyAssignmentFromValue, isClassDecoratorProperty} from './template';
import {Span} from './types';
import {findTightestNode} from './utils';
import {AstResult, Span} from './types';
import {findTightestNode, getPropertyAssignmentFromValue, isClassDecoratorProperty} from './utils';
/**
* 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 ts from 'typescript';
import {AstResult} from './common';
import {createDiagnostic, Diagnostic} from './diagnostic_messages';
import {getTemplateExpressionDiagnostics} from './expression_diagnostics';
import * as ng from './types';
import {TypeScriptServiceHost} from './typescript_host';
import {findPropertyValueOfType, findTightestNode, offsetSpan, spanOf} from './utils';
/**
* Return diagnostic information for the parsed AST of the template.
* @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;
if (parseErrors && parseErrors.length) {
return parseErrors.map(e => {

View File

@ -6,33 +6,22 @@
* 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 {AstType} from './expression_type';
import {BuiltinType, Definition, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols';
import * as ng from './types';
import {findOutputBinding, getPathToNodeAtPosition} from './utils';
export interface DiagnosticTemplateInfo {
fileName?: string;
offset: number;
query: SymbolQuery;
members: SymbolTable;
htmlAst: Node[];
templateAst: TemplateAst[];
source: string;
}
export function getTemplateExpressionDiagnostics(info: DiagnosticTemplateInfo): ng.Diagnostic[] {
export function getTemplateExpressionDiagnostics(info: ng.DiagnosticTemplateInfo): ng.Diagnostic[] {
const visitor = new ExpressionDiagnosticsVisitor(
info, (path: TemplateAstPath) => getExpressionScope(info, path));
templateVisitAll(visitor, info.templateAst);
return visitor.diagnostics;
}
function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] {
function getReferences(info: ng.DiagnosticTemplateInfo): SymbolDeclaration[] {
const result: SymbolDeclaration[] = [];
function processReferences(references: ReferenceAst[]) {
@ -68,7 +57,7 @@ function getReferences(info: DiagnosticTemplateInfo): SymbolDeclaration[] {
return result;
}
function getDefinitionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined {
function getDefinitionOf(info: ng.DiagnosticTemplateInfo, ast: TemplateAst): Definition|undefined {
if (info.fileName) {
const templateOffset = info.offset;
return [{
@ -88,7 +77,7 @@ function getDefinitionOf(info: DiagnosticTemplateInfo, ast: TemplateAst): Defini
* @param path template AST path
*/
function getVarDeclarations(
info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration[] {
info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration[] {
const results: SymbolDeclaration[] = [];
for (let current = path.head; current; current = path.childOf(current)) {
if (!(current instanceof EmbeddedTemplateAst)) {
@ -154,7 +143,7 @@ function getVariableTypeFromDirectiveContext(
* @param templateElement
*/
function refinedVariableType(
value: string, mergedTable: SymbolTable, info: DiagnosticTemplateInfo,
value: string, mergedTable: SymbolTable, info: ng.DiagnosticTemplateInfo,
templateElement: EmbeddedTemplateAst): Symbol {
if (value === '$implicit') {
// Special case: ngFor directive
@ -206,7 +195,7 @@ function refinedVariableType(
}
function getEventDeclaration(
info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration|undefined {
info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolDeclaration|undefined {
const event = path.tail;
if (!(event instanceof BoundEventAst)) {
// No event available in this context.
@ -241,7 +230,7 @@ function getEventDeclaration(
* derived for.
*/
export function getExpressionScope(
info: DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable {
info: ng.DiagnosticTemplateInfo, path: TemplateAstPath): SymbolTable {
let result = info.members;
const references = getReferences(info);
const variables = getVarDeclarations(info, path);
@ -262,7 +251,7 @@ class ExpressionDiagnosticsVisitor extends RecursiveTemplateAstVisitor {
diagnostics: ng.Diagnostic[] = [];
constructor(
private info: DiagnosticTemplateInfo,
private info: ng.DiagnosticTemplateInfo,
private getExpressionScope: (path: TemplateAstPath, includeEvent: boolean) => SymbolTable) {
super();
this.path = new AstPath<TemplateAst>([]);
@ -386,16 +375,3 @@ function hasTemplateReference(type: CompileTypeMetadata): boolean {
function spanOf(sourceSpan: ParseSourceSpan): Span {
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 * as ng from './types';
export interface ExpressionDiagnosticsContext {
interface ExpressionDiagnosticsContext {
inEvent?: boolean;
}
@ -225,7 +225,7 @@ export class AstType implements AstVisitor {
return this.anyType;
}
visitImplicitReceiver(ast: ImplicitReceiver): Symbol {
visitImplicitReceiver(_ast: ImplicitReceiver): Symbol {
const _this = this;
// Return a pseudo-symbol for the implicit receiver.
// The members of the implicit receiver are what is defined by the
@ -247,11 +247,11 @@ export class AstType implements AstVisitor {
signatures(): Signature[] {
return [];
},
selectSignature(types): Signature |
selectSignature(_types): Signature |
undefined {
return undefined;
},
indexed(argument): Symbol |
indexed(_argument): Symbol |
undefined {
return undefined;
},
@ -366,7 +366,7 @@ export class AstType implements AstVisitor {
return this.getType(ast.value);
}
visitQuote(ast: Quote) {
visitQuote(_ast: Quote) {
// The type of a quoted expression is 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
// beginning of an expression.
tail.visit({
visitBinary(ast) {},
visitChain(ast) {},
visitConditional(ast) {},
visitFunctionCall(ast) {},
visitImplicitReceiver(ast) {},
visitInterpolation(ast) {
visitBinary(_ast) {},
visitChain(_ast) {},
visitConditional(_ast) {},
visitFunctionCall(_ast) {},
visitImplicitReceiver(_ast) {},
visitInterpolation(_ast) {
result = undefined;
},
visitKeyedRead(ast) {},
visitKeyedWrite(ast) {},
visitLiteralArray(ast) {},
visitLiteralMap(ast) {},
visitLiteralPrimitive(ast) {},
visitMethodCall(ast) {},
visitKeyedRead(_ast) {},
visitKeyedWrite(_ast) {},
visitLiteralArray(_ast) {},
visitLiteralMap(_ast) {},
visitLiteralPrimitive(_ast) {},
visitMethodCall(_ast) {},
visitPipe(ast) {
if (position >= ast.exp.span.end &&
(!ast.args || !ast.args.length || position < (<AST>ast.args[0]).span.start)) {
@ -74,8 +74,8 @@ export function getExpressionCompletions(
result = templateInfo.query.getPipes();
}
},
visitPrefixNot(ast) {},
visitNonNullAssert(ast) {},
visitPrefixNot(_ast) {},
visitNonNullAssert(_ast) {},
visitPropertyRead(ast) {
const receiverType = getType(ast.receiver);
result = receiverType ? receiverType.members() : scope;
@ -84,7 +84,7 @@ export function getExpressionCompletions(
const receiverType = getType(ast.receiver);
result = receiverType ? receiverType.members() : scope;
},
visitQuote(ast) {
visitQuote(_ast) {
// For a quote, return the members of any (if there are any).
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
// beginning of an expression.
tail.visit({
visitBinary(ast) {},
visitChain(ast) {},
visitConditional(ast) {},
visitFunctionCall(ast) {},
visitImplicitReceiver(ast) {},
visitInterpolation(ast) {},
visitKeyedRead(ast) {},
visitKeyedWrite(ast) {},
visitLiteralArray(ast) {},
visitLiteralMap(ast) {},
visitLiteralPrimitive(ast) {},
visitBinary(_ast) {},
visitChain(_ast) {},
visitConditional(_ast) {},
visitFunctionCall(_ast) {},
visitImplicitReceiver(_ast) {},
visitInterpolation(_ast) {},
visitKeyedRead(_ast) {},
visitKeyedWrite(_ast) {},
visitLiteralArray(_ast) {},
visitLiteralMap(_ast) {},
visitLiteralPrimitive(_ast) {},
visitMethodCall(ast) {
const receiverType = getType(ast.receiver);
symbol = receiverType && receiverType.members().get(ast.name);
@ -159,8 +159,8 @@ export function getExpressionSymbol(
};
}
},
visitPrefixNot(ast) {},
visitNonNullAssert(ast) {},
visitPrefixNot(_ast) {},
visitNonNullAssert(_ast) {},
visitPropertyRead(ast) {
const receiverType = getType(ast.receiver);
symbol = receiverType && receiverType.members().get(ast.name);
@ -177,7 +177,7 @@ export function getExpressionSymbol(
// ^^^^^^ value; visited separately as a nested AST
span = {start, end: start + ast.name.length};
},
visitQuote(ast) {},
visitQuote(_ast) {},
visitSafeMethodCall(ast) {
const receiverType = getType(ast.receiver);
symbol = receiverType && receiverType.members().get(ast.name);

View File

@ -8,7 +8,6 @@
import {NgAnalyzedModules} from '@angular/compiler';
import * as ts from 'typescript';
import {AstResult} from './common';
import {locateSymbols} from './locate_symbol';
import * as ng from './types';
import {inSpan} from './utils';
@ -27,7 +26,8 @@ const SYMBOL_INTERFACE = ts.SymbolDisplayPartKind[ts.SymbolDisplayPartKind.inter
* @param analyzedModules all NgModules in the program.
*/
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];
if (!symbolInfo) {
return;

View File

@ -453,7 +453,3 @@ export function eventNames(elementName: string): string[] {
export function propertyNames(elementName: string): string[] {
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(
fileName: string, position: number,
options?: tss.GetCompletionsAtPositionOptions): tss.CompletionInfo|undefined {
_options?: tss.GetCompletionsAtPositionOptions): tss.CompletionInfo|undefined {
this.host.getAnalyzedModules(); // same role as 'synchronizeHostData'
const ast = this.host.getTemplateAstAtPosition(fileName, position);
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 * as tss from 'typescript/lib/tsserverlibrary';
import {AstResult} from './common';
import {diagnosticInfoFromTemplateInfo, getExpressionScope} from './expression_diagnostics';
import {getExpressionScope} from './expression_diagnostics';
import {getExpressionSymbol} from './expressions';
import {Definition, DirectiveKind, Span, Symbol} from './types';
import {findOutputBinding, findTemplateAstAt, getPathToNodeAtPosition, inSpan, invertMap, isNarrower, offsetSpan, spanOf} from './utils';
export interface SymbolInfo {
symbol: Symbol;
span: tss.TextSpan;
staticSymbol?: StaticSymbol;
}
import {AstResult, Definition, DirectiveKind, Span, Symbol, SymbolInfo} from './types';
import {diagnosticInfoFromTemplateInfo, findOutputBinding, findTemplateAstAt, getPathToNodeAtPosition, inSpan, invertMap, isNarrower, offsetSpan, spanOf} from './utils';
/**
* 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(
{
visitNgContent(ast) {},
visitEmbeddedTemplate(ast) {},
visitNgContent(_ast) {},
visitEmbeddedTemplate(_ast) {},
visitElement(ast) {
const component = ast.directives.find(d => d.directive.isComponent);
if (component) {
@ -113,7 +106,7 @@ function locateSymbol(ast: TemplateAst, path: TemplateAstPath, info: AstResult):
symbol = ast.value && info.template.query.getTypeSymbol(tokenReference(ast.value));
span = spanOf(ast);
},
visitVariable(ast) {},
visitVariable(_ast) {},
visitEvent(ast) {
if (!attributeValueSymbol(ast.handler)) {
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) {
// Need to cast because 'reference' is typed as any
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
*/
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 {AstResult} from './common';
import {BuiltinType, DeclarationKind, Definition, PipeInfo, Pipes, Signature, Span, Symbol, SymbolDeclaration, SymbolQuery, SymbolTable} from './symbols';
export {
BuiltinType,
DeclarationKind,
Definition,
PipeInfo,
Pipes,
Signature,
Span,
StaticSymbol,
Symbol,
SymbolDeclaration,
SymbolQuery,
SymbolTable
};
import {Span, Symbol, SymbolQuery, SymbolTable} from './symbols';
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
@ -67,15 +54,6 @@ export interface TemplateSource {
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
*
@ -132,15 +110,6 @@ export interface Declaration {
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
* to the `LanguageService` requests.
@ -178,7 +147,7 @@ export interface LanguageServiceHost {
/**
* 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.
@ -196,45 +165,6 @@ export interface LanguageServiceHost {
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.
*/
@ -312,45 +242,6 @@ export interface Diagnostic {
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()`.
*
@ -364,3 +255,38 @@ export type LanguageService = Pick<
ts.LanguageService,
'getCompletionsAtPosition'|'getDefinitionAndBoundSpan'|'getQuickInfoAtPosition'|
'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
*/
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 * as tss from 'typescript/lib/tsserverlibrary';
import {AstResult} from './common';
import {createLanguageService} from './language_service';
import {ReflectorHost} from './reflector_host';
import {ExternalTemplate, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue, InlineTemplate} from './template';
import {Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
import {findTightestNode, getDirectiveClassLike} from './utils';
import {ExternalTemplate, InlineTemplate} from './template';
import {AstResult, Declaration, DeclarationError, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
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.
*/
export class DummyResourceLoader extends ResourceLoader {
get(url: string): Promise<string> {
get(_url: string): Promise<string> {
return Promise.resolve('');
}
}
@ -78,10 +77,10 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
readonly tsLsHost: tss.LanguageServiceHost, private readonly tsLS: tss.LanguageService) {
this.summaryResolver = new AotSummaryResolver(
{
loadSummary(filePath: string) {
loadSummary(_filePath: string) {
return null;
},
isSourceFile(sourceFilePath: string) {
isSourceFile(_sourceFilePath: string) {
return true;
},
toSummaryFileName(sourceFilePath: string) {
@ -172,7 +171,7 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
this.resolver.clearCache();
const analyzeHost = {
isSourceFile(filePath: string) {
isSourceFile(_filePath: string) {
return true;
}
};

View File

@ -57,8 +57,14 @@ export function getClassMembersFromDeclaration(
return new TypeWrapper(type, {node: source, program, checker}).members();
}
export function getClassFromStaticSymbol(
program: ts.Program, type: StaticSymbol): ts.ClassDeclaration|undefined {
export function getPipesTable(
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);
if (source) {
return ts.forEachChild(source, child => {
@ -74,12 +80,6 @@ export function getClassFromStaticSymbol(
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 {
private typeCache = new Map<BuiltinType, Symbol>();
private pipesCache: SymbolTable|undefined;
@ -123,7 +123,7 @@ class TypeScriptSymbolQuery implements SymbolQuery {
return result || this.getBuiltinType(BuiltinType.Any);
}
getArrayType(type: Symbol): Symbol {
getArrayType(_type: Symbol): Symbol {
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));
}
function selectSignature(type: ts.Type, context: TypeContext, types: Symbol[]): Signature|
function selectSignature(type: ts.Type, context: TypeContext, _types: Symbol[]): Signature|
undefined {
// 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).
@ -404,7 +404,7 @@ class SymbolWrapper implements Symbol {
return selectSignature(this.tsType, this.context, types);
}
indexed(argument: Symbol): Symbol|undefined {
indexed(_argument: Symbol): Symbol|undefined {
return undefined;
}
@ -475,7 +475,7 @@ class DeclaredSymbol implements Symbol {
return this.type.typeArguments();
}
indexed(argument: Symbol): Symbol|undefined {
indexed(_argument: Symbol): Symbol|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`
const result = new Map<string, ts.Symbol>();
for (const symbol of symbols) {
@ -548,8 +548,7 @@ class SymbolTableWrapper implements SymbolTable {
* @param context program context
* @param type original TypeScript type of entity owning the symbols, if known
*/
constructor(
symbols: ts.SymbolTable|ts.Symbol[], private context: TypeContext, private type?: ts.Type) {
constructor(symbols: ts.SymbolTable|ts.Symbol[], private context: TypeContext, type?: ts.Type) {
symbols = symbols || [];
if (Array.isArray(symbols)) {
@ -727,7 +726,7 @@ class PipeSymbol implements Symbol {
return signature;
}
indexed(argument: Symbol): Symbol|undefined {
indexed(_argument: Symbol): Symbol|undefined {
return undefined;
}
@ -786,10 +785,10 @@ function findClassSymbolInContext(type: StaticSymbol, context: TypeContext): ts.
class EmptyTable implements SymbolTable {
public readonly size: number = 0;
get(key: string): Symbol|undefined {
get(_key: string): Symbol|undefined {
return undefined;
}
has(key: string): boolean {
has(_key: string): boolean {
return false;
}
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 * as ts from 'typescript';
import {AstResult, SelectorInfo} from './common';
import {Span, Symbol, SymbolQuery} from './types';
import {AstResult, DiagnosticTemplateInfo, SelectorInfo, Span, Symbol, SymbolQuery} from './types';
export interface SpanHolder {
interface SpanHolder {
sourceSpan: ParseSourceSpan;
endSourceSpan?: ParseSourceSpan|null;
children?: SpanHolder[];
}
export function isParseSourceSpan(value: any): value is ParseSourceSpan {
function isParseSourceSpan(value: any): value is ParseSourceSpan {
return value && !!value.start;
}
@ -80,14 +79,16 @@ export function getSelectors(info: AstResult): SelectorInfo {
return {selectors: results, map};
}
export function isTypescriptVersion(low: string, high?: string) {
const version = ts.version;
if (version.substring(0, low.length) < low) return false;
if (high && (version.substring(0, high.length) > high)) return false;
return true;
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,
};
}
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",
"language_service_spec.ts",
"reflector_host_spec.ts",
"template_spec.ts",
"ts_plugin_spec.ts",
"typescript_host_spec.ts",
"utils_spec.ts",

View File

@ -14,7 +14,7 @@ import * as fs from 'fs';
import * as path from 'path';
import * as ts from 'typescript';
import {DiagnosticTemplateInfo} from '../src/expression_diagnostics';
import {DiagnosticTemplateInfo} from '../src/types';
import {getClassMembers, getPipesTable, getSymbolQuery} from '../src/typescript_symbols';
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 {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';
@ -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 {
return `
import {Component} from '@angular/core';

View File

@ -9,7 +9,8 @@
import * as ng from '@angular/compiler';
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', () => {
it('should return a directive class', () => {
@ -80,3 +81,42 @@ describe('getPathToNodeAtPosition', () => {
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');
});
});