docs-util: extract feature flags in generated OAS (#12554)

This commit is contained in:
Shahed Nasser
2025-05-21 14:25:40 +03:00
committed by GitHub
parent bda143673d
commit 61d3bdde4a
3 changed files with 80 additions and 4 deletions
@@ -605,10 +605,12 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
getInformationFromTags(node: ts.Node): { getInformationFromTags(node: ts.Node): {
deprecatedTag: ts.JSDocTag | undefined deprecatedTag: ts.JSDocTag | undefined
versionTag: ts.JSDocTag | undefined versionTag: ts.JSDocTag | undefined
featureFlagTag: ts.JSDocTag | undefined
} { } {
const nodeComments = ts.getJSDocCommentsAndTags(node) const nodeComments = ts.getJSDocCommentsAndTags(node)
let deprecatedTag: ts.JSDocTag | undefined let deprecatedTag: ts.JSDocTag | undefined
let versionTag: ts.JSDocTag | undefined let versionTag: ts.JSDocTag | undefined
let featureFlagTag: ts.JSDocTag | undefined
nodeComments.forEach((comment) => { nodeComments.forEach((comment) => {
if (!("tags" in comment)) { if (!("tags" in comment)) {
@@ -623,12 +625,17 @@ class DefaultKindGenerator<T extends ts.Node = ts.Node> {
if (tag.tagName.getText() === "version") { if (tag.tagName.getText() === "version") {
versionTag = tag versionTag = tag
} }
if (tag.tagName.getText() === "featureFlag") {
featureFlagTag = tag
}
}) })
}) })
return { return {
deprecatedTag, deprecatedTag,
versionTag, versionTag,
featureFlagTag,
} }
} }
} }
@@ -435,7 +435,8 @@ class OasKindGenerator extends FunctionKindGenerator {
} }
// check deprecation and version in tags // check deprecation and version in tags
const { deprecatedTag, versionTag } = this.getInformationFromTags(node) const { deprecatedTag, versionTag, featureFlagTag } =
this.getInformationFromTags(node)
if (deprecatedTag) { if (deprecatedTag) {
oas.deprecated = true oas.deprecated = true
@@ -450,6 +451,12 @@ class OasKindGenerator extends FunctionKindGenerator {
: undefined : undefined
} }
if (featureFlagTag) {
oas["x-featureFlag"] = featureFlagTag.comment
? (featureFlagTag.comment as string)
: undefined
}
return formatOas(oas, oasPrefix) return formatOas(oas, oasPrefix)
} }
@@ -784,7 +791,8 @@ class OasKindGenerator extends FunctionKindGenerator {
} }
// check deprecation and version in tags // check deprecation and version in tags
const { deprecatedTag, versionTag } = this.getInformationFromTags(node) const { deprecatedTag, versionTag, featureFlagTag } =
this.getInformationFromTags(node)
if (deprecatedTag) { if (deprecatedTag) {
oas.deprecated = true oas.deprecated = true
@@ -804,6 +812,14 @@ class OasKindGenerator extends FunctionKindGenerator {
delete oas["x-version"] delete oas["x-version"]
} }
if (featureFlagTag) {
oas["x-featureFlag"] = featureFlagTag.comment
? (featureFlagTag.comment as string)
: undefined
} else {
delete oas["x-featureFlag"]
}
return formatOas(oas, oasPrefix) return formatOas(oas, oasPrefix)
} }
@@ -1562,6 +1578,27 @@ class OasKindGenerator extends FunctionKindGenerator {
}) })
}) || undefined // avoid showing it as false in the generated OAS }) || undefined // avoid showing it as false in the generated OAS
let featureFlag: string | undefined
commentsAndTags.some((comment) => {
if (!("tags" in comment)) {
return false
}
comment.tags?.some((tag) => {
if (tag.tagName.getText() !== "featureFlag" || !tag.comment) {
return false
}
featureFlag =
typeof tag.comment === "string" ? tag.comment : tag.comment.join(" ")
return true
})
return featureFlag !== undefined
})
switch (true) { switch (true) {
case isEnum || isEnumParent: case isEnum || isEnumParent:
const enumMembers: string[] = [] const enumMembers: string[] = []
@@ -1584,6 +1621,7 @@ class OasKindGenerator extends FunctionKindGenerator {
description, description,
enum: enumMembers, enum: enumMembers,
deprecated: isDeprecated, deprecated: isDeprecated,
"x-featureFlag": featureFlag,
} }
case itemType.isLiteral() || typeAsString === "RegExp": case itemType.isLiteral() || typeAsString === "RegExp":
const isString = const isString =
@@ -1602,6 +1640,7 @@ class OasKindGenerator extends FunctionKindGenerator {
name: title, name: title,
}), }),
deprecated: isDeprecated, deprecated: isDeprecated,
"x-featureFlag": featureFlag,
} }
case itemType.flags === ts.TypeFlags.String || case itemType.flags === ts.TypeFlags.String ||
itemType.flags === ts.TypeFlags.Number || itemType.flags === ts.TypeFlags.Number ||
@@ -1622,6 +1661,7 @@ class OasKindGenerator extends FunctionKindGenerator {
name: title, name: title,
}), }),
deprecated: isDeprecated, deprecated: isDeprecated,
"x-featureFlag": featureFlag,
} }
case ("intrinsicName" in itemType && case ("intrinsicName" in itemType &&
itemType.intrinsicName === "boolean") || itemType.intrinsicName === "boolean") ||
@@ -1634,6 +1674,7 @@ class OasKindGenerator extends FunctionKindGenerator {
? this.getDefaultValue(symbol?.valueDeclaration) ? this.getDefaultValue(symbol?.valueDeclaration)
: undefined, : undefined,
deprecated: isDeprecated, deprecated: isDeprecated,
"x-featureFlag": featureFlag,
} }
case this.checker.isArrayType(itemType): case this.checker.isArrayType(itemType):
return { return {
@@ -1656,6 +1697,7 @@ class OasKindGenerator extends FunctionKindGenerator {
saveSchema, saveSchema,
...rest, ...rest,
}), }),
"x-featureFlag": featureFlag,
} }
case itemType.isUnion(): case itemType.isUnion():
// if it's a union of literal types, // if it's a union of literal types,
@@ -1675,6 +1717,7 @@ class OasKindGenerator extends FunctionKindGenerator {
enum: cleanedUpTypes.map( enum: cleanedUpTypes.map(
(unionType) => (unionType as ts.LiteralType).value (unionType) => (unionType as ts.LiteralType).value
), ),
"x-featureFlag": featureFlag,
} }
} }
@@ -1690,12 +1733,17 @@ class OasKindGenerator extends FunctionKindGenerator {
) )
if (oneOfItems.length === 1) { if (oneOfItems.length === 1) {
return oneOfItems[0] return {
...oneOfItems[0],
"x-featureFlag": oneOfItems[0]["x-featureFlag"] || featureFlag,
deprecated: oneOfItems[0].deprecated || isDeprecated,
}
} }
return { return {
oneOf: oneOfItems, oneOf: oneOfItems,
deprecated: isDeprecated, deprecated: isDeprecated,
"x-featureFlag": featureFlag,
} }
case itemType.isIntersection(): case itemType.isIntersection():
const allOfItems = this.typesHelper const allOfItems = this.typesHelper
@@ -1712,12 +1760,17 @@ class OasKindGenerator extends FunctionKindGenerator {
}) })
if (allOfItems.length === 1) { if (allOfItems.length === 1) {
return allOfItems[0] return {
...allOfItems[0],
"x-featureFlag": allOfItems[0]["x-featureFlag"] || featureFlag,
deprecated: allOfItems[0].deprecated || isDeprecated,
}
} }
return { return {
allOf: allOfItems, allOf: allOfItems,
deprecated: isDeprecated, deprecated: isDeprecated,
"x-featureFlag": featureFlag,
} }
case typeAsString.startsWith("Pick"): case typeAsString.startsWith("Pick"):
const pickTypeArgs = const pickTypeArgs =
@@ -1807,6 +1860,7 @@ class OasKindGenerator extends FunctionKindGenerator {
: undefined, : undefined,
// this is changed later // this is changed later
required: undefined, required: undefined,
"x-featureFlag": featureFlag,
} }
const properties: Record< const properties: Record<
@@ -2472,6 +2526,19 @@ class OasKindGenerator extends FunctionKindGenerator {
} }
} }
if (oldSchemaObj?.["x-featureFlag"] !== newSchemaObj?.["x-featureFlag"]) {
// avoid many changes to exising OAS
if (!newSchemaObj?.["x-featureFlag"]) {
if (oldSchemaObj!["x-featureFlag"]) {
wasUpdated = true
}
delete oldSchemaObj!["x-featureFlag"]
} else {
oldSchemaObj!["x-featureFlag"] = newSchemaObj["x-featureFlag"]
wasUpdated = true
}
}
if (!wasUpdated) { if (!wasUpdated) {
const requiredChanged = const requiredChanged =
oldSchemaObj!.required?.length !== newSchemaObj?.required?.length || oldSchemaObj!.required?.length !== newSchemaObj?.required?.length ||
@@ -13,6 +13,7 @@ export declare type OpenApiOperation = Partial<OpenAPIV3.OperationObject> & {
"x-events"?: OasEvent[] "x-events"?: OasEvent[]
"x-deprecated_message"?: string "x-deprecated_message"?: string
"x-version"?: string "x-version"?: string
"x-featureFlag"?: string
} }
export declare type CommonCliOptions = { export declare type CommonCliOptions = {
@@ -23,6 +24,7 @@ export declare type CommonCliOptions = {
export declare type OpenApiSchema = OpenAPIV3.SchemaObject & { export declare type OpenApiSchema = OpenAPIV3.SchemaObject & {
"x-schemaName"?: string "x-schemaName"?: string
"x-featureFlag"?: string
} }
export declare interface OpenApiTagObject extends OpenAPIV3.TagObject { export declare interface OpenApiTagObject extends OpenAPIV3.TagObject {