From 83f868bf4754b49174f5bb2a0e36366efd689c0b Mon Sep 17 00:00:00 2001 From: Frank Weigel Date: Wed, 11 Dec 2024 17:31:47 +0100 Subject: [PATCH] feat(dts-generator): change how enums are referenced in the generated types --- .../api-report/dts-generator.api.md | 3 + .../src/generate-from-objects.ts | 11 +++ packages/dts-generator/src/generate.ts | 1 + .../dts-generator/src/phases/dts-code-gen.ts | 80 ++++++++++++++----- .../dts-generator/src/phases/json-fixer.ts | 13 +++ .../dts-generator/src/phases/json-to-ast.ts | 16 +++- packages/dts-generator/src/types/ast.d.ts | 4 + 7 files changed, 107 insertions(+), 21 deletions(-) diff --git a/packages/dts-generator/api-report/dts-generator.api.md b/packages/dts-generator/api-report/dts-generator.api.md index 660efdd..61ad88d 100644 --- a/packages/dts-generator/api-report/dts-generator.api.md +++ b/packages/dts-generator/api-report/dts-generator.api.md @@ -39,6 +39,9 @@ export interface Directives { badInterfaces: string[]; badMethods: string[]; badSymbols: string[]; + deprecatedEnumAliases: { + [fqn: string]: string; + }; forwardDeclarations: { [libraryName: string]: ConcreteSymbol[]; }; diff --git a/packages/dts-generator/src/generate-from-objects.ts b/packages/dts-generator/src/generate-from-objects.ts index 680f1ec..f864ed4 100644 --- a/packages/dts-generator/src/generate-from-objects.ts +++ b/packages/dts-generator/src/generate-from-objects.ts @@ -84,6 +84,16 @@ export interface Directives { overlays: { [libraryName: string]: ConcreteSymbol[]; }; + + /** + * If a symbol of kind "enum" is used as key in this map, this enum is a deprecated + * alias for another enum, whose name is given as value in the map. + * For such deprecated aliases for enums, a different type signature is generated, + * see method `genDeprecatedAliasForEnum` in dts-code-gen.ts. + */ + deprecatedEnumAliases: { + [fqn: string]: string; + }; } /** @@ -124,6 +134,7 @@ const defaultOptions: GenerateFromObjectsConfig = { forwardDeclarations: {}, fqnToIgnore: {}, overlays: {}, + deprecatedEnumAliases: {}, }, generateGlobals: false, }; diff --git a/packages/dts-generator/src/generate.ts b/packages/dts-generator/src/generate.ts index ff23b57..74f7045 100644 --- a/packages/dts-generator/src/generate.ts +++ b/packages/dts-generator/src/generate.ts @@ -32,6 +32,7 @@ async function loadDirectives(directivesPaths: string[]) { forwardDeclarations: {}, fqnToIgnore: {}, overlays: {}, + deprecatedEnumAliases: {}, }; function mergeDirectives(loadedDirectives: Directives) { diff --git a/packages/dts-generator/src/phases/dts-code-gen.ts b/packages/dts-generator/src/phases/dts-code-gen.ts index 9108a90..dc7d53c 100644 --- a/packages/dts-generator/src/phases/dts-code-gen.ts +++ b/packages/dts-generator/src/phases/dts-code-gen.ts @@ -293,7 +293,7 @@ function genExport(_export: Export) { exportAsDefault: _export.asDefault, }); case "Enum": - if (_export.asDefault) { + if (_export.asDefault && !_export.expression.deprecatedAliasFor) { // TS does not allow export of enums as default // see https://github.com/microsoft/TypeScript/issues/3320 return ( @@ -559,7 +559,7 @@ function genMethodOrFunction( text += ")"; let hasReturnType = ast.returns !== undefined && ast.returns.type; - text += `: ${hasReturnType ? genType(ast.returns.type) : "void"}`; + text += `: ${hasReturnType ? genType(ast.returns.type, "out") : "void"}`; return text; } @@ -609,7 +609,7 @@ function genInterfaceProperty(ast: Variable) { text += applyTsIgnore(ast); text += `${ast.name}${ast.optional ? "?" : ""} : ${ - ast.type ? genType(ast.type) : "any" + ast.type ? genType(ast.type, "inout") : "any" }` + NL; return text; } @@ -626,14 +626,16 @@ function genConstExport( if (options.export && options.exportAsDefault) { text += JSDOC(ast) + NL; text += applyTsIgnore(ast); - text += `const ${ast.name} : ${ast.type ? genType(ast.type) : "any"};` + NL; + text += + `const ${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"};` + + NL; text += NL; text += `export default ${ast.name};` + NL; } else if (options.export) { text += JSDOC(ast) + NL; text += applyTsIgnore(ast); text += - `export const ${ast.name} : ${ast.type ? genType(ast.type) : "any"};` + + `export const ${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"};` + NL; } return text; @@ -648,7 +650,7 @@ function genField(ast: Variable) { text += JSDOC(ast) + NL; text += applyTsIgnore(ast); text += ast.static ? "static " : ""; - text += `${ast.name} : ${ast.type ? genType(ast.type) : "any"}` + NL; + text += `${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"}` + NL; return text; } @@ -677,7 +679,7 @@ function genParameter(ast: Parameter) { }); text += "}" + NL; } else { - text += `: ${ast.type ? genType(ast.type) : "any"}`; + text += `: ${ast.type ? genType(ast.type, "in") : "any"}`; } return text; @@ -694,6 +696,10 @@ function genEnum( exportAsDefault: false, }, ) { + if (ast.deprecatedAliasFor) { + return genDeprecatedAliasForEnum(ast, options); + } + let text = ""; text += JSDOC(ast) + NL; text += @@ -707,6 +713,42 @@ function genEnum( return text; } +/** + * @param ast + * @return + */ +function genDeprecatedAliasForEnum( + ast: Enum, + options: { export: boolean; exportAsDefault?: boolean } = { + export: undefined, + exportAsDefault: false, + }, +) { + if (!options.export) { + console.error( + "deprecated alias is only supported for exported enums", + ast, + options, + ); + throw new TypeError( + `deprecated alias is only supported for exported enums (${ast.name})`, + ); + } + let text = ""; + text += "export {"; + text += JSDOC(ast) + NL; + text += + `${ast.deprecatedAliasFor} as ${options.exportAsDefault ? "default " : ast.name}` + + NL; + text += "}" + NL; + text += JSDOC(ast) + NL; + text += + `export type ${options.exportAsDefault ? "default " : ast.name} = ${ast.deprecatedAliasFor} | keyof typeof ${ast.deprecatedAliasFor};` + + NL; + + return text; +} + /** * * @param ast @@ -731,7 +773,7 @@ function genEnumValue(ast: Variable, withValue = false) { function genVariable(ast: Variable) { let text = ""; text += JSDOC(ast) + NL; - text += `export const ${ast.name} : ${genType(ast.type)};` + NL; + text += `export const ${ast.name} : ${genType(ast.type, "const")};` + NL; return text; } @@ -783,7 +825,7 @@ function hasSimpleElementType(ast: ArrayType): boolean { * @param ast * @returns */ -function genType(ast: Type): string { +function genType(ast: Type, usage: string = "unknown"): string { let text; switch (ast.kind) { case "TypeReference": @@ -799,15 +841,15 @@ function genType(ast: Type): string { if (ast.nullable) { text += `|null`; } - if (ast.isStandardEnum) { + if (ast.isStandardEnum && usage === "in") { text = `(${text} | keyof typeof ${ast.typeName})`; // TODO parentheses not always required } return text; case "ArrayType": if (hasSimpleElementType(ast)) { - return `${genType(ast.elementType)}[]`; + return `${genType(ast.elementType, usage)}[]`; } - return `Array<${genType(ast.elementType)}>`; + return `Array<${genType(ast.elementType, usage)}>`; case "LiteralType": return String(ast.literal); case "TypeLiteral": @@ -815,24 +857,24 @@ function genType(ast: Type): string { let ptext = ""; ptext += JSDOC(prop) + NL; ptext += - `${prop.name}${prop.optional ? "?" : ""}: ${genType(prop.type)},` + + `${prop.name}${prop.optional ? "?" : ""}: ${genType(prop.type, usage)},` + NL; return ptext; }).join("")}}`; case "UnionType": const unionTypes: string[] = _.map(ast.types, (variantType) => { if (variantType.kind === "FunctionType") { - return `(${genType(variantType)})`; + return `(${genType(variantType, usage)})`; } - return genType(variantType); + return genType(variantType, usage); }); return unionTypes.join(" | "); case "IntersectionType": const intersectionTypes: string[] = _.map(ast.types, (variantType) => { if (variantType.kind === "FunctionType") { - return `(${genType(variantType)})`; + return `(${genType(variantType, usage)})`; } - return genType(variantType); + return genType(variantType, usage); }); return intersectionTypes.join(" & "); case "FunctionType": @@ -840,8 +882,8 @@ function genType(ast: Type): string { if (!_.isEmpty(ast.typeParameters)) { text += `<${_.map(ast.typeParameters, (param) => param.name).join(", ")}>`; // TODO defaults, constraints, expressions } - text += `(${_.map(ast.parameters, (param) => `${param.name}: ${genType(param.type)}`).join(", ")})`; - text += ` => ${ast.type ? genType(ast.type) : "void"}`; + text += `(${_.map(ast.parameters, (param) => `${param.name}: ${genType(param.type, "in")}`).join(", ")})`; + text += ` => ${ast.type ? genType(ast.type, "out") : "void"}`; return text; case "NativeTSTypeExpression": // native TS type expression, emit the 'type' string "as is" diff --git a/packages/dts-generator/src/phases/json-fixer.ts b/packages/dts-generator/src/phases/json-fixer.ts index fdfa9a1..3d10b1f 100644 --- a/packages/dts-generator/src/phases/json-fixer.ts +++ b/packages/dts-generator/src/phases/json-fixer.ts @@ -839,6 +839,18 @@ function removeRestrictedMembers(json: ApiJSON) { }); } +function markDeprecatedAliasesForEnums( + symbols: ConcreteSymbol[], + directives: Directives, +) { + const deprecatedEnumAliases = directives.deprecatedEnumAliases; + symbols.forEach((symbol) => { + if (symbol.kind === "enum" && symbol.name in deprecatedEnumAliases) { + symbol.deprecatedAliasFor = deprecatedEnumAliases[symbol.name]; + } + }); +} + function _prepareApiJson( json: ApiJSON, directives: Directives, @@ -853,6 +865,7 @@ function _prepareApiJson( convertNamespacesIntoTypedefsOrInterfaces(json.symbols, directives); determineMissingExportsForTypes(json.symbols); parseTypeExpressions(json.symbols); + markDeprecatedAliasesForEnums(json.symbols, directives); if (options.mainLibrary) { addForwardDeclarations(json, directives); addInterfaceWithModuleNames(json.symbols); diff --git a/packages/dts-generator/src/phases/json-to-ast.ts b/packages/dts-generator/src/phases/json-to-ast.ts index d666f76..eb4d97d 100644 --- a/packages/dts-generator/src/phases/json-to-ast.ts +++ b/packages/dts-generator/src/phases/json-to-ast.ts @@ -396,12 +396,20 @@ class ConvertGlobalsToImports extends ASTVisitor { symbolForType["ui5-metadata"].stereotype === "enum" ); } - _visitTypeName(typeName: string, usage: "extends" | "implements") { + _visitTypeName(typeName: string, usage: string) { return this._import( typeName, usage !== "extends" && usage !== "implements", ); } + _visitEnum(_enum: Enum) { + if (_enum.deprecatedAliasFor) { + _enum.deprecatedAliasFor = this._visitTypeName( + _enum.deprecatedAliasFor, + "alias", + ); + } + } _visitTypeReference(type) { if (this.mode !== "type-alias") { type.isStandardEnum = this._isStandardEnum(type.typeName); @@ -1639,7 +1647,10 @@ function buildInterfaceFromObject(ui5Object): Interface { * @returns */ function buildEnum(ui5Enum: EnumSymbol) { - assertKnownProps(["name", "basename", "properties"], ui5Enum); + assertKnownProps( + ["name", "basename", "properties", "deprecatedAliasFor"], + ui5Enum, + ); const isStandardEnum = ui5Enum["ui5-metadata"] != null && @@ -1650,6 +1661,7 @@ function buildEnum(ui5Enum: EnumSymbol) { name: ui5Enum.basename, withValues: true, isLibraryEnum: ui5Enum.module.endsWith("/library"), + deprecatedAliasFor: ui5Enum.deprecatedAliasFor, values: _.map(ui5Enum.properties, (prop) => buildVariableWithValue(prop, isStandardEnum), ), diff --git a/packages/dts-generator/src/types/ast.d.ts b/packages/dts-generator/src/types/ast.d.ts index 6c2f330..c1f660e 100644 --- a/packages/dts-generator/src/types/ast.d.ts +++ b/packages/dts-generator/src/types/ast.d.ts @@ -82,6 +82,10 @@ export interface Enum extends AstSymbol, UI5JSDocs { values: VariableWithValue[]; withValues: true; isLibraryEnum: boolean; + /** + * When set, this enum is a deprecated alias for another enum whose name is given by this property + */ + deprecatedAliasFor?: string; } // Other Nodes