Skip to content

Commit

Permalink
[INTERNAL] Improve support for ES6+ Syntax
Browse files Browse the repository at this point in the history
JIRA: CPOUI5FOUNDATION-374
  • Loading branch information
matz3 authored and flovogt committed Aug 22, 2022
1 parent a6146a5 commit 7308a04
Show file tree
Hide file tree
Showing 23 changed files with 1,073 additions and 113 deletions.
28 changes: 21 additions & 7 deletions lib/lbt/analyzer/FioriElementsAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -165,19 +165,33 @@ class FioriElementsAnalyzer {
const TA = defineCall.findImportName("sap/fe/core/TemplateAssembler.js");
// console.log("local name for TemplateAssembler: %s", TA);
if ( TA && defineCall.factory ) {
defineCall.factory.body.body.forEach( (stmt) => {
if ( stmt.type === Syntax.ReturnStatement &&
isMethodCall(stmt.argument, [TA, "getTemplateComponent"]) &&
stmt.argument.arguments.length > 2 &&
stmt.argument.arguments[2].type === "ObjectExpression" ) {
templateName = this._analyzeTemplateClassDefinition(stmt.argument.arguments[2]) || templateName;
if (defineCall.factory.type === Syntax.ArrowFunctionExpression &&
defineCall.factory.expression === true) {
if ( this._isTemplateClassDefinition(TA, defineCall.factory.body) ) {
templateName =
this._analyzeTemplateClassDefinition(defineCall.factory.body.arguments[2]) || templateName;
}
});
} else {
defineCall.factory.body.body.forEach( (stmt) => {
if ( stmt.type === Syntax.ReturnStatement &&
this._isTemplateClassDefinition(TA, stmt.argument)
) {
templateName =
this._analyzeTemplateClassDefinition(stmt.argument.arguments[2]) || templateName;
}
});
}
}
}
return templateName;
}

_isTemplateClassDefinition(TA, node) {
return isMethodCall(node, [TA, "getTemplateComponent"]) &&
node.arguments.length > 2 &&
node.arguments[2].type === "ObjectExpression";
}

