Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[FEATURE] jsdoc: Improve support for ES6+ syntax #785

Merged
merged 27 commits into from
Sep 9, 2022
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
1df5ae5
ArrowFunctions + ArrowFunctionExpression.expression
d3xter666 Aug 15, 2022
7f42f00
Type validation + Arrow fuction expression
d3xter666 Aug 15, 2022
6fb8ec0
ArrowFunctionExpression cover all cases
d3xter666 Aug 16, 2022
eea010b
Refactor ArrowFunctionExpression
d3xter666 Aug 16, 2022
9193b4e
Avoid "computed" Identifier to be used at it might lead to hidden bugs
d3xter666 Aug 17, 2022
17c9686
Optional Chaining for MemberExpression & CallExpression
d3xter666 Aug 18, 2022
1499f33
Literals for Syntax.OptionalMemberExpression/OptionalCallExpression
d3xter666 Aug 18, 2022
5bb7a2e
Enhance LogicalExpression to support "||" and "??" oprators
d3xter666 Aug 19, 2022
de73361
Cover ChainExpression
d3xter666 Aug 19, 2022
3256d92
Wrapper Expressions generic approach
d3xter666 Aug 19, 2022
a9bb985
Cover possible retruning statements in JS
d3xter666 Aug 22, 2022
26448a1
Support alternate definitions of modules
d3xter666 Aug 22, 2022
3f2ad7a
ES6 Class definition export
d3xter666 Aug 22, 2022
a2ab576
Fix eslint checks
d3xter666 Aug 22, 2022
a6f23b5
Add tests
d3xter666 Aug 23, 2022
ca7fdd8
Enable all tests
d3xter666 Aug 23, 2022
9a419d0
Extend Syntax types "manually"
d3xter666 Aug 23, 2022
167e822
Fix types
d3xter666 Aug 23, 2022
8adad20
Bugfix chaining: There might be multilevel chains
d3xter666 Aug 23, 2022
8c47c34
Simplify tests
d3xter666 Aug 23, 2022
baa7205
Support of TemplateLiterals
d3xter666 Aug 24, 2022
1577586
Handle TemplateLiterals
d3xter666 Aug 25, 2022
7ce0b03
Bugfixes: Parse TemplateLiterals as strings
d3xter666 Aug 25, 2022
1efa268
Add JSDoc to the samples
d3xter666 Aug 25, 2022
9fa8ccf
Annotate properly ArrowFunctionExpression w/ expression: true
d3xter666 Aug 29, 2022
9172576
Updated api.json expectation
d3xter666 Aug 29, 2022
27bfd49
remove metamodel tag
d3xter666 Sep 4, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 41 additions & 34 deletions lib/processors/jsdoc/lib/ui5/plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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}')`);
Expand All @@ -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 ) {
Expand All @@ -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;
Expand All @@ -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);
}
}

Expand Down Expand Up @@ -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);
}
Expand All @@ -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
);

Expand Down Expand Up @@ -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])
);
}

Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -869,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,
Expand Down Expand Up @@ -1243,25 +1251,24 @@ 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++]);
}
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}'`);
}
Expand Down Expand Up @@ -2789,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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
d3xter666 marked this conversation as resolved.
Show resolved Hide resolved
*/
class Foo extends Bar {
make() {
sap.ui.require("conditional/module1");
}
};
}

return Foo;
});
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,40 @@
* - ArrowFunctionExpression
*/
window.someRandomModule ||
sap.ui.define(["/.a"], (a) =>
a.extend("aaa", {
metadata: {
properties: {
MyProp: {
type: "boolean",
group: "Misc",
defaultValue: false,
sap.ui.define(
["./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) =>
a.extend(`library.j.aaa`, {
metadata: {
properties: {
/**
* MyProp property
* @since 1.46
*/
MyProp: {
type: "boolean",
group: `Misc`,
defaultValue: false,
},
},
},
},
})
})
);
Original file line number Diff line number Diff line change
@@ -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"}]}
{"$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 <code>sClassName</code> and enriches it with the information contained in <code>oClassInfo</code>.\n\n<code>oClassInfo</code> 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 <code>MyProp</code>"},"description":"Gets current value of property {@link #getMyProp MyProp}.\n\nMyProp property\n\nDefault value is <code>false</code>."},{"name":"setMyProp","visibility":"public","since":"1.46","returnValue":{"type":"this","description":"Reference to <code>this</code> in order to allow method chaining"},"parameters":[{"name":"bMyProp","type":"boolean","optional":true,"defaultValue":false,"description":"New value for property <code>MyProp</code>"}],"description":"Sets a new value for property {@link #getMyProp MyProp}.\n\nMyProp property\n\nWhen called with a value of <code>null</code> or <code>undefined</code>, the default value of the property will be restored.\n\nDefault value is <code>false</code>."}]},{"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"}}]}
19 changes: 17 additions & 2 deletions test/fixtures/library.j/main/src/library/j/dependency-es6-1.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
});
44 changes: 34 additions & 10 deletions test/fixtures/library.j/main/src/library/j/dependency-es6-2.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,40 @@
* - ArrowFunctionExpression
*/
window.someRandomModule ||
sap.ui.define(["/.a"], (a) =>
a.extend("aaa", {
metadata: {
properties: {
MyProp: {
type: "boolean",
group: "Misc",
defaultValue: false,
sap.ui.define(
["./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) =>
a.extend(`library.j.aaa`, {
metadata: {
properties: {
/**
* MyProp property
* @since 1.46
*/
MyProp: {
type: "boolean",
group: `Misc`,
defaultValue: false,
},
},
},
},
})
})
);