Skip to content

WIP: MongoPlugin in QuickType #1

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .claude/settings.local.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"permissions": {
"allow": [
"Bash(npm run quicktype:*)",
"Bash(ls:*)",
"Bash(npm run build:*)",
"Bash(node:*)",
"Bash(grep:*)",
"Bash(mkdir:*)",
"Bash(npx:*)",
"Bash(npm link:*)",
"Bash(quicktype:*)",
"Bash(chmod:*)",
"Bash(tsc)",
"Bash(find:*)",
"Bash(cat:*)",
"Bash(DEBUG=1 quicktype test-snake-case.json --lang kotlin --framework jackson --mongo-collection asset_github_repository)",
"Bash(DEBUG=1 node dist/index.js test-snake-case.json --lang kotlin --framework jackson --mongo-collection asset_github_repository)"
],
"deny": []
}
}
15 changes: 15 additions & 0 deletions packages/quicktype-core/src/ConvenienceRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ import {
nullableFromUnion,
separateNamedTypes,
} from "./Type/TypeUtils";
import { getPluginRunner, type PluginRunner } from "./PluginSupport";

const wordWrap: (s: string) => string = _wordwrap(90);

Expand Down Expand Up @@ -170,17 +171,31 @@ export abstract class ConvenienceRenderer extends Renderer {
private _cycleBreakerTypes?: Set<Type> | undefined;

private _alphabetizeProperties = false;

protected pluginRunner: PluginRunner | undefined;

public constructor(
targetLanguage: TargetLanguage,
renderContext: RenderContext,
) {
super(targetLanguage, renderContext);
// Plugin runner will be initialized by subclasses that pass options
}

public get topLevels(): ReadonlyMap<string, Type> {
return this.typeGraph.topLevels;
}

// Plugin hook methods - to be called by language renderers
protected runPluginHook(hookName: string, context?: any): void {
if (this.pluginRunner) {
this.pluginRunner.runHook(hookName, context);
}
}

protected initializePlugins(options: any): void {
this.pluginRunner = getPluginRunner(this, options);
}

/**
* Return an array of strings which are not allowed as names in the global
Expand Down
41 changes: 41 additions & 0 deletions packages/quicktype-core/src/PluginSupport.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Plugin support stub for quicktype-core
// This allows the core package to support plugins without circular dependencies

export interface PluginContext {
options: Record<string, any>;
language: string;
framework?: string;
emitLine: (code: any) => void;
emitAnnotation: (annotation: string) => void;
}

export interface PluginRunner {
runHook(hookName: string, context?: any): void;
}

// Global plugin runner instance - will be set by the main quicktype package
export let globalPluginRunner: PluginRunner | undefined;

export function setGlobalPluginRunner(runner: PluginRunner): void {
globalPluginRunner = runner;
}

export function getPluginRunner(renderer: any, options: any): PluginRunner | undefined {
if (!globalPluginRunner) return undefined;

// Create a context-specific runner
return {
runHook(hookName: string, context?: any) {
const fullContext = {
renderer,
options,
language: renderer.targetLanguage.name,
framework: options.framework,
emitLine: (code: any) => renderer.emitLine(code),
emitAnnotation: (annotation: string) => renderer.emitLine(annotation),
...context
};
globalPluginRunner!.runHook(hookName, fullContext);
}
};
}
1 change: 1 addition & 0 deletions packages/quicktype-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
type OptionValues,
} from "./RendererOptions";
export { TargetLanguage, type MultiFileRenderResult } from "./TargetLanguage";
export { setGlobalPluginRunner, type PluginRunner } from "./PluginSupport";

export {
type MultiWord,
Expand Down
22 changes: 21 additions & 1 deletion packages/quicktype-core/src/language/Java/JavaJacksonRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,28 @@ export class JacksonRenderer extends JavaRenderer {
default:
break;
}

// Run plugin hook for property annotations
const propertyAnnotations: string[] = [];
const originalEmitLine = this.emitLine.bind(this);

// Temporarily override emitLine to capture annotations
(this as any).emitLine = (line: string) => {
propertyAnnotations.push(line);
};
(this as any).emitAnnotation = (line: string) => {
propertyAnnotations.push(line);
};

this.runPluginHook('beforeProperty', {
propertyName: this.sourcelikeToString(_propertyName),
jsonName: jsonName
});

// Restore original emitLine
(this as any).emitLine = originalEmitLine;

return [...superAnnotations, ...annotations];
return [...superAnnotations, ...annotations, ...propertyAnnotations];
}

protected importsForType(t: ClassType | UnionType | EnumType): string[] {
Expand Down
14 changes: 13 additions & 1 deletion packages/quicktype-core/src/language/Java/JavaRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ export class JavaRenderer extends ConvenienceRenderer {
protected readonly _options: OptionValues<typeof javaOptions>,
) {
super(targetLanguage, renderContext);

// Initialize plugin runner
this.initializePlugins(_options);

switch (_options.dateTimeProvider) {
case "legacy":
Expand Down Expand Up @@ -265,6 +268,9 @@ export class JavaRenderer extends ConvenienceRenderer {
for (const pkg of imports) {
this.emitLine("import ", pkg, ";");
}

// Run plugin hook for additional imports
this.runPluginHook('afterImports');
}

protected emitFileHeader(fileName: Sourcelike, imports: string[]): void {
Expand Down Expand Up @@ -439,10 +445,16 @@ export class JavaRenderer extends ConvenienceRenderer {
return this.javaType(reference, t);
}

protected emitClassAttributes(_c: ClassType, _className: Name): void {
protected emitClassAttributes(c: ClassType, className: Name): void {
if (this._options.lombok) {
this.emitLine("@lombok.Data");
}

// Run plugin hook for class annotations
this.runPluginHook('beforeClass', {
classType: c,
className: className
});
}

protected annotationsForAccessor(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,6 @@ import { matchType, nullableFromUnion } from "../../Type/TypeUtils";
import { KotlinRenderer } from "./KotlinRenderer";
import type { kotlinOptions } from "./language";
import { stringEscape } from "./utils";

export class KotlinJacksonRenderer extends KotlinRenderer {
public constructor(
targetLanguage: TargetLanguage,
Expand Down Expand Up @@ -79,6 +78,9 @@ import com.fasterxml.jackson.databind.module.SimpleModule
import com.fasterxml.jackson.databind.node.*
import com.fasterxml.jackson.databind.ser.std.StdSerializer
import com.fasterxml.jackson.module.kotlin.*`);

// Call emitImports to run plugin hooks
this.emitImports();

const hasUnions = iterableSome(
this.typeGraph.allNamedTypes(),
Expand Down Expand Up @@ -260,6 +262,14 @@ import com.fasterxml.jackson.module.kotlin.*`);
if (rename !== undefined) {
meta.push(() => this.emitLine(rename));
}

// Call parent class to handle plugin hooks
super.renameAttribute(name, jsonName, required, meta);
}

protected emitClassAnnotations(c: Type, className: Name): void {
// Call parent class to handle plugin hooks
super.emitClassAnnotations(c, className);
}

protected emitEnumDefinition(e: EnumType, enumName: Name): void {
Expand Down
50 changes: 44 additions & 6 deletions packages/quicktype-core/src/language/Kotlin/KotlinRenderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export class KotlinRenderer extends ConvenienceRenderer {
protected readonly _kotlinOptions: OptionValues<typeof kotlinOptions>,
) {
super(targetLanguage, renderContext);

// Initialize plugin runner
this.initializePlugins(_kotlinOptions);
}

protected forbiddenNamesForGlobalNamespace(): readonly string[] {
Expand Down Expand Up @@ -212,6 +215,12 @@ export class KotlinRenderer extends ConvenienceRenderer {
this.emitLine("package ", this._kotlinOptions.packageName);
this.ensureBlankLine();
}

protected emitImports(): void {
// Base implementation - framework renderers can override
// Run plugin hook for additional imports
this.runPluginHook('afterImports');
}

protected emitTopLevelPrimitive(t: PrimitiveType, name: Name): void {
const elementType = this.kotlinType(t);
Expand Down Expand Up @@ -314,17 +323,46 @@ export class KotlinRenderer extends ConvenienceRenderer {
this.emitLine(")");
}

protected emitClassAnnotations(_c: Type, _className: Name): void {
// to be overridden
protected emitClassAnnotations(c: Type, className: Name): void {
// Run plugin hook for class annotations
this.runPluginHook('beforeClass', {
classType: c,
className: className
});
}

protected renameAttribute(
_name: Name,
_jsonName: string,
name: Name,
jsonName: string,
_required: boolean,
_meta: Array<() => void>,
meta: Array<() => void>,
): void {
// to be overridden
// Run plugin hook for property annotations
const propertyAnnotations: string[] = [];
const originalEmitLine = this.emitLine.bind(this);

// Temporarily override emitLine to capture annotations
(this as any).emitLine = (line: string) => {
propertyAnnotations.push(line);
};
(this as any).emitAnnotation = (line: string) => {
propertyAnnotations.push(line);
};

this.runPluginHook('beforeProperty', {
propertyName: this.sourcelikeToString(name),
jsonName: jsonName
});

// Restore original emitLine
(this as any).emitLine = originalEmitLine;

// Add captured annotations to meta
if (propertyAnnotations.length > 0) {
meta.push(() => {
propertyAnnotations.forEach(line => this.emitLine(line));
});
}
}

protected emitEnumDefinition(e: EnumType, enumName: Name): void {
Expand Down
11 changes: 7 additions & 4 deletions packages/quicktype-core/src/language/Kotlin/language.ts
Original file line number Diff line number Diff line change
Expand Up @@ -61,16 +61,19 @@ export class KotlinTargetLanguage extends TargetLanguage<
untypedOptionValues: RendererOptions<Lang>,
): ConvenienceRenderer {
const options = getOptionValues(kotlinOptions, untypedOptionValues);

// Merge in any additional options from plugins (like mongo-collection)
const allOptions = { ...options, ...untypedOptionValues };

switch (options.framework) {
case "None":
return new KotlinRenderer(this, renderContext, options);
return new KotlinRenderer(this, renderContext, allOptions);
case "Jackson":
return new KotlinJacksonRenderer(this, renderContext, options);
return new KotlinJacksonRenderer(this, renderContext, allOptions);
case "Klaxon":
return new KotlinKlaxonRenderer(this, renderContext, options);
return new KotlinKlaxonRenderer(this, renderContext, allOptions);
case "KotlinX":
return new KotlinXRenderer(this, renderContext, options);
return new KotlinXRenderer(this, renderContext, allOptions);
default:
return assertNever(options.framework);
}
Expand Down
Loading