_analyzeTemplateClassDefinition(clazz) {
const defaultValue = getValue(clazz, ["metadata", "properties", "templateName", "defaultValue"]);
if ( isString(defaultValue) ) {
Expand Down
139 changes: 87 additions & 52 deletions lib/lbt/analyzer/JSModuleAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@ const escope = require("escope");
const ModuleName = require("../utils/ModuleName");
const {Format: ModuleFormat} = require("../resources/ModuleInfo");
const UI5ClientConstants = require("../UI5ClientConstants");
const {findOwnProperty, getLocation, getPropertyKey, isMethodCall, isString} = require("../utils/ASTUtils");
const {
findOwnProperty, getLocation, getPropertyKey,
isMethodCall, isString, getStringValue} = require("../utils/ASTUtils");
const log = require("@ui5/logger").getLogger("lbt:analyzer:JSModuleAnalyzer");

// ------------------------------------------------------------------------------------------------------------------
Expand Down Expand Up @@ -64,7 +66,7 @@ const EnrichedVisitorKeys = (function() {
BreakStatement: [],
CallExpression: [], // special handling
CatchClause: ["param", "body"],
ChainExpression: [],
ChainExpression: ["expression"],
ClassBody: [],
ClassDeclaration: [],
ClassExpression: [],
Expand Down Expand Up @@ -541,21 +543,27 @@ class JSModuleAnalyzer {

function onDeclare(node) {
const args = node.arguments;
if ( args.length > 0 && isString(args[0]) ) {
const name = ModuleName.fromUI5LegacyName( args[0].value );
if ( nModuleDeclarations === 1 && !mainModuleFound) {
// if this is the first declaration, then this is the main module declaration
// note that this overrides an already given name
setMainModuleInfo(name, getDocumentation(node));
} else if ( nModuleDeclarations > 1 && name === info.name ) {
// ignore duplicate declarations (e.g. in behavior file of design time controls)
log.warn(`duplicate declaration of module name at ${getLocation(args)} in ${name}`);
if (args.length > 0) {
const value = getStringValue(args[0]);
if (value !== undefined) {
const name = ModuleName.fromUI5LegacyName(value);
if ( nModuleDeclarations === 1 && !mainModuleFound) {
// if this is the first declaration, then this is the main module declaration
// note that this overrides an already given name
setMainModuleInfo(name, getDocumentation(node));
} else if ( nModuleDeclarations > 1 && name === info.name ) {
// ignore duplicate declarations (e.g. in behavior file of design time controls)
log.warn(`duplicate declaration of module name at ${getLocation(args)} in ${name}`);
} else {
// otherwise it is just a submodule declaration
info.addSubModule(name);
}
return;
} else {
// otherwise it is just a submodule declaration
info.addSubModule(name);
log.error("jQuery.sap.declare: module name could not be determined from first argument:", args[0]);
}
} else {
log.error("jQuery.sap.declare: module name could not be determined from first argument:", args[0]);
log.error("jQuery.sap.declare: module name could not be determined, no arguments are given");
}
}

Expand All @@ -569,20 +577,26 @@ class JSModuleAnalyzer {

// determine the name of the module
let name = null;
if ( i < nArgs && isString(args[i]) ) {
name = ModuleName.fromRequireJSName( args[i++].value );
if ( name === defaultName ) {
// hardcoded name equals the file name, so this definition qualifies as main module definition
setMainModuleInfo(name, desc);
} else {
info.addSubModule(name);
if ( candidateName == null ) {
// remember the name and description in case no other module qualifies as main module
candidateName = name;
candidateDescription = desc;
if ( i < nArgs ) {
const value = getStringValue( args[i] );
if ( value !== undefined ) {
name = ModuleName.fromRequireJSName(value);
if ( name === defaultName ) {
// hardcoded name equals the file name, so this definition qualifies as main module definition
setMainModuleInfo(name, desc);
} else {
info.addSubModule(name);
if ( candidateName == null ) {
// remember the name and description in case no other module qualifies as main module
candidateName = name;
candidateDescription = desc;
}
}
i++;
}
} else {
}

if ( !name ) {
nUnnamedDefines++;
if ( nUnnamedDefines > 1 ) {
throw new Error(
Expand Down Expand Up @@ -614,15 +628,25 @@ class JSModuleAnalyzer {
// UI5 signature with one or many required modules
for (let i = 0; i < nArgs; i++) {
const arg = args[i];
if ( isString(arg) ) {
const requiredModuleName = ModuleName.fromUI5LegacyName( arg.value );
const value = getStringValue(arg);
if ( value !== undefined ) {
const requiredModuleName = ModuleName.fromUI5LegacyName( value );
info.addDependency(requiredModuleName, conditional);
} else if ( arg.type == Syntax.ConditionalExpression &&
isString(arg.consequent) && isString(arg.alternate) ) {
const requiredModuleName1 = ModuleName.fromUI5LegacyName( arg.consequent.value );
info.addDependency(requiredModuleName1, true);
const requiredModuleName2 = ModuleName.fromUI5LegacyName( arg.alternate.value );
info.addDependency(requiredModuleName2, true);
} else if ( arg.type == Syntax.ConditionalExpression) {
const consequentValue = getStringValue(arg.consequent);
const alternateValue = getStringValue(arg.alternate);
if ( consequentValue !== undefined ) {
const requiredModuleName1 = ModuleName.fromUI5LegacyName( consequentValue );
info.addDependency(requiredModuleName1, true);
}
if ( alternateValue !== undefined ) {
const requiredModuleName2 = ModuleName.fromUI5LegacyName( alternateValue );
info.addDependency(requiredModuleName2, true);
}
if ( consequentValue === undefined || alternateValue === undefined ) {
log.verbose("jQuery.sap.require: cannot evaluate dynamic arguments: ", arg && arg.type);
info.dynamicDependencies = true;
}
} else {
log.verbose("jQuery.sap.require: cannot evaluate dynamic arguments: ", arg && arg.type);
info.dynamicDependencies = true;
Expand All @@ -637,9 +661,10 @@ class JSModuleAnalyzer {
const i = 0;

if ( i < nArgs ) {
if ( isString(args[i]) ) {
const value = getStringValue(args[i]);
if ( value !== undefined ) {
// sap.ui.requireSync does not support relative dependencies
const moduleName = ModuleName.fromRequireJSName( args[i].value );
const moduleName = ModuleName.fromRequireJSName( value );
info.addDependency(moduleName, conditional);
} else {
log.verbose("sap.ui.requireSync: cannot evaluate dynamic arguments: ", args[i] && args[i].type);
Expand All @@ -654,18 +679,24 @@ class JSModuleAnalyzer {
let i = 0;

// determine the name of the module
if ( i < nArgs && isString(args[i]) ) {
const moduleName = ModuleName.fromRequireJSName( args[i++].value );
info.addSubModule(moduleName);

// add dependencies
// to correctly identify dependencies e.g. of a library-preload
const elementArg = args[i++];
if (elementArg && elementArg.type === Syntax.ArrayExpression) {
elementArg.elements.forEach((element) => {
const dependencyName = ModuleName.resolveRelativeRequireJSName(moduleName, element.value);
info.addDependency(dependencyName, conditional);
});
if ( i < nArgs ) {
const value = getStringValue(args[i++]);
if ( value !== undefined ) {
const moduleName = ModuleName.fromRequireJSName( value );
info.addSubModule(moduleName);

// add dependencies
// to correctly identify dependencies e.g. of a library-preload
const elementArg = args[i++];
if (elementArg && elementArg.type === Syntax.ArrayExpression) {
elementArg.elements.forEach((element) => {
const dependencyName = ModuleName.resolveRelativeRequireJSName(moduleName,
getStringValue(element));
info.addDependency(dependencyName, conditional);
});
}
} else {
log.warn("sap.ui.predefine call has a non supported type for module name (ignored)");
}
} else {
log.warn("sap.ui.predefine call is missing a module name (ignored)");
Expand All @@ -692,6 +723,9 @@ class JSModuleAnalyzer {
if ( modules && modules.type == Syntax.ObjectExpression ) {
modules.properties.forEach( function(property) {
let moduleName = getPropertyKey(property);
if ( !moduleName ) {
return;
}
if ( namesUseLegacyNotation ) {
moduleName = ModuleName.fromUI5LegacyName(moduleName);
}
Expand All @@ -705,16 +739,17 @@ class JSModuleAnalyzer {
function analyzeDependencyArray(array, conditional, name) {
// console.log(array);
array.forEach( (item) => {
if ( isString(item) ) {
const value = getStringValue(item);
if ( value !== undefined ) {
// ignore special AMD dependencies (require, exports, module)
if ( SPECIAL_AMD_DEPENDENCIES.indexOf(item.value) >= 0 ) {
if ( SPECIAL_AMD_DEPENDENCIES.indexOf(value) >= 0 ) {
return;
}
let requiredModule;
if (name == null) {
requiredModule = ModuleName.fromRequireJSName( item.value );
requiredModule = ModuleName.fromRequireJSName( value );
} else {
requiredModule = ModuleName.resolveRelativeRequireJSName(name, item.value);
requiredModule = ModuleName.resolveRelativeRequireJSName(name, value);
}
info.addDependency( requiredModule, conditional );
} else {
Expand Down
28 changes: 21 additions & 7 deletions lib/lbt/analyzer/SmartTemplateAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -134,19 +134,33 @@ class TemplateComponentAnalyzer {
const TA = defineCall.findImportName("sap/suite/ui/generic/template/lib/TemplateAssembler.js");
// console.log("local name for TemplateAssembler: %s", TA);
if ( TA && defineCall.factory ) {
defineCall.factory.body.body.forEach( (stmt) => {
if ( stmt.type === Syntax.ReturnStatement &&
isMethodCall(stmt.argument, [TA, "getTemplateComponent"]) &&
stmt.argument.arguments.length > 2 &&
stmt.argument.arguments[2].type === "ObjectExpression" ) {
templateName = this._analyzeTemplateClassDefinition(stmt.argument.arguments[2]) || templateName;
if (defineCall.factory.type === Syntax.ArrowFunctionExpression &&
defineCall.factory.expression === true) {
if ( this._isTemplateClassDefinition(TA, defineCall.factory.body) ) {
templateName =
this._analyzeTemplateClassDefinition(defineCall.factory.body.arguments[2]) || templateName;
}
});
} else {
defineCall.factory.body.body.forEach( (stmt) => {
if ( stmt.type === Syntax.ReturnStatement &&
this._isTemplateClassDefinition(TA, stmt.argument)
) {
templateName =
this._analyzeTemplateClassDefinition(stmt.argument.arguments[2]) || templateName;
}
});
}
}
}
return templateName;
}

_isTemplateClassDefinition(TA, node) {
return isMethodCall(node, [TA, "getTemplateComponent"]) &&
node.arguments.length > 2 &&
node.arguments[2].type === "ObjectExpression";
}

_analyzeTemplateClassDefinition(clazz) {
const defaultValue = getValue(clazz, ["metadata", "properties", "templateName", "defaultValue"]);
if ( isString(defaultValue) ) {
Expand Down
36 changes: 24 additions & 12 deletions lib/lbt/analyzer/XMLCompositeAnalyzer.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,30 @@ class XMLCompositeAnalyzer {
const XMLC = defineCall.findImportName("sap/ui/core/XMLComposite.js");
// console.log("local name for XMLComposite: %s", XMLC);
if ( XMLC && defineCall.factory ) {
defineCall.factory.body.body.forEach( (stmt) => {
if ( stmt.type === Syntax.VariableDeclaration ) {
stmt.declarations.forEach( (decl) => {
fragmentName = this._checkForXMLCClassDefinition( XMLC, decl.init ) || fragmentName;
});
} else if ( stmt.type === Syntax.ExpressionStatement &&
stmt.expression.type === Syntax.AssignmentExpression ) {
fragmentName = this._checkForXMLCClassDefinition( XMLC, stmt.expression.right ) || fragmentName;
}
});
if ( fragmentName ) {
const fragmentModule = ModuleName.fromUI5LegacyName( fragmentName, ".control.xml" );
if (defineCall.factory.type === Syntax.ArrowFunctionExpression &&
defineCall.factory.expression === true) {
fragmentName = this._checkForXMLCClassDefinition(XMLC, defineCall.factory.body);
} else {
defineCall.factory.body.body.forEach((stmt) => {
if (stmt.type === Syntax.VariableDeclaration) {
stmt.declarations.forEach((decl) => {
fragmentName = this._checkForXMLCClassDefinition(XMLC, decl.init) || fragmentName;
});
} else if (
stmt.type === Syntax.ReturnStatement &&
( stmt?.argument?.type === Syntax.CallExpression && stmt.argument.arguments?.length > 1 &&
stmt.argument.arguments[1].type === Syntax.ObjectExpression)) {
fragmentName =
this._checkForXMLCClassDefinition(XMLC, stmt.argument) || fragmentName;
} else if (stmt.type === Syntax.ExpressionStatement &&
stmt.expression.type === Syntax.AssignmentExpression) {
fragmentName =
this._checkForXMLCClassDefinition(XMLC, stmt.expression.right) || fragmentName;
}
});
}
if (fragmentName) {
const fragmentModule = ModuleName.fromUI5LegacyName(fragmentName, ".control.xml");
log.verbose("fragment control: add dependency to template fragment %s", fragmentModule);
info.addDependency(fragmentModule);
}
Expand Down
15 changes: 15 additions & 0 deletions lib/lbt/analyzer/analyzeLibraryJS.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
"use strict";
const {parseJS, Syntax, VisitorKeys} = require("../utils/parseUtils");
const {getPropertyKey, isMethodCall, isIdentifier, getStringArray} = require("../utils/ASTUtils");
const log = require("@ui5/logger").getLogger("lbt:analyzer:LibraryJS");

const CALL__SAP_UI_GETCORE = ["sap", "ui", "getCore"];

Expand All @@ -25,8 +26,14 @@ async function analyze(resource) {
node.arguments.length === 1 &&
node.arguments[0].type === Syntax.ObjectExpression ) {
node.arguments[0].properties.forEach( (prop) => {
if (prop.type === Syntax.SpreadElement) {
// TODO: Support interpreting SpreadElements
return;
}

const key = getPropertyKey(prop);
const value = prop.value;

if ( key === "noLibraryCSS" &&
(value.type === Syntax.Literal && typeof value.value === "boolean") ) {
libInfo.noLibraryCSS = value.value;
Expand All @@ -38,6 +45,14 @@ async function analyze(resource) {
libInfo.controls = getStringArray(value, true);
} else if ( key === "elements" && value.type == Syntax.ArrayExpression ) {
libInfo.elements = getStringArray(value, true);
} else if ( ["designtime", "dependencies", "extensions", "name", "version"].includes(key) ) {
// do nothing, for all other supported properties
} else {
log.error(
"Unexpected property '" + key +
"' or wrong type for '" + key +
"' in sap.ui.getCore().initLibrary call in '" + resource.getPath() + "'"
);
}
});

Expand Down
2 changes: 1 addition & 1 deletion lib/lbt/bundle/Builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -648,7 +648,7 @@ async function rewriteDefine({moduleName, moduleContent, moduleSourceMap}) {

// Inject module name if missing
if ( defineCall.arguments.length == 0 ||
defineCall.arguments[0].type !== Syntax.Literal ) {
![Syntax.Literal, Syntax.TemplateLiteral].includes(defineCall.arguments[0].type)) {
let value = `"${ModuleName.toRequireJSName(moduleName)}"`;
let index;

Expand Down
4 changes: 3 additions & 1 deletion lib/lbt/calls/SapUiDefine.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,9 @@ class SapUiDefineCall {
this.dependencyInsertionIdx = this.dependencyArray.elements.length;
}

if ( i < args.length && args[i].type === Syntax.FunctionExpression ) {
if ( i < args.length && (
args[i].type === Syntax.FunctionExpression || args[i].type === Syntax.ArrowFunctionExpression)
) {
this.factory = args[i++];
params = this.factory.params;
this.paramNames = params.map( (param) => {
Expand Down
Loading

0 comments on commit 7308a04

Please sign in to comment.