Skip to content

Commit

Permalink
feat: #605 - Add insertMember-like methods to TypeElementMemberedNode.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed May 20, 2019
1 parent fd4d698 commit 93a070d
Show file tree
Hide file tree
Showing 7 changed files with 254 additions and 7 deletions.
25 changes: 24 additions & 1 deletion lib/ts-morph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3111,6 +3111,28 @@ declare type TypedNodeExtensionType = Node<ts.Node & {
export declare function TypeElementMemberedNode<T extends Constructor<TypeElementMemberedNodeExtensionType>>(Base: T): Constructor<TypeElementMemberedNode> & T;

export interface TypeElementMemberedNode {
/**
* Adds a member.
* @param member - Member to add.
*/
addMember(member: string | WriterFunction | TypeElementMemberStructures): TypeElementTypes | CommentTypeElement;
/**
* Adds members.
* @param members - Collection of members to add.
*/
addMembers(members: string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[]): (TypeElementTypes | CommentTypeElement)[];
/**
* Inserts a member.
* @param index - Child index to insert at.
* @param member - Member to insert.
*/
insertMember(index: number, member: string | WriterFunction | TypeElementMemberStructures): TypeElementTypes | CommentTypeElement;
/**
* Inserts members.
* @param index - Child index to insert at.
* @param members - Collection of members to insert.
*/
insertMembers(index: number, members: string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[]): (TypeElementTypes | CommentTypeElement)[];
/**
* Add construct signature.
* @param structure - Structure representing the construct signature.
Expand Down Expand Up @@ -10447,7 +10469,8 @@ export declare abstract class SettingsContainer<T extends object> {

export declare type StatementStructures = ClassDeclarationStructure | EnumDeclarationStructure | FunctionDeclarationStructure | InterfaceDeclarationStructure | NamespaceDeclarationStructure | TypeAliasDeclarationStructure | ImportDeclarationStructure | ExportDeclarationStructure | ExportAssignmentStructure | VariableStatementStructure;
export declare type ClassMemberStructures = ConstructorDeclarationStructure | GetAccessorDeclarationStructure | SetAccessorDeclarationStructure | MethodDeclarationStructure | PropertyDeclarationStructure;
export declare type InterfaceMemberStructures = CallSignatureDeclarationStructure | ConstructSignatureDeclarationStructure | IndexSignatureDeclarationStructure | MethodSignatureStructure | PropertySignatureStructure;
export declare type TypeElementMemberStructures = CallSignatureDeclarationStructure | ConstructSignatureDeclarationStructure | IndexSignatureDeclarationStructure | MethodSignatureStructure | PropertySignatureStructure;
export declare type InterfaceMemberStructures = TypeElementMemberStructures;
export declare type ObjectLiteralElementMemberStructures = PropertyAssignmentStructure | ShorthandPropertyAssignmentStructure | SpreadAssignmentStructure | GetAccessorDeclarationStructure | SetAccessorDeclarationStructure | MethodDeclarationStructure;
export declare type JsxStructures = JsxAttributeStructure | JsxSpreadAttributeStructure | JsxElementStructure | JsxSelfClosingElementStructure;
export declare type Structures = StatementStructures | ClassMemberStructures | EnumMemberStructure | InterfaceMemberStructures | ObjectLiteralElementMemberStructures | JsxStructures | FunctionDeclarationOverloadStructure | MethodDeclarationOverloadStructure | ConstructorDeclarationOverloadStructure | ParameterDeclarationStructure | TypeParameterDeclarationStructure | SourceFileStructure | ExportSpecifierStructure | ImportSpecifierStructure | VariableDeclarationStructure | JSDocStructure | DecoratorStructure;
Expand Down
60 changes: 57 additions & 3 deletions src/compiler/ast/base/TypeElementMemberedNode.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import { CodeBlockWriter } from "../../../codeBlockWriter";
import * as errors from "../../../errors";
import { getEndIndexFromArray, insertIntoBracesOrSourceFileWithGetChildren } from "../../../manipulation";
import { getEndIndexFromArray, insertIntoBracesOrSourceFileWithGetChildren, insertIntoBracesOrSourceFileWithGetChildrenWithComments } from "../../../manipulation";
import { CallSignatureDeclarationStructure, ConstructSignatureDeclarationStructure, IndexSignatureDeclarationStructure, MethodSignatureStructure,
PropertySignatureStructure, TypeElementMemberedNodeStructure, OptionalKind, Structure } from "../../../structures";
import { Constructor } from "../../../types";
PropertySignatureStructure, TypeElementMemberedNodeStructure, OptionalKind, Structure, TypeElementMemberStructures } from "../../../structures";
import { Constructor, WriterFunction } from "../../../types";
import { SyntaxKind, ts } from "../../../typescript";
import { getNodeByNameOrFindFunction, getNotFoundErrorMessageForNameOrFindFunction } from "../../../utils";
import { TypeElementTypes } from "../aliases";
Expand All @@ -16,6 +16,28 @@ import { ExtendedParser } from "../utils";
export type TypeElementMemberedNodeExtensionType = Node<ts.Node & { members: ts.NodeArray<ts.TypeElement>; }>;

export interface TypeElementMemberedNode {
/**
* Adds a member.
* @param member - Member to add.
*/
addMember(member: string | WriterFunction | TypeElementMemberStructures): TypeElementTypes | CommentTypeElement;
/**
* Adds members.
* @param members - Collection of members to add.
*/
addMembers(members: string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[]): (TypeElementTypes | CommentTypeElement)[];
/**
* Inserts a member.
* @param index - Child index to insert at.
* @param member - Member to insert.
*/
insertMember(index: number, member: string | WriterFunction | TypeElementMemberStructures): TypeElementTypes | CommentTypeElement;
/**
* Inserts members.
* @param index - Child index to insert at.
* @param members - Collection of members to insert.
*/
insertMembers(index: number, members: string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[]): (TypeElementTypes | CommentTypeElement)[];
/**
* Add construct signature.
* @param structure - Structure representing the construct signature.
Expand Down Expand Up @@ -236,6 +258,38 @@ export interface TypeElementMemberedNode {

export function TypeElementMemberedNode<T extends Constructor<TypeElementMemberedNodeExtensionType>>(Base: T): Constructor<TypeElementMemberedNode> & T {
return class extends Base implements TypeElementMemberedNode {
addMember(member: string | WriterFunction | TypeElementMemberStructures): TypeElementTypes | CommentTypeElement {
return this.addMembers([member])[0];
}

addMembers(members: string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[]): (TypeElementTypes | CommentTypeElement)[] {
return this.insertMembers(getEndIndexFromArray(this.getMembersWithComments()), members);
}

insertMember(index: number, member: string | WriterFunction | TypeElementMemberStructures): TypeElementTypes | CommentTypeElement {
return this.insertMembers(index, [member])[0];
}

insertMembers(index: number, members: string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[]): (TypeElementTypes | CommentTypeElement)[] {
return insertIntoBracesOrSourceFileWithGetChildrenWithComments({
getIndexedChildren: () => this.getMembersWithComments(),
index,
parent: this,
write: writer => {
writer.newLineIfLastNot();

// create a new writer here because the class member printer will add a blank line in certain cases
// at the front if it's not on the first line of a block
const memberWriter = this._getWriter();
const memberPrinter = this._context.structurePrinterFactory.forTypeElementMember();
memberPrinter.printTexts(memberWriter, members);
writer.write(memberWriter.toString());

writer.newLineIfLastNot();
}
}) as (TypeElementTypes | CommentTypeElement)[];
}

addConstructSignature(structure: OptionalKind<ConstructSignatureDeclarationStructure>) {
return this.addConstructSignatures([structure])[0];
}
Expand Down
5 changes: 5 additions & 0 deletions src/factories/StructurePrinterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,11 @@ export class StructurePrinterFactory {
return new structurePrinters.TypeElementMemberedNodeStructurePrinter(this);
}

@Memoize
forTypeElementMember(): structurePrinters.TypeElementMemberStructurePrinter {
return new structurePrinters.TypeElementMemberStructurePrinter(this);
}

@Memoize
forJsxAttribute(): structurePrinters.JsxAttributeStructurePrinter {
return new structurePrinters.JsxAttributeStructurePrinter(this);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { CodeBlockWriter } from "../../codeBlockWriter";
import * as errors from "../../errors";
import { StructurePrinterFactory } from "../../factories";
import { TypeElementMemberStructures, StructureKind } from "../../structures";
import { WriterFunction } from "../../types";
import { isLastNonWhitespaceCharCloseBrace } from "../helpers";
import { Printer } from "../Printer";

export type TypeElementStructuresArrayItem = string | WriterFunction | TypeElementMemberStructures;

export class TypeElementMemberStructurePrinter extends Printer<TypeElementStructuresArrayItem> {
constructor(private readonly factory: StructurePrinterFactory) {
super();
}

printTexts(writer: CodeBlockWriter, members: ReadonlyArray<TypeElementStructuresArrayItem> | string | WriterFunction | undefined) {
if (members == null)
return;

if (typeof members === "string" || members instanceof Function)
this.printText(writer, members);
else {
for (const member of members) {
if (isLastNonWhitespaceCharCloseBrace(writer))
writer.blankLineIfLastNot();
else if (!writer.isAtStartOfFirstLineOfBlock())
writer.newLineIfLastNot();

this.printText(writer, member);
}
}
}

printText(writer: CodeBlockWriter, members: TypeElementStructuresArrayItem) {
if (typeof members === "string" || members instanceof Function || members == null) {
this.printTextOrWriterFunc(writer, members);
return;
}

switch (members.kind) {
case StructureKind.PropertySignature:
this.factory.forPropertySignature().printText(writer, members);
break;
case StructureKind.MethodSignature:
this.factory.forMethodSignature().printText(writer, members);
break;
case StructureKind.CallSignature:
this.factory.forCallSignatureDeclaration().printText(writer, members);
break;
case StructureKind.IndexSignature:
this.factory.forIndexSignatureDeclaration().printText(writer, members);
break;
case StructureKind.ConstructSignature:
this.factory.forConstructSignatureDeclaration().printText(writer, members);
break;
default:
errors.throwNotImplementedForNeverValueError(members);
}
}
}
1 change: 1 addition & 0 deletions src/structurePrinters/interface/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ export * from "./InterfaceDeclarationStructurePrinter";
export * from "./MethodSignatureStructurePrinter";
export * from "./PropertySignatureStructurePrinter";
export * from "./TypeElementMemberedNodeStructurePrinter";
export * from "./TypeElementMemberStructurePrinter";
4 changes: 3 additions & 1 deletion src/structures/aliases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,11 @@ export type StatementStructures = ClassDeclarationStructure | EnumDeclarationStr
export type ClassMemberStructures = ConstructorDeclarationStructure | GetAccessorDeclarationStructure | SetAccessorDeclarationStructure | MethodDeclarationStructure
| PropertyDeclarationStructure;

export type InterfaceMemberStructures = CallSignatureDeclarationStructure | ConstructSignatureDeclarationStructure | IndexSignatureDeclarationStructure
export type TypeElementMemberStructures = CallSignatureDeclarationStructure | ConstructSignatureDeclarationStructure | IndexSignatureDeclarationStructure
| MethodSignatureStructure | PropertySignatureStructure;

export type InterfaceMemberStructures = TypeElementMemberStructures;

export type ObjectLiteralElementMemberStructures = PropertyAssignmentStructure | ShorthandPropertyAssignmentStructure | SpreadAssignmentStructure
| GetAccessorDeclarationStructure | SetAccessorDeclarationStructure | MethodDeclarationStructure;

Expand Down
106 changes: 104 additions & 2 deletions src/tests/compiler/ast/base/typeElementMemberedNodeTests.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,114 @@
import { expect } from "chai";
import { CallSignatureDeclaration, ConstructSignatureDeclaration, IndexSignatureDeclaration, InterfaceDeclaration, MethodSignature, PropertySignature,
TypeElementMemberedNode, CommentTypeElement, TypeLiteralNode } from "../../../../compiler";
TypeElementMemberedNode, CommentTypeElement, TypeLiteralNode, Node } from "../../../../compiler";
import { CallSignatureDeclarationStructure, ConstructSignatureDeclarationStructure, IndexSignatureDeclarationStructure, MethodSignatureStructure,
PropertySignatureStructure, TypeElementMemberedNodeStructure, StructureKind } from "../../../../structures";
PropertySignatureStructure, TypeElementMemberedNodeStructure, TypeElementMemberStructures, StructureKind } from "../../../../structures";
import { WriterFunction } from "../../../../types";
import { SyntaxKind } from "../../../../typescript";
import { getInfoFromText, getInfoFromTextWithDescendant, OptionalKindAndTrivia } from "../../testHelpers";

describe(nameof(TypeElementMemberedNode), () => {
describe(nameof<TypeElementMemberedNode>(d => d.addMember), () => {
function doTest(startCode: string, member: string | WriterFunction | TypeElementMemberStructures, expectedCode: string) {
const { sourceFile, firstChild } = getInfoFromText<InterfaceDeclaration>(startCode);
const result = firstChild.addMember(member);
expect(sourceFile.getFullText()).to.equal(expectedCode);
expect(result).to.be.instanceOf(Node);
}

it("should add a member", () => {
const expectedText = "interface i {\n // test\n p1;\n p2;\n p3;\n}";
doTest("interface i {\n // test\n p1;\n p2;\n}", {
kind: StructureKind.PropertySignature,
name: "p3"
}, expectedText);
});
});

describe(nameof<TypeElementMemberedNode>(d => d.addMembers), () => {
type MembersType = string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[];
function doTest(startCode: string, members: MembersType, expectedCode: string, expectedResultCount: number) {
const { sourceFile, firstChild } = getInfoFromText<InterfaceDeclaration>(startCode);
const result = firstChild.addMembers(members);
expect(sourceFile.getFullText()).to.equal(expectedCode);
expect(result.length).to.equal(expectedResultCount);
}

it("should add members", () => {
const expectedText = "interface i {\n // test\n p1;\n p2;\n p3;\n m4();\n}";
doTest("interface i {\n // test\n p1;\n p2;\n}", [{
kind: StructureKind.PropertySignature,
name: "p3"
}, {
kind: StructureKind.MethodSignature,
name: "m4"
}], expectedText, 2);
});
});

describe(nameof<TypeElementMemberedNode>(d => d.insertMember), () => {
function doTest(startCode: string, insertIndex: number, member: string | WriterFunction | TypeElementMemberStructures, expectedCode: string) {
const { sourceFile, firstChild } = getInfoFromText<InterfaceDeclaration>(startCode);
const result = firstChild.insertMember(insertIndex, member);
expect(sourceFile.getFullText()).to.equal(expectedCode);
expect(result).to.be.instanceOf(Node);
}

it("should insert at the specified index", () => {
const expectedText = "interface i {\n p1;\n p2;\n p3;\n}";
doTest("interface i {\n p1;\n p3;\n}", 1, {
kind: StructureKind.PropertySignature,
name: "p2"
}, expectedText);
});
});

describe(nameof<TypeElementMemberedNode>(d => d.insertMembers), () => {
type MembersType = string | WriterFunction | (string | WriterFunction | TypeElementMemberStructures)[];
function doTest(startCode: string, insertIndex: number, members: MembersType, expectedCode: string, expectedResultCount: number) {
const { sourceFile, firstChild } = getInfoFromText<InterfaceDeclaration>(startCode);
const result = firstChild.insertMembers(insertIndex, members);
expect(sourceFile.getFullText()).to.equal(expectedCode);
expect(result.length).to.equal(expectedResultCount);
}

it("should accept providing a string", () => {
doTest("interface i {\n}", 0, "// test", "interface i {\n // test\n}", 1);
});

it("should accept providing a writer function", () => {
doTest("interface i {\n}", 0, writer => writer.write("// test"), "interface i {\n // test\n}", 1);
});

it("should insert all the different kinds of members", () => {
const expectedText = `interface i {\n new();\n p1;\n m1();\n [key: string];\n (): void;\n // testing\n}`;
doTest("interface i {\n}", 0, [{
kind: StructureKind.ConstructSignature
}, {
kind: StructureKind.PropertySignature,
name: "p1"
}, {
kind: StructureKind.MethodSignature,
name: "m1"
}, {
kind: StructureKind.IndexSignature
}, {
kind: StructureKind.CallSignature
},
"// testing"
], expectedText, 6);
});

it("should insert between members", () => {
const expectedText = "interface i {\n p1;\n p2;\n p3;\n}";
doTest("interface i {\n p1;\n p3;\n}", 1, [{
kind: StructureKind.PropertySignature,
name: "p2"
}
], expectedText, 1);
});
});

describe(nameof<TypeElementMemberedNode>(d => d.insertConstructSignatures), () => {
function doTest(startCode: string, insertIndex: number, structures: OptionalKindAndTrivia<ConstructSignatureDeclarationStructure>[], expectedCode: string) {
const { firstChild } = getInfoFromText<InterfaceDeclaration>(startCode);
Expand Down

0 comments on commit 93a070d

Please sign in to comment.