Skip to content

Commit bb52401

Browse files
authored
feat(ISSUE-685): constants support (#831)
* feat(ISSUE-685): constants support, required support, enums/consts jsodc comments, tests for it * chore(ISSUE-685): remove posibility of 'required' in field props * chore(ISSUE-685): import js fixes * tests(ISSUE-685): fix tests for enums and consts
1 parent 3586c72 commit bb52401

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+10154
-2246
lines changed

src/transform/schema.ts

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import {
88
tsReadonly,
99
tsTupleOf,
1010
tsUnionOf,
11+
parseSingleSimpleValue,
12+
ParsedSimpleValue,
1113
} from "../utils.js";
1214

1315
interface TransformSchemaObjOptions extends GlobalContext {
@@ -112,12 +114,15 @@ export function transformSchemaObj(node: any, options: TransformSchemaObjOptions
112114
output += nodeType(node);
113115
break;
114116
}
117+
case "const": {
118+
output += parseSingleSimpleValue(node.const, node.nullable);
119+
break;
120+
}
115121
case "enum": {
116-
const items: Array<string | number | boolean> = [];
122+
const items: Array<ParsedSimpleValue> = [];
117123
(node.enum as unknown[]).forEach((item) => {
118-
if (typeof item === "string") items.push(`'${item.replace(/'/g, "\\'")}'`);
119-
else if (typeof item === "number" || typeof item === "boolean") items.push(item);
120-
else if (item === null && !node.nullable) items.push("null");
124+
const value = parseSingleSimpleValue(item, node.nullable);
125+
items.push(value);
121126
});
122127
output += tsUnionOf(items);
123128
break;

src/utils.ts

Lines changed: 41 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,16 @@
11
import { OpenAPI2, OpenAPI3, ReferenceObject } from "./types";
22

33
type CommentObject = {
4-
title?: string; // not jsdoc
5-
format?: string; // not jsdoc
4+
const?: boolean; // jsdoc without value
5+
default?: string; // jsdoc with value
66
deprecated?: boolean; // jsdoc without value
77
description?: string; // jsdoc with value
8-
default?: string; // jsdoc with value
8+
enum?: boolean; // jsdoc without value
99
example?: string; // jsdoc with value
10+
format?: string; // not jsdoc
11+
nullable?: boolean; // Node information
12+
title?: string; // not jsdoc
13+
type: string; // Type of node
1014
};
1115

1216
/**
@@ -32,6 +36,15 @@ export function prepareComment(v: CommentObject): string | void {
3236
if (v[field]) commentsArray.push(`@${field} ${v[field]} `);
3337
}
3438

39+
// * JSDOC 'Constant' without value
40+
if (v.const) commentsArray.push(`@constant `);
41+
42+
// * JSDOC 'Enum' with type
43+
if (v.enum) {
44+
const canBeNull = v.nullable ? `|${null}` : "";
45+
commentsArray.push(`@enum {${v.type}${canBeNull}}`);
46+
}
47+
3548
if (!commentsArray.length) return;
3649

3750
return comment(commentsArray.join("\n"));
@@ -68,6 +81,25 @@ export function isRef(obj: any): obj is ReferenceObject {
6881
return !!obj.$ref;
6982
}
7083

84+
export type ParsedSimpleValue = string | number | boolean;
85+
86+
/**
87+
* For parsing CONST / ENUM single values
88+
* @param value - node.const or node.enum[I] for parsing
89+
* @param isNodeNullable - node.nullable
90+
* @returns parsed value
91+
*/
92+
export function parseSingleSimpleValue(value: unknown, isNodeNullable = false): ParsedSimpleValue {
93+
if (typeof value === "string") return `'${value.replace(/'/g, "\\'")}'`;
94+
95+
if (typeof value === "number" || typeof value === "boolean") return value;
96+
97+
if (value === null && !isNodeNullable) return "null";
98+
99+
// Edge case
100+
return `${value}`;
101+
}
102+
71103
/** Return type of node (works for v2 or v3, as there are no conflicting types) */
72104
type SchemaObjectType =
73105
| "anyOf"
@@ -79,6 +111,7 @@ type SchemaObjectType =
79111
| "oneOf"
80112
| "ref"
81113
| "string"
114+
| "const"
82115
| "unknown";
83116
export function nodeType(obj: any): SchemaObjectType {
84117
if (!obj || typeof obj !== "object") {
@@ -89,6 +122,11 @@ export function nodeType(obj: any): SchemaObjectType {
89122
return "ref";
90123
}
91124

125+
// const
126+
if (obj.const) {
127+
return "const";
128+
}
129+
92130
// enum
93131
if (Array.isArray(obj.enum) && obj.enum.length) {
94132
return "enum";

test/bin/expected/prettier-js.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ export interface components {
7474
quantity?: number
7575
/** Format: date-time */
7676
shipDate?: string
77-
/** @description Order Status */
77+
/**
78+
* @description Order Status
79+
* @enum {string}
80+
*/
7881
status?: 'placed' | 'approved' | 'delivered'
7982
complete?: boolean
8083
}
@@ -111,7 +114,10 @@ export interface components {
111114
name: string
112115
photoUrls: string[]
113116
tags?: components['schemas']['Tag'][]
114-
/** @description pet status in the store */
117+
/**
118+
* @description pet status in the store
119+
* @enum {string}
120+
*/
115121
status?: 'available' | 'pending' | 'sold'
116122
}
117123
ApiResponse: {

test/bin/expected/prettier-json.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ export interface components {
7474
quantity?: number
7575
/** Format: date-time */
7676
shipDate?: string
77-
/** @description Order Status */
77+
/**
78+
* @description Order Status
79+
* @enum {string}
80+
*/
7881
status?: 'placed' | 'approved' | 'delivered'
7982
complete?: boolean
8083
}
@@ -111,7 +114,10 @@ export interface components {
111114
name: string
112115
photoUrls: string[]
113116
tags?: components['schemas']['Tag'][]
114-
/** @description pet status in the store */
117+
/**
118+
* @description pet status in the store
119+
* @enum {string}
120+
*/
115121
status?: 'available' | 'pending' | 'sold'
116122
}
117123
ApiResponse: {

test/bin/expected/specs/manifold.ts

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -556,7 +556,9 @@ export interface definitions {
556556
};
557557
Region: {
558558
id: definitions["ID"];
559+
/** @enum {string} */
559560
type: "region";
561+
/** @enum {integer} */
560562
version: 1;
561563
body: definitions["RegionBody"];
562564
};
@@ -590,7 +592,9 @@ export interface definitions {
590592
};
591593
Provider: {
592594
id: definitions["ID"];
595+
/** @enum {integer} */
593596
version: 1;
597+
/** @enum {string} */
594598
type: "provider";
595599
body: definitions["ProviderBody"];
596600
};
@@ -633,13 +637,17 @@ export interface definitions {
633637
base_url?: string;
634638
/** Format: url */
635639
sso_url?: string;
640+
/** @enum {string} */
636641
version?: "v1";
637642
/** @default [object Object] */
638643
features?: {
639644
access_code?: boolean;
640645
sso?: boolean;
641646
plan_change?: boolean;
642-
/** @default multiple */
647+
/**
648+
* @default multiple
649+
* @enum {string}
650+
*/
643651
credential?: "none" | "single" | "multiple" | "unknown";
644652
};
645653
};
@@ -678,6 +686,7 @@ export interface definitions {
678686
FeatureType: {
679687
label: definitions["Label"];
680688
name: definitions["Name"];
689+
/** @enum {string} */
681690
type: "boolean" | "string" | "number";
682691
/** @description This sets whether or not the feature can be customized by a consumer. */
683692
customizable?: boolean;
@@ -803,6 +812,7 @@ export interface definitions {
803812
ProductImageURL: string;
804813
/** @description List of tags for product categorization and search */
805814
ProductTags: definitions["Label"][];
815+
/** @enum {string} */
806816
ProductState: "available" | "hidden" | "grandfathered" | "new" | "upcoming";
807817
/** @default [object Object] */
808818
ProductListing: {
@@ -854,6 +864,8 @@ export interface definitions {
854864
* Pre-Order, should not be used yet. But in the future it should allow people to
855865
* pre-provision a resource for when it does go live.
856866
* Public, means the resource is live and everyone should be able to provision it.
867+
*
868+
* @enum {string}
857869
*/
858870
ProductProvisioning: "provider-only" | "pre-order" | "public";
859871
/** @default [object Object] */
@@ -877,6 +889,8 @@ export interface definitions {
877889
* @description Describes how the region for a resource is specified, if
878890
* unspecified, then regions have no impact on this
879891
* resource.
892+
*
893+
* @enum {string}
880894
*/
881895
region?: "user-specified" | "unspecified";
882896
/**
@@ -888,6 +902,7 @@ export interface definitions {
888902
* * `unknown`: The credential type is unknown.
889903
*
890904
* @default multiple
905+
* @enum {string}
891906
*/
892907
credential?: "none" | "single" | "multiple" | "unknown";
893908
};
@@ -921,7 +936,9 @@ export interface definitions {
921936
};
922937
feature_types: definitions["FeatureType"][];
923938
billing: {
939+
/** @enum {string} */
924940
type: "monthly-prorated" | "monthly-anniversary" | "annual-anniversary";
941+
/** @enum {string} */
925942
currency: "usd";
926943
};
927944
integration: {
@@ -930,14 +947,17 @@ export interface definitions {
930947
base_url: string;
931948
/** Format: url */
932949
sso_url?: string;
950+
/** @enum {string} */
933951
version: "v1";
934952
features: definitions["ProductIntegrationFeatures"];
935953
};
936954
tags?: definitions["ProductTags"];
937955
};
938956
Product: {
939957
id: definitions["ID"];
958+
/** @enum {integer} */
940959
version: 1;
960+
/** @enum {string} */
941961
type: "product";
942962
body: definitions["ProductBody"];
943963
};
@@ -966,6 +986,7 @@ export interface definitions {
966986
/** @description Dollar value in cents. */
967987
cost: number;
968988
};
989+
/** @enum {string} */
969990
PlanState: "hidden" | "available" | "grandfathered" | "unlisted";
970991
ExpandedPlanBody: definitions["PlanBody"] & {
971992
/** @description An array of feature definitions for the plan, as defined on the Product. */
@@ -984,13 +1005,17 @@ export interface definitions {
9841005
};
9851006
Plan: {
9861007
id: definitions["ID"];
1008+
/** @enum {integer} */
9871009
version: 1;
1010+
/** @enum {string} */
9881011
type: "plan";
9891012
body: definitions["PlanBody"];
9901013
};
9911014
ExpandedPlan: {
9921015
id: definitions["ID"];
1016+
/** @enum {integer} */
9931017
version: 1;
1018+
/** @enum {string} */
9941019
type: "plan";
9951020
body: definitions["ExpandedPlanBody"];
9961021
};
@@ -1030,7 +1055,9 @@ export interface definitions {
10301055
PriceFormula: string;
10311056
ExpandedProduct: {
10321057
id: definitions["ID"];
1058+
/** @enum {integer} */
10331059
version: 1;
1060+
/** @enum {string} */
10341061
type: "product";
10351062
body: definitions["ProductBody"];
10361063
plans?: definitions["ExpandedPlan"][];

test/bin/expected/specs/petstore.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ export interface components {
7474
quantity?: number;
7575
/** Format: date-time */
7676
shipDate?: string;
77-
/** @description Order Status */
77+
/**
78+
* @description Order Status
79+
* @enum {string}
80+
*/
7881
status?: "placed" | "approved" | "delivered";
7982
complete?: boolean;
8083
};
@@ -111,7 +114,10 @@ export interface components {
111114
name: string;
112115
photoUrls: string[];
113116
tags?: components["schemas"]["Tag"][];
114-
/** @description pet status in the store */
117+
/**
118+
* @description pet status in the store
119+
* @enum {string}
120+
*/
115121
status?: "available" | "pending" | "sold";
116122
};
117123
ApiResponse: {

test/bin/expected/stdout.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ export interface components {
7474
quantity?: number;
7575
/** Format: date-time */
7676
shipDate?: string;
77-
/** @description Order Status */
77+
/**
78+
* @description Order Status
79+
* @enum {string}
80+
*/
7881
status?: "placed" | "approved" | "delivered";
7982
complete?: boolean;
8083
};
@@ -111,7 +114,10 @@ export interface components {
111114
name: string;
112115
photoUrls: string[];
113116
tags?: components["schemas"]["Tag"][];
114-
/** @description pet status in the store */
117+
/**
118+
* @description pet status in the store
119+
* @enum {string}
120+
*/
115121
status?: "available" | "pending" | "sold";
116122
};
117123
ApiResponse: {

0 commit comments

Comments
 (0)