From 1df5ae52491a3cfa50d96f46531071810b5215f7 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 15 Aug 2022 14:28:15 +0300 Subject: [PATCH 01/27] ArrowFunctions + ArrowFunctionExpression.expression --- lib/processors/jsdoc/lib/ui5/plugin.js | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 5cbe12878..d8244a5e5 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -249,13 +249,19 @@ function analyzeModuleDefinition(node) { currentModule.dependencies = convertValue(args[arg], "string[]"); arg++; } - if ( arg < args.length && args[arg].type === Syntax.FunctionExpression ) { + if ( arg < args.length && + [Syntax.FunctionExpression, Syntax.ArrowFunctionExpression].includes(args[arg].type)) { + currentModule.factory = args[arg]; arg++; } if ( currentModule.dependencies && currentModule.factory ) { for ( let i = 0; i < currentModule.dependencies.length && i < currentModule.factory.params.length; i++ ) { - const name = currentModule.factory.params[i].name; + const name = + (currentModule.factory.params[i].type === Syntax.ObjectPattern) + ? currentModule.factory.params[i].properties[0].value.name // ObjectPattern means destructuring of the parameter + : currentModule.factory.params[i].name; // simple Identifier + const module = resolveModuleName(currentModule.module, currentModule.dependencies[i]); debug(` import ${name} from '${module}'`); currentModule.localNames[name] = { @@ -265,7 +271,7 @@ function analyzeModuleDefinition(node) { } } if ( currentModule.factory ) { - collectShortcuts(currentModule.factory.body); + collectShortcuts(currentModule.factory); } } @@ -276,7 +282,8 @@ function analyzeModuleDefinition(node) { * * @param {ASTNode} body AST node of a function body that shall be searched for shortcuts */ -function collectShortcuts(body) { +function collectShortcuts(factory) { + const body = factory.body; function checkAssignment(name, valueNode) { if ( valueNode.type === Syntax.Literal ) { @@ -345,6 +352,11 @@ function collectShortcuts(body) { } } }); + } else if ( factory.type === Syntax.ArrowFunctionExpression && factory.expression === true) { + if ( body && isExtendCall(body) ) { + debug(` found default export class definition: return .extend('${body.arguments[0].value}', ...)`); + currentModule.defaultExportClass = body.arguments[0].value; + } } if ( currentModule.defaultExport && currentModule.localNames[currentModule.defaultExport] ) { @@ -1140,7 +1152,7 @@ function collectDataTypeInfo(extendCall, classDoclet) { if ( def && def.isValid - && def.isValid.value.type === Syntax.FunctionExpression + && [Syntax.FunctionExpression, Syntax.ArrowFunctionExpression].includes(def.isValid.value.type) && def.isValid.value.params.length === 1 && def.isValid.value.params[0].type === Syntax.Identifier && def.isValid.value.body.body.length === 1 ) { From 7f42f008a3ba0d30cfdd097615950bb9e375e388 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 15 Aug 2022 15:06:46 +0300 Subject: [PATCH 02/27] Type validation + Arrow fuction expression --- lib/processors/jsdoc/lib/ui5/plugin.js | 31 +++++++++++++++++--------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index d8244a5e5..17048e80c 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -1150,25 +1150,34 @@ function collectDataTypeInfo(extendCall, classDoclet) { base = "any"; } + const isArrowFuncExpression = + def && + def.isValid && + def.isValid.value.type === Syntax.ArrowFunctionExpression && + def.isValid.value.expression === true; + if ( def && def.isValid && [Syntax.FunctionExpression, Syntax.ArrowFunctionExpression].includes(def.isValid.value.type) && def.isValid.value.params.length === 1 && def.isValid.value.params[0].type === Syntax.Identifier - && def.isValid.value.body.body.length === 1 ) { + && (isArrowFuncExpression || def.isValid.value.body.body.length === 1) ) { + const varname = def.isValid.value.params[0].name; - const stmt = def.isValid.value.body.body[0]; - if ( stmt.type === Syntax.ReturnStatement && stmt.argument ) { - if ( stmt.argument.type === Syntax.CallExpression - && stmt.argument.callee.type === Syntax.MemberExpression - && stmt.argument.callee.object.type === Syntax.Literal - && stmt.argument.callee.object.regex - && stmt.argument.callee.property.type === Syntax.Identifier - && stmt.argument.callee.property.name === 'test' ) { - pattern = stmt.argument.callee.object.regex.pattern; + const stmt = isArrowFuncExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; + + if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowFuncExpression ) { + const stmtArgument = isArrowFuncExpression ? stmt : stmt.argument; + if ( stmtArgument.type === Syntax.CallExpression + && stmtArgument.callee.type === Syntax.MemberExpression + && stmtArgument.callee.object.type === Syntax.Literal + && stmtArgument.callee.object.regex + && stmtArgument.callee.property.type === Syntax.Identifier + && stmtArgument.callee.property.name === 'test' ) { + pattern = stmtArgument.callee.object.regex.pattern; // console.log(pattern); } else { - range = determineValueRange(stmt.argument, varname, false); + range = determineValueRange(stmtArgument, varname, false); } } else if ( stmt.type === Syntax.IfStatement && stmt.consequent.type === Syntax.BlockStatement From 6fb8ec0cb69477dae3ef312e6a1a3d9b647f92a7 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 16 Aug 2022 11:59:48 +0300 Subject: [PATCH 03/27] ArrowFunctionExpression cover all cases --- lib/processors/jsdoc/lib/ui5/plugin.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 17048e80c..3362bfe8b 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -1000,7 +1000,7 @@ function collectClassInfo(extendCall, classDoclet) { return oClassInfo; } -function collectDesigntimeInfo(dtNode) { +function collectDesigntimeInfo(dtNodeArgument) { function each(node, defaultKey, callback) { const map = node && createPropertyMap(node.value); @@ -1022,7 +1022,7 @@ function collectDesigntimeInfo(dtNode) { let oDesigntimeInfo; - const map = createPropertyMap(dtNode.argument); + const map = createPropertyMap(dtNodeArgument); if (map.annotations) { @@ -2618,10 +2618,12 @@ exports.astNodeVisitor = { } - if (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression && /\.designtime\.js$/.test(currentSourceName) ) { + const isArrowExpression = node.type === Syntax.ArrowFunctionExpression && node.expression === true && node.body.type === Syntax.ObjectExpression; + if ((isArrowExpression || (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression)) && /\.designtime\.js$/.test(currentSourceName) ) { + const nodeArgument = isArrowExpression ? node.body : node.argument; // assume this node to return designtime metadata. Collect it and remember it by its module name - const oDesigntimeInfo = collectDesigntimeInfo(node); + const oDesigntimeInfo = collectDesigntimeInfo(nodeArgument); if ( oDesigntimeInfo ) { designtimeInfos[currentModule.module] = oDesigntimeInfo; info(`collected designtime info ${currentModule.module}`); @@ -2651,14 +2653,20 @@ exports.astNodeVisitor = { } }); - } else if ( node.type === Syntax.ReturnStatement && isExtendCall(node.argument) ) { + } else if ( (node.type === Syntax.ReturnStatement || (node.type === Syntax.ArrowFunctionExpression && node.expression === true)) + && isExtendCall(node.argument || node.body) ) { + + const nodeArgument = + node.type === Syntax.ArrowFunctionExpression && node.expression === true + ? node.body + : node.argument; // return Something.extend(...) - const className = node.argument.arguments[0].value; - const comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(node.argument, className); + const className = nodeArgument.arguments[0].value; + const comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(nodeArgument, className); // console.log(`ast node with comment ${comment}`); - processExtendCall(node.argument, comment, true); + processExtendCall(nodeArgument, comment, true); } else if ( node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.AssignmentExpression ) { if ( isCreateDataTypeCall(node.expression.right) ) { From eea010b451e6ecd0e82d80b9aac6e383dbd4f4e7 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 16 Aug 2022 12:10:56 +0300 Subject: [PATCH 04/27] Refactor ArrowFunctionExpression --- lib/processors/jsdoc/lib/ui5/plugin.js | 33 +++++++++++++------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 3362bfe8b..25884186a 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -352,7 +352,7 @@ function collectShortcuts(factory) { } } }); - } else if ( factory.type === Syntax.ArrowFunctionExpression && factory.expression === true) { + } else if ( isArrowFuncExpression(factory) ) { if ( body && isExtendCall(body) ) { debug(` found default export class definition: return .extend('${body.arguments[0].value}', ...)`); currentModule.defaultExportClass = body.arguments[0].value; @@ -460,6 +460,14 @@ function isExtendCall(node) { } +function isArrowFuncExpression(node) { + return ( + node && + node.type === Syntax.ArrowFunctionExpression && + node.expression === true + ); +} + function isSapUiDefineCall(node) { return ( @@ -1150,24 +1158,20 @@ function collectDataTypeInfo(extendCall, classDoclet) { base = "any"; } - const isArrowFuncExpression = - def && - def.isValid && - def.isValid.value.type === Syntax.ArrowFunctionExpression && - def.isValid.value.expression === true; + const isArrowExpression = isArrowFuncExpression(def && def.isValid && def.isValid.value); if ( def && def.isValid && [Syntax.FunctionExpression, Syntax.ArrowFunctionExpression].includes(def.isValid.value.type) && def.isValid.value.params.length === 1 && def.isValid.value.params[0].type === Syntax.Identifier - && (isArrowFuncExpression || def.isValid.value.body.body.length === 1) ) { + && (isArrowExpression || def.isValid.value.body.body.length === 1) ) { const varname = def.isValid.value.params[0].name; - const stmt = isArrowFuncExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; + const stmt = isArrowExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; - if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowFuncExpression ) { - const stmtArgument = isArrowFuncExpression ? stmt : stmt.argument; + if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowExpression ) { + const stmtArgument = isArrowExpression ? stmt : stmt.argument; if ( stmtArgument.type === Syntax.CallExpression && stmtArgument.callee.type === Syntax.MemberExpression && stmtArgument.callee.object.type === Syntax.Literal @@ -2618,7 +2622,7 @@ exports.astNodeVisitor = { } - const isArrowExpression = node.type === Syntax.ArrowFunctionExpression && node.expression === true && node.body.type === Syntax.ObjectExpression; + const isArrowExpression = isArrowFuncExpression(node) && node.body.type === Syntax.ObjectExpression; if ((isArrowExpression || (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression)) && /\.designtime\.js$/.test(currentSourceName) ) { const nodeArgument = isArrowExpression ? node.body : node.argument; @@ -2653,13 +2657,10 @@ exports.astNodeVisitor = { } }); - } else if ( (node.type === Syntax.ReturnStatement || (node.type === Syntax.ArrowFunctionExpression && node.expression === true)) + } else if ( (node.type === Syntax.ReturnStatement || isArrowFuncExpression(node)) && isExtendCall(node.argument || node.body) ) { - const nodeArgument = - node.type === Syntax.ArrowFunctionExpression && node.expression === true - ? node.body - : node.argument; + const nodeArgument = isArrowFuncExpression(node) ? node.body : node.argument; // return Something.extend(...) From 9193b4e319492f8db22ff65bc1d1187b400cd3dc Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Wed, 17 Aug 2022 13:00:49 +0300 Subject: [PATCH 05/27] Avoid "computed" Identifier to be used at it might lead to hidden bugs --- lib/processors/jsdoc/lib/ui5/plugin.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 25884186a..eec0509e2 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -381,12 +381,13 @@ function guessSingularName(sPluralName) { } function getPropertyKey(prop) { - if ( prop.key.type === Syntax.Identifier ) { + if ( prop.type === Syntax.SpreadElement ) { + // TODO: Support interpreting SpreadElements + return; + } else if ( prop.key.type === Syntax.Identifier && prop.computed !== true ) { return prop.key.name; } else if ( prop.key.type === Syntax.Literal ) { return String(prop.key.value); - } else { - return prop.key.toSource(); } } From 17c9686ee8d68374154ac1f23d259f83e99d23e8 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Thu, 18 Aug 2022 11:51:04 +0300 Subject: [PATCH 06/27] Optional Chaining for MemberExpression & CallExpression --- lib/processors/jsdoc/lib/ui5/plugin.js | 50 ++++++++++++++------------ 1 file changed, 28 insertions(+), 22 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index eec0509e2..db7a9af0a 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -292,7 +292,7 @@ function collectShortcuts(factory) { raw: valueNode.raw }; debug("compile time constant found ", name, valueNode.value); - } else if ( valueNode.type === Syntax.MemberExpression ) { + } else if ( isMemberExpression(valueNode) ) { const _import = getLeftmostName(valueNode); const local = _import && currentModule.localNames[_import]; const objectName = getObjectName(valueNode); @@ -445,12 +445,24 @@ function createPropertyMap(node, defaultKey) { return result; } + +function isMemberExpression(node) { + return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); +} + +function isCaleeMemberExpression(node) { + return ( + node && + [Syntax.CallExpression, Syntax.OptionalCallExpression].includes(node.type) && + isMemberExpression(node.callee) + ); +} + function isExtendCall(node) { return ( node - && node.type === Syntax.CallExpression - && node.callee.type === Syntax.MemberExpression + && isCaleeMemberExpression(node) && node.callee.property.type === Syntax.Identifier && node.callee.property.name === 'extend' && node.arguments.length >= 2 @@ -473,9 +485,8 @@ function isSapUiDefineCall(node) { return ( node - && node.type === Syntax.CallExpression - && node.callee.type === Syntax.MemberExpression - && node.callee.object.type === Syntax.MemberExpression + && isCaleeMemberExpression(node) + && isMemberExpression(node.callee.object) && node.callee.object.object.type === Syntax.Identifier && node.callee.object.object.name === 'sap' && node.callee.object.property.type === Syntax.Identifier @@ -489,8 +500,7 @@ function isSapUiDefineCall(node) { function isCreateDataTypeCall(node) { return ( node - && node.type === Syntax.CallExpression - && node.callee.type === Syntax.MemberExpression + && isCaleeMemberExpression(node) && getResolvedObjectName(node.callee.object) === "sap.ui.base.DataType" && node.callee.property.type === Syntax.Identifier && node.callee.property.name === 'createType' @@ -500,9 +510,8 @@ function isCreateDataTypeCall(node) { function isRequireSyncCall(node) { return ( node - && node.type === Syntax.CallExpression - && node.callee.type === Syntax.MemberExpression - && node.callee.object.type === Syntax.MemberExpression + && isCaleeMemberExpression(node) + && isMemberExpression(node.callee.object) && node.callee.object.object.type === Syntax.Identifier && node.callee.object.object.name === 'sap' && node.callee.object.property.type === Syntax.Identifier @@ -515,9 +524,8 @@ function isRequireSyncCall(node) { function isProbingRequireCall(node) { return ( node - && node.type === Syntax.CallExpression - && node.callee.type === Syntax.MemberExpression - && node.callee.object.type === Syntax.MemberExpression + && isCaleeMemberExpression(node) + && isMemberExpression(node.callee.object) && node.callee.object.object.type === Syntax.Identifier && node.callee.object.object.name === 'sap' && node.callee.object.property.type === Syntax.Identifier @@ -542,7 +550,7 @@ function isCompileTimeConstant(node) { } function getObjectName(node) { - if ( node.type === Syntax.MemberExpression && !node.computed && node.property.type === Syntax.Identifier ) { + if ( isMemberExpression(node) && !node.computed && node.property.type === Syntax.Identifier ) { const prefix = getObjectName(node.object); return prefix ? prefix + "." + node.property.name : null; } else if ( node.type === Syntax.Identifier ) { @@ -557,7 +565,7 @@ function getObjectName(node) { * returns the leftmost identifier a */ function getLeftmostName(node) { - while ( node.type === Syntax.MemberExpression ) { + while ( isMemberExpression(node) ) { node = node.object; } if ( node.type === Syntax.Identifier ) { @@ -625,7 +633,7 @@ function convertValueWithRaw(node, type, propertyName) { raw: node.operator + node.argument.raw }; - } else if ( node.type === Syntax.MemberExpression && type ) { + } else if ( isMemberExpression(node) && type ) { // enum value (a.b.c) value = getResolvedObjectName(node); @@ -757,7 +765,7 @@ function collectClassInfo(extendCall, classDoclet) { if ( classDoclet && classDoclet.augments && classDoclet.augments.length === 1 ) { baseType = classDoclet.augments[0]; } - if ( extendCall.callee.type === Syntax.MemberExpression ) { + if ( isMemberExpression(extendCall.callee) ) { const baseCandidate = getResolvedObjectName(extendCall.callee.object); if ( baseCandidate && baseType == null ) { baseType = baseCandidate; @@ -1143,8 +1151,7 @@ function collectDataTypeInfo(extendCall, classDoclet) { if ( i < args.length ) { if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { base = args[i++].value; - } else if ( args[i].type === Syntax.CallExpression - && args[i].callee.type === Syntax.MemberExpression + } else if ( isCaleeMemberExpression(args[i]) && getResolvedObjectName(args[i].callee.object) === "sap.ui.base.DataType" && args[i].callee.property.type === Syntax.Identifier && args[i].callee.property.name === 'getType' @@ -1173,8 +1180,7 @@ function collectDataTypeInfo(extendCall, classDoclet) { if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowExpression ) { const stmtArgument = isArrowExpression ? stmt : stmt.argument; - if ( stmtArgument.type === Syntax.CallExpression - && stmtArgument.callee.type === Syntax.MemberExpression + if ( isCaleeMemberExpression(stmtArgument) && stmtArgument.callee.object.type === Syntax.Literal && stmtArgument.callee.object.regex && stmtArgument.callee.property.type === Syntax.Identifier From 1499f33d2b19f2ffccc6c8f03473150600f3aa5f Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Thu, 18 Aug 2022 16:09:37 +0300 Subject: [PATCH 07/27] Literals for Syntax.OptionalMemberExpression/OptionalCallExpression There's no such thing as Syntax.OptionalMemberExpression/OptionalCallExpression, but JSDoc parser provides such "types" in case of testing. So, we need to stringify those in order to catch those cases --- lib/processors/jsdoc/lib/ui5/plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index db7a9af0a..b8fd85408 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -447,13 +447,13 @@ function createPropertyMap(node, defaultKey) { function isMemberExpression(node) { - return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); + return node && [Syntax.MemberExpression, "OptionalMemberExpression"].includes(node.type); } function isCaleeMemberExpression(node) { return ( node && - [Syntax.CallExpression, Syntax.OptionalCallExpression].includes(node.type) && + [Syntax.CallExpression, "OptionalCallExpression"].includes(node.type) && isMemberExpression(node.callee) ); } From 5bb7a2ee0475076b546f02cdc5fd49c327d7535a Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Fri, 19 Aug 2022 10:03:11 +0300 Subject: [PATCH 08/27] Enhance LogicalExpression to support "||" and "??" oprators --- lib/processors/jsdoc/lib/ui5/plugin.js | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index b8fd85408..9ed63197f 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -1124,12 +1124,19 @@ function determineValueRangeBorder(range, expression, varname, inverse) { function determineValueRange(expression, varname, inverse) { const range = {}; if ( expression.type === Syntax.LogicalExpression - && expression.operator === '&&' && expression.left.type === Syntax.BinaryExpression - && expression.right.type === Syntax.BinaryExpression - && determineValueRangeBorder(range, expression.left, varname, inverse) - && determineValueRangeBorder(range, expression.right, varname, inverse) ) { - return range; + && expression.right.type === Syntax.BinaryExpression ) { + + if ( expression.operator === "&&" + && determineValueRangeBorder(range, expression.left, varname, inverse) + && determineValueRangeBorder(range, expression.right, varname, inverse) ) { + return range; + } else if ( ["||", "??"].includes(expression.operator) + && ( determineValueRangeBorder(range, expression.left, varname, inverse) + || determineValueRangeBorder(range, expression.right, varname, inverse) )) { + return range; + } + } else if ( expression.type === Syntax.BinaryExpression && determineValueRangeBorder(range, expression, varname, inverse) ) { return range; From de733613a901dc2dceeaaa346925ba6b919dd8e4 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Fri, 19 Aug 2022 11:54:38 +0300 Subject: [PATCH 09/27] Cover ChainExpression --- lib/processors/jsdoc/lib/ui5/plugin.js | 40 ++++++++++++++++++++------ 1 file changed, 31 insertions(+), 9 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 9ed63197f..f933308b9 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -286,6 +286,8 @@ function collectShortcuts(factory) { const body = factory.body; function checkAssignment(name, valueNode) { + valueNode = resolvePotentialChainExpression(valueNode); + if ( valueNode.type === Syntax.Literal ) { currentModule.localNames[name] = { value: valueNode.value, @@ -445,6 +447,14 @@ function createPropertyMap(node, defaultKey) { return result; } +function resolvePotentialChainExpression(node) { + let curNode = node; + if ( node && node.type === "ChainExpression") { + curNode = node.expression; + } + + return curNode; +} function isMemberExpression(node) { return node && [Syntax.MemberExpression, "OptionalMemberExpression"].includes(node.type); @@ -459,6 +469,7 @@ function isCaleeMemberExpression(node) { } function isExtendCall(node) { + node = resolvePotentialChainExpression(node); return ( node @@ -482,6 +493,7 @@ function isArrowFuncExpression(node) { } function isSapUiDefineCall(node) { + node = resolvePotentialChainExpression(node); return ( node @@ -498,6 +510,8 @@ function isSapUiDefineCall(node) { } function isCreateDataTypeCall(node) { + node = resolvePotentialChainExpression(node); + return ( node && isCaleeMemberExpression(node) @@ -508,6 +522,8 @@ function isCreateDataTypeCall(node) { } function isRequireSyncCall(node) { + node = resolvePotentialChainExpression(node); + return ( node && isCaleeMemberExpression(node) @@ -522,6 +538,8 @@ function isRequireSyncCall(node) { } function isProbingRequireCall(node) { + node = resolvePotentialChainExpression(node); + return ( node && isCaleeMemberExpression(node) @@ -1156,15 +1174,17 @@ function collectDataTypeInfo(extendCall, classDoclet) { def = createPropertyMap(args[i++]); } if ( i < args.length ) { + const node = resolvePotentialChainExpression(args[i]); + if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { base = args[i++].value; - } else if ( isCaleeMemberExpression(args[i]) - && getResolvedObjectName(args[i].callee.object) === "sap.ui.base.DataType" - && args[i].callee.property.type === Syntax.Identifier - && args[i].callee.property.name === 'getType' - && args[i].arguments.length === 1 - && args[i].arguments[0].type === Syntax.Literal - && typeof args[i].arguments[0].value === 'string' ) { + } else if ( isCaleeMemberExpression(node) + && getResolvedObjectName(node.callee.object) === "sap.ui.base.DataType" + && node.callee.property.type === Syntax.Identifier + && node.callee.property.name === 'getType' + && node.arguments.length === 1 + && node.arguments[0].type === Syntax.Literal + && typeof node.arguments[0].value === 'string' ) { base = args[i++].arguments[0].value; } else { future(`could not identify base type of data type '${name}'`); @@ -1186,7 +1206,9 @@ function collectDataTypeInfo(extendCall, classDoclet) { const stmt = isArrowExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowExpression ) { - const stmtArgument = isArrowExpression ? stmt : stmt.argument; + const stmtArgument = resolvePotentialChainExpression( + isArrowExpression ? stmt : stmt.argument + ); if ( isCaleeMemberExpression(stmtArgument) && stmtArgument.callee.object.type === Syntax.Literal && stmtArgument.callee.object.regex @@ -2624,7 +2646,7 @@ exports.astNodeVisitor = { if ( node.type === Syntax.ExpressionStatement ) { if ( isSapUiDefineCall(node.expression) ) { - analyzeModuleDefinition(node.expression); + analyzeModuleDefinition( resolvePotentialChainExpression(node.expression) ); /* } else if ( isJQuerySapDeclareCall(node.expression) && node.expression.arguments.length > 0 From 3256d92dea2a9f3cd4354da828bf787b6dbfead3 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Fri, 19 Aug 2022 14:38:01 +0300 Subject: [PATCH 10/27] Wrapper Expressions generic approach Some expressions might be wrappers, like ChainExpression, AwaitExpression. If we do not need to make something very specific with them, we need to resolve the underlying structures --- lib/processors/jsdoc/lib/ui5/plugin.js | 39 ++++++++++++++++---------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index f933308b9..98a4fe8df 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -286,7 +286,7 @@ function collectShortcuts(factory) { const body = factory.body; function checkAssignment(name, valueNode) { - valueNode = resolvePotentialChainExpression(valueNode); + valueNode = resolvePotentialWrapperExpression(valueNode); if ( valueNode.type === Syntax.Literal ) { currentModule.localNames[name] = { @@ -447,13 +447,22 @@ function createPropertyMap(node, defaultKey) { return result; } -function resolvePotentialChainExpression(node) { - let curNode = node; - if ( node && node.type === "ChainExpression") { - curNode = node.expression; - } +/** + * Resolves potential wrapper expressions like: ChainExpression, AwaitExpression, etc. + * @param {Node} node + * @returns + */ +function resolvePotentialWrapperExpression(node) { + switch (node && node.type) { + case "ChainExpression": + return node.expression; - return curNode; + case Syntax.AwaitExpression: + return node.argument; + + default: + return node; + } } function isMemberExpression(node) { @@ -469,7 +478,7 @@ function isCaleeMemberExpression(node) { } function isExtendCall(node) { - node = resolvePotentialChainExpression(node); + node = resolvePotentialWrapperExpression(node); return ( node @@ -493,7 +502,7 @@ function isArrowFuncExpression(node) { } function isSapUiDefineCall(node) { - node = resolvePotentialChainExpression(node); + node = resolvePotentialWrapperExpression(node); return ( node @@ -510,7 +519,7 @@ function isSapUiDefineCall(node) { } function isCreateDataTypeCall(node) { - node = resolvePotentialChainExpression(node); + node = resolvePotentialWrapperExpression(node); return ( node @@ -522,7 +531,7 @@ function isCreateDataTypeCall(node) { } function isRequireSyncCall(node) { - node = resolvePotentialChainExpression(node); + node = resolvePotentialWrapperExpression(node); return ( node @@ -538,7 +547,7 @@ function isRequireSyncCall(node) { } function isProbingRequireCall(node) { - node = resolvePotentialChainExpression(node); + node = resolvePotentialWrapperExpression(node); return ( node @@ -1174,7 +1183,7 @@ function collectDataTypeInfo(extendCall, classDoclet) { def = createPropertyMap(args[i++]); } if ( i < args.length ) { - const node = resolvePotentialChainExpression(args[i]); + const node = resolvePotentialWrapperExpression(args[i]); if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { base = args[i++].value; @@ -1206,7 +1215,7 @@ function collectDataTypeInfo(extendCall, classDoclet) { const stmt = isArrowExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowExpression ) { - const stmtArgument = resolvePotentialChainExpression( + const stmtArgument = resolvePotentialWrapperExpression( isArrowExpression ? stmt : stmt.argument ); if ( isCaleeMemberExpression(stmtArgument) @@ -2646,7 +2655,7 @@ exports.astNodeVisitor = { if ( node.type === Syntax.ExpressionStatement ) { if ( isSapUiDefineCall(node.expression) ) { - analyzeModuleDefinition( resolvePotentialChainExpression(node.expression) ); + analyzeModuleDefinition( resolvePotentialWrapperExpression(node.expression) ); /* } else if ( isJQuerySapDeclareCall(node.expression) && node.expression.arguments.length > 0 From a9bb985ff2a1869cc43000e4e2d81acfe5202718 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 22 Aug 2022 14:29:23 +0300 Subject: [PATCH 11/27] Cover possible retruning statements in JS --- lib/processors/jsdoc/lib/ui5/plugin.js | 48 +++++++++++++++++++------- 1 file changed, 36 insertions(+), 12 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 98a4fe8df..08605bc01 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -345,12 +345,14 @@ function collectShortcuts(factory) { && stmt.expression.type === Syntax.AssignmentExpression && stmt.expression.left.type === Syntax.Identifier ) { checkAssignment(stmt.expression.left.name, stmt.expression.right); - } else if ( stmt.type === Syntax.ReturnStatement ) { - if ( stmt.argument && stmt.argument.type === Syntax.Identifier ) { - currentModule.defaultExport = stmt.argument.name; - } else if ( stmt.argument && isExtendCall(stmt.argument) ) { - debug(` found default export class definition: return .extend('${stmt.argument.arguments[0].value}', ...)`); - currentModule.defaultExportClass = stmt.argument.arguments[0].value; + } else if ( isReturningNode(stmt) ) { + const stmtArgument = resolvePotentialWrapperExpression(stmt).argument; + + if ( stmtArgument && stmtArgument.type === Syntax.Identifier ) { + currentModule.defaultExport = stmtArgument.name; + } else if ( stmtArgument && isExtendCall(stmtArgument) ) { + debug(` found default export class definition: return .extend('${stmtArgument.arguments[0].value}', ...)`); + currentModule.defaultExportClass = stmtArgument.arguments[0].value; } } }); @@ -460,6 +462,12 @@ function resolvePotentialWrapperExpression(node) { case Syntax.AwaitExpression: return node.argument; + case Syntax.ExpressionStatement: + if ( node.expression.type === Syntax.YieldExpression ) { + return node.expression.argument.type === Syntax.UpdateExpression + ? node.expression.argument + : node.expression; + } default: return node; } @@ -501,6 +509,18 @@ function isArrowFuncExpression(node) { ); } +/** + * Checks whether the node is of a "returning" type + * + * @param {Node} node + * @returns {Boolean} + */ +function isReturningNode(node) { + return (node && node.type === Syntax.ReturnStatement) + || (node && node.type === Syntax.ExpressionStatement && node.expression.type === Syntax.YieldExpression) + || isArrowFuncExpression(node); +} + function isSapUiDefineCall(node) { node = resolvePotentialWrapperExpression(node); @@ -1214,7 +1234,7 @@ function collectDataTypeInfo(extendCall, classDoclet) { const varname = def.isValid.value.params[0].name; const stmt = isArrowExpression ? def.isValid.value.body : def.isValid.value.body.body[0]; - if ( (stmt.type === Syntax.ReturnStatement && stmt.argument) || isArrowExpression ) { + if ( isReturningNode(stmt) || isArrowExpression ) { const stmtArgument = resolvePotentialWrapperExpression( isArrowExpression ? stmt : stmt.argument ); @@ -2668,8 +2688,10 @@ exports.astNodeVisitor = { } const isArrowExpression = isArrowFuncExpression(node) && node.body.type === Syntax.ObjectExpression; - if ((isArrowExpression || (node.type === Syntax.ReturnStatement && node.argument && node.argument.type === Syntax.ObjectExpression)) && /\.designtime\.js$/.test(currentSourceName) ) { - const nodeArgument = isArrowExpression ? node.body : node.argument; + const nodeArgument = isArrowExpression + ? node.body + : resolvePotentialWrapperExpression(node).argument; + if (isArrowExpression || (isReturningNode(node) && nodeArgument && nodeArgument.type === Syntax.ObjectExpression) && /\.designtime\.js$/.test(currentSourceName) ) { // assume this node to return designtime metadata. Collect it and remember it by its module name const oDesigntimeInfo = collectDesigntimeInfo(nodeArgument); @@ -2702,10 +2724,12 @@ exports.astNodeVisitor = { } }); - } else if ( (node.type === Syntax.ReturnStatement || isArrowFuncExpression(node)) - && isExtendCall(node.argument || node.body) ) { + } else if ( isReturningNode(node) + && isExtendCall(resolvePotentialWrapperExpression(node).argument || node.body) ) { - const nodeArgument = isArrowFuncExpression(node) ? node.body : node.argument; + const nodeArgument = isArrowFuncExpression(node) + ? node.body + : resolvePotentialWrapperExpression(node).argument; // return Something.extend(...) From 26448a168b49dfe3bd1162aea50f7639080bac17 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 22 Aug 2022 15:19:43 +0300 Subject: [PATCH 12/27] Support alternate definitions of modules --- lib/processors/jsdoc/lib/ui5/plugin.js | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 08605bc01..1721b76ed 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -386,7 +386,6 @@ function guessSingularName(sPluralName) { function getPropertyKey(prop) { if ( prop.type === Syntax.SpreadElement ) { - // TODO: Support interpreting SpreadElements return; } else if ( prop.key.type === Syntax.Identifier && prop.computed !== true ) { return prop.key.name; @@ -2673,18 +2672,27 @@ exports.astNodeVisitor = { } } - if ( node.type === Syntax.ExpressionStatement ) { - if ( isSapUiDefineCall(node.expression) ) { - analyzeModuleDefinition( resolvePotentialWrapperExpression(node.expression) ); - /* + if ( [Syntax.ExpressionStatement, Syntax.LogicalExpression].includes(node.type) ) { + let nodeToAnalyze; + if (isSapUiDefineCall(node.expression)) { + nodeToAnalyze = node.expression; + + /* } else if ( isJQuerySapDeclareCall(node.expression) && node.expression.arguments.length > 0 && node.expression.arguments[0].type === Syntax.Literal && typeof node.expression.arguments[0].value === "string" ) { warning(`module has explicit module name ${node.expression.arguments[0].value}`); */ + } else if (isSapUiDefineCall(node.left)) { + nodeToAnalyze = node.left; + } else if (isSapUiDefineCall(node.right)) { + nodeToAnalyze = node.right; } + nodeToAnalyze && analyzeModuleDefinition( + resolvePotentialWrapperExpression(nodeToAnalyze) + ); } const isArrowExpression = isArrowFuncExpression(node) && node.body.type === Syntax.ObjectExpression; From 3f2ad7add3c647d8c67972a9ec2b3acfe66a9c4c Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 22 Aug 2022 15:41:35 +0300 Subject: [PATCH 13/27] ES6 Class definition export --- lib/processors/jsdoc/lib/ui5/plugin.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 1721b76ed..2ff100c7d 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -350,6 +350,9 @@ function collectShortcuts(factory) { if ( stmtArgument && stmtArgument.type === Syntax.Identifier ) { currentModule.defaultExport = stmtArgument.name; + } else if ( stmtArgument && stmtArgument.type === Syntax.ClassExpression && stmtArgument.id && stmtArgument.id.type === Syntax.Identifier ) { + debug(` found default export class definition: return class '${stmtArgument.id.name}'`); + currentModule.defaultExportClass = stmtArgument.id.name; } else if ( stmtArgument && isExtendCall(stmtArgument) ) { debug(` found default export class definition: return .extend('${stmtArgument.arguments[0].value}', ...)`); currentModule.defaultExportClass = stmtArgument.arguments[0].value; From a2ab57669cbb3e5638e62a2b2ceb061f28a80654 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 22 Aug 2022 15:53:27 +0300 Subject: [PATCH 14/27] Fix eslint checks --- lib/processors/jsdoc/lib/ui5/plugin.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 2ff100c7d..44c3dfd05 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -454,7 +454,7 @@ function createPropertyMap(node, defaultKey) { /** * Resolves potential wrapper expressions like: ChainExpression, AwaitExpression, etc. * @param {Node} node - * @returns + * @returns {Node} the resolved node */ function resolvePotentialWrapperExpression(node) { switch (node && node.type) { From a6f23b59e2a6cd533926f2f8f8d6a492c7209f9e Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 23 Aug 2022 14:11:41 +0300 Subject: [PATCH 15/27] Add tests --- .../resources/library/j/dependency-es6-1.js | 19 ++++++++++++++ .../resources/library/j/dependency-es6-2.js | 21 +++++++++++++++ .../resources/library/j/dependency-es6-3.js | 12 +++++++++ .../dest/resources/library/j/some.js | 26 +++++++++++-------- .../main/src/library/j/dependency-es6-1.js | 19 ++++++++++++++ .../main/src/library/j/dependency-es6-2.js | 21 +++++++++++++++ .../main/src/library/j/dependency-es6-3.js | 12 +++++++++ .../library.j/main/src/library/j/some.js | 26 +++++++++++-------- test/lib/builder/builder.js | 2 +- 9 files changed, 135 insertions(+), 23 deletions(-) create mode 100644 test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js create mode 100644 test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js create mode 100644 test/expected/build/library.j/dest/resources/library/j/dependency-es6-3.js create mode 100644 test/fixtures/library.j/main/src/library/j/dependency-es6-1.js create mode 100644 test/fixtures/library.j/main/src/library/j/dependency-es6-2.js create mode 100644 test/fixtures/library.j/main/src/library/j/dependency-es6-3.js diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js new file mode 100644 index 000000000..afe5a63bd --- /dev/null +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js @@ -0,0 +1,19 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - LogicalExpression + * - ArrowFunction + * - ChainExpression + * - ClassDeclaration + */ +window.someRandomModule || + (sap?.ui).define(["Bar"], (Bar) => { + return class Foo extends Bar { + make() { + sap.ui.require("conditional/module1"); + } + }; + }); diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js new file mode 100644 index 000000000..697201178 --- /dev/null +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js @@ -0,0 +1,21 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - ArrowFunctionExpression + */ +sap.ui.define(["/.a"], (a) => + a.extend("aaa", { + metadata: { + properties: { + MyProp: { + type: "Boolean", + group: "Misc", + defaultValue: true, + }, + }, + }, + }) +); diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-3.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-3.js new file mode 100644 index 000000000..3f059af2c --- /dev/null +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-3.js @@ -0,0 +1,12 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - Generators + * - YeldExpression + */ +sap.ui.define([], function* someGenerator(genVar) { + yield genVar++; +}); diff --git a/test/expected/build/library.j/dest/resources/library/j/some.js b/test/expected/build/library.j/dest/resources/library/j/some.js index 8fd81bb68..4d7f5afce 100644 --- a/test/expected/build/library.j/dest/resources/library/j/some.js +++ b/test/expected/build/library.j/dest/resources/library/j/some.js @@ -2,15 +2,19 @@ * ${copyright} */ -sap.ui.define([], - function() { - "use strict"; +sap.ui.define( + ["./dependency-es6-1"], + ["./dependency-es6-2"], + ["./dependency-es6-3"], + function (dep1, dep2, dep3) { + "use strict"; - /** - * @alias library.j - * @namespace - * @public - */ - var SomeFunction = function() {}; - -}, /* bExport= */ true); + /** + * @alias library.j + * @namespace + * @public + */ + var SomeFunction = function () {}; + }, + /* bExport= */ true +); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js new file mode 100644 index 000000000..afe5a63bd --- /dev/null +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js @@ -0,0 +1,19 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - LogicalExpression + * - ArrowFunction + * - ChainExpression + * - ClassDeclaration + */ +window.someRandomModule || + (sap?.ui).define(["Bar"], (Bar) => { + return class Foo extends Bar { + make() { + sap.ui.require("conditional/module1"); + } + }; + }); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js new file mode 100644 index 000000000..697201178 --- /dev/null +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js @@ -0,0 +1,21 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - ArrowFunctionExpression + */ +sap.ui.define(["/.a"], (a) => + a.extend("aaa", { + metadata: { + properties: { + MyProp: { + type: "Boolean", + group: "Misc", + defaultValue: true, + }, + }, + }, + }) +); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-3.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-3.js new file mode 100644 index 000000000..3f059af2c --- /dev/null +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-3.js @@ -0,0 +1,12 @@ +/*! + * ${copyright} + */ + +/** + * Covers: + * - Generators + * - YeldExpression + */ +sap.ui.define([], function* someGenerator(genVar) { + yield genVar++; +}); diff --git a/test/fixtures/library.j/main/src/library/j/some.js b/test/fixtures/library.j/main/src/library/j/some.js index 8fd81bb68..4d7f5afce 100644 --- a/test/fixtures/library.j/main/src/library/j/some.js +++ b/test/fixtures/library.j/main/src/library/j/some.js @@ -2,15 +2,19 @@ * ${copyright} */ -sap.ui.define([], - function() { - "use strict"; +sap.ui.define( + ["./dependency-es6-1"], + ["./dependency-es6-2"], + ["./dependency-es6-3"], + function (dep1, dep2, dep3) { + "use strict"; - /** - * @alias library.j - * @namespace - * @public - */ - var SomeFunction = function() {}; - -}, /* bExport= */ true); + /** + * @alias library.j + * @namespace + * @public + */ + var SomeFunction = function () {}; + }, + /* bExport= */ true +); diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index 1eac0d246..aadc42992 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -874,7 +874,7 @@ test.serial("Build library.i with manifest info taken from .library and library. t.pass(); }); -test.serial("Build library.j with JSDoc build only", async (t) => { +test.serial.only("Build library.j with JSDoc build only", async (t) => { const destPath = path.join("test", "tmp", "build", "library.j", "dest"); const expectedPath = path.join("test", "expected", "build", "library.j", "dest"); From ca7fdd857379bf5b2909419d85cf27a973a6f4c7 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 23 Aug 2022 14:12:14 +0300 Subject: [PATCH 16/27] Enable all tests --- test/lib/builder/builder.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/lib/builder/builder.js b/test/lib/builder/builder.js index aadc42992..1eac0d246 100644 --- a/test/lib/builder/builder.js +++ b/test/lib/builder/builder.js @@ -874,7 +874,7 @@ test.serial("Build library.i with manifest info taken from .library and library. t.pass(); }); -test.serial.only("Build library.j with JSDoc build only", async (t) => { +test.serial("Build library.j with JSDoc build only", async (t) => { const destPath = path.join("test", "tmp", "build", "library.j", "dest"); const expectedPath = path.join("test", "expected", "build", "library.j", "dest"); From 9a419d0c243fe6b33dc7ef2857278cead81a56f8 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 23 Aug 2022 14:28:48 +0300 Subject: [PATCH 17/27] Extend Syntax types "manually" --- lib/processors/jsdoc/lib/ui5/plugin.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 44c3dfd05..fd97af4b3 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -53,7 +53,14 @@ */ /* imports */ -const Syntax = require('jsdoc/src/syntax').Syntax; +const Syntax = { + // Those are currently not in the syntax.js file. + ChainExpression: "ChainExpression", + OptionalMemberExpression: "OptionalMemberExpression", + OptionalCallExpression: "OptionalCallExpression", + + ...require("jsdoc/src/syntax").Syntax, +}; const Doclet = require('jsdoc/doclet').Doclet; const fs = require('jsdoc/fs'); const path = require('jsdoc/path'); @@ -458,7 +465,7 @@ function createPropertyMap(node, defaultKey) { */ function resolvePotentialWrapperExpression(node) { switch (node && node.type) { - case "ChainExpression": + case Syntax.ChainExpression: return node.expression; case Syntax.AwaitExpression: @@ -476,13 +483,13 @@ function resolvePotentialWrapperExpression(node) { } function isMemberExpression(node) { - return node && [Syntax.MemberExpression, "OptionalMemberExpression"].includes(node.type); + return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); } function isCaleeMemberExpression(node) { return ( node && - [Syntax.CallExpression, "OptionalCallExpression"].includes(node.type) && + [Syntax.CallExpression, Syntax.OptionalCallExpression].includes(node.type) && isMemberExpression(node.callee) ); } From 167e8227fa6b10e1730154168ec2252e9e046cf6 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 23 Aug 2022 14:35:25 +0300 Subject: [PATCH 18/27] Fix types --- .../library.j/dest/resources/library/j/dependency-es6-2.js | 4 ++-- .../fixtures/library.j/main/src/library/j/dependency-es6-2.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js index 697201178..12be1da3b 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js @@ -11,9 +11,9 @@ sap.ui.define(["/.a"], (a) => metadata: { properties: { MyProp: { - type: "Boolean", + type: "boolean", group: "Misc", - defaultValue: true, + defaultValue: false, }, }, }, diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js index 697201178..12be1da3b 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js @@ -11,9 +11,9 @@ sap.ui.define(["/.a"], (a) => metadata: { properties: { MyProp: { - type: "Boolean", + type: "boolean", group: "Misc", - defaultValue: true, + defaultValue: false, }, }, }, From 8adad20924558131fa826df4dd20884fc81a1183 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 23 Aug 2022 15:58:36 +0300 Subject: [PATCH 19/27] Bugfix chaining: There might be multilevel chains --- lib/processors/jsdoc/lib/ui5/plugin.js | 91 ++++++++++++++++---------- 1 file changed, 58 insertions(+), 33 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index fd97af4b3..20fa96127 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -370,6 +370,9 @@ function collectShortcuts(factory) { if ( body && isExtendCall(body) ) { debug(` found default export class definition: return .extend('${body.arguments[0].value}', ...)`); currentModule.defaultExportClass = body.arguments[0].value; + } else if ( body && body.type === Syntax.ClassExpression && body.id && body.id.type === Syntax.Identifier ) { + debug(` found default export class definition: return class '${body.id.name}'`); + currentModule.defaultExportClass = body.id.name; } } @@ -482,6 +485,29 @@ function resolvePotentialWrapperExpression(node) { } } +/** + * Strips the ChainExpression wrapper if such + * + * @param {Node} rootNode + * @param {String} path + * @returns {Node} + */ +function stripChainWrappers(rootNode, path) { + const strip = (node) => + node && node.type === Syntax.ChainExpression ? node.expression : node; + + let curNode = strip(rootNode); + let chunks = path && path.split("."); + let name; + + while (chunks && chunks.length) { + name = chunks.shift(); + curNode = curNode && strip(curNode[name]); + } + + return curNode; +} + function isMemberExpression(node) { return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); } @@ -495,13 +521,13 @@ function isCaleeMemberExpression(node) { } function isExtendCall(node) { - node = resolvePotentialWrapperExpression(node); + node = stripChainWrappers(node); return ( node && isCaleeMemberExpression(node) - && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === 'extend' + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'extend' && node.arguments.length >= 2 && node.arguments[0].type === Syntax.Literal && typeof node.arguments[0].value === "string" @@ -531,63 +557,62 @@ function isReturningNode(node) { } function isSapUiDefineCall(node) { - node = resolvePotentialWrapperExpression(node); return ( - node - && isCaleeMemberExpression(node) - && isMemberExpression(node.callee.object) - && node.callee.object.object.type === Syntax.Identifier - && node.callee.object.object.name === 'sap' - && node.callee.object.property.type === Syntax.Identifier - && node.callee.object.property.name === 'ui' - && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === 'define' + stripChainWrappers(node) + && isCaleeMemberExpression(stripChainWrappers(node)) + && isMemberExpression(stripChainWrappers(node, "callee.object")) + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'define' + && stripChainWrappers(node, "callee.object.object").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.object").name === 'sap' + && stripChainWrappers(node, "callee.object.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.property").name === 'ui' ); } function isCreateDataTypeCall(node) { - node = resolvePotentialWrapperExpression(node); + node = stripChainWrappers(node); return ( node && isCaleeMemberExpression(node) - && getResolvedObjectName(node.callee.object) === "sap.ui.base.DataType" - && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === 'createType' + && getResolvedObjectName(stripChainWrappers(node, "callee.object")) === "sap.ui.base.DataType" + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'createType' ); } function isRequireSyncCall(node) { - node = resolvePotentialWrapperExpression(node); + node = stripChainWrappers(node); return ( node && isCaleeMemberExpression(node) - && isMemberExpression(node.callee.object) - && node.callee.object.object.type === Syntax.Identifier - && node.callee.object.object.name === 'sap' - && node.callee.object.property.type === Syntax.Identifier - && node.callee.object.property.name === 'ui' - && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === 'requireSync' + && isMemberExpression( stripChainWrappers(node, "callee.object") ) + && stripChainWrappers(node, "callee.object.object").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.object").name === 'sap' + && stripChainWrappers(node, "callee.object.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.property").name === 'ui' + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'requireSync' ); } function isProbingRequireCall(node) { - node = resolvePotentialWrapperExpression(node); + node = stripChainWrappers(node); return ( node && isCaleeMemberExpression(node) - && isMemberExpression(node.callee.object) - && node.callee.object.object.type === Syntax.Identifier - && node.callee.object.object.name === 'sap' - && node.callee.object.property.type === Syntax.Identifier - && node.callee.object.property.name === 'ui' - && node.callee.property.type === Syntax.Identifier - && node.callee.property.name === 'require' + && isMemberExpression( stripChainWrappers(node, "callee.object") ) + && stripChainWrappers(node, "callee.object.object").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.object").name === 'sap' + && stripChainWrappers(node, "callee.object.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.object.property").name === 'ui' + && stripChainWrappers(node, "callee.property").type === Syntax.Identifier + && stripChainWrappers(node, "callee.property").name === 'require' && node.arguments.length === 1 && node.arguments[0].type === Syntax.Literal && typeof node.arguments[0].value === 'string' // TODO generalize to statically analyzable constants From 8c47c3420a792f4ef92be7b5a8bd6acc42ad0cee Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Tue, 23 Aug 2022 16:09:24 +0300 Subject: [PATCH 20/27] Simplify tests --- .../resources/library/j/dependency-es6-1.js | 16 ++++++------- .../resources/library/j/dependency-es6-2.js | 23 ++++++++++--------- .../main/src/library/j/dependency-es6-1.js | 16 ++++++------- .../main/src/library/j/dependency-es6-2.js | 23 ++++++++++--------- 4 files changed, 38 insertions(+), 40 deletions(-) diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js index afe5a63bd..3b377ff88 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js @@ -4,16 +4,14 @@ /** * Covers: - * - LogicalExpression * - ArrowFunction * - ChainExpression * - ClassDeclaration */ -window.someRandomModule || - (sap?.ui).define(["Bar"], (Bar) => { - return class Foo extends Bar { - make() { - sap.ui.require("conditional/module1"); - } - }; - }); +(sap?.ui).define(["Bar"], (Bar) => { + return class Foo extends Bar { + make() { + sap.ui.require("conditional/module1"); + } + }; +}); diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js index 12be1da3b..62b2be8e8 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js @@ -6,16 +6,17 @@ * Covers: * - ArrowFunctionExpression */ -sap.ui.define(["/.a"], (a) => - a.extend("aaa", { - metadata: { - properties: { - MyProp: { - type: "boolean", - group: "Misc", - defaultValue: false, +window.someRandomModule || + sap.ui.define(["/.a"], (a) => + a.extend("aaa", { + metadata: { + properties: { + MyProp: { + type: "boolean", + group: "Misc", + defaultValue: false, + }, }, }, - }, - }) -); + }) + ); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js index afe5a63bd..3b377ff88 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js @@ -4,16 +4,14 @@ /** * Covers: - * - LogicalExpression * - ArrowFunction * - ChainExpression * - ClassDeclaration */ -window.someRandomModule || - (sap?.ui).define(["Bar"], (Bar) => { - return class Foo extends Bar { - make() { - sap.ui.require("conditional/module1"); - } - }; - }); +(sap?.ui).define(["Bar"], (Bar) => { + return class Foo extends Bar { + make() { + sap.ui.require("conditional/module1"); + } + }; +}); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js index 12be1da3b..62b2be8e8 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js @@ -6,16 +6,17 @@ * Covers: * - ArrowFunctionExpression */ -sap.ui.define(["/.a"], (a) => - a.extend("aaa", { - metadata: { - properties: { - MyProp: { - type: "boolean", - group: "Misc", - defaultValue: false, +window.someRandomModule || + sap.ui.define(["/.a"], (a) => + a.extend("aaa", { + metadata: { + properties: { + MyProp: { + type: "boolean", + group: "Misc", + defaultValue: false, + }, }, }, - }, - }) -); + }) + ); From baa720500bed08f2c8caf4e314264c3c969376ee Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Wed, 24 Aug 2022 13:27:51 +0300 Subject: [PATCH 21/27] Support of TemplateLiterals - only withput expression - treated like normal strings --- lib/processors/jsdoc/lib/ui5/plugin.js | 13 +++++++++++++ .../dest/resources/library/j/dependency-es6-1.js | 2 +- .../main/src/library/j/dependency-es6-1.js | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index 20fa96127..d8100cf13 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -508,6 +508,14 @@ function stripChainWrappers(rootNode, path) { return curNode; } +function isTemplateLiteralWithoutExpression(node) { + return ( + node?.type === Syntax.TemplateLiteral && + node?.expressions?.length === 0 && + node?.quasis?.length === 1 + ); +} + function isMemberExpression(node) { return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); } @@ -784,6 +792,11 @@ function convertValueWithRaw(node, type, propertyName) { }; } + } else if ( isTemplateLiteralWithoutExpression(node) ) { + return { + value: node?.quasis?.[0]?.value?.cooked, + raw: node?.quasis?.[0]?.value?.raw, + }; } value = "...see text or source"; diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js index 3b377ff88..9d8c9cd68 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js @@ -8,7 +8,7 @@ * - ChainExpression * - ClassDeclaration */ -(sap?.ui).define(["Bar"], (Bar) => { +(sap?.ui).define([`Bar`], (Bar) => { return class Foo extends Bar { make() { sap.ui.require("conditional/module1"); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js index 3b377ff88..9d8c9cd68 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js @@ -8,7 +8,7 @@ * - ChainExpression * - ClassDeclaration */ -(sap?.ui).define(["Bar"], (Bar) => { +(sap?.ui).define([`Bar`], (Bar) => { return class Foo extends Bar { make() { sap.ui.require("conditional/module1"); From 1577586afc467291b24adec73d9e015ad9c79f4d Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Thu, 25 Aug 2022 11:04:26 +0300 Subject: [PATCH 22/27] Handle TemplateLiterals --- lib/processors/jsdoc/lib/ui5/plugin.js | 71 ++++++++++--------- .../resources/library/j/dependency-es6-2.js | 4 +- .../main/src/library/j/dependency-es6-2.js | 4 +- 3 files changed, 43 insertions(+), 36 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index d8100cf13..a6ef9aa11 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -241,9 +241,8 @@ function resolveModuleName(base, name) { function analyzeModuleDefinition(node) { const args = node.arguments; let arg = 0; - if ( arg < args.length - && args[arg].type === Syntax.Literal && typeof args[arg].value === 'string' ) { - currentModule.name = args[arg].value; + if ( arg < args.length && isStringLiteral(args[arg]) ) { + currentModule.name = convertValue(args[arg]); warning(`module explicitly defined a module name '${currentModule.name}'`); const resourceModuleName = getModuleName(currentModule.resource); if (currentModule.name !== resourceModuleName) { @@ -313,11 +312,9 @@ function collectShortcuts(factory) { debug(` found local shortcut: ${name} ${currentModule.localNames[name]}`); } } else if ( isRequireSyncCall(valueNode) || isProbingRequireCall(valueNode) ) { - if ( valueNode.arguments[0] - && valueNode.arguments[0].type === Syntax.Literal - && typeof valueNode.arguments[0].value === 'string' ) { + if ( valueNode.arguments[0] && isStringLiteral(valueNode.arguments[0]) ) { currentModule.localNames[name] = { - module: valueNode.arguments[0].value + module: convertValue(valueNode.arguments[0]) // no (or empty) path }; debug(` found local import: ${name} = ${valueNode.callee.property.name}('${valueNode.arguments[0].value}')`); @@ -331,8 +328,8 @@ function collectShortcuts(factory) { } } - if ( body.type === Syntax.BlockStatement ) { - body.body.forEach(function ( stmt ) { + if ( body.type === Syntax.BlockStatement || isArrowFuncExpression(factory) ) { + const itemsResolver = function ( stmt ) { // console.log(stmt); if ( stmt.type === Syntax.FunctionDeclaration ) { if ( stmt.id && stmt.id.type === Syntax.Identifier && stmt.loc && stmt.loc.start ) { @@ -353,7 +350,9 @@ function collectShortcuts(factory) { && stmt.expression.left.type === Syntax.Identifier ) { checkAssignment(stmt.expression.left.name, stmt.expression.right); } else if ( isReturningNode(stmt) ) { - const stmtArgument = resolvePotentialWrapperExpression(stmt).argument; + const stmtArgument = isArrowFuncExpression(stmt) + ? stmt.body + : resolvePotentialWrapperExpression(stmt).argument; if ( stmtArgument && stmtArgument.type === Syntax.Identifier ) { currentModule.defaultExport = stmtArgument.name; @@ -362,17 +361,15 @@ function collectShortcuts(factory) { currentModule.defaultExportClass = stmtArgument.id.name; } else if ( stmtArgument && isExtendCall(stmtArgument) ) { debug(` found default export class definition: return .extend('${stmtArgument.arguments[0].value}', ...)`); - currentModule.defaultExportClass = stmtArgument.arguments[0].value; + currentModule.defaultExportClass = convertValue(stmtArgument.arguments[0]); } } - }); - } else if ( isArrowFuncExpression(factory) ) { - if ( body && isExtendCall(body) ) { - debug(` found default export class definition: return .extend('${body.arguments[0].value}', ...)`); - currentModule.defaultExportClass = body.arguments[0].value; - } else if ( body && body.type === Syntax.ClassExpression && body.id && body.id.type === Syntax.Identifier ) { - debug(` found default export class definition: return class '${body.id.name}'`); - currentModule.defaultExportClass = body.id.name; + }; + + if (isArrowFuncExpression(factory)) { + itemsResolver(factory); + } else { + body.body.forEach(itemsResolver); } } @@ -516,6 +513,19 @@ function isTemplateLiteralWithoutExpression(node) { ); } +/** + * Checks whether a node is Literal or TemplateLiteral without an expression + * + * @param {Node} node + * @returns {String} + */ +function isStringLiteral(node) { + return ( + (node && node.type === Syntax.Literal && typeof node.value === "string") + || isTemplateLiteralWithoutExpression(node) + ); +} + function isMemberExpression(node) { return node && [Syntax.MemberExpression, Syntax.OptionalMemberExpression].includes(node.type); } @@ -537,8 +547,7 @@ function isExtendCall(node) { && stripChainWrappers(node, "callee.property").type === Syntax.Identifier && stripChainWrappers(node, "callee.property").name === 'extend' && node.arguments.length >= 2 - && node.arguments[0].type === Syntax.Literal - && typeof node.arguments[0].value === "string" + && isStringLiteral(node.arguments[0]) && node.arguments[1].type === Syntax.ObjectExpression ); @@ -622,8 +631,7 @@ function isProbingRequireCall(node) { && stripChainWrappers(node, "callee.property").type === Syntax.Identifier && stripChainWrappers(node, "callee.property").name === 'require' && node.arguments.length === 1 - && node.arguments[0].type === Syntax.Literal - && typeof node.arguments[0].value === 'string' // TODO generalize to statically analyzable constants + && isStringLiteral(node.arguments[0]) ); } @@ -820,10 +828,10 @@ function convertStringArray(node) { } const result = []; for ( let i = 0; i < node.elements.length; i++ ) { - if ( node.elements[i].type !== Syntax.Literal || typeof node.elements[i].value !== 'string' ) { + if ( !isStringLiteral(node.elements[i]) ) { throw new Error("not a string literal"); } - result.push(node.elements[i].value); + result.push( convertValue(node.elements[i]) ); } // console.log(result); return result; @@ -1243,8 +1251,8 @@ function collectDataTypeInfo(extendCall, classDoclet) { let i = 0, name, def, base, pattern, range; - if ( i < args.length && args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { - name = args[i++].value; + if ( i < args.length && isStringLiteral(args[i]) ) { + name = convertValue(args[i++]); } if ( i < args.length && args[i].type === Syntax.ObjectExpression ) { def = createPropertyMap(args[i++]); @@ -1252,16 +1260,15 @@ function collectDataTypeInfo(extendCall, classDoclet) { if ( i < args.length ) { const node = resolvePotentialWrapperExpression(args[i]); - if ( args[i].type === Syntax.Literal && typeof args[i].value === 'string' ) { - base = args[i++].value; + if ( isStringLiteral(args[i]) ) { + base = convertValue(args[i++]); } else if ( isCaleeMemberExpression(node) && getResolvedObjectName(node.callee.object) === "sap.ui.base.DataType" && node.callee.property.type === Syntax.Identifier && node.callee.property.name === 'getType' && node.arguments.length === 1 - && node.arguments[0].type === Syntax.Literal - && typeof node.arguments[0].value === 'string' ) { - base = args[i++].arguments[0].value; + && isStringLiteral(node.arguments[0]) ) { + base = convertValue(args[i++].arguments[0]); } else { future(`could not identify base type of data type '${name}'`); } diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js index 62b2be8e8..91a934476 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js @@ -8,12 +8,12 @@ */ window.someRandomModule || sap.ui.define(["/.a"], (a) => - a.extend("aaa", { + a.extend(`aaa`, { metadata: { properties: { MyProp: { type: "boolean", - group: "Misc", + group: `Misc`, defaultValue: false, }, }, diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js index 62b2be8e8..91a934476 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js @@ -8,12 +8,12 @@ */ window.someRandomModule || sap.ui.define(["/.a"], (a) => - a.extend("aaa", { + a.extend(`aaa`, { metadata: { properties: { MyProp: { type: "boolean", - group: "Misc", + group: `Misc`, defaultValue: false, }, }, From 7ce0b03f6ecd4dd62ea73f011470ef008e297f50 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Thu, 25 Aug 2022 16:32:59 +0300 Subject: [PATCH 23/27] Bugfixes: Parse TemplateLiterals as strings --- lib/processors/jsdoc/lib/ui5/plugin.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/processors/jsdoc/lib/ui5/plugin.js b/lib/processors/jsdoc/lib/ui5/plugin.js index a6ef9aa11..951d89e6b 100644 --- a/lib/processors/jsdoc/lib/ui5/plugin.js +++ b/lib/processors/jsdoc/lib/ui5/plugin.js @@ -877,7 +877,7 @@ function collectClassInfo(extendCall, classDoclet) { } const oClassInfo = { - name : extendCall.arguments[0].value, + name : convertValue(extendCall.arguments[0]), baseType : baseType, interfaces : [], doc : classDoclet && classDoclet.description, @@ -2796,7 +2796,7 @@ exports.astNodeVisitor = { // return Something.extend(...) - const className = nodeArgument.arguments[0].value; + const className = convertValue(nodeArgument.arguments[0]); const comment = getLeadingCommentNode(node, className) || getLeadingCommentNode(nodeArgument, className); // console.log(`ast node with comment ${comment}`); processExtendCall(nodeArgument, comment, true); From 1efa26874ea3109a3056949a5f028c9eff145011 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Thu, 25 Aug 2022 16:38:51 +0300 Subject: [PATCH 24/27] Add JSDoc to the samples --- .../resources/library/j/dependency-es6-1.js | 19 ++++++++++++-- .../resources/library/j/dependency-es6-2.js | 26 +++++++++++++++++-- .../main/src/library/j/dependency-es6-1.js | 19 ++++++++++++-- .../main/src/library/j/dependency-es6-2.js | 26 +++++++++++++++++-- 4 files changed, 82 insertions(+), 8 deletions(-) diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js index 9d8c9cd68..09ae9ea87 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js @@ -9,9 +9,24 @@ * - ClassDeclaration */ (sap?.ui).define([`Bar`], (Bar) => { - return class Foo extends Bar { + /** + * @class + * My super documentation of this class + * + * @extends library.j.Bar + * + * @author SAP SE + * @version ${version} + * + * @public + * @alias library.j.Foo + * @ui5-metamodel text + */ + class Foo extends Bar { make() { sap.ui.require("conditional/module1"); } - }; + } + + return Foo; }); diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js index 91a934476..332423ee9 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js @@ -7,10 +7,32 @@ * - ArrowFunctionExpression */ window.someRandomModule || - sap.ui.define(["/.a"], (a) => - a.extend(`aaa`, { + sap.ui.define(["./a"], (a) => + /** + * Constructor for a new library.j.aaa. + * + * @param {string} [sId] ID for the new control, generated automatically if no ID is given + * @param {object} [mSettings] Initial settings for the new control + * + * @class + * + * @author SAP SE + * @version ${version} + * + * @constructor + * @extends library.j.a + * @public + * @since 1.22 + * @alias library.j.aaa + * @ui5-metamodel This control will also be described in the UI5 (legacy) design time meta model. + */ + a.extend(`library.j.aaa`, { metadata: { properties: { + /** + * MyProp property + * @since 1.46 + */ MyProp: { type: "boolean", group: `Misc`, diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js index 9d8c9cd68..09ae9ea87 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js @@ -9,9 +9,24 @@ * - ClassDeclaration */ (sap?.ui).define([`Bar`], (Bar) => { - return class Foo extends Bar { + /** + * @class + * My super documentation of this class + * + * @extends library.j.Bar + * + * @author SAP SE + * @version ${version} + * + * @public + * @alias library.j.Foo + * @ui5-metamodel text + */ + class Foo extends Bar { make() { sap.ui.require("conditional/module1"); } - }; + } + + return Foo; }); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js index 91a934476..332423ee9 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js @@ -7,10 +7,32 @@ * - ArrowFunctionExpression */ window.someRandomModule || - sap.ui.define(["/.a"], (a) => - a.extend(`aaa`, { + sap.ui.define(["./a"], (a) => + /** + * Constructor for a new library.j.aaa. + * + * @param {string} [sId] ID for the new control, generated automatically if no ID is given + * @param {object} [mSettings] Initial settings for the new control + * + * @class + * + * @author SAP SE + * @version ${version} + * + * @constructor + * @extends library.j.a + * @public + * @since 1.22 + * @alias library.j.aaa + * @ui5-metamodel This control will also be described in the UI5 (legacy) design time meta model. + */ + a.extend(`library.j.aaa`, { metadata: { properties: { + /** + * MyProp property + * @since 1.46 + */ MyProp: { type: "boolean", group: `Misc`, From 9fa8ccfabb33b3b7b672aca284daa5e9108aee58 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 29 Aug 2022 11:45:13 +0300 Subject: [PATCH 25/27] Annotate properly ArrowFunctionExpression w/ expression: true --- .../resources/library/j/dependency-es6-2.js | 30 ++++++++++--------- .../main/src/library/j/dependency-es6-2.js | 30 ++++++++++--------- 2 files changed, 32 insertions(+), 28 deletions(-) diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js index 332423ee9..541fe9b5f 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-2.js @@ -7,7 +7,8 @@ * - ArrowFunctionExpression */ window.someRandomModule || - sap.ui.define(["./a"], (a) => + sap.ui.define( + ["./a"], /** * Constructor for a new library.j.aaa. * @@ -26,19 +27,20 @@ window.someRandomModule || * @alias library.j.aaa * @ui5-metamodel This control will also be described in the UI5 (legacy) design time meta model. */ - a.extend(`library.j.aaa`, { - metadata: { - properties: { - /** - * MyProp property - * @since 1.46 - */ - MyProp: { - type: "boolean", - group: `Misc`, - defaultValue: false, + (a) => + a.extend(`library.j.aaa`, { + metadata: { + properties: { + /** + * MyProp property + * @since 1.46 + */ + MyProp: { + type: "boolean", + group: `Misc`, + defaultValue: false, + }, }, }, - }, - }) + }) ); diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js index 332423ee9..541fe9b5f 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-2.js @@ -7,7 +7,8 @@ * - ArrowFunctionExpression */ window.someRandomModule || - sap.ui.define(["./a"], (a) => + sap.ui.define( + ["./a"], /** * Constructor for a new library.j.aaa. * @@ -26,19 +27,20 @@ window.someRandomModule || * @alias library.j.aaa * @ui5-metamodel This control will also be described in the UI5 (legacy) design time meta model. */ - a.extend(`library.j.aaa`, { - metadata: { - properties: { - /** - * MyProp property - * @since 1.46 - */ - MyProp: { - type: "boolean", - group: `Misc`, - defaultValue: false, + (a) => + a.extend(`library.j.aaa`, { + metadata: { + properties: { + /** + * MyProp property + * @since 1.46 + */ + MyProp: { + type: "boolean", + group: `Misc`, + defaultValue: false, + }, }, }, - }, - }) + }) ); From 9172576b653e29fdd756901f3ee4db29df3bf1a4 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Mon, 29 Aug 2022 11:53:13 +0300 Subject: [PATCH 26/27] Updated api.json expectation --- .../library.j/dest/test-resources/library/j/designtime/api.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json b/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json index 6c7e52bc6..109d26662 100644 --- a/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json +++ b/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json @@ -1 +1 @@ -{"$schema-ref":"http://schemas.sap.com/sapui5/designtime/api.json/1.0","version":"1.0.0","library":"library.j","symbols":[{"kind":"namespace","name":"library.j","basename":"j","resource":"library/j/some.js","module":"library/j/some","static":true,"visibility":"public"}]} \ No newline at end of file +{"$schema-ref":"http://schemas.sap.com/sapui5/designtime/api.json/1.0","version":"1.0.0","library":"library.j","symbols":[{"kind":"namespace","name":"library.j","basename":"j","resource":"library/j/some.js","module":"library/j/some","static":true,"visibility":"public"},{"kind":"class","name":"library.j.aaa","basename":"aaa","resource":"library/j/dependency-es6-2.js","module":"library/j/dependency-es6-2","export":"","static":true,"visibility":"public","since":"1.22","extends":"library.j.a","ui5-metamodel":true,"ui5-metadata":{"properties":[{"name":"MyProp","type":"boolean","defaultValue":false,"group":"undefined","visibility":"public","since":"1.46","description":"MyProp property","methods":["getMyProp","setMyProp"]}]},"constructor":{"visibility":"public","parameters":[{"name":"sId","type":"string","optional":true,"description":"ID for the new control, generated automatically if no ID is given"},{"name":"mSettings","type":"object","optional":true,"description":"Initial settings for the new control"}],"description":"Constructor for a new library.j.aaa."},"methods":[{"name":"extend","visibility":"public","static":true,"returnValue":{"type":"function","description":"Created class / constructor function"},"parameters":[{"name":"sClassName","type":"string","optional":false,"description":"Name of the class being created"},{"name":"oClassInfo","type":"object","optional":true,"description":"Object literal with information about the class"},{"name":"FNMetaImpl","type":"function","optional":true,"description":"Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class"}],"description":"Creates a new subclass of class library.j.aaa with name sClassName and enriches it with the information contained in oClassInfo.\n\noClassInfo might contain the same kind of information as described in {@link library.j.a.extend}."},{"name":"getMetadata","visibility":"public","static":true,"returnValue":{"type":"sap.ui.base.Metadata","description":"Metadata object describing this class"},"description":"Returns a metadata object for class library.j.aaa."},{"name":"getMyProp","visibility":"public","since":"1.46","returnValue":{"type":"boolean","description":"Value of property MyProp"},"description":"Gets current value of property {@link #getMyProp MyProp}.\n\nMyProp property\n\nDefault value is false."},{"name":"setMyProp","visibility":"public","since":"1.46","returnValue":{"type":"this","description":"Reference to this in order to allow method chaining"},"parameters":[{"name":"bMyProp","type":"boolean","optional":true,"defaultValue":false,"description":"New value for property MyProp"}],"description":"Sets a new value for property {@link #getMyProp MyProp}.\n\nMyProp property\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is false."}]},{"kind":"class","name":"library.j.Foo","basename":"Foo","resource":"library/j/dependency-es6-1.js","module":"library/j/dependency-es6-1","static":true,"visibility":"public","extends":"library.j.Bar","description":"My super documentation of this class","ui5-metamodel":true,"constructor":{"visibility":"public"}}]} \ No newline at end of file From 27bfd49fa94e5feedfd752203a925f311431b683 Mon Sep 17 00:00:00 2001 From: Yavor Ivanov Date: Sun, 4 Sep 2022 21:35:33 +0300 Subject: [PATCH 27/27] remove metamodel tag --- .../library.j/dest/resources/library/j/dependency-es6-1.js | 1 - .../library.j/dest/test-resources/library/j/designtime/api.json | 2 +- test/fixtures/library.j/main/src/library/j/dependency-es6-1.js | 1 - 3 files changed, 1 insertion(+), 3 deletions(-) diff --git a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js index 09ae9ea87..1bec63ae1 100644 --- a/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js +++ b/test/expected/build/library.j/dest/resources/library/j/dependency-es6-1.js @@ -20,7 +20,6 @@ * * @public * @alias library.j.Foo - * @ui5-metamodel text */ class Foo extends Bar { make() { diff --git a/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json b/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json index 109d26662..0bf0fd3a6 100644 --- a/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json +++ b/test/expected/build/library.j/dest/test-resources/library/j/designtime/api.json @@ -1 +1 @@ -{"$schema-ref":"http://schemas.sap.com/sapui5/designtime/api.json/1.0","version":"1.0.0","library":"library.j","symbols":[{"kind":"namespace","name":"library.j","basename":"j","resource":"library/j/some.js","module":"library/j/some","static":true,"visibility":"public"},{"kind":"class","name":"library.j.aaa","basename":"aaa","resource":"library/j/dependency-es6-2.js","module":"library/j/dependency-es6-2","export":"","static":true,"visibility":"public","since":"1.22","extends":"library.j.a","ui5-metamodel":true,"ui5-metadata":{"properties":[{"name":"MyProp","type":"boolean","defaultValue":false,"group":"undefined","visibility":"public","since":"1.46","description":"MyProp property","methods":["getMyProp","setMyProp"]}]},"constructor":{"visibility":"public","parameters":[{"name":"sId","type":"string","optional":true,"description":"ID for the new control, generated automatically if no ID is given"},{"name":"mSettings","type":"object","optional":true,"description":"Initial settings for the new control"}],"description":"Constructor for a new library.j.aaa."},"methods":[{"name":"extend","visibility":"public","static":true,"returnValue":{"type":"function","description":"Created class / constructor function"},"parameters":[{"name":"sClassName","type":"string","optional":false,"description":"Name of the class being created"},{"name":"oClassInfo","type":"object","optional":true,"description":"Object literal with information about the class"},{"name":"FNMetaImpl","type":"function","optional":true,"description":"Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class"}],"description":"Creates a new subclass of class library.j.aaa with name sClassName and enriches it with the information contained in oClassInfo.\n\noClassInfo might contain the same kind of information as described in {@link library.j.a.extend}."},{"name":"getMetadata","visibility":"public","static":true,"returnValue":{"type":"sap.ui.base.Metadata","description":"Metadata object describing this class"},"description":"Returns a metadata object for class library.j.aaa."},{"name":"getMyProp","visibility":"public","since":"1.46","returnValue":{"type":"boolean","description":"Value of property MyProp"},"description":"Gets current value of property {@link #getMyProp MyProp}.\n\nMyProp property\n\nDefault value is false."},{"name":"setMyProp","visibility":"public","since":"1.46","returnValue":{"type":"this","description":"Reference to this in order to allow method chaining"},"parameters":[{"name":"bMyProp","type":"boolean","optional":true,"defaultValue":false,"description":"New value for property MyProp"}],"description":"Sets a new value for property {@link #getMyProp MyProp}.\n\nMyProp property\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is false."}]},{"kind":"class","name":"library.j.Foo","basename":"Foo","resource":"library/j/dependency-es6-1.js","module":"library/j/dependency-es6-1","static":true,"visibility":"public","extends":"library.j.Bar","description":"My super documentation of this class","ui5-metamodel":true,"constructor":{"visibility":"public"}}]} \ No newline at end of file +{"$schema-ref":"http://schemas.sap.com/sapui5/designtime/api.json/1.0","version":"1.0.0","library":"library.j","symbols":[{"kind":"namespace","name":"library.j","basename":"j","resource":"library/j/some.js","module":"library/j/some","static":true,"visibility":"public"},{"kind":"class","name":"library.j.aaa","basename":"aaa","resource":"library/j/dependency-es6-2.js","module":"library/j/dependency-es6-2","export":"","static":true,"visibility":"public","since":"1.22","extends":"library.j.a","ui5-metamodel":true,"ui5-metadata":{"properties":[{"name":"MyProp","type":"boolean","defaultValue":false,"group":"undefined","visibility":"public","since":"1.46","description":"MyProp property","methods":["getMyProp","setMyProp"]}]},"constructor":{"visibility":"public","parameters":[{"name":"sId","type":"string","optional":true,"description":"ID for the new control, generated automatically if no ID is given"},{"name":"mSettings","type":"object","optional":true,"description":"Initial settings for the new control"}],"description":"Constructor for a new library.j.aaa."},"methods":[{"name":"extend","visibility":"public","static":true,"returnValue":{"type":"function","description":"Created class / constructor function"},"parameters":[{"name":"sClassName","type":"string","optional":false,"description":"Name of the class being created"},{"name":"oClassInfo","type":"object","optional":true,"description":"Object literal with information about the class"},{"name":"FNMetaImpl","type":"function","optional":true,"description":"Constructor function for the metadata object; if not given, it defaults to the metadata implementation used by this class"}],"description":"Creates a new subclass of class library.j.aaa with name sClassName and enriches it with the information contained in oClassInfo.\n\noClassInfo might contain the same kind of information as described in {@link library.j.a.extend}."},{"name":"getMetadata","visibility":"public","static":true,"returnValue":{"type":"sap.ui.base.Metadata","description":"Metadata object describing this class"},"description":"Returns a metadata object for class library.j.aaa."},{"name":"getMyProp","visibility":"public","since":"1.46","returnValue":{"type":"boolean","description":"Value of property MyProp"},"description":"Gets current value of property {@link #getMyProp MyProp}.\n\nMyProp property\n\nDefault value is false."},{"name":"setMyProp","visibility":"public","since":"1.46","returnValue":{"type":"this","description":"Reference to this in order to allow method chaining"},"parameters":[{"name":"bMyProp","type":"boolean","optional":true,"defaultValue":false,"description":"New value for property MyProp"}],"description":"Sets a new value for property {@link #getMyProp MyProp}.\n\nMyProp property\n\nWhen called with a value of null or undefined, the default value of the property will be restored.\n\nDefault value is false."}]},{"kind":"class","name":"library.j.Foo","basename":"Foo","resource":"library/j/dependency-es6-1.js","module":"library/j/dependency-es6-1","static":true,"visibility":"public","extends":"library.j.Bar","description":"My super documentation of this class","constructor":{"visibility":"public"}}]} \ No newline at end of file diff --git a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js index 09ae9ea87..1bec63ae1 100644 --- a/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js +++ b/test/fixtures/library.j/main/src/library/j/dependency-es6-1.js @@ -20,7 +20,6 @@ * * @public * @alias library.j.Foo - * @ui5-metamodel text */ class Foo extends Bar { make() {