Skip to content

Commit 76ca64e

Browse files
authored
feat: standalone printStructure function (#1670)
1 parent 0c57a18 commit 76ca64e

7 files changed

Lines changed: 425 additions & 22 deletions

File tree

docs/manipulation/structures.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,53 @@ Or you can use the `addX` or `insertX` methods with a structure:
6161
sourceFile.addClass({ name: "NewClass", ...classDeclaration.getStructure() });
6262
```
6363

64+
### Printing to string
65+
66+
Structures can be printed to a string using the `printStructure` function. This is useful for code generation without needing a `Project` instance.
67+
68+
```ts
69+
import { printStructure, StructureKind } from "ts-morph";
70+
71+
const code = printStructure({
72+
kind: StructureKind.Class,
73+
name: "MyClass",
74+
isExported: true,
75+
properties: [{ name: "myProp", type: "string" }],
76+
methods: [{
77+
name: "myMethod",
78+
parameters: [{ name: "param", type: "number" }],
79+
returnType: "void",
80+
}],
81+
});
82+
```
83+
84+
Outputs:
85+
86+
```ts
87+
export class MyClass {
88+
myProp: string;
89+
90+
myMethod(param: number): void {
91+
}
92+
}
93+
```
94+
95+
Mostly any structure with a `kind` property can be printed, including `SourceFile`, `Interface`, `Enum`, `Function`, `TypeAlias`, and more.
96+
97+
#### Options
98+
99+
Formatting can be customized with an optional second argument:
100+
101+
```ts
102+
const code = printStructure(structure, {
103+
indentNumberOfSpaces: 2,
104+
useTabs: false,
105+
newLine: "\n",
106+
useSingleQuote: true,
107+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces: true,
108+
});
109+
```
110+
64111
### Traversing structures
65112

66113
#### `Structure` type guards

docs/setup/ast-viewers.md

Lines changed: 1 addition & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ An AST viewer is a useful way to help understand the TypeScript AST for some sou
88

99
### TypeScript AST Viewer
1010

11-
I've created this very basic web-based TypeScript AST viewer.
11+
I've created this web-based TypeScript AST viewer that also gives information on symbols and types.
1212

1313
[TypeScript AST Viewer](http://ts-ast-viewer.com)
1414

@@ -23,24 +23,3 @@ Features:
2323
[![TypeScript AST Viewer](images/ts-ast-viewer.png)](http://ts-ast-viewer.com)
2424

2525
I will improve and add more functionality to this in the future. You can help contribute to its progress [here](https://github.com/dsherret/ts-ast-viewer).
26-
27-
### Atom TypeScript
28-
29-
This AST viewer gives an excellent view of the AST.
30-
31-
1. Install [Atom](https://atom.io/).
32-
2. Install [atom-typescript](https://atom.io/packages/atom-typescript).
33-
3. Create a new typescript file.
34-
4. Paste in your typescript code.
35-
36-
![TypeScript file](images/atom-file.png)
37-
38-
5. Important: Ensure the current typescript document has focus.
39-
6. Open the command palette (Windows/Linux: `ctrl+shift+p`, Mac: `cmd+shift+p`).
40-
7. Type `TypeScript: Ast Full` and hit enter (or run `TypeScript: Ast` to get the ast without tokens).
41-
42-
![Command Palette](images/atom-command-palette.png)
43-
44-
8. A new tab will appear with the AST.
45-
46-
[![atom-typescript AST Viewer](images/atom-ast_small.png)](images/atom-ast.png)

packages/ts-morph/lib/ts-morph.d.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,32 @@ export declare class Writers {
883883
static returnStatement(value: WriterFunctionOrValue): WriterFunction;
884884
}
885885

886+
/**
887+
* Prints a structure to a string.
888+
* @param structure - Structure to print.
889+
* @param options - Options for formatting the output.
890+
*/
891+
export declare function printStructure(structure: Structures, options?: PrintStructureOptions): string;
892+
893+
/** Options for printing a structure to a string. */
894+
export interface PrintStructureOptions {
895+
/** Number of spaces for indentation. Defaults to 4. Ignored when useTabs is true. */
896+
indentNumberOfSpaces?: number;
897+
/** Whether to use tabs for indentation. Defaults to false. */
898+
useTabs?: boolean;
899+
/** Newline character. Defaults to "\n". */
900+
newLine?: "\n" | "\r\n";
901+
/** Whether to use single quotes. Defaults to false. */
902+
useSingleQuote?: boolean;
903+
/**
904+
* Whether to insert a space after opening and before closing non-empty braces.
905+
*
906+
* ex. `import { Item } from "./Item";` or `import {Item} from "./Item";`
907+
* @remarks Defaults to true.
908+
*/
909+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
910+
}
911+
886912
export type WriterFunctionOrValue = string | number | WriterFunction;
887913
export type AssertionKey = Identifier | StringLiteral;
888914
export type PropertyName = Identifier | StringLiteral | NumericLiteral | ComputedPropertyName | PrivateIdentifier | NoSubstitutionTemplateLiteral | BigIntLiteral;

packages/ts-morph/src/main.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,5 @@ export {
7272
} from "./utils/tsconfig/getCompilerOptionsFromTsConfig";
7373
import { Writers } from "./structurePrinters/Writers";
7474
export { Writers };
75+
export { printStructure, type PrintStructureOptions } from "./structurePrinters/printStructure";
7576
export type { WriterFunctionOrValue } from "./structurePrinters/Writers";

packages/ts-morph/src/structurePrinters/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ export * from "./jsx";
1212
export * from "./module";
1313
export * from "./NodePrinter";
1414
export * from "./Printer";
15+
export * from "./printStructure";
1516
export * from "./statement";
1617
export * from "./types";
1718
export * from "./variable";
Lines changed: 209 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,209 @@
1+
import { errors } from "@ts-morph/common";
2+
import CodeBlockWriter from "code-block-writer";
3+
import { StructurePrinterFactory } from "../factories/StructurePrinterFactory";
4+
import { StructureKind, Structures } from "../structures";
5+
6+
/** Options for printing a structure to a string. */
7+
export interface PrintStructureOptions {
8+
/** Number of spaces for indentation. Defaults to 4. Ignored when useTabs is true. */
9+
indentNumberOfSpaces?: number;
10+
/** Whether to use tabs for indentation. Defaults to false. */
11+
useTabs?: boolean;
12+
/** Newline character. Defaults to "\n". */
13+
newLine?: "\n" | "\r\n";
14+
/** Whether to use single quotes. Defaults to false. */
15+
useSingleQuote?: boolean;
16+
/**
17+
* Whether to insert a space after opening and before closing non-empty braces.
18+
*
19+
* ex. `import { Item } from "./Item";` or `import {Item} from "./Item";`
20+
* @remarks Defaults to true.
21+
*/
22+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces?: boolean;
23+
}
24+
25+
/**
26+
* Prints a structure to a string.
27+
* @param structure - Structure to print.
28+
* @param options - Options for formatting the output.
29+
*/
30+
export function printStructure(structure: Structures, options?: PrintStructureOptions): string {
31+
const {
32+
indentNumberOfSpaces = 4,
33+
useTabs = false,
34+
newLine = "\n",
35+
useSingleQuote = false,
36+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces = true,
37+
} = options ?? {};
38+
39+
const factory = new StructurePrinterFactory(() => ({
40+
insertSpaceAfterOpeningAndBeforeClosingNonemptyBraces,
41+
convertTabsToSpaces: !useTabs,
42+
indentSize: indentNumberOfSpaces,
43+
tabSize: indentNumberOfSpaces,
44+
}));
45+
46+
const writer = new CodeBlockWriter({
47+
indentNumberOfSpaces: useTabs ? undefined : indentNumberOfSpaces,
48+
useTabs,
49+
newLine,
50+
useSingleQuote,
51+
});
52+
53+
printStructureByKind(writer, factory, structure);
54+
55+
return writer.toString();
56+
}
57+
58+
function printStructureByKind(writer: CodeBlockWriter, factory: StructurePrinterFactory, structure: Structures) {
59+
const notAmbient = { isAmbient: false };
60+
switch (structure.kind) {
61+
// statements
62+
case StructureKind.Class:
63+
factory.forClassDeclaration(notAmbient).printText(writer, structure);
64+
break;
65+
case StructureKind.Interface:
66+
factory.forInterfaceDeclaration().printText(writer, structure);
67+
break;
68+
case StructureKind.Enum:
69+
factory.forEnumDeclaration().printText(writer, structure);
70+
break;
71+
case StructureKind.Function:
72+
factory.forFunctionDeclaration(notAmbient).printText(writer, structure);
73+
break;
74+
case StructureKind.FunctionOverload:
75+
throw new errors.NotSupportedError("Function overload structures cannot be printed standalone. Print the parent function structure instead.");
76+
case StructureKind.TypeAlias:
77+
factory.forTypeAliasDeclaration().printText(writer, structure);
78+
break;
79+
case StructureKind.VariableStatement:
80+
factory.forVariableStatement().printText(writer, structure);
81+
break;
82+
case StructureKind.ImportDeclaration:
83+
factory.forImportDeclaration().printText(writer, structure);
84+
break;
85+
case StructureKind.ExportDeclaration:
86+
factory.forExportDeclaration().printText(writer, structure);
87+
break;
88+
case StructureKind.ExportAssignment:
89+
factory.forExportAssignment().printText(writer, structure);
90+
break;
91+
case StructureKind.Module:
92+
factory.forModuleDeclaration(notAmbient).printText(writer, structure);
93+
break;
94+
case StructureKind.SourceFile:
95+
factory.forSourceFile(notAmbient).printText(writer, structure);
96+
break;
97+
98+
// class members
99+
case StructureKind.Constructor:
100+
factory.forConstructorDeclaration(notAmbient).printText(writer, structure);
101+
break;
102+
case StructureKind.ConstructorOverload:
103+
throw new errors.NotSupportedError("Constructor overload structures cannot be printed standalone. Print the parent constructor structure instead.");
104+
case StructureKind.Method:
105+
factory.forMethodDeclaration(notAmbient).printText(writer, structure);
106+
break;
107+
case StructureKind.MethodOverload:
108+
throw new errors.NotSupportedError("Method overload structures cannot be printed standalone. Print the parent method structure instead.");
109+
case StructureKind.Property:
110+
factory.forPropertyDeclaration().printText(writer, structure);
111+
break;
112+
case StructureKind.GetAccessor:
113+
factory.forGetAccessorDeclaration(notAmbient).printText(writer, structure);
114+
break;
115+
case StructureKind.SetAccessor:
116+
factory.forSetAccessorDeclaration(notAmbient).printText(writer, structure);
117+
break;
118+
case StructureKind.ClassStaticBlock:
119+
factory.forClassStaticBlockDeclaration().printText(writer, structure);
120+
break;
121+
122+
// interface members
123+
case StructureKind.CallSignature:
124+
factory.forCallSignatureDeclaration().printText(writer, structure);
125+
break;
126+
case StructureKind.ConstructSignature:
127+
factory.forConstructSignatureDeclaration().printText(writer, structure);
128+
break;
129+
case StructureKind.IndexSignature:
130+
factory.forIndexSignatureDeclaration().printText(writer, structure);
131+
break;
132+
case StructureKind.MethodSignature:
133+
factory.forMethodSignature().printText(writer, structure);
134+
break;
135+
case StructureKind.PropertySignature:
136+
factory.forPropertySignature().printText(writer, structure);
137+
break;
138+
139+
// enum
140+
case StructureKind.EnumMember:
141+
factory.forEnumMember().printText(writer, structure);
142+
break;
143+
144+
// module
145+
case StructureKind.ExportSpecifier:
146+
factory.forNamedImportExportSpecifier().printText(writer, structure);
147+
break;
148+
case StructureKind.ImportSpecifier:
149+
factory.forNamedImportExportSpecifier().printText(writer, structure);
150+
break;
151+
case StructureKind.ImportAttribute:
152+
factory.forImportAttribute().printText(writer, structure);
153+
break;
154+
155+
// object literal expression
156+
case StructureKind.PropertyAssignment:
157+
factory.forPropertyAssignment().printText(writer, structure);
158+
break;
159+
case StructureKind.ShorthandPropertyAssignment:
160+
factory.forShorthandPropertyAssignment().printText(writer, structure);
161+
break;
162+
case StructureKind.SpreadAssignment:
163+
factory.forSpreadAssignment().printText(writer, structure);
164+
break;
165+
166+
// function
167+
case StructureKind.Parameter:
168+
factory.forParameterDeclaration().printText(writer, structure);
169+
break;
170+
171+
// type
172+
case StructureKind.TypeParameter:
173+
factory.forTypeParameterDeclaration().printText(writer, structure);
174+
break;
175+
case StructureKind.VariableDeclaration:
176+
factory.forVariableDeclaration().printText(writer, structure);
177+
break;
178+
179+
// decorator
180+
case StructureKind.Decorator:
181+
factory.forDecorator().printText(writer, structure);
182+
break;
183+
184+
// doc
185+
case StructureKind.JSDoc:
186+
factory.forJSDoc().printText(writer, structure);
187+
break;
188+
case StructureKind.JSDocTag:
189+
factory.forJSDocTag({ printStarsOnNewLine: true }).printText(writer, structure);
190+
break;
191+
192+
// jsx
193+
case StructureKind.JsxAttribute:
194+
factory.forJsxAttribute().printText(writer, structure);
195+
break;
196+
case StructureKind.JsxSpreadAttribute:
197+
factory.forJsxSpreadAttribute().printText(writer, structure);
198+
break;
199+
case StructureKind.JsxElement:
200+
factory.forJsxElement().printText(writer, structure);
201+
break;
202+
case StructureKind.JsxSelfClosingElement:
203+
factory.forJsxSelfClosingElement().printText(writer, structure);
204+
break;
205+
206+
default:
207+
errors.throwNotImplementedForNeverValueError(structure);
208+
}
209+
}

0 commit comments

Comments
 (0)