Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(typescript): support inlined types (and initialize an AST) #4937

Open
wants to merge 13 commits into
base: main
Choose a base branch
from
148,049 changes: 0 additions & 148,049 deletions generators/typescript/.yarn/releases/yarn-1.22.22.cjs

This file was deleted.

9 changes: 6 additions & 3 deletions generators/typescript/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,17 +25,20 @@
"organize-imports": "organize-imports-cli tsconfig.json",
"depcheck": "depcheck"
},
"devDependencies": {
"dependencies": {
"@fern-api/core-utils": "workspace:*",
"@fern-api/generator-commons": "workspace:*",
"@fern-fern/ir-sdk": "53.8.0",
"@fern-fern/ir-sdk": "53.14.0"
},
"devDependencies": {
"@types/jest": "^29.5.12",
"@types/node": "^18.7.18",
"depcheck": "^1.4.6",
"eslint": "^8.56.0",
"organize-imports-cli": "^0.10.0",
"prettier": "^2.7.1",
"typescript": "4.6.4",
"vitest": "^2.0.5"
"vitest": "^2.0.5",
"zod": "^3.22.3"
}
}
18 changes: 18 additions & 0 deletions generators/typescript/codegen/src/ast/AstNode.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { AbstractAstNode } from "@fern-api/generator-commons";
import { Writer } from "./Writer";
import * as prettier from "prettier";

export abstract class AstNode extends AbstractAstNode {
/**
* Writes the node to a string.
*/
public toString(): string {
const writer = new Writer();
this.write(writer);
return writer.toString();
}

public toStringFormatted(): string {
return prettier.format(this.toString(), { parser: "typescript", tabWidth: 4, printWidth: 120 });
}
}
13 changes: 5 additions & 8 deletions generators/typescript/codegen/src/ast/CodeBlock.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,18 @@
import { CodeBlock as CommonCodeBlock } from "@fern-api/generator-commons";
import { AstNode, Writer } from "../typescript";
import { AbstractAstNode, CodeBlock as CommonCodeBlock } from "@fern-api/generator-commons";
import { Writer } from "./Writer";

export declare namespace CodeBlock {
/* Write arbitrary code */
type Args = CommonCodeBlock.Arg<Writer>;
}

