diff --git a/src/ConvenienceRenderer.ts b/src/ConvenienceRenderer.ts index 1efcccca..ec86a41d 100644 --- a/src/ConvenienceRenderer.ts +++ b/src/ConvenienceRenderer.ts @@ -24,7 +24,14 @@ import { Sourcelike, sourcelikeToSource, serializeRenderResult } from "./Source" import { trimEnd } from "lodash"; import { declarationsForGraph, DeclarationIR, cycleBreakerTypesForGraph, Declaration } from "./DeclarationIR"; import { TypeAttributeStoreView } from "./TypeGraph"; -import { TypeAttributeKind, descriptionTypeAttributeKind } from "./TypeAttributes"; +import { TypeAttributeKind, descriptionTypeAttributeKind, propertyDescriptionsTypeAttributeKind } from "./TypeAttributes"; + +function splitDescription(description: string | undefined): string[] | undefined { + if (description === undefined) return undefined; + description = description.trim(); + if (description === "") return undefined; + return description.split("\n").map(l => l.trim()); +} export type ForbiddenWordsInfo = { names: (Name | string)[]; includeGlobalForbidden: boolean }; @@ -136,10 +143,13 @@ export abstract class ConvenienceRenderer extends Renderer { protected descriptionForType(t: Type): string[] | undefined { let description = this.typeGraph.attributeStore.tryGet(descriptionTypeAttributeKind, t); - if (description === undefined) return undefined; - description = description.trim(); - if (description === "") return undefined; - return description.split("\n").map(l => l.trim()); + return splitDescription(description); + } + + protected descriptionForClassProperty(c: ClassType, name: string): string[] | undefined { + const descriptions = this.typeGraph.attributeStore.tryGet(propertyDescriptionsTypeAttributeKind, c); + if (descriptions === undefined) return undefined; + return splitDescription(descriptions.get(name)); } protected setUpNaming(): OrderedSet { diff --git a/src/JSONSchemaInput.ts b/src/JSONSchemaInput.ts index 635c83cf..3965a8ab 100644 --- a/src/JSONSchemaInput.ts +++ b/src/JSONSchemaInput.ts @@ -9,7 +9,7 @@ import { TypeGraphBuilder, TypeRef } from "./TypeBuilder"; import { TypeNames } from "./TypeNames"; import { unifyTypes } from "./UnifyClasses"; import { makeNamesTypeAttributes, modifyTypeNames, singularizeTypeNames } from "./TypeNames"; -import { TypeAttributes, descriptionTypeAttributeKind } from "./TypeAttributes"; +import { TypeAttributes, descriptionTypeAttributeKind, propertyDescriptionsTypeAttributeKind } from "./TypeAttributes"; enum PathElementKind { Root, @@ -170,12 +170,25 @@ export function schemaToType( function makeClass(path: Ref, attributes: TypeAttributes, properties: StringMap, requiredArray: string[]): TypeRef { const required = Set(requiredArray); + const propertiesMap = Map(properties); + const propertyDescriptions = propertiesMap.map(propSchema => { + if (typeof propSchema === "object") { + const desc = propSchema.description; + if (typeof desc === "string") { + return desc; + } + } + return undefined; + }).filter(v => v !== undefined) as Map; + if (!propertyDescriptions.isEmpty()) { + attributes = propertyDescriptionsTypeAttributeKind.setInAttributes(attributes, propertyDescriptions); + } const result = typeBuilder.getUniqueClassType(attributes, true); setTypeForPath(path, result); // FIXME: We're using a Map instead of an OrderedMap here because we represent // the JSON Schema as a JavaScript object, which has no map ordering. Ideally // we would use a JSON parser that preserves order. - const props = Map(properties).map((propSchema, propName) => { + const props = propertiesMap.map((propSchema, propName) => { const t = toType( checkStringMap(propSchema), path.push({ kind: PathElementKind.Property, name: propName }), diff --git a/src/Language/Golang.ts b/src/Language/Golang.ts index 64ce7409..2a22b421 100644 --- a/src/Language/Golang.ts +++ b/src/Language/Golang.ts @@ -83,6 +83,11 @@ function isValueType(t: Type): boolean { return primitiveValueTypeKinds.indexOf(kind) >= 0 || kind === "class" || kind === "enum"; } +function singleDescriptionComment(description: string[] | undefined): string { + if (description === undefined) return ""; + return "// " + description.join("; "); +} + class GoRenderer extends ConvenienceRenderer { private _topLevelUnmarshalNames = Map(); @@ -201,7 +206,8 @@ class GoRenderer extends ConvenienceRenderer { let columns: Sourcelike[][] = []; this.forEachClassProperty(c, "none", (name, jsonName, p) => { const goType = this.goType(p.type, true); - columns.push([[name, " "], [goType, " "], ['`json:"', stringEscape(jsonName), '"`']]); + const comment = singleDescriptionComment(this.descriptionForClassProperty(c, jsonName)); + columns.push([[name, " "], [goType, " "], ['`json:"', stringEscape(jsonName), '"`'], comment]); }); this.emitDescription(this.descriptionForType(c)); this.emitStruct(className, columns); diff --git a/src/TypeAttributes.ts b/src/TypeAttributes.ts index b1272b1a..276ec133 100644 --- a/src/TypeAttributes.ts +++ b/src/TypeAttributes.ts @@ -65,4 +65,12 @@ export function combineTypeAttributes(attributeArray: TypeAttributes[]): TypeAtt return first.mergeWith((aa, ab, kind) => kind.combine(aa, ab), ...rest); } -export const descriptionTypeAttributeKind = new TypeAttributeKind("description", undefined); +function combineDescriptions(a: string, b: string): string { + return a.trim() + "\n\n" + b.trim(); +} + +export const descriptionTypeAttributeKind = new TypeAttributeKind("description", combineDescriptions); +export const propertyDescriptionsTypeAttributeKind = new TypeAttributeKind>( + "propertyDescriptions", + (a, b) => a.mergeWith(combineDescriptions, b) +); diff --git a/test/inputs/schema/description.schema b/test/inputs/schema/description.schema index e7121a1a..bc3f6f34 100644 --- a/test/inputs/schema/description.schema +++ b/test/inputs/schema/description.schema @@ -10,6 +10,13 @@ "type": "string", "enum": ["foo", "bar"], "description": "An enumeration" + }, + "foo": { + "type": "number" + }, + "bar": { + "type": "boolean", + "description": "A pretty boolean" } }, "required": ["union", "enum"],