refactor(language-service): add generic decorator property verifications (#32252)
This PR makes finding class declarations properties in decorators are applied to more generic to all properties that may be in a decorator, and adds helper methods enabling getting the property assignment of a property value and verifying that a property assignment is actually in a decorator applied to a class. This is done so that it will be easier to provide Angular definitions for decorator properties moving forward. Most immediately, this will provide decorator class verification for #32238. PR Close #32252
This commit is contained in:
parent
3dd614db61
commit
c624b14e8e
@ -127,30 +127,34 @@ export class ExternalTemplate extends BaseTemplate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a template node, return the ClassDeclaration node that corresponds to
|
* Returns a property assignment from the assignment value, or `undefined` if there is no
|
||||||
* the component class for the template.
|
* 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,
|
* For example,
|
||||||
*
|
*
|
||||||
* @Component({
|
* @Component({
|
||||||
* template: '<div></div>' <-- template node
|
* template: '<div></div>'
|
||||||
|
* ^^^^^^^^^^^^^^^^^^^^^^^---- property assignment
|
||||||
* })
|
* })
|
||||||
* class AppComponent {}
|
* class AppComponent {}
|
||||||
* ^---- class declaration node
|
* ^---- class declaration node
|
||||||
*
|
*
|
||||||
* @param node template node
|
* @param propAsgn property assignment
|
||||||
*/
|
*/
|
||||||
export function getClassDeclFromTemplateNode(node: ts.Node): ts.ClassDeclaration|undefined {
|
export function getClassDeclFromDecoratorProp(propAsgnNode: ts.PropertyAssignment):
|
||||||
if (!ts.isStringLiteralLike(node)) {
|
ts.ClassDeclaration|undefined {
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!node.parent || !ts.isPropertyAssignment(node.parent)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
const propAsgnNode = node.parent;
|
|
||||||
if (propAsgnNode.name.getText() !== 'template') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) {
|
if (!propAsgnNode.parent || !ts.isObjectLiteralExpression(propAsgnNode.parent)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -169,3 +173,14 @@ export function getClassDeclFromTemplateNode(node: ts.Node): ts.ClassDeclaration
|
|||||||
const classDeclNode = decorator.parent;
|
const classDeclNode = decorator.parent;
|
||||||
return classDeclNode;
|
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);
|
||||||
|
}
|
||||||
|
@ -13,7 +13,7 @@ import * as ts from 'typescript';
|
|||||||
import {AstResult, isAstResult} from './common';
|
import {AstResult, isAstResult} from './common';
|
||||||
import {createLanguageService} from './language_service';
|
import {createLanguageService} from './language_service';
|
||||||
import {ReflectorHost} from './reflector_host';
|
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 {Declaration, DeclarationError, Diagnostic, DiagnosticKind, DiagnosticMessageChain, LanguageService, LanguageServiceHost, Span, TemplateSource} from './types';
|
||||||
import {findTightestNode, getDirectiveClassLike} from './utils';
|
import {findTightestNode, getDirectiveClassLike} from './utils';
|
||||||
|
|
||||||
@ -310,7 +310,11 @@ export class TypeScriptServiceHost implements LanguageServiceHost {
|
|||||||
if (!ts.isStringLiteralLike(node)) {
|
if (!ts.isStringLiteralLike(node)) {
|
||||||
return;
|
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
|
if (!classDecl || !classDecl.name) { // Does not handle anonymous class
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import * as ts from 'typescript';
|
import * as ts from 'typescript';
|
||||||
import {getClassDeclFromTemplateNode} from '../src/template';
|
import {getClassDeclFromDecoratorProp} from '../src/template';
|
||||||
import {toh} from './test_data';
|
import {toh} from './test_data';
|
||||||
import {MockTypescriptHost} from './test_utils';
|
import {MockTypescriptHost} from './test_utils';
|
||||||
|
|
||||||
@ -22,7 +22,10 @@ describe('getClassDeclFromTemplateNode', () => {
|
|||||||
class MyComponent {}`,
|
class MyComponent {}`,
|
||||||
ts.ScriptTarget.ES2015, true /* setParentNodes */);
|
ts.ScriptTarget.ES2015, true /* setParentNodes */);
|
||||||
function visit(node: ts.Node): ts.ClassDeclaration|undefined {
|
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);
|
const classDecl = sourceFile.forEachChild(visit);
|
||||||
expect(classDecl).toBeTruthy();
|
expect(classDecl).toBeTruthy();
|
||||||
@ -37,9 +40,8 @@ describe('getClassDeclFromTemplateNode', () => {
|
|||||||
const sourceFile = tsLS.getProgram() !.getSourceFile('/app/app.component.ts');
|
const sourceFile = tsLS.getProgram() !.getSourceFile('/app/app.component.ts');
|
||||||
expect(sourceFile).toBeTruthy();
|
expect(sourceFile).toBeTruthy();
|
||||||
const classDecl = sourceFile !.forEachChild(function visit(node): ts.Node | undefined {
|
const classDecl = sourceFile !.forEachChild(function visit(node): ts.Node | undefined {
|
||||||
const candidate = getClassDeclFromTemplateNode(node);
|
if (ts.isPropertyAssignment(node)) {
|
||||||
if (candidate) {
|
return getClassDeclFromDecoratorProp(node);
|
||||||
return candidate;
|
|
||||||
}
|
}
|
||||||
return node.forEachChild(visit);
|
return node.forEachChild(visit);
|
||||||
});
|
});
|
||||||
|
Loading…
x
Reference in New Issue
Block a user