Skip to content

Commit

Permalink
feat: Ability to set a type with a writer.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Apr 30, 2018
1 parent 8e2cd54 commit 5dc3565
Show file tree
Hide file tree
Showing 17 changed files with 81 additions and 24 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"lint": "gulp tslint",
"build": "gulp typescript",
"test-run": "gulp test-run",
"code-generate": "ts-node scripts/code-generate",
"code-generate": "ts-node scripts/code-generate --ignoreDiagnostics",
"refactor": "ts-node scripts/refactor",
"output-wrapped-nodes": "ts-node scripts/outputWrappedNodesInfo",
"publish-code-verification": "npm run code-verification && npm run ensure-no-definition-file-errors && npm run ensure-declaration-file-not-changed",
Expand Down
12 changes: 9 additions & 3 deletions scripts/createStructurePrinterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,26 +62,32 @@ export function createStructurePrinterFactory(inspector: TsSimpleAstInspector) {
bodyText: `return new structurePrinters.${name}(${ctorParams.map(ctorParamToArgument).join(", ")});`,
parameters: exposedCtorParams.map(p => ({
name: p.getNameOrThrow(),
type: p.getTypeNodeOrThrow().getText()
type: getTypeText(p),
hasQuestionToken: p.isOptional()
}))
});
}

return methods;

function exposeCtorParam(ctorParam: ParameterDeclaration) {
const typeName = ctorParam.getTypeNodeOrThrow().getText();
const typeName = getTypeText(ctorParam);
if (typeName === "StructurePrinterFactory")
return false;
return true;
}

function ctorParamToArgument(ctorParam: ParameterDeclaration) {
const typeName = ctorParam.getTypeNodeOrThrow().getText();
const typeName = getTypeText(ctorParam);
if (typeName === "StructurePrinterFactory")
return "this";
return ctorParam.getNameOrThrow();
}

function getTypeText(param: ParameterDeclaration) {
const typeNode = param.getTypeNode();
return typeNode == null ? param.getType().getText() : typeNode.getText();
}
}

