diff --git a/packages/language-service/src/template.ts b/packages/language-service/src/template.ts index 904d146ff4..a8642f18b9 100644 --- a/packages/language-service/src/template.ts +++ b/packages/language-service/src/template.ts @@ -127,30 +127,34 @@ export class ExternalTemplate extends BaseTemplate { } /** - * Given a template node, return the ClassDeclaration node that corresponds to - * the component class for the template. + * 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: '
' <-- template node + * template: '' + * ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment * }) * class AppComponent {} * ^---- class declaration node * - * @param node template node + * @param propAsgn property assignment */ -export function getClassDeclFromTemplateNode(node: ts.Node): ts.ClassDeclaration|undefined { - if (!ts.isStringLiteralLike(node)) { - return; - } - if (!node.parent || !ts.isPropertyAssignment(node.parent)) { - return; - } - const propAsgnNode = node.parent; - if (propAsgnNode.name.getText() !== 'template') { - return; - } +export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment): + ts.ClassDeclaration|undefined { if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) { return; } @@ -169,3 +173,14 @@ export function getClassDeclFromTemplateNode(node: ts.Node): ts.ClassDeclaration 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); +} diff --git a/packages/language-service/src/typescript_host.ts b/packages/language-service/src/typescript_host.ts index d6c968c9c7..517ddd73a2 100644 --- a/packages/language-service/src/typescript_host.ts +++ b/packages/language-service/src/typescript_host.ts @@ -13,7 +13,7 @@ import * as ts from 'typescript'; import {AstResult, isAstResult} from './common'; import {createLanguageService} from './language_service'; import {ReflectorHost} from './reflector_host'; -import {ExternalTemplate, InlineTemplate, getClassDeclFromTemplateNode} from './template'; +import {ExternalTemplate, InlineTemplate, getClassDeclFromDecoratorProp, getPropertyAssignmentFromValue} from './template'; import {Declaration, DeclarationError, Diagnostic, DiagnosticKind, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types'; import {findTightestNode, getDirectiveClassLike} from './utils'; @@ -310,7 +310,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost { if (!ts.isStringLiteralLike(node)) { return; } - const classDecl = getClassDeclFromTemplateNode(node); + const tmplAsgn = getPropertyAssignmentFromValue(node); + if (!tmplAsgn || tmplAsgn.name.getText() !== 'template') { + return; + } + const classDecl = getClassDeclFromDecoratorProp(tmplAsgn); if (!classDecl || !classDecl.name) { // Does not handle anonymous class return; } diff --git a/packages/language-service/test/template_spec.ts b/packages/language-service/test/template_spec.ts index 1992af4650..24ba382d51 100644 --- a/packages/language-service/test/template_spec.ts +++ b/packages/language-service/test/template_spec.ts @@ -7,7 +7,7 @@ */ import * as ts from 'typescript'; -import {getClassDeclFromTemplateNode} from '../src/template'; +import {getClassDeclFromDecoratorProp} from '../src/template'; import {toh} from './test_data'; import {MockTypescriptHost} from './test_utils'; @@ -22,7 +22,10 @@ describe('getClassDeclFromTemplateNode', () => { class MyComponent {}`, ts.ScriptTarget.ES2015, true /* setParentNodes */); function visit(node: ts.Node): ts.ClassDeclaration|undefined { - return getClassDeclFromTemplateNode(node) || node.forEachChild(visit); + if (ts.isPropertyAssignment(node)) { + return getClassDeclFromDecoratorProp(node); + } + return node.forEachChild(visit); } const classDecl = sourceFile.forEachChild(visit); expect(classDecl).toBeTruthy(); @@ -37,9 +40,8 @@ describe('getClassDeclFromTemplateNode', () => { const sourceFile = tsLS.getProgram() !.getSourceFile('/app/app.component.ts'); expect(sourceFile).toBeTruthy(); const classDecl = sourceFile !.forEachChild(function visit(node): ts.Node | undefined { - const candidate = getClassDeclFromTemplateNode(node); - if (candidate) { - return candidate; + if (ts.isPropertyAssignment(node)) { + return getClassDeclFromDecoratorProp(node); } return node.forEachChild(visit); });