Skip to content

Commit 259491a

Browse files
committed
feat(dts-generator): add special generation for deprecated enums
To improve compatibility with existing code, deprecated enums that are aliases for new, non-deprecated enums are now generated as a re-export. - a directives file (.dtsgenrc) may now contain a map `deprecatedEnumAliases` which maps from deprecated enum name to the new name - when ESM types are generated, such deprecated enums are generated as a re-export of the new enum. A re-export preserves both, the type and the object nature of the enum. - when global types are generated, nothing changed as there's no equivalent to the re-export when using namespaces - additionally, the type alternative of using literals instead of enum values is no longer generated for the return type of a function, but it's now also generated for the type aliases, as long as they're not used as return type
1 parent b05fbcf commit 259491a

File tree

9 files changed

+124
-9053
lines changed

9 files changed

+124
-9053
lines changed

packages/dts-generator/api-report/dts-generator.api.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ export interface Directives {
3939
badInterfaces: string[];
4040
badMethods: string[];
4141
badSymbols: string[];
42+
deprecatedEnumAliases: {
43+
[fqn: string]: string;
44+
};
4245
forwardDeclarations: {
4346
[libraryName: string]: ConcreteSymbol[];
4447
};

packages/dts-generator/src/generate-from-objects.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,16 @@ export interface Directives {
8484
overlays: {
8585
[libraryName: string]: ConcreteSymbol[];
8686
};
87+
88+
/**
89+
* If a symbol of kind "enum" is used as key in this map, this enum is a deprecated
90+
* alias for another enum, whose name is given as value in the map.
91+
* For such deprecated aliases for enums, a different type signature is generated,
92+
* see method `genDeprecatedAliasForEnum` in dts-code-gen.ts.
93+
*/
94+
deprecatedEnumAliases: {
95+
[fqn: string]: string;
96+
};
8797
}
8898

8999
/**
@@ -124,6 +134,7 @@ const defaultOptions: GenerateFromObjectsConfig = {
124134
forwardDeclarations: {},
125135
fqnToIgnore: {},
126136
overlays: {},
137+
deprecatedEnumAliases: {},
127138
},
128139
generateGlobals: false,
129140
};

packages/dts-generator/src/generate.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ async function loadDirectives(directivesPaths: string[]) {
3232
forwardDeclarations: {},
3333
fqnToIgnore: {},
3434
overlays: {},
35+
deprecatedEnumAliases: {},
3536
};
3637

3738
function mergeDirectives(loadedDirectives: Directives) {

packages/dts-generator/src/phases/dts-code-gen.ts

Lines changed: 59 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ function genExport(_export: Export) {
293293
exportAsDefault: _export.asDefault,
294294
});
295295
case "Enum":
296-
if (_export.asDefault) {
296+
if (_export.asDefault && !_export.expression.deprecatedAliasFor) {
297297
// TS does not allow export of enums as default
298298
// see https://github.com/microsoft/TypeScript/issues/3320
299299
return (
@@ -559,7 +559,7 @@ function genMethodOrFunction(
559559
text += ")";
560560

561561
let hasReturnType = ast.returns !== undefined && ast.returns.type;
562-
text += `: ${hasReturnType ? genType(ast.returns.type) : "void"}`;
562+
text += `: ${hasReturnType ? genType(ast.returns.type, "returnValue") : "void"}`;
563563

564564
return text;
565565
}
@@ -609,7 +609,7 @@ function genInterfaceProperty(ast: Variable) {
609609
text += applyTsIgnore(ast);
610610
text +=
611611
`${ast.name}${ast.optional ? "?" : ""} : ${
612-
ast.type ? genType(ast.type) : "any"
612+
ast.type ? genType(ast.type, "property") : "any"
613613
}` + NL;
614614
return text;
615615
}
@@ -626,14 +626,16 @@ function genConstExport(
626626
if (options.export && options.exportAsDefault) {
627627
text += JSDOC(ast) + NL;
628628
text += applyTsIgnore(ast);
629-
text += `const ${ast.name} : ${ast.type ? genType(ast.type) : "any"};` + NL;
629+
text +=
630+
`const ${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"};` +
631+
NL;
630632
text += NL;
631633
text += `export default ${ast.name};` + NL;
632634
} else if (options.export) {
633635
text += JSDOC(ast) + NL;
634636
text += applyTsIgnore(ast);
635637
text +=
636-
`export const ${ast.name} : ${ast.type ? genType(ast.type) : "any"};` +
638+
`export const ${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"};` +
637639
NL;
638640
}
639641
return text;
@@ -648,7 +650,8 @@ function genField(ast: Variable) {
648650
text += JSDOC(ast) + NL;
649651
text += applyTsIgnore(ast);
650652
text += ast.static ? "static " : "";
651-
text += `${ast.name} : ${ast.type ? genType(ast.type) : "any"}` + NL;
653+
text +=
654+
`${ast.name} : ${ast.type ? genType(ast.type, "property") : "any"}` + NL;
652655
return text;
653656
}
654657

@@ -677,7 +680,7 @@ function genParameter(ast: Parameter) {
677680
});
678681
text += "}" + NL;
679682
} else {
680-
text += `: ${ast.type ? genType(ast.type) : "any"}`;
683+
text += `: ${ast.type ? genType(ast.type, "parameter") : "any"}`;
681684
}
682685

683686
return text;
@@ -694,6 +697,10 @@ function genEnum(
694697
exportAsDefault: false,
695698
},
696699
) {
700+
if (options.export && ast.deprecatedAliasFor) {
701+
return genDeprecatedAliasForEnum(ast, options);
702+
}
703+
697704
let text = "";
698705
text += JSDOC(ast) + NL;
699706
text +=
@@ -707,6 +714,38 @@ function genEnum(
707714
return text;
708715
}
709716

717+
/**
718+
* @param ast
719+
* @return
720+
*/
721+
function genDeprecatedAliasForEnum(
722+
ast: Enum,
723+
options: { export: boolean; exportAsDefault?: boolean } = {
724+
export: undefined,
725+
exportAsDefault: false,
726+
},
727+
) {
728+
if (!options.export) {
729+
console.error(
730+
"deprecated alias is only supported for exported enums",
731+
ast,
732+
options,
733+
);
734+
throw new TypeError(
735+
`deprecated alias is only supported for exported enums (${ast.name})`,
736+
);
737+
}
738+
let text = "";
739+
text += "export {";
740+
text += JSDOC(ast) + NL;
741+
text +=
742+
`${ast.deprecatedAliasFor} as ${options.exportAsDefault ? "default " : ast.name}` +
743+
NL;
744+
text += "}" + NL;
745+
746+
return text;
747+
}
748+
710749
/**
711750
*
712751
* @param ast
@@ -731,7 +770,7 @@ function genEnumValue(ast: Variable, withValue = false) {
731770
function genVariable(ast: Variable) {
732771
let text = "";
733772
text += JSDOC(ast) + NL;
734-
text += `export const ${ast.name} : ${genType(ast.type)};` + NL;
773+
text += `export const ${ast.name} : ${genType(ast.type, "const")};` + NL;
735774

736775
return text;
737776
}
@@ -781,9 +820,10 @@ function hasSimpleElementType(ast: ArrayType): boolean {
781820

782821
/**
783822
* @param ast
823+
* @param usage Context in which the type is used
784824
* @returns
785825
*/
786-
function genType(ast: Type): string {
826+
function genType(ast: Type, usage: string = "unknown"): string {
787827
let text;
788828
switch (ast.kind) {
789829
case "TypeReference":
@@ -799,49 +839,49 @@ function genType(ast: Type): string {
799839
if (ast.nullable) {
800840
text += `|null`;
801841
}
802-
if (ast.isStandardEnum) {
842+
if (ast.isStandardEnum && usage !== "returnValue") {
803843
text = `(${text} | keyof typeof ${ast.typeName})`; // TODO parentheses not always required
804844
}
805845
return text;
806846
case "ArrayType":
807847
if (hasSimpleElementType(ast)) {
808-
return `${genType(ast.elementType)}[]`;
848+
return `${genType(ast.elementType, usage)}[]`;
809849
}
810-
return `Array<${genType(ast.elementType)}>`;
850+
return `Array<${genType(ast.elementType, usage)}>`;
811851
case "LiteralType":
812852
return String(ast.literal);
813853
case "TypeLiteral":
814854
return `{${NL}${_.map(ast.members, (prop) => {
815855
let ptext = "";
816856
ptext += JSDOC(prop) + NL;
817857
ptext +=
818-
`${prop.name}${prop.optional ? "?" : ""}: ${genType(prop.type)},` +
858+
`${prop.name}${prop.optional ? "?" : ""}: ${genType(prop.type, usage)},` +
819859
NL;
820860
return ptext;
821861
}).join("")}}`;
822862
case "UnionType":
823863
const unionTypes: string[] = _.map(ast.types, (variantType) => {
824864
if (variantType.kind === "FunctionType") {
825-
return `(${genType(variantType)})`;
865+
return `(${genType(variantType, usage)})`;
826866
}
827-
return genType(variantType);
867+
return genType(variantType, usage);
828868
});
829869
return unionTypes.join(" | ");
830870
case "IntersectionType":
831871
const intersectionTypes: string[] = _.map(ast.types, (variantType) => {
832872
if (variantType.kind === "FunctionType") {
833-
return `(${genType(variantType)})`;
873+
return `(${genType(variantType, usage)})`;
834874
}
835-
return genType(variantType);
875+
return genType(variantType, usage);
836876
});
837877
return intersectionTypes.join(" & ");
838878
case "FunctionType":
839879
text = "";
840880
if (!_.isEmpty(ast.typeParameters)) {
841881
text += `<${_.map(ast.typeParameters, (param) => param.name).join(", ")}>`; // TODO defaults, constraints, expressions
842882
}
843-
text += `(${_.map(ast.parameters, (param) => `${param.name}: ${genType(param.type)}`).join(", ")})`;
844-
text += ` => ${ast.type ? genType(ast.type) : "void"}`;
883+
text += `(${_.map(ast.parameters, (param) => `${param.name}: ${genType(param.type, "parameter")}`).join(", ")})`;
884+
text += ` => ${ast.type ? genType(ast.type, "returnValue") : "void"}`;
845885
return text;
846886
case "NativeTSTypeExpression":
847887
// native TS type expression, emit the 'type' string "as is"

packages/dts-generator/src/phases/json-fixer.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -839,6 +839,28 @@ function removeRestrictedMembers(json: ApiJSON) {
839839
});
840840
}
841841

842+
/**
843+
* The map `deprecatedEnumAliases`, which is part of the directives, can list deprecated enums
844+
* for which a special type alias should be generated.
845+
*
846+
* In this method, the aliases are added to those enums, both as a marker and as input for
847+
* later generation of the alias.
848+
*
849+
* @param symbols Array of symbols for a library
850+
* @param directives Directives for all libraries
851+
*/
852+
function markDeprecatedAliasesForEnums(
853+
symbols: ConcreteSymbol[],
854+
directives: Directives,
855+
) {
856+
const deprecatedEnumAliases = directives.deprecatedEnumAliases;
857+
symbols.forEach((symbol) => {
858+
if (symbol.kind === "enum" && symbol.name in deprecatedEnumAliases) {
859+
symbol.deprecatedAliasFor = deprecatedEnumAliases[symbol.name];
860+
}
861+
});
862+
}
863+
842864
function _prepareApiJson(
843865
json: ApiJSON,
844866
directives: Directives,
@@ -853,6 +875,7 @@ function _prepareApiJson(
853875
convertNamespacesIntoTypedefsOrInterfaces(json.symbols, directives);
854876
determineMissingExportsForTypes(json.symbols);
855877
parseTypeExpressions(json.symbols);
878+
markDeprecatedAliasesForEnums(json.symbols, directives);
856879
if (options.mainLibrary) {
857880
addForwardDeclarations(json, directives);
858881
addInterfaceWithModuleNames(json.symbols);

packages/dts-generator/src/phases/json-to-ast.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -391,17 +391,29 @@ class ConvertGlobalsToImports extends ASTVisitor {
391391
this.#scopeBuilder.topLevelScope as ModuleBuilder
392392
).typeUniverse.get(type);
393393
return (
394-
symbolForType != null &&
395-
symbolForType["ui5-metadata"] != null &&
396-
symbolForType["ui5-metadata"].stereotype === "enum"
394+
(symbolForType != null &&
395+
symbolForType["ui5-metadata"] != null &&
396+
symbolForType["ui5-metadata"].stereotype === "enum") ||
397+
(generateGlobals === false &&
398+
symbolForType != null &&
399+
symbolForType.deprecatedAliasFor &&
400+
this._isStandardEnum(symbolForType.deprecatedAliasFor))
397401
);
398402
}
399-
_visitTypeName(typeName: string, usage: "extends" | "implements") {
403+
_visitTypeName(typeName: string, usage: string) {
400404
return this._import(
401405
typeName,
402406
usage !== "extends" && usage !== "implements",
403407
);
404408
}
409+
_visitEnum(_enum: Enum) {
410+
if (_enum.deprecatedAliasFor) {
411+
_enum.deprecatedAliasFor = this._visitTypeName(
412+
_enum.deprecatedAliasFor,
413+
"alias",
414+
);
415+
}
416+
}
405417
_visitTypeReference(type) {
406418
if (this.mode !== "type-alias") {
407419
type.isStandardEnum = this._isStandardEnum(type.typeName);
@@ -1639,7 +1651,10 @@ function buildInterfaceFromObject(ui5Object): Interface {
16391651
* @returns
16401652
*/
16411653
function buildEnum(ui5Enum: EnumSymbol) {
1642-
assertKnownProps(["name", "basename", "properties"], ui5Enum);
1654+
assertKnownProps(
1655+
["name", "basename", "properties", "deprecatedAliasFor"],
1656+
ui5Enum,
1657+
);
16431658

16441659
const isStandardEnum =
16451660
ui5Enum["ui5-metadata"] != null &&
@@ -1650,6 +1665,7 @@ function buildEnum(ui5Enum: EnumSymbol) {
16501665
name: ui5Enum.basename,
16511666
withValues: true,
16521667
isLibraryEnum: ui5Enum.module.endsWith("/library"),
1668+
deprecatedAliasFor: ui5Enum.deprecatedAliasFor,
16531669
values: _.map(ui5Enum.properties, (prop) =>
16541670
buildVariableWithValue(prop, isStandardEnum),
16551671
),

packages/dts-generator/src/types/api-json.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export type EnumSymbol = SymbolBase & {
157157
"ui5-metadata"?: {
158158
stereotype?: "enum";
159159
};
160+
deprecatedAliasFor?: string;
160161
[k: string]: any;
161162
};
162163
/**

packages/dts-generator/src/types/ast.d.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,10 @@ export interface Enum extends AstSymbol, UI5JSDocs {
8282
values: VariableWithValue[];
8383
withValues: true;
8484
isLibraryEnum: boolean;
85+
/**
86+
* When set, this enum is a deprecated alias for another enum whose name is given by this property
87+
*/
88+
deprecatedAliasFor?: string;
8589
}
8690

8791
// Other Nodes
@@ -206,7 +210,7 @@ export interface TypeReference {
206210
typeName: string;
207211
nullable?: boolean;
208212
typeArguments?: Type[];
209-
isStandardEnum?: boolean;
213+
isStandardEnum?: boolean; // only set when generating esm types
210214
}
211215

212216
export interface TypeLiteral {

0 commit comments

Comments
 (0)