Skip to content

Commit

Permalink
feat(go): Integrate TypeScript generator (#5816)
Browse files Browse the repository at this point in the history
  • Loading branch information
amckinney authored Jan 31, 2025
1 parent c42e096 commit 12b5bbb
Show file tree
Hide file tree
Showing 32 changed files with 526 additions and 31 deletions.
28 changes: 28 additions & 0 deletions generators/go-v2/ast/src/ast/File.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { AstNode } from "./core/AstNode";
import { Writer } from "./core/Writer";

export declare namespace File {
interface Args {
/* The list of nodes in the file. */
nodes?: AstNode[];
}
}

export class File extends AstNode {
public readonly nodes: AstNode[];

constructor({ nodes }: File.Args = { nodes: [] }) {
super();
this.nodes = nodes ?? [];
}

public add(...nodes: AstNode[]): void {
this.nodes.push(...nodes);
}

public write(writer: Writer): void {
for (const node of this.nodes) {
node.write(writer);
}
}
}
14 changes: 7 additions & 7 deletions generators/go-v2/ast/src/ast/Struct.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export class Struct extends AstNode {
public write(writer: Writer): void {
writer.writeNode(new Comment({ docs: this.docs }));
writer.write(`type ${this.name} struct {`);
if (this.fields.length > 0) {
if (this.fields.length === 0) {
writer.writeLine("}");
} else {
writer.newLine();
Expand All @@ -53,13 +53,13 @@ export class Struct extends AstNode {
writer.dedent();
writer.writeLine("}");
}
if (this.constructor != null || this.methods.length > 0) {
writer.newLine();
}
for (const method of this.methods) {
writer.writeNode(method);

if (this.methods.length > 0) {
writer.newLine();
for (const method of this.methods) {
writer.writeNode(method);
writer.newLine();
}
}
return;
}
}
48 changes: 48 additions & 0 deletions generators/go-v2/ast/src/ast/__test__/Snippets.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { Field } from "../Field";
import { File } from "../File";
import { Func } from "../Func";
import { GoTypeReference } from "../GoTypeReference";
import { Struct } from "../Struct";
import { Type } from "../Type";
import { TypeInstantiation } from "../TypeInstantiation";
import { AstNode } from "../core/AstNode";
Expand Down Expand Up @@ -281,6 +285,50 @@ describe("Snippets", () => {
});
});

describe("file", () => {
it("import collision", () => {
const file = new File();
const foo = new Struct({
name: "Foo",
importPath: "github.com/acme/acme-go"
});
foo.addField(
new Field({
name: "Name",
type: Type.reference(
new GoTypeReference({
name: "Identifier",
importPath: "github.com/acme/acme-go/common"
})
)
})
);
const bar = new Struct({
name: "Bar",
importPath: "github.com/acme/acme-go"
});
bar.addField(
new Field({
name: "Name",
type: Type.reference(
new GoTypeReference({
name: "Identifier",
importPath: "github.com/acme/acme-go/nested/common"
})
)
})
);
file.add(foo, bar);
const content = file.toString({
packageName: "example",
rootImportPath: "github.com/acme/acme-go",
importPath: "github.com/acme/consumer",
customConfig: {}
});
expect(content).toMatchSnapshot();
});
});

const USER_TYPE_REFERENCE = new GoTypeReference({
name: "User",
importPath: "github.com/acme/acme-go"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -210,3 +210,20 @@ var value = acme.User{
Age: 42,
}"
`;

exports[`file > import collision 1`] = `
"package example
import (
common "github.com/acme/acme-go/common"
_common "github.com/acme/acme-go/nested/common"
)
type Foo struct {
Name common.Identifier
}
type Bar struct {
Name _common.Identifier
}
"
`;
3 changes: 2 additions & 1 deletion generators/go-v2/ast/src/ast/core/Writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ export class Writer extends AbstractWriter {
if (maybeAlias != null) {
return maybeAlias;
}
const set = new Set<Alias>(Object.values(this.imports));
let alias = this.getValidAlias(basename(importPath));
while (alias in this.imports) {
while (set.has(alias)) {
alias = "_" + alias;
}
this.imports[importPath] = alias;
Expand Down
1 change: 1 addition & 0 deletions generators/go-v2/ast/src/ast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export { CodeBlock } from "./CodeBlock";
export { Writer } from "./core/Writer";
export { Enum } from "./Enum";
export { Field } from "./Field";
export { File } from "././File";
export { Func } from "./Func";
export { FuncInvocation } from "./FuncInvocation";
export { GoTypeReference } from "./GoTypeReference";
Expand Down
15 changes: 14 additions & 1 deletion generators/go-v2/ast/src/context/AbstractGoGeneratorContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,12 @@ import { RelativeFilePath } from "@fern-api/path-utils";

import {
FernFilepath,
HttpService,
IntermediateRepresentation,
Literal,
Name,
PrimitiveTypeV1,
ServiceId,
Subpackage,
SubpackageId,
TypeDeclaration,
Expand Down Expand Up @@ -50,6 +52,14 @@ export abstract class AbstractGoGeneratorContext<
});
}

public getHttpServiceOrThrow(serviceId: ServiceId): HttpService {
const service = this.ir.services[serviceId];
if (service == null) {
throw new Error(`Service with id ${serviceId} not found`);
}
return service;
}

public getSubpackageOrThrow(subpackageId: SubpackageId): Subpackage {
const subpackage = this.ir.subpackages[subpackageId];
if (subpackage == null) {
Expand Down Expand Up @@ -229,7 +239,10 @@ export abstract class AbstractGoGeneratorContext<
return this.ir.types[typeId];
}

public abstract getLocationForTypeId(typeId: TypeId): FileLocation;
public getLocationForTypeId(typeId: TypeId): FileLocation {
const typeDeclaration = this.getTypeDeclarationOrThrow(typeId);
return this.getFileLocation(typeDeclaration.name.fernFilepath);
}

protected getFileLocation(filepath: FernFilepath, suffix?: string): FileLocation {
let parts = filepath.packagePath.map((path) => path.pascalCase.safeName.toLowerCase());
Expand Down
6 changes: 6 additions & 0 deletions generators/go-v2/ast/src/go.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
CodeBlock,
Enum,
Field,
File,
Func,
FuncInvocation,
GoTypeReference,
Expand All @@ -23,6 +24,10 @@ export function field(args: Field.Args): Field {
return new Field(args);
}

export function file(args: File.Args = {}): File {
return new File(args);
}

export function func(args: Func.Args): Func {
return new Func(args);
}
Expand Down Expand Up @@ -56,6 +61,7 @@ export {
CodeBlock,
Enum,
Field,
File,
Func,
FuncInvocation,
GoTypeReference as TypeReference,
Expand Down
1 change: 1 addition & 0 deletions generators/go-v2/ast/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export { AbstractGoGeneratorContext, type FileLocation } from "./context/Abstrac
export { BaseGoCustomConfigSchema } from "./custom-config/BaseGoCustomConfigSchema";
export { resolveRootImportPath } from "./custom-config/resolveRootImportPath";
export * as go from "./go";
export { GoFile } from "./ast/core/GoFile";
2 changes: 2 additions & 0 deletions generators/go-v2/base/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@
"@fern-api/base-generator": "workspace:*",
"@fern-api/fs-utils": "workspace:*",
"@fern-api/go-ast": "workspace:*",
"@fern-api/logging-execa": "workspace:*",
"@fern-fern/ir-sdk": "^55.0.0",
"@yarnpkg/esbuild-plugin-pnp": "^3.0.0-rc.14",
"esbuild": "^0.24.0",
"tsup": "^8.0.2",
Expand Down
24 changes: 24 additions & 0 deletions generators/go-v2/base/src/cli/AbstractGoGeneratorCli.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { AbstractGeneratorCli, parseIR } from "@fern-api/base-generator";
import { AbsoluteFilePath } from "@fern-api/fs-utils";
import { AbstractGoGeneratorContext } from "@fern-api/go-ast";
import { BaseGoCustomConfigSchema } from "@fern-api/go-ast";

import { IntermediateRepresentation } from "@fern-fern/ir-sdk/api";
import * as IrSerialization from "@fern-fern/ir-sdk/serialization";

export abstract class AbstractGoGeneratorCli<
CustomConfig extends BaseGoCustomConfigSchema,
GoGeneratorContext extends AbstractGoGeneratorContext<CustomConfig>
> extends AbstractGeneratorCli<CustomConfig, IntermediateRepresentation, GoGeneratorContext> {
/**
* Parses the IR for the PHP generators
* @param irFilepath
* @returns
*/
protected async parseIntermediateRepresentation(irFilepath: string): Promise<IntermediateRepresentation> {
return await parseIR<IntermediateRepresentation>({
absolutePathToIR: AbsoluteFilePath.of(irFilepath),
parse: IrSerialization.IntermediateRepresentation.parse
});
}
}
1 change: 1 addition & 0 deletions generators/go-v2/base/src/cli/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AbstractGoGeneratorCli } from "./AbstractGoGeneratorCli";
10 changes: 4 additions & 6 deletions generators/go-v2/base/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
void runCli();

export async function runCli(): Promise<void> {
// eslint-disable-next-line no-console
console.log("Noop...");
}
export { AbstractGoGeneratorCli } from "./cli/AbstractGoGeneratorCli";
export { GoFile } from "./project/GoFile";
export { GoProject } from "./project/GoProject";
export { FileGenerator } from "./FileGenerator";
49 changes: 49 additions & 0 deletions generators/go-v2/base/src/project/GoFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { AbstractFormatter, File } from "@fern-api/base-generator";
import { RelativeFilePath } from "@fern-api/fs-utils";
import { BaseGoCustomConfigSchema, go } from "@fern-api/go-ast";

export declare namespace GoFile {
interface Args {
/* The node to be written to the Go source file */
node: go.AstNode;
/* Directory of the file */
directory: RelativeFilePath;
/* Filename of the file */
filename: string;
/* The package name of the file */
packageName: string;
/* The root import path of the module */
rootImportPath: string;
/* The import path of the file */
importPath: string;
/* Custom generator config */
customConfig: BaseGoCustomConfigSchema;
/* Optional formatter */
formatter?: AbstractFormatter;
}
}

export class GoFile extends File {
constructor({
node,
directory,
filename,
packageName,
rootImportPath,
importPath,
customConfig,
formatter
}: GoFile.Args) {
super(
filename,
directory,
node.toString({
packageName,
rootImportPath,
importPath,
customConfig,
formatter
})
);
}
}
58 changes: 56 additions & 2 deletions generators/go-v2/base/src/project/GoProject.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,56 @@
// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class GoProject {}
import { mkdir } from "fs/promises";

import { AbstractProject, File } from "@fern-api/base-generator";
import { AbsoluteFilePath } from "@fern-api/fs-utils";
import { AbstractGoGeneratorContext, BaseGoCustomConfigSchema } from "@fern-api/go-ast";
import { loggingExeca } from "@fern-api/logging-execa";

/**
* In memory representation of a Go project.
*/
export class GoProject extends AbstractProject<AbstractGoGeneratorContext<BaseGoCustomConfigSchema>> {
private sourceFiles: File[] = [];

public constructor({ context }: { context: AbstractGoGeneratorContext<BaseGoCustomConfigSchema> }) {
super(context);
}

public addGoFiles(file: File): void {
this.sourceFiles.push(file);
}

public async persist(): Promise<void> {
await this.writeGoFiles({
absolutePathToDirectory: this.absolutePathToOutputDirectory,
files: this.sourceFiles
});
}

private async writeGoFiles({
absolutePathToDirectory,
files
}: {
absolutePathToDirectory: AbsoluteFilePath;
files: File[];
}): Promise<AbsoluteFilePath> {
await this.mkdir(absolutePathToDirectory);
await Promise.all(files.map(async (file) => await file.write(absolutePathToDirectory)));
if (files.length > 0) {
// TODO: Uncomment this once the go-v2 generator is responsible for producing the go.mod file.
// Otherwise, we get a "directory prefix . does not contain main module or its selected dependencies" error.
//
// ---
//
// await loggingExeca(this.context.logger, "go", ["fmt", "./..."], {
// doNotPipeOutput: true,
// cwd: absolutePathToDirectory
// });
}
return absolutePathToDirectory;
}

private async mkdir(absolutePathToDirectory: AbsoluteFilePath): Promise<void> {
this.context.logger.debug(`mkdir ${absolutePathToDirectory}`);
await mkdir(absolutePathToDirectory, { recursive: true });
}
}
1 change: 1 addition & 0 deletions generators/go-v2/base/src/project/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { GoFile } from "./GoFile";
export { GoProject } from "./GoProject";
1 change: 1 addition & 0 deletions generators/go-v2/base/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"references": [
{ "path": "../../../packages/commons/core-utils" },
{ "path": "../../../packages/commons/fs-utils" },
{ "path": "../../../packages/commons/logging-execa" },
{ "path": "../../base" },
{ "path": "../ast" }
]
Expand Down
Loading

0 comments on commit 12b5bbb

Please sign in to comment.