Skip to content

Commit

Permalink
feat: #473 - MethodDeclaration should extend QuestionTokenableNode.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Oct 24, 2018
1 parent abbe9dd commit 674d3d2
Show file tree
Hide file tree
Showing 11 changed files with 70 additions and 44 deletions.
9 changes: 5 additions & 4 deletions lib/ts-simple-ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -705,6 +705,7 @@ export declare class TypeGuards {
*/
static hasName(node: Node): node is Node & {
getName(): string;
getNameNode(): Node;
};
/**
* Gets if the node has a body.
Expand Down Expand Up @@ -3937,9 +3938,9 @@ export declare class GetAccessorDeclaration extends GetAccessorDeclarationBase<t
getStructure(): GetAccessorDeclarationStructure;
}

declare const MethodDeclarationBase: Constructor<ChildOrderableNode> & Constructor<TextInsertableNode> & Constructor<OverloadableNode> & Constructor<BodyableNode> & Constructor<DecoratableNode> & Constructor<AbstractableNode> & Constructor<ScopedNode> & Constructor<StaticableNode> & Constructor<AsyncableNode> & Constructor<GeneratorableNode> & Constructor<FunctionLikeDeclaration> & Constructor<PropertyNamedNode> & typeof Node;
declare const MethodDeclarationBase: Constructor<ChildOrderableNode> & Constructor<TextInsertableNode> & Constructor<OverloadableNode> & Constructor<BodyableNode> & Constructor<DecoratableNode> & Constructor<AbstractableNode> & Constructor<ScopedNode> & Constructor<QuestionTokenableNode> & Constructor<StaticableNode> & Constructor<AsyncableNode> & Constructor<GeneratorableNode> & Constructor<FunctionLikeDeclaration> & Constructor<PropertyNamedNode> & typeof Node;

declare const MethodDeclarationOverloadBase: Constructor<JSDocableNode> & Constructor<ChildOrderableNode> & Constructor<TextInsertableNode> & Constructor<ScopedNode> & Constructor<TypeParameteredNode> & Constructor<AbstractableNode> & Constructor<StaticableNode> & Constructor<AsyncableNode> & Constructor<ModifierableNode> & Constructor<GeneratorableNode> & Constructor<SignaturedDeclaration> & typeof Node;
declare const MethodDeclarationOverloadBase: Constructor<JSDocableNode> & Constructor<ChildOrderableNode> & Constructor<TextInsertableNode> & Constructor<ScopedNode> & Constructor<TypeParameteredNode> & Constructor<AbstractableNode> & Constructor<QuestionTokenableNode> & Constructor<StaticableNode> & Constructor<AsyncableNode> & Constructor<ModifierableNode> & Constructor<GeneratorableNode> & Constructor<SignaturedDeclaration> & typeof Node;

export declare class MethodDeclaration extends MethodDeclarationBase<ts.MethodDeclaration> {
/**
Expand Down Expand Up @@ -9907,14 +9908,14 @@ export interface GetAccessorDeclarationStructure extends GetAccessorDeclarationS
interface GetAccessorDeclarationSpecificStructure {
}

export interface MethodDeclarationStructure extends MethodDeclarationSpecificStructure, PropertyNamedNodeStructure, StaticableNodeStructure, DecoratableNodeStructure, AbstractableNodeStructure, ScopedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, FunctionLikeDeclarationStructure, BodyableNodeStructure {
export interface MethodDeclarationStructure extends MethodDeclarationSpecificStructure, PropertyNamedNodeStructure, StaticableNodeStructure, DecoratableNodeStructure, AbstractableNodeStructure, ScopedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, FunctionLikeDeclarationStructure, BodyableNodeStructure, QuestionTokenableNodeStructure {
}

interface MethodDeclarationSpecificStructure {
overloads?: MethodDeclarationOverloadStructure[];
}

export interface MethodDeclarationOverloadStructure extends StaticableNodeStructure, AbstractableNodeStructure, ScopedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, SignaturedDeclarationStructure, TypeParameteredNodeStructure, JSDocableNodeStructure {
export interface MethodDeclarationOverloadStructure extends StaticableNodeStructure, AbstractableNodeStructure, ScopedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, SignaturedDeclarationStructure, TypeParameteredNodeStructure, JSDocableNodeStructure, QuestionTokenableNodeStructure {
}

export interface PropertyDeclarationStructure extends PropertyDeclarationSpecificStructure, PropertyNamedNodeStructure, TypedNodeStructure, QuestionTokenableNodeStructure, ExclamationTokenableNodeStructure, StaticableNodeStructure, ScopedNodeStructure, JSDocableNodeStructure, ReadonlyableNodeStructure, InitializerExpressionableNodeStructure, DecoratableNodeStructure, AbstractableNodeStructure {
Expand Down
3 changes: 3 additions & 0 deletions src/compiler/ast/base/QuestionTokenableNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export function QuestionTokenableNode<T extends Constructor<QuestionTokenableNod
return this;

function getInsertPos(this: QuestionTokenableNode & Node) {
if (TypeGuards.hasName(this))
return this.getNameNode().getEnd();

const colonNode = this.getFirstChildByKind(SyntaxKind.ColonToken);
if (colonNode != null)
return colonNode.getStart();
Expand Down
7 changes: 3 additions & 4 deletions src/compiler/ast/class/MethodDeclaration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,19 @@ import { removeOverloadableClassMember } from "../../../manipulation";
import * as getStructureFuncs from "../../../manipulation/helpers/getStructureFunctions";
import { MethodDeclarationOverloadStructure, MethodDeclarationStructure, MethodDeclarationSpecificStructure } from "../../../structures";
import { SyntaxKind, ts } from "../../../typescript";
import { Signature } from "../../symbols";
import { AsyncableNode, BodyableNode, ChildOrderableNode, DecoratableNode, GeneratorableNode, PropertyNamedNode, ScopedNode, StaticableNode,
TextInsertableNode, SignaturedDeclaration, ModifierableNode, JSDocableNode, TypeParameteredNode } from "../base";
TextInsertableNode, SignaturedDeclaration, ModifierableNode, JSDocableNode, TypeParameteredNode, QuestionTokenableNode } from "../base";
import { callBaseSet } from "../callBaseSet";
import { Node } from "../common";
import { FunctionLikeDeclaration, insertOverloads, OverloadableNode } from "../function";
import { AbstractableNode } from "./base";
import { callBaseGetStructure } from "../callBaseGetStructure";

export const MethodDeclarationBase = ChildOrderableNode(TextInsertableNode(OverloadableNode(BodyableNode(DecoratableNode(AbstractableNode(ScopedNode(
StaticableNode(AsyncableNode(GeneratorableNode(FunctionLikeDeclaration(PropertyNamedNode(Node)))))
QuestionTokenableNode(StaticableNode(AsyncableNode(GeneratorableNode(FunctionLikeDeclaration(PropertyNamedNode(Node))))))
)))))));
export const MethodDeclarationOverloadBase = JSDocableNode(ChildOrderableNode(TextInsertableNode(ScopedNode(TypeParameteredNode(AbstractableNode(
StaticableNode(AsyncableNode(ModifierableNode(GeneratorableNode(SignaturedDeclaration(Node)
QuestionTokenableNode(StaticableNode(AsyncableNode(ModifierableNode(GeneratorableNode(SignaturedDeclaration(Node))
))))))))));

export class MethodDeclaration extends MethodDeclarationBase<ts.MethodDeclaration> {
Expand Down
1 change: 1 addition & 0 deletions src/manipulation/helpers/getStructureFunctions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ export function fromMethodDeclarationOverload(node: compiler.MethodDeclaration):
ObjectUtils.assign(structure, getMixinStructureFuncs.fromStaticableNode(node));
ObjectUtils.assign(structure, getMixinStructureFuncs.fromAbstractableNode(node));
ObjectUtils.assign(structure, getMixinStructureFuncs.fromScopedNode(node));
ObjectUtils.assign(structure, getMixinStructureFuncs.fromQuestionTokenableNode(node));
return structure;
}
export function fromFunctionDeclarationOverload(node: compiler.FunctionDeclaration): structures.FunctionDeclarationOverloadStructure {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ export class MethodDeclarationStructurePrinter extends FactoryStructurePrinter<M
setValueIfUndefined(overload, "scope", structure.scope);
setValueIfUndefined(overload, "isStatic", structure.isStatic); // allow people to do stupid things
setValueIfUndefined(overload, "isAbstract", structure.isAbstract);
setValueIfUndefined(overload, "hasQuestionToken", structure.hasQuestionToken);
}

return overloads;
Expand Down Expand Up @@ -72,6 +73,7 @@ export class MethodDeclarationStructurePrinter extends FactoryStructurePrinter<M
this.factory.forDecorator().printTexts(writer, (structure as MethodDeclarationStructure).decorators);
this.factory.forModifierableNode().printText(writer, structure);
writer.write(name);
writer.conditionalWrite(structure.hasQuestionToken, "?");
this.factory.forTypeParameterDeclaration().printTextsWithBrackets(writer, structure.typeParameters);
writer.write("(");
this.factory.forParameterDeclaration().printTexts(writer, structure.parameters);
Expand Down
8 changes: 5 additions & 3 deletions src/structures/class/MethodDeclarationStructure.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { AbstractableNodeStructure, AsyncableNodeStructure, BodyableNodeStructure, DecoratableNodeStructure, GeneratorableNodeStructure,
JSDocableNodeStructure, PropertyNamedNodeStructure, ScopedNodeStructure, SignaturedDeclarationStructure, StaticableNodeStructure,
TypeParameteredNodeStructure } from "../base";
TypeParameteredNodeStructure, QuestionTokenableNodeStructure } from "../base";
import { FunctionLikeDeclarationStructure } from "../function";

export interface MethodDeclarationStructure
extends MethodDeclarationSpecificStructure, PropertyNamedNodeStructure, StaticableNodeStructure, DecoratableNodeStructure, AbstractableNodeStructure,
ScopedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, FunctionLikeDeclarationStructure, BodyableNodeStructure
ScopedNodeStructure, AsyncableNodeStructure, GeneratorableNodeStructure, FunctionLikeDeclarationStructure, BodyableNodeStructure,
QuestionTokenableNodeStructure
{
}

Expand All @@ -15,6 +16,7 @@ export interface MethodDeclarationSpecificStructure {

export interface MethodDeclarationOverloadStructure
extends StaticableNodeStructure, AbstractableNodeStructure, ScopedNodeStructure, AsyncableNodeStructure,
GeneratorableNodeStructure, SignaturedDeclarationStructure, TypeParameteredNodeStructure, JSDocableNodeStructure
GeneratorableNodeStructure, SignaturedDeclarationStructure, TypeParameteredNodeStructure, JSDocableNodeStructure,
QuestionTokenableNodeStructure
{
}
62 changes: 38 additions & 24 deletions src/tests/compiler/base/questionTokenableNodeTests.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,61 @@
import { expect } from "chai";
import { ParameterDeclaration, ClassDeclaration, PropertyDeclaration, QuestionTokenableNode } from "../../../compiler";
import * as errors from "../../../errors";
import { SyntaxKind } from "../../../typescript";
import { ClassDeclaration, PropertyDeclaration, QuestionTokenableNode } from "../../../compiler";
import { QuestionTokenableNodeStructure } from "../../../structures";
import { getInfoFromText, getInfoFromTextWithDescendant } from "../testHelpers";
import { getInfoFromText } from "../testHelpers";

describe(nameof(QuestionTokenableNode), () => {
function getInfoWithFirstPropertyFromText(text: string) {
function getInfoWithFirstMember(text: string) {
const result = getInfoFromText<ClassDeclaration>(text);
return {...result, firstProperty: result.firstChild.getInstanceProperties()[0] as PropertyDeclaration };
return {...result, firstMember: result.firstChild.getMembers()[0] as QuestionTokenableNode };
}

describe(nameof<QuestionTokenableNode>(d => d.hasQuestionToken), () => {
function doTest(text: string, value: boolean) {
const {firstProperty} = getInfoWithFirstPropertyFromText(text);
expect(firstProperty.hasQuestionToken()).to.equal(value);
const {firstMember} = getInfoWithFirstMember(text);
expect(firstMember.hasQuestionToken()).to.equal(value);
}

it("should have a question token when has one", () => {
it("should have when property with one", () => {
doTest("class MyClass { prop?: string; }", true);
});

it("should not have a question token when not has one", () => {
it("should have when a method with one", () => {
doTest("declare class MyClass { myMethod?(): string; }", true);
});

it("should not have when not has one", () => {
doTest("class MyClass { prop: string; }", false);
});
});

describe(nameof<QuestionTokenableNode>(d => d.getQuestionTokenNode), () => {
it("should be get the question token node", () => {
const {firstProperty} = getInfoWithFirstPropertyFromText("class MyClass { prop?: string; }");
expect(firstProperty.getQuestionTokenNode()!.getText()).to.equal("?");
const {firstMember} = getInfoWithFirstMember("class MyClass { prop?: string; }");
expect(firstMember.getQuestionTokenNode()!.getText()).to.equal("?");
});

it("should be undefined when not optional", () => {
const {firstProperty} = getInfoWithFirstPropertyFromText("class MyClass { prop: string;} ");
expect(firstProperty.getQuestionTokenNode()).to.be.undefined;
const {firstMember} = getInfoWithFirstMember("class MyClass { prop: string;} ");
expect(firstMember.getQuestionTokenNode()).to.be.undefined;
});
});

describe(nameof<QuestionTokenableNode>(d => d.getQuestionTokenNodeOrThrow), () => {
it("should be get the question token node", () => {
const {firstProperty} = getInfoWithFirstPropertyFromText("class MyClass {\nprop?: string;}\n");
expect(firstProperty.getQuestionTokenNodeOrThrow().getText()).to.equal("?");
const {firstMember} = getInfoWithFirstMember("class MyClass {\nprop?: string;}\n");
expect(firstMember.getQuestionTokenNodeOrThrow().getText()).to.equal("?");
});

it("should throw when not optional", () => {
const {firstProperty} = getInfoWithFirstPropertyFromText("class MyClass {\nprop: string;}\n");
expect(() => firstProperty.getQuestionTokenNodeOrThrow()).to.throw();
const {firstMember} = getInfoWithFirstMember("class MyClass {\nprop: string;}\n");
expect(() => firstMember.getQuestionTokenNodeOrThrow()).to.throw();
});
});

describe(nameof<QuestionTokenableNode>(d => d.setHasQuestionToken), () => {
function doTest(startText: string, value: boolean, expected: string) {
const {firstProperty, sourceFile} = getInfoWithFirstPropertyFromText(startText);
firstProperty.setHasQuestionToken(value);
const {firstMember, sourceFile} = getInfoWithFirstMember(startText);
firstMember.setHasQuestionToken(value);
expect(sourceFile.getFullText()).to.be.equal(expected);
}

Expand All @@ -80,12 +82,24 @@ describe(nameof(QuestionTokenableNode), () => {
it("should be set as optional when has no type nor semi-colon", () => {
doTest("class MyClass { prop }", true, "class MyClass { prop? }");
});

it("should set when a method", () => {
doTest("declare class MyClass { method(): string; }", true, "declare class MyClass { method?(): string; }");
});

it("should set when a method with type parameters", () => {
doTest("declare class MyClass { method<T>(): string; }", true, "declare class MyClass { method?<T>(): string; }");
});

it("should remove when a method", () => {
doTest("declare class MyClass { method?(): string; }", false, "declare class MyClass { method(): string; }");
});
});

describe(nameof<PropertyDeclaration>(p => p.set), () => {
function doTest(startCode: string, structure: QuestionTokenableNodeStructure, expectedCode: string) {
const {firstProperty, sourceFile} = getInfoWithFirstPropertyFromText(startCode);
firstProperty.set(structure);
const {firstMember, sourceFile} = getInfoWithFirstMember(startCode);
(firstMember as PropertyDeclaration).set(structure);
expect(sourceFile.getText()).to.equal(expectedCode);
}

Expand All @@ -104,8 +118,8 @@ describe(nameof(QuestionTokenableNode), () => {

describe(nameof<PropertyDeclaration>(p => p.getStructure), () => {
function doTest(startCode: string, hasToken: boolean) {
const {firstProperty, sourceFile} = getInfoWithFirstPropertyFromText(startCode);
expect(firstProperty.getStructure().hasQuestionToken).to.equal(hasToken);
const {firstMember} = getInfoWithFirstMember(startCode);
expect((firstMember as PropertyDeclaration).getStructure().hasQuestionToken).to.equal(hasToken);
}

it("should be false when not has one", () => {
Expand Down
7 changes: 4 additions & 3 deletions src/tests/compiler/class/base/classDeclarationBaseTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -766,7 +766,8 @@ describe(nameof(ClassLikeDeclarationBase), () => {
scope: Scope.Public,
isAsync: true,
isGenerator: true,
overloads: [{}, { scope: Scope.Private, isStatic: false }],
hasQuestionToken: true,
overloads: [{}, { scope: Scope.Private, isStatic: false, hasQuestionToken: false }],
parameters: [{ name: "param" }],
returnType: "number",
typeParameters: [{ name: "T" }],
Expand All @@ -779,8 +780,8 @@ describe(nameof(ClassLikeDeclarationBase), () => {
bodyText: "console.log('here');"
};
doTest("class c {\n}", 0, [structure],
"class c {\n public static myMethod();\n private myMethod();\n" +
" /**\n * Test\n */\n @dec\n public static async myMethod<T>(param): number {\n" +
"class c {\n public static myMethod?();\n private myMethod();\n" +
" /**\n * Test\n */\n @dec\n public static async myMethod?<T>(param): number {\n" +
" type T = string;\n\n interface I {\n }\n\n enum E {\n }\n\n" +
" function F() {\n }\n\n class C {\n }\n\n namespace N {\n }\n\n" +
" console.log('here');\n" +
Expand Down
9 changes: 6 additions & 3 deletions src/tests/compiler/class/methodDeclarationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -239,15 +239,16 @@ describe(nameof(MethodDeclaration), () => {
expect(structure).to.deep.equal(expectedStructure);
}

it("should get structure when empty", () => {
doTest("class Test { abstract method() {} }", {
it("should get structure when abstract", () => {
doTest("class Test { abstract method?() {} }", {
bodyText: "",
decorators: [],
docs: [],
isAbstract: true,
isAsync: false,
isGenerator: false,
isStatic: false,
hasQuestionToken: true,
name: "method",
overloads: [],
parameters: [],
Expand Down Expand Up @@ -275,6 +276,7 @@ class Test {
isAsync: true,
isGenerator: true,
isStatic: true,
hasQuestionToken: false,
name: "method",
overloads: [{
docs: [{ description: "overload" }],
Expand All @@ -285,7 +287,8 @@ class Test {
scope: Scope.Protected,
parameters: [{ name: "p" }],
typeParameters: [{ name: "T" }],
isAbstract: false
isAbstract: false,
hasQuestionToken: false
}],
parameters: [{ name: "param" }],
returnType: "string",
Expand Down
3 changes: 1 addition & 2 deletions src/tests/compiler/function/functionDeclarationTests.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { expect, assert } from "chai";
import { expect } from "chai";
import { FunctionDeclaration } from "../../../compiler";
import { FunctionDeclarationStructure, FunctionDeclarationOverloadStructure, FunctionDeclarationSpecificStructure } from "../../../structures";
import { getInfoFromText } from "../testHelpers";
import { TypeGuards } from "../../../utils/TypeGuards";

describe(nameof(FunctionDeclaration), () => {
describe(nameof<FunctionDeclaration>(f => f.getName), () => {
Expand Down

0 comments on commit 674d3d2

Please sign in to comment.