112 lines
2.9 KiB
TypeScript
112 lines
2.9 KiB
TypeScript
/**
|
|
* Shared utility helpers for the OpenAPI generator.
|
|
*
|
|
* This module centralizes the small transformations used while rendering
|
|
* generated TypeScript, including operation-name normalization, optional
|
|
* description handling, safe JSDoc comment emission, and direct array merging
|
|
* for code-generation accumulators.
|
|
*
|
|
* @since 4.0.0
|
|
*/
|
|
import * as String from "effect/String"
|
|
import * as UndefinedOr from "effect/UndefinedOr"
|
|
|
|
/**
|
|
* Converts an OpenAPI name into the generator's camel-case form.
|
|
*
|
|
* **Details**
|
|
*
|
|
* Separators are removed, leading digits are ignored, and letters following a
|
|
* separator or digit are upper-cased without otherwise changing letter casing.
|
|
*
|
|
* @category converting
|
|
* @since 4.0.0
|
|
*/
|
|
export const camelize = (self: string): string => {
|
|
let str = ""
|
|
let hadSymbol = false
|
|
for (let i = 0; i < self.length; i++) {
|
|
const charCode = self.charCodeAt(i)
|
|
if (
|
|
(charCode >= 65 && charCode <= 90) ||
|
|
(charCode >= 97 && charCode <= 122)
|
|
) {
|
|
str += hadSymbol ? self[i].toUpperCase() : self[i]
|
|
hadSymbol = false
|
|
} else if (charCode >= 48 && charCode <= 57) {
|
|
if (str.length > 0) {
|
|
str += self[i]
|
|
hadSymbol = true
|
|
}
|
|
} else if (str.length > 0) {
|
|
hadSymbol = true
|
|
}
|
|
}
|
|
return str
|
|
}
|
|
|
|
/**
|
|
* Converts an OpenAPI operation id into the exported operation identifier used
|
|
* by generated TypeScript modules.
|
|
*
|
|
* @category converting
|
|
* @since 4.0.0
|
|
*/
|
|
export const identifier = (operationId: string) => String.capitalize(camelize(operationId))
|
|
|
|
/**
|
|
* Extracts a trimmed, non-empty string from an unknown value.
|
|
*
|
|
* **Details**
|
|
*
|
|
* Returns `undefined` for non-string values and for strings containing only
|
|
* whitespace.
|
|
*
|
|
* @category filtering
|
|
* @since 4.0.0
|
|
*/
|
|
export const nonEmptyString = (a: unknown): string | undefined => {
|
|
if (typeof a === "string") {
|
|
const trimmed = String.trim(a)
|
|
if (String.isNonEmpty(trimmed)) {
|
|
return trimmed
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Renders an optional description as a JSDoc block for generated TypeScript.
|
|
*
|
|
* **Details**
|
|
*
|
|
* Returns an empty string when the description is absent and escapes any
|
|
* closing comment marker so generated source remains syntactically valid.
|
|
*
|
|
* @category converting
|
|
* @since 4.0.0
|
|
*/
|
|
export const toComment = UndefinedOr.match({
|
|
onUndefined: () => "",
|
|
onDefined: (description: string) =>
|
|
`/**
|
|
* ${description.replace(/\*\//g, " * /").split("\n").join("\n* ")}
|
|
*/\n`
|
|
})
|
|
|
|
/**
|
|
* Appends every element from `source` into `destination` in order.
|
|
*
|
|
* **Details**
|
|
*
|
|
* This mutates `destination` directly, which avoids allocating an intermediate
|
|
* array when generator code needs to merge collections.
|
|
*
|
|
* @category concatenating
|
|
* @since 4.0.0
|
|
*/
|
|
export const spreadElementsInto = <A>(source: Array<A>, destination: Array<A>): void => {
|
|
for (let i = 0; i < source.length; i++) {
|
|
destination.push(source[i])
|
|
}
|
|
}
|