function isAllowedStructurePrinter(name: string) {
Expand Down
11 changes: 9 additions & 2 deletions src/compiler/base/TypedNode.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { ts, SyntaxKind } from "../../typescript";
import { CodeBlockWriter } from "../../codeBlockWriter";
import { Constructor } from "../../Constructor";
import { TypedNodeStructure } from "../../structures";
import { callBaseFill } from "../callBaseFill";
import * as errors from "../../errors";
import { insertIntoParentTextRange, removeChildren } from "../../manipulation";
import { StringUtils } from "../../utils";
import { StringUtils, getTextFromStringOrWriter } from "../../utils";
import { Node } from "../common";
import { Type } from "../type/Type";
import { TypeNode } from "../type/TypeNode";
Expand All @@ -20,6 +21,11 @@ export interface TypedNode {
* Gets the type node or throws if none exists.
*/
getTypeNodeOrThrow(): TypeNode;
/**
* Sets the type.
* @param writerFunction - Writer function to set the type with.
*/
setType(writerFunction: (writer: CodeBlockWriter) => void): this;
/**
* Sets the type.
* @param text - Text to set the type to.
Expand All @@ -41,7 +47,8 @@ export function TypedNode<T extends Constructor<TypedNodeExtensionType>>(Base: T
return errors.throwIfNullOrUndefined(this.getTypeNode(), "Expected to find a type node.");
}

setType(text: string) {
setType(textOrWriterFunction: string | ((writer: CodeBlockWriter) => void)) {
const text = getTextFromStringOrWriter(this.getWriterWithQueuedChildIndentation(), textOrWriterFunction);
if (StringUtils.isNullOrWhitespace(text))
return this.removeType();

Expand Down
5 changes: 5 additions & 0 deletions src/factories/StructurePrinterFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export class StructurePrinterFactory {
return new structurePrinters.ModifierableNodeStructurePrinter(this);
}

@Memoize
forTypedNode(separator: string, alwaysWrite?: boolean): structurePrinters.TypedNodeStructurePrinter {
return new structurePrinters.TypedNodeStructurePrinter(this, separator, alwaysWrite);
}

@Memoize
forJSDoc(): structurePrinters.JSDocStructurePrinter {
return new structurePrinters.JSDocStructurePrinter(this);
Expand Down
30 changes: 30 additions & 0 deletions src/structurePrinters/base/TypedNodeStructurePrinter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { CodeBlockWriter } from "../../codeBlockWriter";
import { TypedNodeStructure, QuestionTokenableNodeStructure } from "../../structures";
import { StructurePrinterFactory } from "../../factories";
import { StringUtils } from "../../utils";
import { FactoryStructurePrinter } from "../FactoryStructurePrinter";

export class TypedNodeStructurePrinter extends FactoryStructurePrinter<TypedNodeStructure> {
constructor(factory: StructurePrinterFactory, private readonly separator: string, private readonly alwaysWrite = false) {
super(factory);
}

printText(writer: CodeBlockWriter, structure: TypedNodeStructure) {
let { type } = structure;
if (type == null && this.alwaysWrite === false)
return;

type = type || "any";

// todo: hacky, will need to change this in the future...
const initializerText = typeof type === "string" ? type : getTextForWriterFunc(type);
if (!StringUtils.isNullOrWhitespace(initializerText))
writer.write(`${this.separator} ${initializerText}`);

function getTextForWriterFunc(writerFunc: (writer: CodeBlockWriter) => void) {
const newWriter = new CodeBlockWriter(writer.getOptions());
writerFunc(newWriter);
return newWriter.toString();
}
}
}
1 change: 1 addition & 0 deletions src/structurePrinters/base/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from "./InitializerExpressionableNodeStructurePrinter";
export * from "./ModifierableNodeStructurePrinter";
export * from "./TypedNodeStructurePrinter";
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { CodeBlockWriter } from "../../codeBlockWriter";
import { PropertyDeclarationStructure } from "../../structures";
import { StringUtils } from "../../utils";
import { FactoryStructurePrinter } from "../FactoryStructurePrinter";
import { NewLineFormattingStructuresPrinter } from "../formatting";

Expand All @@ -18,7 +17,7 @@ export class PropertyDeclarationStructurePrinter extends FactoryStructurePrinter
writer.write(structure.name);
writer.conditionalWrite(structure.hasQuestionToken, "?");
writer.conditionalWrite(structure.hasExclamationToken && !structure.hasQuestionToken, "!");
writer.conditionalWrite(!StringUtils.isNullOrWhitespace(structure.type), `: ${structure.type}`);
this.factory.forTypedNode(":").printText(writer, structure);
this.factory.forInitializerExpressionableNode().printText(writer, structure);
writer.write(";");
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,7 @@ export class ParameterDeclarationStructurePrinter extends FactoryStructurePrinte
writer.conditionalWrite(structure.isRestParameter, "...");
writer.write(structure.name);
writer.conditionalWrite(structure.hasQuestionToken, "?");
if (!StringUtils.isNullOrWhitespace(structure.type) || structure.hasQuestionToken)
writer.write(`: ${structure.type || "any"}`);
this.factory.forTypedNode(":", structure.hasQuestionToken).printText(writer, structure);
this.factory.forInitializerExpressionableNode().printText(writer, structure);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ export class PropertySignatureStructurePrinter extends FactoryStructurePrinter<P
this.factory.forModifierableNode().printText(writer, structure);
writer.write(structure.name);
writer.conditionalWrite(structure.hasQuestionToken, "?");
if (!StringUtils.isNullOrWhitespace(structure.type))
writer.write(`: ${structure.type}`);
this.factory.forTypedNode(":").printText(writer, structure);
// why would someone write an initializer? I guess let them do it...
this.factory.forInitializerExpressionableNode().printText(writer, structure);
writer.write(";");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ export class VariableDeclarationStructurePrinter extends FactoryStructurePrinter
printText(writer: CodeBlockWriter, structure: VariableDeclarationStructure) {
writer.write(structure.name);
writer.conditionalWrite(structure.hasExclamationToken, "!");
if (structure.type != null)
writer.write(": " + structure.type);
this.factory.forTypedNode(":").printText(writer, structure);
this.factory.forInitializerExpressionableNode().printText(writer, structure);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export class TypeAliasDeclarationStructurePrinter extends FactoryStructurePrinte
this.factory.forModifierableNode().printText(writer, structure);
writer.write(`type ${structure.name}`);
this.factory.forTypeParameterDeclaration().printTextsWithBrackets(writer, structure.typeParameters);
writer.write(` = ${ structure.type };`);
this.factory.forTypedNode(" =").printText(writer, structure);
writer.write(";");
}
}
6 changes: 4 additions & 2 deletions src/structures/base/TypedNodeStructure.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
export interface TypedNodeStructure {
type?: string;
import { CodeBlockWriter } from "../../codeBlockWriter";

export interface TypedNodeStructure {
type?: string | ((writer: CodeBlockWriter) => void);
}
5 changes: 3 additions & 2 deletions src/structures/type/TypeAliasDeclarationStructure.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { NamedNodeStructure, TypedNodeStructure, TypeParameteredNodeStructure, JSDocableNodeStructure, AmbientableNodeStructure, ExportableNodeStructure } from "../base";
import { CodeBlockWriter } from "../../codeBlockWriter";
import { NamedNodeStructure, TypedNodeStructure, TypeParameteredNodeStructure, JSDocableNodeStructure, AmbientableNodeStructure, ExportableNodeStructure } from "../base";

export interface TypeAliasDeclarationStructure
extends NamedNodeStructure, TypedNodeStructure, TypeParameteredNodeStructure, JSDocableNodeStructure, AmbientableNodeStructure, ExportableNodeStructure
{
type: string; // make required (from base)
type: string | ((writer: CodeBlockWriter) => void); // make required (from base)
}
8 changes: 6 additions & 2 deletions src/tests/compiler/base/typedNodeTests.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { expect } from "chai";
import { TypedNode, VariableStatement, TypeAliasDeclaration, ClassDeclaration, PropertyDeclaration, FunctionDeclaration } from "../../../compiler";
import { Node, TypedNode, VariableStatement, TypeAliasDeclaration, ClassDeclaration, PropertyDeclaration, FunctionDeclaration } from "../../../compiler";
import { TypedNodeStructure } from "../../../structures";
import { getInfoFromText } from "../testHelpers";

Expand All @@ -9,7 +9,7 @@ describe(nameof(TypedNode), () => {
const explicitVarDeclaration = mainSourceFile.getVariableStatements()[1].getDeclarations()[0];
const typeAliasDeclaration = mainSourceFile.getTypeAliases()[0];

describe(nameof<TypedNode>(n => n.getType), () => {
describe(nameof<Node>(n => n.getType), () => {
it("should get the expected implicit type", () => {
expect(implicitVarDeclaration.getType().getText()).to.equal("number");
});
Expand Down Expand Up @@ -172,6 +172,10 @@ describe(nameof(TypedNode), () => {
doTest("type myAlias = string;", { type: "number" }, "type myAlias = number;");
});

it("should modify when setting as a writer function", () => {
doTest("type myAlias = string;", { type: writer => writer.write("number") }, "type myAlias = number;");
});

it("should not modify anything if the structure doesn't change anything", () => {
doTest("type myAlias = string;", {}, "type myAlias = string;");
});
Expand Down
4 changes: 2 additions & 2 deletions src/tests/compiler/statement/statementedNode/classTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,13 +61,13 @@ describe(nameof(StatementedNode), () => {
isDefaultExport: true,
isExported: true,
typeParameters: [{ name: "T" }],
properties: [{ name: "p" }],
properties: [{ name: "p", type: writer => writer.write("number") }],
methods: [{ name: "m" }],
getAccessors: [{ name: "g" }, { name: "s" }, { name: "g2" }, { name: "g3" }],
setAccessors: [{ name: "s" }, { name: "g" }, { name: "s2" }]
};
const expectedText = "/**\n * Test\n */\n@D\nexport default abstract class C<T> extends Base implements IBase, IBase2 {\n" +
" p;\n\n" +
" p: number;\n\n" +
" constructor() {\n }\n\n" +
" constructor() {\n }\n\n" +
" get g() {\n }\n\n set g() {\n }\n\n get s() {\n }\n\n set s() {\n }\n\n" +
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ describe(nameof(StatementedNode), () => {
});

it("should insert at the start with one new lines for a type alias after", () => {
doTest("type Identifier2 = string;\n", 0, [{ name: "Identifier1", type: "string" }],
doTest("type Identifier2 = string;\n", 0, [{ name: "Identifier1", type: writer => writer.write("string") }],
"type Identifier1 = string;\ntype Identifier2 = string;\n");
});

Expand Down
4 changes: 4 additions & 0 deletions src/tests/compiler/type/typeAliasDeclarationTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@ describe(nameof(TypeAliasDeclaration), () => {
it("should change the property when setting", () => {
doTest("type Identifier = string;", { type: "number" }, "type Identifier = number;");
});

it("should change the property when setting as a writer function", () => {
doTest("type Identifier = string;", { type: writer => writer.write("number") }, "type Identifier = number;");
});
});

describe(nameof<TypeAliasDeclaration>(d => d.remove), () => {
Expand Down

0 comments on commit 5dc3565

Please sign in to comment.