Skip to content

Commit

Permalink
feat(dts-generator): change how enums are referenced in the generated…
Browse files Browse the repository at this point in the history
… types
  • Loading branch information
codeworrior committed Dec 11, 2024
1 parent b05fbcf commit 83f868b
Show file tree
Hide file tree
Showing 7 changed files with 107 additions and 21 deletions.
3 changes: 3 additions & 0 deletions packages/dts-generator/api-report/dts-generator.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,9 @@ export interface Directives {
badInterfaces: string[];
badMethods: string[];
badSymbols: string[];
deprecatedEnumAliases: {
[fqn: string]: string;
};
forwardDeclarations: {
[libraryName: string]: ConcreteSymbol[];
};
Expand Down
11 changes: 11 additions & 0 deletions packages/dts-generator/src/generate-from-objects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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;
};
}

/**
Expand Down Expand Up @@ -124,6 +134,7 @@ const defaultOptions: GenerateFromObjectsConfig = {
forwardDeclarations: {},
fqnToIgnore: {},
overlays: {},
deprecatedEnumAliases: {},
},
generateGlobals: false,
};
Expand Down
1 change: 1 addition & 0 deletions packages/dts-generator/src/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ async function loadDirectives(directivesPaths: string[]) {
forwardDeclarations: {},
fqnToIgnore: {},
overlays: {},
deprecatedEnumAliases: {},
};

function mergeDirectives(loadedDirectives: Directives) {
Expand Down
80 changes: 61 additions & 19 deletions packages/dts-generator/src/phases/dts-code-gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand All @@ -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;
Expand All @@ -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;
}

Expand Down Expand Up @@ -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;
Expand All @@ -694,6 +696,10 @@ function genEnum(
exportAsDefault: false,
},
) {
if (ast.deprecatedAliasFor) {
return genDeprecatedAliasForEnum(ast, options);
}

let text = "";
text += JSDOC(ast) + NL;
text +=
Expand All @@ -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
Expand All @@ -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;
}
Expand Down Expand Up @@ -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":
Expand All @@ -799,49 +841,49 @@ 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":
return `{${NL}${_.map(ast.members, (prop) => {
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":
text = "";
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"
Expand Down
13 changes: 13 additions & 0 deletions packages/dts-generator/src/phases/json-fixer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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);
Expand Down
16 changes: 14 additions & 2 deletions packages/dts-generator/src/phases/json-to-ast.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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 &&
Expand All @@ -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),
),
Expand Down
4 changes: 4 additions & 0 deletions packages/dts-generator/src/types/ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 83f868b

Please sign in to comment.