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(go): Integrate TypeScript generator #5816

Merged
merged 14 commits into from
Jan 31, 2025
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
Loading