Skip to content

Commit 42fc290

Browse files
codeworriorakudev
andauthored
feat(dts-generator): change how enums are referenced in the generated… (#478)
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 --------- Co-authored-by: akudev <[email protected]>
1 parent b05fbcf commit 42fc290

File tree

15 files changed

+498
-554
lines changed

15 files changed

+498
-554
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/docs/TECHNICAL.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ Using `directives` as input and the information whether modules or globals shoul
5252
- `convertNamespacesIntoTypedefsOrInterfaces` converts namespaces which are used as substitute for static objects into something else (static object or typedef); contains hardcoded knowledge about sap.ui.Device and uses "namespacesToInterfaces" directive; examples: 'jQuery.sap.PseudoEvents', 'jQuery.sap.storage', 'module:sap/base/Log', 'sap.ui.base.BindingInfo', 'sap.ui.core.AppCacheBuster', 'sap.ui.core.BusyIndicator' to object and 'sap.ui.core.AbsoluteCSSSize', 'sap.ui.core.Collision', 'sap.ui.core.CSSColor', 'sap.ui.core.Dock', 'sap.ui.core.URI' to typedef.
5353
- `determineMissingExportsForTypes` adds exports for typedefs and interfaces where they are needed for the designtime - they are so far omitted because not needed at runtime
5454
- `parseTypeExpressions` converts all type expressions into a TypeScript AST
55+
- `markDeprecatedAliasesForEnums` marks enums (based on the directives) which are deprecated and should only be generated as alias for the new replacement enum
5556
- `addForwardDeclarations` (from directives) - this relates to inverse dependencies
5657
- `addInterfaceWithModuleNames` adds all visible modules to `sap.IUI5DefineDependencyNames`, which is merged across libraries from TypeScript perspective and can be used to type module imports
5758
- `addConstructorSettingsInterfaces` and `addEventParameterInterfaces` create two additional interfaces defining important structures:

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: 73 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -286,43 +286,23 @@ function genImport(entity: Import) {
286286

287287
function genExport(_export: Export) {
288288
if (_export.expression) {
289+
const options = {
290+
export: _export.export,
291+
exportAsDefault: _export.asDefault,
292+
};
289293
switch (_export.expression.kind) {
290294
case "Class":
291-
return genClass(_export.expression, {
292-
export: _export.export,
293-
exportAsDefault: _export.asDefault,
294-
});
295+
return genClass(_export.expression, options);
295296
case "Enum":
296-
if (_export.asDefault) {
297-
// TS does not allow export of enums as default
298-
// see https://github.com/microsoft/TypeScript/issues/3320
299-
return (
300-
genEnum(_export.expression) +
301-
NL +
302-
`export default ${_export.expression.name}`
303-
);
304-
}
305-
return genEnum(_export.expression, { export: _export.export });
297+
return genEnum(_export.expression, options);
306298
case "TypeAliasDeclaration":
307-
return genTypeDefinition(_export.expression, {
308-
export: _export.export,
309-
exportAsDefault: _export.asDefault,
310-
});
299+
return genTypeDefinition(_export.expression, options);
311300
case "Interface":
312-
return genInterface(_export.expression, {
313-
export: _export.export,
314-
exportAsDefault: _export.asDefault,
315-
});
301+
return genInterface(_export.expression, options);
316302
case "FunctionDesc":
317-
return genFunction(_export.expression, {
318-
export: _export.export,
319-
exportAsDefault: _export.asDefault,
320-
});
303+
return genFunction(_export.expression, options);
321304
case "Variable":
322-
return genConstExport(_export.expression, {
323-
export: _export.export,
324-
exportAsDefault: _export.asDefault,
325-
});
305+
return genConstExport(_export.expression, options);
326306
case "Namespace":
327307
return genNamespace(_export.expression, {
328308
export:
@@ -559,7 +539,7 @@ function genMethodOrFunction(
559539
text += ")";
560540

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

564544
return text;
565545
}
@@ -609,7 +589,7 @@ function genInterfaceProperty(ast: Variable) {
609589
text += applyTsIgnore(ast);
610590
text +=
611591
`${ast.name}${ast.optional ? "?" : ""} : ${
612-
ast.type ? genType(ast.type) : "any"
592+
ast.type ? genType(ast.type, "property") : "any"
613593
}` + NL;
614594
return text;
615595
}
@@ -626,14 +606,16 @@ function genConstExport(
626606
if (options.export && options.exportAsDefault) {
627607
text += JSDOC(ast) + NL;
628608
text += applyTsIgnore(ast);
629-
text += `const ${ast.name} : ${ast.type ? genType(ast.type) : "any"};` + NL;
609+
text +=
610+
`const ${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"};` +
611+
NL;
630612
text += NL;
631613
text += `export default ${ast.name};` + NL;
632614
} else if (options.export) {
633615
text += JSDOC(ast) + NL;
634616
text += applyTsIgnore(ast);
635617
text +=
636-
`export const ${ast.name} : ${ast.type ? genType(ast.type) : "any"};` +
618+
`export const ${ast.name} : ${ast.type ? genType(ast.type, "const") : "any"};` +
637619
NL;
638620
}
639621
return text;
@@ -648,7 +630,8 @@ function genField(ast: Variable) {
648630
text += JSDOC(ast) + NL;
649631
text += applyTsIgnore(ast);
650632
text += ast.static ? "static " : "";
651-
text += `${ast.name} : ${ast.type ? genType(ast.type) : "any"}` + NL;
633+
text +=
634+
`${ast.name} : ${ast.type ? genType(ast.type, "property") : "any"}` + NL;
652635
return text;
653636
}
654637

@@ -677,7 +660,7 @@ function genParameter(ast: Parameter) {
677660
});
678661
text += "}" + NL;
679662
} else {
680-
text += `: ${ast.type ? genType(ast.type) : "any"}`;
663+
text += `: ${ast.type ? genType(ast.type, "parameter") : "any"}`;
681664
}
682665

683666
return text;
@@ -694,15 +677,55 @@ function genEnum(
694677
exportAsDefault: false,
695678
},
696679
) {
680+
if (options.export && ast.deprecatedAliasFor) {
681+
return genDeprecatedAliasForEnum(ast, options);
682+
}
683+
697684
let text = "";
698685
text += JSDOC(ast) + NL;
699686
text +=
700-
`${options.export ? "export " + (options.exportAsDefault ? "default " : "") : ""}enum ${ast.name} {` +
687+
`${options.export && !options.exportAsDefault ? "export " : ""}enum ${ast.name} {` +
701688
NL;
702689
text += APPEND_ITEMS(ast.values, (prop: Variable) =>
703690
genEnumValue(prop, ast.withValues),
704691
);
705692
text += "}";
693+
if (options.export && options.exportAsDefault) {
694+
// TS does not allow export of enums as default
695+
// see https://github.com/microsoft/TypeScript/issues/3320
696+
text += NL + `export default ${ast.name}`;
697+
}
698+
return text;
699+
}
700+
701+
/**
702+
* @param ast
703+
* @return
704+
*/
705+
function genDeprecatedAliasForEnum(
706+
ast: Enum,
707+
options: { export: boolean; exportAsDefault?: boolean } = {
708+
export: undefined,
709+
exportAsDefault: false,
710+
},
711+
) {
712+
if (!options.export) {
713+
console.error(
714+
"deprecated alias is only supported for exported enums",
715+
ast,
716+
options,
717+
);
718+
throw new TypeError(
719+
`deprecated alias is only supported for exported enums (${ast.name})`,
720+
);
721+
}
722+
let text = "";
723+
text += "export {";
724+
text += JSDOC(ast) + NL;
725+
text +=
726+
`${ast.deprecatedAliasFor} as ${options.exportAsDefault ? "default " : ast.name}` +
727+
NL;
728+
text += "}" + NL;
706729

707730
return text;
708731
}
@@ -731,7 +754,7 @@ function genEnumValue(ast: Variable, withValue = false) {
731754
function genVariable(ast: Variable) {
732755
let text = "";
733756
text += JSDOC(ast) + NL;
734-
text += `export const ${ast.name} : ${genType(ast.type)};` + NL;
757+
text += `export const ${ast.name} : ${genType(ast.type, "const")};` + NL;
735758

736759
return text;
737760
}
@@ -781,9 +804,10 @@ function hasSimpleElementType(ast: ArrayType): boolean {
781804

782805
/**
783806
* @param ast
807+
* @param usage Context in which the type is used
784808
* @returns
785809
*/
786-
function genType(ast: Type): string {
810+
function genType(ast: Type, usage: string = "unknown"): string {
787811
let text;
788812
switch (ast.kind) {
789813
case "TypeReference":
@@ -799,49 +823,49 @@ function genType(ast: Type): string {
799823
if (ast.nullable) {
800824
text += `|null`;
801825
}
802-
if (ast.isStandardEnum) {
826+
if (ast.isStandardEnum && usage !== "returnValue") {
803827
text = `(${text} | keyof typeof ${ast.typeName})`; // TODO parentheses not always required
804828
}
805829
return text;
806830
case "ArrayType":
807831
if (hasSimpleElementType(ast)) {
808-
return `${genType(ast.elementType)}[]`;
832+
return `${genType(ast.elementType, usage)}[]`;
809833
}
810-
return `Array<${genType(ast.elementType)}>`;
834+
return `Array<${genType(ast.elementType, usage)}>`;
811835
case "LiteralType":
812836
return String(ast.literal);
813837
case "TypeLiteral":
814838
return `{${NL}${_.map(ast.members, (prop) => {
815839
let ptext = "";
816840
ptext += JSDOC(prop) + NL;
817841
ptext +=
818-
`${prop.name}${prop.optional ? "?" : ""}: ${genType(prop.type)},` +
842+
`${prop.name}${prop.optional ? "?" : ""}: ${genType(prop.type, usage)},` +
819843
NL;
820844
return ptext;
821845
}).join("")}}`;
822846
case "UnionType":
823847
const unionTypes: string[] = _.map(ast.types, (variantType) => {
824848
if (variantType.kind === "FunctionType") {
825-
return `(${genType(variantType)})`;
849+
return `(${genType(variantType, usage)})`;
826850
}
827-
return genType(variantType);
851+
return genType(variantType, usage);
828852
});
829853
return unionTypes.join(" | ");
830854
case "IntersectionType":
831855
const intersectionTypes: string[] = _.map(ast.types, (variantType) => {
832856
if (variantType.kind === "FunctionType") {
833-
return `(${genType(variantType)})`;
857+
return `(${genType(variantType, usage)})`;
834858
}
835-
return genType(variantType);
859+
return genType(variantType, usage);
836860
});
837861
return intersectionTypes.join(" & ");
838862
case "FunctionType":
839863
text = "";
840864
if (!_.isEmpty(ast.typeParameters)) {
841865
text += `<${_.map(ast.typeParameters, (param) => param.name).join(", ")}>`; // TODO defaults, constraints, expressions
842866
}
843-
text += `(${_.map(ast.parameters, (param) => `${param.name}: ${genType(param.type)}`).join(", ")})`;
844-
text += ` => ${ast.type ? genType(ast.type) : "void"}`;
867+
text += `(${_.map(ast.parameters, (param) => `${param.name}: ${genType(param.type, "parameter")}`).join(", ")})`;
868+
text += ` => ${ast.type ? genType(ast.type, "returnValue") : "void"}`;
845869
return text;
846870
case "NativeTSTypeExpression":
847871
// 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
/**

0 commit comments

Comments
 (0)