Skip to content

Commit

Permalink
feat: support ControllerExtensions extended with .override(...)
Browse files Browse the repository at this point in the history
Recognize (and remove) a dummy function ControllerExtension.use(...)
that only exists on TypeScript side and takes care of converting the
class resulting from SomeExtension.override(...) to the instance type
that needs to be presented to TypeScript for the member property.
  • Loading branch information
akudev committed Jun 26, 2024
1 parent 9c9165b commit ba68506
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 14 deletions.
22 changes: 22 additions & 0 deletions packages/plugin/__test__/__snapshots__/test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -1665,6 +1665,28 @@ sap.ui.define(["sap/Class"], function (SAPClass) {
});"
`;
exports[`typescript ts-class-controller-extension-extended.ts 1`] = `
"sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/mvc/ControllerExtension", "sap/fe/core/controllerextensions/Routing"], function (Controller, ControllerExtension, Routing) {
"use strict";
const MyExtendedController = Controller.extend("test.controller.MyExtendedController", {
routing: Routing.override({})
});
return MyExtendedController;
});"
`;
exports[`typescript ts-class-controller-extension-extended-explicit-type.ts 1`] = `
"sap.ui.define(["sap/ui/core/mvc/Controller", "sap/ui/core/mvc/ControllerExtension", "sap/fe/core/controllerextensions/Routing"], function (Controller, ControllerExtension, Routing) {
"use strict";
const MyExtendedController = Controller.extend("test.controller.MyExtendedController", {
routing: Routing.override({})
});
return MyExtendedController;
});"
`;
exports[`typescript ts-class-controller-extension-usage.ts 1`] = `
"sap.ui.define(["sap/ui/core/mvc/Controller", "sap/fe/core/controllerextensions/Routing", "sap/fe/core/controllerextensions/OtherExtension", "sap/fe/core/controllerextensions/ThirdExtension", "sap/fe/core/controllerextensions/DoubleExportExtension", "sap/fe/core/controllerextensions/ManyExtensions"], function (Controller, Routing, sap_fe_core_controllerextensions_OtherExtension, ThirdExtension, sap_fe_core_controllerextensions_DoubleExportExtension, extensionCollection) {
"use strict";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Controller from "sap/ui/core/mvc/Controller";
import ControllerExtension from "sap/ui/core/mvc/ControllerExtension";
import Routing from "sap/fe/core/controllerextensions/Routing";

/**
* @namespace test.controller
*/
export default class MyExtendedController extends Controller {
routing: Routing = ControllerExtension.use(Routing.override({}));
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import Controller from "sap/ui/core/mvc/Controller";
import ControllerExtension from "sap/ui/core/mvc/ControllerExtension";
import Routing from "sap/fe/core/controllerextensions/Routing";

/**
* @namespace test.controller
*/
export default class MyExtendedController extends Controller {
routing = ControllerExtension.use(Routing.override({}));
}
59 changes: 45 additions & 14 deletions packages/plugin/src/classes/helpers/classes.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,15 +77,15 @@ export function convertClassToUI5Extend(
const membersToAssign = [];
const params = isConstructor
? member.params?.map((param) => {
// handling of parameter properties for constructors (TypeScript):
// https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties
// -> extracting the real parameters and store the members to assign
if (param.type === "TSParameterProperty") {
membersToAssign.push(param.parameter);
return param.parameter;
}
return param;
})
// handling of parameter properties for constructors (TypeScript):
// https://www.typescriptlang.org/docs/handbook/2/classes.html#parameter-properties
// -> extracting the real parameters and store the members to assign
if (param.type === "TSParameterProperty") {
membersToAssign.push(param.parameter);
return param.parameter;
}
return param;
})
: member.params;
const func = t.functionExpression(
member.key,
Expand Down Expand Up @@ -212,6 +212,37 @@ export function convertClassToUI5Extend(

if (!member.value) continue; // remove all other un-initialized static class props (typescript)

// Now handle the case where an extended controller extension - not just e.g. "Routing", but
// "Routing.override({...})" - is assigned to the class property. To make this work in TypeScript code
// (Routing.override({...}) returns a class, but the "routing" member property needs to be typed as an instance),
// we require the following notation, using a dummy function ControllerExtension.use() that does actually
// not exist at runtime, but does the required class-to-instance conversion from TypeScript perspective:
// routing: ControllerExtension.use(Routing.override({...}))
// In this case, the code in the "if" clause above was not executed, regardless of the presence of the
// @transformControllerExtension marker, because it is not a type assignment. In the resulting code, the
// "ControllerExtension.use(...)" part should be removed and the content of the brackets should be assigned
// directly to the member property.
if (t.isCallExpression(member.value)) {
const callee = member.value.callee;
if (
t.isMemberExpression(callee) &&
t.isIdentifier(callee.object) &&
t.isIdentifier(callee.property) &&
callee.property.name === "use" // we are looking for "ControllerExtension.use(...)"
) {
const importDeclaration = getImportDeclaration(
memberPath.hub.file.opts.filename,
callee.object.name // usually, but not necessarily always: "ControllerExtension"...
);
// ...hence we rather look at the imported module name to be sure
if (importDeclaration.source.value === "sap/ui/core/mvc/ControllerExtension") {
member.value = member.value.arguments[0];
extendProps.unshift(buildObjectProperty(member)); // add it to the properties of the extend() config object
continue; // prevent the member from also being added to the constructor
}
}
}

// Special handling for TypeScript limitation where metadata, renderer and overrides must be properties.
if (["metadata", "renderer", "overrides"].includes(memberName)) {
if (opts.overridesToOverride && member.key.name === "overrides") {
Expand Down Expand Up @@ -280,12 +311,12 @@ export function convertClassToUI5Extend(
const bindToId = t.identifier(bindToMethodName);
const bindMethodDeclaration = bindToConstructor
? th.buildInheritingConstructor({
SUPER: t.identifier(superClassName),
})
SUPER: t.identifier(superClassName),
})
: th.buildInheritingFunction({
NAME: bindToId,
SUPER: t.identifier(superClassName),
});
NAME: bindToId,
SUPER: t.identifier(superClassName),
});
bindMethod = ast.convertFunctionDeclarationToExpression(
bindMethodDeclaration
);
Expand Down

0 comments on commit ba68506

Please sign in to comment.