export class CodeBlock extends AstNode {
private args: CodeBlock.Args;

public constructor(args: CodeBlock.Args) {
export class CodeBlock extends AbstractAstNode {
public constructor(private readonly arg: CodeBlock.Args) {
super();
this.args = args;
}

public write(writer: Writer): void {
const commonCodeBlock = new CommonCodeBlock(this.args);
const commonCodeBlock = new CommonCodeBlock(this.arg);
return commonCodeBlock.write(writer);
}
}
21 changes: 21 additions & 0 deletions generators/typescript/codegen/src/ast/DocString.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { AstNode } from "./AstNode";
import { CodeBlock } from "./CodeBlock";
import { Writer } from "./Writer";

export class DocString extends AstNode {
public constructor(private readonly docs: string) {
super();
}

public write(writer: Writer): void {
if (!this.docs.includes("\n")) {
writer.writeLine(`/* ${this.docs} */`);
} else {
writer.writeLine(`/**`);
this.docs.split("\n").forEach((line) => {
writer.writeLine(` * ${line}`);
});
writer.writeLine(" */");
}
}
}
79 changes: 79 additions & 0 deletions generators/typescript/codegen/src/ast/Interface.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import { AstNode } from "./AstNode";
import { Writer } from "./Writer";
import { Type } from "./Type";
import { Reference } from "./Reference";
import { DocString } from "./DocString";

export declare namespace Interface {
interface Args {
/* Whether to export */
export?: boolean;
/* The name of the variable */
name: string;
/* Properties on the interface */
properties: Property[];
/* The interfaces that this extends */
extends?: Reference[];

docs?: string;
}

interface Property {
/* The name of the property */
name: string;
/* The type of the property */
type: Type;
/* Quesiton mark */
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Typographical error in comment

There's a typo in the comment on line 23. "Quesiton" should be "Question".

Apply this diff to fix the typo:

-        /* Quesiton mark */
+        /* Question mark */
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
/* Quesiton mark */
/* Question mark */

questionMark?: boolean;

docs?: string;
}
}

export class Interface extends AstNode {
public constructor(private readonly args: Interface.Args) {
super();
}

public write(writer: Writer): void {
if (this.args.docs != null) {
writer.writeNode(new DocString(this.args.docs));
writer.writeLine();
}
if (this.args.export) {
writer.write("export ");
}
writer.write(` interface ${this.args.name}`);
if (this.args.extends != null && this.args.extends.length > 0) {
const numBaseInterfaces = this.args.extends.length;

writer.write(` extends `);
this.args.extends.forEach((extend, idx) => {
writer.writeNode(extend);
if (idx < numBaseInterfaces - 1) {
writer.write(", ");
} else {
writer.write(" ");
}
dsinghvi marked this conversation as resolved.
Show resolved Hide resolved
});
}

writer.writeLine("{");

writer.indent();
for (const property of this.args.properties) {
if (property.docs != null) {
writer.writeNode(new DocString(property.docs));
}
writer.write(`"${property.name}\"`);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Unnecessary escape character in template string

The backslash before the closing quote in the template string is unnecessary and may cause incorrect output.

Update the line to remove the escape character:

-writer.write(`"${property.name}\"`);
+writer.write(`"${property.name}"`);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
writer.write(`"${property.name}\"`);
writer.write(`"${property.name}"`);

if (property.questionMark) {
writer.write("?");
}
writer.write(": ");
writer.writeNodeStatement(property.type);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Use writeNode instead of writeNodeStatement for inline types

In the context of writing property types within an interface, writeNode should be used instead of writeNodeStatement to avoid adding unnecessary semicolons or newlines.

Update the line as follows:

-writer.writeNodeStatement(property.type);
+writer.writeNode(property.type);
writer.write(";");
writer.writeLine();

This ensures that each property ends with a semicolon and that the formatting is consistent.

Committable suggestion was skipped due to low confidence.

}
writer.dedent();

writer.writeLine("}");
}
}
60 changes: 60 additions & 0 deletions generators/typescript/codegen/src/ast/Namespace.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { AstNode } from "./AstNode";
import { Writer } from "./Writer";
import { Interface } from "./Interface";

export declare namespace Namespace {
interface Args {
/* Whether to export */
export?: boolean;
/* Namespace of the name */
name?: string;
}
}

export class Namespace extends AstNode {
/**
* The ordered elements inside of a namespace
*/
private elements: (Interface | Namespace)[] = [];

public constructor(private readonly args: Namespace.Args) {
super();
}

public addNamespace(interface_: Namespace): void {
this.elements.push(interface_);
}

public addInterface(interface_: Interface): void {
this.elements.push(interface_);
}

public write(writer: Writer): void {
if (this.args.export) {
writer.write("export ");
}

let setAmbience = false;
if (!writer.isAmbient) {
writer.write("declare ");
writer.setAmbient(true);
dsinghvi marked this conversation as resolved.
Show resolved Hide resolved
setAmbience = true;
}

writer.writeLine(`namespace ${this.args.name} {`);

writer.indent();
for (const element of this.elements) {
writer.writeNode(element);
writer.writeLine();
writer.writeLine();
}
writer.dedent();

writer.writeLine(`}`);

if (setAmbience) {
dsinghvi marked this conversation as resolved.
Show resolved Hide resolved
writer.setAmbient(false);
}
}
}
56 changes: 56 additions & 0 deletions generators/typescript/codegen/src/ast/Reference.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { assertNever } from "@fern-api/core-utils";
import { AstNode } from "./AstNode";
import { Writer } from "./Writer";

export declare namespace Reference {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Avoid using the declare keyword in implementation files

The declare keyword is meant for ambient declarations in .d.ts files to describe external code. Since this is an implementation file, you should remove the declare keyword from the namespace declaration.

Apply this diff to fix the issue:

-export declare namespace Reference {
+export namespace Reference {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export declare namespace Reference {
export namespace Reference {

type Args = NamedImport | ModuleImport;

interface NamedImport {
type: "named";
/* The package or path to import from */
source: string;
/* Name of the reference to import */
name: string;
}

interface ModuleImport {
type: "module";
/* The package or path to import from */
source: string;
/* The module to import from */
module: string;
/**
* The path to access the reference from the module.
* [module, ...name].join(".") is the reference
*/
name: string[];
dsinghvi marked this conversation as resolved.
Show resolved Hide resolved
}
}

export class Reference extends AstNode {
private constructor(public readonly args: Reference.Args) {
super();
}

public write(writer: Writer): void {
writer.addReference(this);
switch (this.args.type) {
case "module":
writer.write([this.args.module, ...this.args.name].join("."));
break;
case "named":
writer.write(this.args.name);
break;
default:
assertNever(this.args);
}
}

public static module(import_: Omit<Reference.ModuleImport, "type">): Reference {
return new Reference({ ...import_, type: "module" });
}

public static named(import_: Omit<Reference.NamedImport, "type">): Reference {
return new Reference({ ...import_, type: "named" });
}
}
Loading
Loading