on 2023 Dec 10 9:33 PM
Hey there,
TL;DR: How to "type" an ENUM/Object which is defined in an AMD module properly, in a full JS based UI5 project so that you get good/better code completion when using it.
as the title says I've been looking into ways to "properly" type an ENUM using JSDoc in a full JS based UI5 application (so a bit older UI5 version, no TypeScript, hence no ES6 modules but AMD syntax i.e. define/require). I have found some ways which could do it, but was wondering if there is a more "native" approach where TypeScript might be able to infer the type based on the module definition or its typed import maybe? Alternatively looking for some confirmation if these approaches make sense.
As I understand it, there is nothing like the newer "as const" declaration of TypeScript possible in this scenario (scenario being: full js app + using JSDoc, no `d.ts` files) but I'd still like to have the actual values of the object typed or shown when using code completion, if possible.
An example of such an ENUM obj. (a raw one) would look like this:
sap.ui.define([], () => {
return Object.freeze({
SOME_KEY1: "first-value",
SOME_KEY2: "second-value",
});
});
Now I've tried adding the regular `@ENUM` tags of JSDoc together with the respective `@property` tags. But that didn't seem to matter, at least I didn't get any "global" typing or type hinting based on that. If I got it right, most likely because they are scoped to the actual module code (so JSDoc does have similar scoping to JS, would make sense). I also tried the `@exports` tag or `@module` but as I understood those are really only for documentation purposes (sure ... just like JSDoc itself :P).
I did also try to not directly add the tags to the return as that doesn't seem to make much sense but instead first assigning the frozen object, or the object itself, previous to freezing it, to a constant and typing the variable itself, but here too, it didn't seem to matter much. Like so:
sap.ui.define([], () => {
/**
* Some ENUM.
* @public
* @enum {string}
*/
return Object.freeze({
/**
* @public
*/
SOME_KEY1: "first-value"
/**
* @public
*/
SOME_KEY2: "second-value",
});
});
// or (you could also separate object from freeze .. doesn't matter) - also ..
// `@public` in this scenario is completely optional, `@readonly` would make sense maybe
sap.ui.define([], () => {
/**
* Some ENUM.
* @public
* @enum {string}
*/
const frozenObj = Object.freeze({
/**
* @public
*/
SOME_KEY1: "first-value"
/**
* @public
*/
SOME_KEY2: "second-value",
});
return frozenObj;
});
What does work is defining the entire thing as it's own type "globally" like so:
// type definition above actual AMD module declaration gets recognized globally in the project
/**
* My ENUM description
*
* @typedef {Object} SomeTypeNameForEnumHere
* @property {string} SOME_KEY1
* @property {string} SOME_KEY2
*/
sap.ui.define(...
This has the downside of only giving code completion for the keys of the object/enum but it is something at least.
What works better but has the downside of being more repetetive, though arguably less effort, is declaring the entire object literal as its own type like so:
/**
* @typedef {{
* SOME_KEY1: "first-value",
* SOME_KEY2: "second-value",
* }} CompleteDeclarationType
* @readonly
*/
This gives me complete code completion even with the actual value in the description. Like so:
Usage on the consuming side:
The above created types are then used on the consuming side with `@param` tags on the AMD factory function to type the imports accordingly.
I've been down an extreme rabbit hole of TypeScript + JSDoc (Googles Closure Compiler etc. ...) interactions and how to type a JS based application and I think aside of this ENUM thing, we're doing quite alright at the moment. I've been made aware of the demo repository of Andreas Kunz where he shows some of the interactions quite well (sadly a bit late, but I'm happy I got to a similar result either way).
I do not want to add a `.d.ts` file and suddenly introduce _actual_ TS into the code base. I also don't have the luxury right now to convert everything to TS or even start doing so.
Open for suggestions, hints or anything of that matter. Are there different, better ways?
Regards,
Marco
Info - The project uses a rather simple `jsconfig.json`
{
"compilerOptions": {
"types": ["@sapui5/types", "@types/qunit", "@types/sinon"],
"alwaysStrict": true,
},
"include": ["webapp/**/*"]
}
Request clarification before answering.
inline JSDoc declarations usually work right before the relevant parenthesis (e.g. https://www.typescriptlang.org/docs/handbook/jsdoc-supported-types.html#casts)
What worked for me in a quick test:
sap.ui.define([], () => {
return Object.freeze(
/**
* this is your ENUM right before the parenthesis
*
* @property {string} SOME_KEY1 bla
* @property {string} SOME_KEY2 fasel
*/ {
SOME_KEY1: "first-value",
SOME_KEY2: "second-value"
}
)
})
hth, v.
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.
Hi Volker, thanks for taking the time and having a look! Did you do anything on the consuming side (diff. class/controller with sap.ui.require ...) for getting code completion? Seems like the type-cast itself sadly doesn't do much for me. I get that it is hard to give a general answer here as there is a lot of things this might depend on so no worries, I appreciate the input!
User | Count |
---|---|
53 | |
8 | |
6 | |
6 | |
5 | |
5 | |
4 | |
4 | |
4 | |
4 |
You must be a registered user to add a comment. If you've already registered, sign in. Otherwise, register and sign in.