Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(language-service): add generic decorator property verifications #32252

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
45 changes: 30 additions & 15 deletions packages/language-service/src/template.ts
Expand Up @@ -124,30 +124,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: '<div></div>' <-- template node
* template: '<div></div>'
* ^^^^^^^^^^^^^^^^^^^^^^^---- 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;
}
Expand All @@ -166,3 +170,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);
}
9 changes: 7 additions & 2 deletions packages/language-service/src/typescript_host.ts
Expand Up @@ -13,10 +13,11 @@ import * as ts from 'typescript';
import {AstResult, TemplateInfo} 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';


/**
* Create a `LanguageServiceHost`
*/
Expand Down Expand Up @@ -309,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;
}
Expand Down
12 changes: 7 additions & 5 deletions packages/language-service/test/template_spec.ts
Expand Up @@ -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';

Expand All @@ -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();
Expand All @@ -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);
});
Expand Down