Skip to content

Commit a55d570

Browse files
committed
[INTERNAL] support TemplateLiterals
1 parent eeb9422 commit a55d570

15 files changed

+363
-63
lines changed

lib/lbt/analyzer/JSModuleAnalyzer.js

Lines changed: 83 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@ const escope = require("escope");
55
const ModuleName = require("../utils/ModuleName");
66
const {Format: ModuleFormat} = require("../resources/ModuleInfo");
77
const UI5ClientConstants = require("../UI5ClientConstants");
8-
const {findOwnProperty, getLocation, getPropertyKey, isMethodCall, isString} = require("../utils/ASTUtils");
8+
const {
9+
findOwnProperty, getLocation, getPropertyKey,
10+
isMethodCall, isString, getStringValue} = require("../utils/ASTUtils");
911
const log = require("@ui5/logger").getLogger("lbt:analyzer:JSModuleAnalyzer");
1012

1113
// ------------------------------------------------------------------------------------------------------------------
@@ -541,21 +543,27 @@ class JSModuleAnalyzer {
541543

542544
function onDeclare(node) {
543545
const args = node.arguments;
544-
if ( args.length > 0 && isString(args[0]) ) {
545-
const name = ModuleName.fromUI5LegacyName( args[0].value );
546-
if ( nModuleDeclarations === 1 && !mainModuleFound) {
547-
// if this is the first declaration, then this is the main module declaration
548-
// note that this overrides an already given name
549-
setMainModuleInfo(name, getDocumentation(node));
550-
} else if ( nModuleDeclarations > 1 && name === info.name ) {
551-
// ignore duplicate declarations (e.g. in behavior file of design time controls)
552-
log.warn(`duplicate declaration of module name at ${getLocation(args)} in ${name}`);
546+
if (args.length > 0) {
547+
const value = getStringValue(args[0]);
548+
if (value) {
549+
const name = ModuleName.fromUI5LegacyName(value);
550+
if ( nModuleDeclarations === 1 && !mainModuleFound) {
551+
// if this is the first declaration, then this is the main module declaration
552+
// note that this overrides an already given name
553+
setMainModuleInfo(name, getDocumentation(node));
554+
} else if ( nModuleDeclarations > 1 && name === info.name ) {
555+
// ignore duplicate declarations (e.g. in behavior file of design time controls)
556+
log.warn(`duplicate declaration of module name at ${getLocation(args)} in ${name}`);
557+
} else {
558+
// otherwise it is just a submodule declaration
559+
info.addSubModule(name);
560+
}
561+
return;
553562
} else {
554-
// otherwise it is just a submodule declaration
555-
info.addSubModule(name);
563+
log.error("jQuery.sap.declare: module name could not be determined from first argument:", args[0]);
556564
}
557565
} else {
558-
log.error("jQuery.sap.declare: module name could not be determined from first argument:", args[0]);
566+
log.error("jQuery.sap.declare: module name could not be determined, no arguments are given");
559567
}
560568
}
561569

@@ -569,20 +577,26 @@ class JSModuleAnalyzer {
569577

570578
// determine the name of the module
571579
let name = null;
572-
if ( i < nArgs && isString(args[i]) ) {
573-
name = ModuleName.fromRequireJSName( args[i++].value );
574-
if ( name === defaultName ) {
575-
// hardcoded name equals the file name, so this definition qualifies as main module definition
576-
setMainModuleInfo(name, desc);
577-
} else {
578-
info.addSubModule(name);
579-
if ( candidateName == null ) {
580-
// remember the name and description in case no other module qualifies as main module
581-
candidateName = name;
582-
candidateDescription = desc;
580+
if ( i < nArgs ) {
581+
const value = getStringValue( args[i] );
582+
if ( value ) {
583+
name = ModuleName.fromRequireJSName(value);
584+
if ( name === defaultName ) {
585+
// hardcoded name equals the file name, so this definition qualifies as main module definition
586+
setMainModuleInfo(name, desc);
587+
} else {
588+
info.addSubModule(name);
589+
if ( candidateName == null ) {
590+
// remember the name and description in case no other module qualifies as main module
591+
candidateName = name;
592+
candidateDescription = desc;
593+
}
583594
}
595+
i++;
584596
}
585-
} else {
597+
}
598+
599+
if ( !name ) {
586600
nUnnamedDefines++;
587601
if ( nUnnamedDefines > 1 ) {
588602
throw new Error(
@@ -614,15 +628,25 @@ class JSModuleAnalyzer {
614628
// UI5 signature with one or many required modules
615629
for (let i = 0; i < nArgs; i++) {
616630
const arg = args[i];
617-
if ( isString(arg) ) {
618-
const requiredModuleName = ModuleName.fromUI5LegacyName( arg.value );
631+
const value = getStringValue(arg);
632+
if ( value ) {
633+
const requiredModuleName = ModuleName.fromUI5LegacyName( value );
619634
info.addDependency(requiredModuleName, conditional);
620-
} else if ( arg.type == Syntax.ConditionalExpression &&
621-
isString(arg.consequent) && isString(arg.alternate) ) {
622-
const requiredModuleName1 = ModuleName.fromUI5LegacyName( arg.consequent.value );
623-
info.addDependency(requiredModuleName1, true);
624-
const requiredModuleName2 = ModuleName.fromUI5LegacyName( arg.alternate.value );
625-
info.addDependency(requiredModuleName2, true);
635+
} else if ( arg.type == Syntax.ConditionalExpression) {
636+
const consequentValue = getStringValue(arg.consequent);
637+
const alternateValue = getStringValue(arg.alternate);
638+
if ( consequentValue ) {
639+
const requiredModuleName1 = ModuleName.fromUI5LegacyName( consequentValue );
640+
info.addDependency(requiredModuleName1, true);
641+
}
642+
if ( alternateValue ) {
643+
const requiredModuleName2 = ModuleName.fromUI5LegacyName( alternateValue );
644+
info.addDependency(requiredModuleName2, true);
645+
}
646+
if ( !consequentValue || !alternateValue ) {
647+
log.verbose("jQuery.sap.require: cannot evaluate dynamic arguments: ", arg && arg.type);
648+
info.dynamicDependencies = true;
649+
}
626650
} else {
627651
log.verbose("jQuery.sap.require: cannot evaluate dynamic arguments: ", arg && arg.type);
628652
info.dynamicDependencies = true;
@@ -637,9 +661,10 @@ class JSModuleAnalyzer {
637661
const i = 0;
638662

639663
if ( i < nArgs ) {
640-
if ( isString(args[i]) ) {
664+
const value = getStringValue(args[i]);
665+
if ( value ) {
641666
// sap.ui.requireSync does not support relative dependencies
642-
const moduleName = ModuleName.fromRequireJSName( args[i].value );
667+
const moduleName = ModuleName.fromRequireJSName( value );
643668
info.addDependency(moduleName, conditional);
644669
} else {
645670
log.verbose("sap.ui.requireSync: cannot evaluate dynamic arguments: ", args[i] && args[i].type);
@@ -654,18 +679,24 @@ class JSModuleAnalyzer {
654679
let i = 0;
655680

656681
// determine the name of the module
657-
if ( i < nArgs && isString(args[i]) ) {
658-
const moduleName = ModuleName.fromRequireJSName( args[i++].value );
659-
info.addSubModule(moduleName);
660-
661-
// add dependencies
662-
// to correctly identify dependencies e.g. of a library-preload
663-
const elementArg = args[i++];
664-
if (elementArg && elementArg.type === Syntax.ArrayExpression) {
665-
elementArg.elements.forEach((element) => {
666-
const dependencyName = ModuleName.resolveRelativeRequireJSName(moduleName, element.value);
667-
info.addDependency(dependencyName, conditional);
668-
});
682+
if ( i < nArgs ) {
683+
const value = getStringValue(args[i++]);
684+
if ( value ) {
685+
const moduleName = ModuleName.fromRequireJSName( value );
686+
info.addSubModule(moduleName);
687+
688+
// add dependencies
689+
// to correctly identify dependencies e.g. of a library-preload
690+
const elementArg = args[i++];
691+
if (elementArg && elementArg.type === Syntax.ArrayExpression) {
692+
elementArg.elements.forEach((element) => {
693+
const dependencyName = ModuleName.resolveRelativeRequireJSName(moduleName,
694+
getStringValue(element));
695+
info.addDependency(dependencyName, conditional);
696+
});
697+
}
698+
} else {
699+
log.warn("sap.ui.predefine call has a non supported type for module name (ignored)");
669700
}
670701
} else {
671702
log.warn("sap.ui.predefine call is missing a module name (ignored)");
@@ -708,16 +739,17 @@ class JSModuleAnalyzer {
708739
function analyzeDependencyArray(array, conditional, name) {
709740
// console.log(array);
710741
array.forEach( (item) => {
711-
if ( isString(item) ) {
742+
const value = getStringValue(item);
743+
if ( value ) {
712744
// ignore special AMD dependencies (require, exports, module)
713-
if ( SPECIAL_AMD_DEPENDENCIES.indexOf(item.value) >= 0 ) {
745+
if ( SPECIAL_AMD_DEPENDENCIES.indexOf(value) >= 0 ) {
714746
return;
715747
}
716748
let requiredModule;
717749
if (name == null) {
718-
requiredModule = ModuleName.fromRequireJSName( item.value );
750+
requiredModule = ModuleName.fromRequireJSName( value );
719751
} else {
720-
requiredModule = ModuleName.resolveRelativeRequireJSName(name, item.value);
752+
requiredModule = ModuleName.resolveRelativeRequireJSName(name, value);
721753
}
722754
info.addDependency( requiredModule, conditional );
723755
} else {

lib/lbt/analyzer/analyzeLibraryJS.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,9 @@ async function analyze(resource) {
4949
// do nothing, for all other supported properties
5050
} else {
5151
log.error(
52-
`Unexpected property: '${key}' in sap.ui.getCore().initLibrary call in '${resource.getPath()}'`
52+
"Unexpected property '" + key +
53+
"' or wrong type for '" + key +
54+
"' in sap.ui.getCore().initLibrary call in '" + resource.getPath() + "'"
5355
);
5456
}
5557
});

lib/lbt/bundle/Builder.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -648,7 +648,7 @@ async function rewriteDefine({moduleName, moduleContent, moduleSourceMap}) {
648648

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

lib/lbt/utils/ASTUtils.js

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,39 @@ const {Syntax} = require("../utils/parseUtils");
1313
* @returns {boolean} Whether the node is a literal and whether its value matches the given string
1414
*/
1515
function isString(node, literal) {
16-
if ( node == null || node.type !== Syntax.Literal || typeof node.value !== "string" ) {
16+
if (isLiteral(node)) {
17+
return literal == null ? true: node.value === literal;
18+
} else if (isTemplateLiteralWithoutExpression(node)) {
19+
return literal == null ? true : getTemplateLiteralValue(node) === literal;
20+
} else {
1721
return false;
1822
}
19-
return literal == null ? true : node.value === literal;
23+
}
24+
25+
function getStringValue(node) {
26+
if (isLiteral(node)) {
27+
return node.value;
28+
} else if (isTemplateLiteralWithoutExpression(node)) {
29+
return getTemplateLiteralValue(node);
30+
}
31+
}
32+
33+
function isLiteral(node) {
34+
return node && node.type === Syntax.Literal && typeof node.value === "string";
35+
}
36+
37+
function isTemplateLiteralWithoutExpression(node) {
38+
if (node && node.type === Syntax.TemplateLiteral &&
39+
node.quasis && node.quasis.length === 1 &&
40+
node.quasis[0].type === Syntax.TemplateElement &&
41+
typeof node.quasis[0].value.raw === "string") {
42+
return true;
43+
}
44+
return false;
45+
}
46+
47+
function getTemplateLiteralValue(node) {
48+
return node?.quasis?.[0]?.value?.cooked;
2049
}
2150

2251
function isBoolean(node, literal) {
@@ -113,6 +142,7 @@ function getStringArray(array, skipNonStringLiterals) {
113142

114143
module.exports = {
115144
isString,
145+
getStringValue,
116146
isBoolean,
117147
isMethodCall,
118148
isNamedObject,
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
jQuery.sap.declare("sap.ui.testmodule");
2+
const Foo = "conditional/module2";
3+
jQuery.sap.require(true ? "conditional/module1" : Foo);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
jQuery.sap.declare("sap.ui.testmodule");
2+
jQuery.sap.require(true ? `conditional/module1` : "conditional/module2");

test/fixtures/lbt/modules/es6-syntax.js

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ sap.ui.define([
33
], (m1) => { // using an arrow function for the module factory
44

55
sap.ui.require(['static/module2'], function() {
6-
sap.ui.require(['static/module3'], function() {});
6+
sap.ui.require(["static/module3"], function() {});
77
sap.ui.require('no-dependency/module1'); // probing API does not introduce a dependency
88
});
99

@@ -60,8 +60,7 @@ sap.ui.define([
6060
let generator = {
6161
*[Symbol.iterator]() {
6262
for (;;) {
63-
sap.ui.require(['conditional/module7']);
64-
yield 1;
63+
yield sap.ui.require(['conditional/module7']);
6564
}
6665
}
6766
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// template literal as module name
2+
sap.ui.predefine(`mypath/mymodule`, [`static/module1`, "static/module2" ], () => {
3+
return sap.ui.require([`static/module3`], () => { }); // template literal when loading module
4+
});
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// template literal as module name
2+
sap.ui.define(`mypath/mymodule`, [`static/module1`, "static/module2" ], () => {
3+
const i = 4;
4+
return sap.ui.require([
5+
`static/module3`,
6+
`not-detected/module${i}` // template literal with expression when loading module, not detected by the JSModuleAnalayzer
7+
], () => { });
8+
});
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// template literal as module name
2+
sap.ui.define(`mypath/mymodule`, [`static/module1`, "static/module2" ], () => {
3+
return sap.ui.require([`static/module3`], () => { }); // template literal when loading module
4+
});

0 commit comments

Comments
 (0)