Handle parentless nodes in nodeIsDecorated (#20314)

This commit is contained in:
Wesley Wigham
2017-11-28 17:01:19 -08:00
committed by GitHub
parent 433bfc555f
commit d79a474e6d
5 changed files with 78 additions and 25 deletions

View File

@@ -13048,7 +13048,7 @@ namespace ts {
// must instead be rewritten to point to a temporary variable to avoid issues with the double-bind
// behavior of class names in ES6.
if (declaration.kind === SyntaxKind.ClassDeclaration
&& nodeIsDecorated(declaration)) {
&& nodeIsDecorated(declaration as ClassDeclaration)) {
let container = getContainingClass(node);
while (container !== undefined) {
if (container === declaration && container.name !== node) {
@@ -20697,7 +20697,7 @@ namespace ts {
// skip this check for nodes that cannot have decorators. These should have already had an error reported by
// checkGrammarDecorators.
if (!nodeCanBeDecorated(node)) {
if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) {
return;
}
@@ -25203,7 +25203,7 @@ namespace ts {
if (!node.decorators) {
return false;
}
if (!nodeCanBeDecorated(node)) {
if (!nodeCanBeDecorated(node, node.parent, node.parent.parent)) {
if (node.kind === SyntaxKind.MethodDeclaration && !nodeIsPresent((<MethodDeclaration>node).body)) {
return grammarErrorOnFirstToken(node, Diagnostics.A_decorator_can_only_decorate_a_method_implementation_not_an_overload);
}

View File

@@ -1261,7 +1261,7 @@ namespace ts {
* the class.
*/
function getDecoratedClassElements(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray<ClassElement> {
return filter(node.members, isStatic ? isStaticDecoratedClassElement : isInstanceDecoratedClassElement);
return filter(node.members, isStatic ? m => isStaticDecoratedClassElement(m, node) : m => isInstanceDecoratedClassElement(m, node));
}
/**
@@ -1270,8 +1270,8 @@ namespace ts {
*
* @param member The class member.
*/
function isStaticDecoratedClassElement(member: ClassElement) {
return isDecoratedClassElement(member, /*isStatic*/ true);
function isStaticDecoratedClassElement(member: ClassElement, parent: ClassLikeDeclaration) {
return isDecoratedClassElement(member, /*isStatic*/ true, parent);
}
/**
@@ -1280,8 +1280,8 @@ namespace ts {
*
* @param member The class member.
*/
function isInstanceDecoratedClassElement(member: ClassElement) {
return isDecoratedClassElement(member, /*isStatic*/ false);
function isInstanceDecoratedClassElement(member: ClassElement, parent: ClassLikeDeclaration) {
return isDecoratedClassElement(member, /*isStatic*/ false, parent);
}
/**
@@ -1290,8 +1290,8 @@ namespace ts {
*
* @param member The class member.
*/
function isDecoratedClassElement(member: ClassElement, isStatic: boolean) {
return nodeOrChildIsDecorated(member)
function isDecoratedClassElement(member: ClassElement, isStatic: boolean, parent: ClassLikeDeclaration) {
return nodeOrChildIsDecorated(member, parent)
&& isStatic === hasModifier(member, ModifierFlags.Static);
}

View File

@@ -1205,7 +1205,10 @@ namespace ts {
return (<CallExpression | Decorator>node).expression;
}
export function nodeCanBeDecorated(node: Node): boolean {
export function nodeCanBeDecorated(node: ClassDeclaration): true;
export function nodeCanBeDecorated(node: ClassElement, parent: Node): boolean;
export function nodeCanBeDecorated(node: Node, parent: Node, grandparent: Node): boolean;
export function nodeCanBeDecorated(node: Node, parent?: Node, grandparent?: Node): boolean {
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
// classes are valid targets
@@ -1213,43 +1216,51 @@ namespace ts {
case SyntaxKind.PropertyDeclaration:
// property declarations are valid if their parent is a class declaration.
return node.parent.kind === SyntaxKind.ClassDeclaration;
return parent.kind === SyntaxKind.ClassDeclaration;
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
case SyntaxKind.MethodDeclaration:
// if this method has a body and its parent is a class declaration, this is a valid target.
return (<FunctionLikeDeclaration>node).body !== undefined
&& node.parent.kind === SyntaxKind.ClassDeclaration;
&& parent.kind === SyntaxKind.ClassDeclaration;
case SyntaxKind.Parameter:
// if the parameter's parent has a body and its grandparent is a class declaration, this is a valid target;
return (<FunctionLikeDeclaration>node.parent).body !== undefined
&& (node.parent.kind === SyntaxKind.Constructor
|| node.parent.kind === SyntaxKind.MethodDeclaration
|| node.parent.kind === SyntaxKind.SetAccessor)
&& node.parent.parent.kind === SyntaxKind.ClassDeclaration;
return (<FunctionLikeDeclaration>parent).body !== undefined
&& (parent.kind === SyntaxKind.Constructor
|| parent.kind === SyntaxKind.MethodDeclaration
|| parent.kind === SyntaxKind.SetAccessor)
&& grandparent.kind === SyntaxKind.ClassDeclaration;
}
return false;
}
export function nodeIsDecorated(node: Node): boolean {
export function nodeIsDecorated(node: ClassDeclaration): boolean;
export function nodeIsDecorated(node: ClassElement, parent: Node): boolean;
export function nodeIsDecorated(node: Node, parent: Node, grandparent: Node): boolean;
export function nodeIsDecorated(node: Node, parent?: Node, grandparent?: Node): boolean {
return node.decorators !== undefined
&& nodeCanBeDecorated(node);
&& nodeCanBeDecorated(node, parent, grandparent);
}
export function nodeOrChildIsDecorated(node: Node): boolean {
return nodeIsDecorated(node) || childIsDecorated(node);
export function nodeOrChildIsDecorated(node: ClassDeclaration): boolean;
export function nodeOrChildIsDecorated(node: ClassElement, parent: Node): boolean;
export function nodeOrChildIsDecorated(node: Node, parent: Node, grandparent: Node): boolean;
export function nodeOrChildIsDecorated(node: Node, parent?: Node, grandparent?: Node): boolean {
return nodeIsDecorated(node, parent, grandparent) || childIsDecorated(node, parent);
}
export function childIsDecorated(node: Node): boolean {
export function childIsDecorated(node: ClassDeclaration): boolean;
export function childIsDecorated(node: Node, parent: Node): boolean;
export function childIsDecorated(node: Node, parent?: Node): boolean {
switch (node.kind) {
case SyntaxKind.ClassDeclaration:
return forEach((<ClassDeclaration>node).members, nodeOrChildIsDecorated);
return forEach((<ClassDeclaration>node).members, m => nodeOrChildIsDecorated(m, node, parent));
case SyntaxKind.MethodDeclaration:
case SyntaxKind.SetAccessor:
return forEach((<FunctionLikeDeclaration>node).parameters, nodeIsDecorated);
return forEach((<FunctionLikeDeclaration>node).parameters, p => nodeIsDecorated(p, node, parent));
}
}

View File

@@ -224,6 +224,32 @@ namespace ts {
}
}
});
// https://github.com/Microsoft/TypeScript/issues/17384
testBaseline("transformAddDecoratedNode", () => {
return ts.transpileModule("", {
transformers: {
before: [transformAddDecoratedNode],
},
compilerOptions: {
target: ts.ScriptTarget.ES5,
newLine: NewLineKind.CarriageReturnLineFeed,
}
}).outputText;
function transformAddDecoratedNode(_context: ts.TransformationContext) {
return (sourceFile: ts.SourceFile): ts.SourceFile => {
return visitNode(sourceFile);
};
function visitNode(sf: ts.SourceFile) {
// produce `class Foo { @Bar baz() {} }`;
const classDecl = ts.createClassDeclaration([], [], "Foo", /*typeParameters*/ undefined, /*heritageClauses*/ undefined, [
ts.createMethod([ts.createDecorator(ts.createIdentifier("Bar"))], [], /**/ undefined, "baz", /**/ undefined, /**/ undefined, [], /**/ undefined, ts.createBlock([]))
]);
return ts.updateSourceFileNode(sf, [classDecl]);
}
}
});
});
}