diff --git a/lib/lbt/analyzer/JSModuleAnalyzer.js b/lib/lbt/analyzer/JSModuleAnalyzer.js index c3db63558..edb506caf 100644 --- a/lib/lbt/analyzer/JSModuleAnalyzer.js +++ b/lib/lbt/analyzer/JSModuleAnalyzer.js @@ -3,6 +3,7 @@ import escope from "escope"; import {fromUI5LegacyName, fromRequireJSName, resolveRelativeRequireJSName} from "../utils/ModuleName.js"; import moduleInfo from "../resources/ModuleInfo.js"; const ModuleFormat = moduleInfo.Format; +import {MODULE__JQUERY_SAP_GLOBAL, MODULE__UI5LOADER_AUTOCONFIG} from "../UI5ClientConstants.js"; import { findOwnProperty, getLocation, @@ -334,6 +335,17 @@ class JSModuleAnalyzer { } } + // depending on the used module APIs, add an implicit dependency to the loader entry module + if ( info.format === ModuleFormat.UI5_LEGACY ) { + info.addImplicitDependency(MODULE__JQUERY_SAP_GLOBAL); + } else if ( info.format === ModuleFormat.UI5_DEFINE ) { + // Note: the implicit dependency for sap.ui.define modules points to the standard UI5 + // loader config module. A more general approach would be to add a dependency to the loader + // only, but then standard configuration would be missed by dependency resolution + // (to be clarified) + info.addImplicitDependency(MODULE__UI5LOADER_AUTOCONFIG); + } + if ( !bIsUi5Module ) { // when there are no indicators for module APIs, mark the module as 'raw' module info.rawModule = true; diff --git a/lib/lbt/analyzer/XMLTemplateAnalyzer.js b/lib/lbt/analyzer/XMLTemplateAnalyzer.js index 0ac3e15e4..6121ea217 100644 --- a/lib/lbt/analyzer/XMLTemplateAnalyzer.js +++ b/lib/lbt/analyzer/XMLTemplateAnalyzer.js @@ -178,6 +178,8 @@ class XMLTemplateAnalyzer { // console.log(result); // clear(); if ( isFragment ) { + // all fragments implicitly depend on the fragment class + this.info.addImplicitDependency(FRAGMENT_MODULE); this._analyzeNode(result); } else { // views require a special handling of the root node @@ -194,6 +196,8 @@ class XMLTemplateAnalyzer { } _analyzeViewRootNode(node) { + this.info.addImplicitDependency(XMLVIEW_MODULE); + const controllerName = getAttribute(node, XMLVIEW_CONTROLLERNAME_ATTRIBUTE); if ( controllerName ) { this._addDependency( fromUI5LegacyName(controllerName, ".controller.js"), this.conditional ); diff --git a/lib/lbt/resources/ModuleInfo.js b/lib/lbt/resources/ModuleInfo.js index f05178481..aadd1e5b1 100644 --- a/lib/lbt/resources/ModuleInfo.js +++ b/lib/lbt/resources/ModuleInfo.js @@ -6,6 +6,14 @@ */ const STRICT = 0; +/** + * An implicit dependency is also strict, but has not been declared. E.g. each UI5 module depends on + * jquery.sap.global. + * + * @private + */ +const IMPLICIT = 1; + /** * A conditional dependency has to be resolved only under certain conditions that typically are * checked at runtime. @@ -104,7 +112,7 @@ class ModuleInfo { // included already as a submodule. // If the dependency was known already, update the kind // only when the new kind is stronger than the current one. - // STRICT is stronger than CONDITIONAL + // STRICT is stronger than IMPLICIT, IMPLICIT is stronger than CONDITIONAL if ( dependency && dependency !== this.name && this.subModules.indexOf(dependency) < 0 && @@ -113,6 +121,10 @@ class ModuleInfo { } } + addImplicitDependency(dependency) { + this._addDependency(dependency, IMPLICIT); + } + addDependency(dependency, conditional) { this._addDependency(dependency, conditional ? CONDITIONAL : STRICT); } @@ -163,6 +175,10 @@ class ModuleInfo { return this._dependencies[dependency] === CONDITIONAL; } + isImplicitDependency(dependency) { + return this._dependencies[dependency] === IMPLICIT; + } + get name() { return this._name; } diff --git a/lib/lbt/resources/ResourceCollector.js b/lib/lbt/resources/ResourceCollector.js index f6f5887eb..63b406101 100644 --- a/lib/lbt/resources/ResourceCollector.js +++ b/lib/lbt/resources/ResourceCollector.js @@ -124,7 +124,7 @@ class ResourceCollector { moduleInfo.dependencies.forEach((dep) => { if ( moduleInfo.isConditionalDependency(dep) ) { resourceInfo.condRequired.add(dep); - } else { + } else if ( !moduleInfo.isImplicitDependency(dep) ) { resourceInfo.required.add(dep); } }); @@ -158,7 +158,7 @@ class ResourceCollector { if (!resourceInfo.required.has(dep)) { resourceInfo.condRequired.add(dep); } - } else { + } else if ( !subModuleInfo.isImplicitDependency(dep) ) { // Move module from condRequired to required if (resourceInfo.condRequired.has(dep)) { resourceInfo.condRequired.delete(dep); diff --git a/test/lib/lbt/analyzer/JSModuleAnalyzer.js b/test/lib/lbt/analyzer/JSModuleAnalyzer.js index c605867ad..0aa2d7c05 100644 --- a/test/lib/lbt/analyzer/JSModuleAnalyzer.js +++ b/test/lib/lbt/analyzer/JSModuleAnalyzer.js @@ -13,6 +13,7 @@ const __dirname = path.dirname(__filename); const EXPECTED_MODULE_NAME = "sap/ui/testmodule.js"; const EXPECTED_DECLARE_DEPENDENCIES = [ + "jquery.sap.global.js", "top/require/void.js", "top/require/var.js", "top/require/assign.js", "top/requireSync/var.js", "top/requireSync/assign.js", "block/require/void.js", "block/require/var.js", "block/require/assign.js", "block/requireSync/var.js", "block/requireSync/assign.js", "nested/scope/require/void.js", @@ -22,10 +23,12 @@ const EXPECTED_DECLARE_DEPENDENCIES = [ ]; const EXPECTED_DEFINE_DEPENDENCIES_NO_LEGACY = [ + "ui5loader-autoconfig.js", "define/arg1.js", "define/arg2.js" ]; const EXPECTED_DEFINE_DEPENDENCIES_WITH_LEGACY = [ + "jquery.sap.global.js", "define/arg1.js", "define/arg2.js", "require/arg1.js" ]; @@ -76,13 +79,16 @@ async function analyzeModule( expectedDependencies, expectedConditionalDependencies, expectedSubmodules, + ignoreImplicitDependencies, rawModule ) { // return analyze(file, name).then( (info) => { t.is(info.name, name, "module name should match"); - const deps = info.dependencies; - + let deps = info.dependencies; + if ( ignoreImplicitDependencies ) { + deps = deps.filter((dep) => !info.isImplicitDependency(dep)); + } if ( expectedDependencies != null ) { assertModuleNamesEqual(t, deps, @@ -170,7 +176,7 @@ test("OldStyleModuleWithoutDeclare", async function(t) { t.true(info.rawModule, "raw module"); assertModuleNamesEqual(t, info.dependencies, - ["dependency1.js", "dependency2.js"], + ["dependency1.js", "dependency2.js", "jquery.sap.global.js"], "dependencies should be correct"); }); }); @@ -183,7 +189,7 @@ test("AMDSpecialDependenciesShouldBeIgnored", async (t) => { await analyzeModule(t, "modules/amd_special_dependencies.js", "modules/amd_special_dependencies.js", - ["modules/dep1.js", "dep2.js", "utils/dep1.js"], + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], [], ["utils/helper1.js", "utils/helper2.js", "utils/helper3.js"] ); @@ -193,7 +199,7 @@ test("AMDMultipleModulesFirstUnnamed", async (t) => { await analyzeModule(t, "modules/amd_multiple_modules_first_unnamed.js", "modules/amd_multiple_modules_first_unnamed.js", - ["modules/dep1.js", "dep2.js", "utils/dep1.js"], + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], [], ["utils/helper1.js", "utils/helper2.js"] ); @@ -203,7 +209,7 @@ test("AMDMultipleModulesOtherThanFirstOneUnnamed", async (t) => { await analyzeModule(t, "modules/amd_multiple_modules_other_than_first_one_unnamed.js", "modules/amd_multiple_modules_other_than_first_one_unnamed.js", - ["modules/dep1.js", "dep2.js", "utils/dep1.js"], + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], [], ["utils/helper1.js", "utils/helper2.js"] ); @@ -213,7 +219,7 @@ test("AMDMultipleNamedModulesNoneMatchingFileName", async (t) => { await analyzeModule(t, "modules/amd_multiple_named_modules_none_matching_filename.js", "modules/amd_multiple_named_modules_none_matching_filename.js", - ["dep2.js", "utils/dep1.js"], + ["dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], [], ["utils/helper1.js", "utils/helper2.js", "utils/helper3.js"] ); @@ -223,7 +229,7 @@ test("AMDMultipleNamedModulesOneMatchingFileName", async (t) => { await analyzeModule(t, "modules/amd_multiple_named_modules_one_matching_filename.js", "modules/amd_multiple_named_modules_one_matching_filename.js", - ["modules/dep1.js", "dep2.js", "utils/dep1.js"], + ["modules/dep1.js", "dep2.js", "utils/dep1.js", "ui5loader-autoconfig.js"], [], ["utils/helper1.js", "utils/helper2.js"] ); @@ -243,7 +249,7 @@ test("AMDSingleNamedModule", async (t) => { await analyzeModule(t, "modules/amd_single_named_module.js", "alternative/name.js", - ["alternative/dep1.js", "dep2.js"], + ["alternative/dep1.js", "dep2.js", "ui5loader-autoconfig.js"], [], [] ); @@ -253,7 +259,7 @@ test("AMDSingleUnnamedModule", async (t) => { await analyzeModule(t, "modules/amd_single_unnamed_module.js", "modules/amd_single_unnamed_module.js", - ["modules/dep1.js", "dep2.js"], + ["modules/dep1.js", "dep2.js", "ui5loader-autoconfig.js"], [], [] ); @@ -347,7 +353,8 @@ test("OldStyleBundle", async (t) => { "sap/ui/base/ManagedObject.js", "sap/ui/core/Core.js", "sap/ui/thirdparty/jquery-mobile-custom.js" - ] + ], + /* ignoreImplicitDependencies: */ true ); }); @@ -408,7 +415,8 @@ test("OldStyleBundleV2", async (t) => { "sap/ui/base/ManagedObject.js", "sap/ui/core/Core.js", "sap/ui/thirdparty/jquery-mobile-custom.js" - ] + ], + /* ignoreImplicitDependencies: */ true ); }); @@ -490,7 +498,8 @@ test("EvoBundle", async (t) => { "sap/ui/Device.js", "sap/ui/thirdparty/jquery.js", "sap/ui/thirdparty/jquery-mobile-custom.js" - ] + ], + /* ignoreImplicitDependencies: */ true ); }); @@ -538,13 +547,16 @@ test("ES6 Syntax", async (t) => { "static/module5.js", "static/module6.js", "static/module7.js", - "static/module8.js" + "static/module8.js", + "ui5loader-autoconfig.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -557,13 +569,16 @@ test("ES6 Syntax (with dynamic dependencies)", async (t) => { "modules/es6-syntax-dynamic-dependencies.js", "modules/es6-syntax-dynamic-dependencies.js"); const expected = [ - "static/module1.js" + "static/module1.js", + "ui5loader-autoconfig.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.true(info.dynamicDependencies, `use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -574,13 +589,16 @@ test("ES6 Syntax (with dynamic dependencies)", async (t) => { test("ES6 Async Module", async (t) => { const info = await analyze("modules/es6-async-module.js", "modules/es6-async-module.js"); const expected = [ - "static/module1.js" + "static/module1.js", + "ui5loader-autoconfig.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -593,13 +611,16 @@ test("ES6 Template Literal", async (t) => { const expected = [ "static/module1.js", "static/module2.js", - "static/module3.js" + "static/module3.js", + "ui5loader-autoconfig.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -613,13 +634,16 @@ test("ES6 Template Literal with Expression", async (t) => { const expected = [ "static/module1.js", "static/module2.js", - "static/module3.js" + "static/module3.js", + "ui5loader-autoconfig.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.true(info.dynamicDependencies, `use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -633,13 +657,16 @@ test("ES6 Template Literal in sap.ui.predefine", async (t) => { const expected = [ "static/module1.js", "static/module2.js", - "static/module3.js" + "static/module3.js", + "ui5loader-autoconfig.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -662,6 +689,7 @@ test("ChainExpression", (t) => { "conditional/module3.js", "conditional/module4.js", "conditional/module5.js", + "jquery.sap.global.js", "static/module1.js", ]; const actual = info.dependencies.sort(); @@ -669,6 +697,8 @@ test("ChainExpression", (t) => { expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); }); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected`); @@ -696,6 +726,7 @@ test("LogicalExpression", (t) => { "conditional/module3.js", "conditional/module4.js", "conditional/module5.js", + "jquery.sap.global.js", "static/module1.js", "static/module2.js", "static/module3.js", @@ -707,6 +738,8 @@ test("LogicalExpression", (t) => { expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); }); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected`); @@ -761,13 +794,16 @@ test("ES2022: PrivateIdentifier, PropertyDefinition, StaticBlock", (t) => { "static/module3.js", "static/module4.js", "static/module5.js", - "static/module6.js" + "static/module6.js", + "ui5loader-autoconfig.js", ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); }); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected`); @@ -788,13 +824,16 @@ test("Conditional import (declare/require)", async (t) => { "modules/declare_require_conditional.js"); const expected = [ "conditional/module1.js", - "conditional/module2.js" + "conditional/module2.js", + "jquery.sap.global.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.false(info.dynamicDependencies, `no use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -806,13 +845,16 @@ test("Dynamic import (declare/require/conditional)", async (t) => { const info = await analyze("modules/declare_dynamic_require_conditional.js", "modules/declare_dynamic_require_conditional.js"); const expected = [ - "conditional/module1.js" + "conditional/module1.js", + "jquery.sap.global.js" ]; const actual = info.dependencies.sort(); t.deepEqual(actual, expected, "module dependencies should match"); expected.forEach((dep) => { t.is(info.isConditionalDependency(dep), /^conditional\//.test(dep), `only dependencies to 'conditional/*' modules should be conditional (${dep})`); + t.is(info.isImplicitDependency(dep), !/^(?:conditional|static)\//.test(dep), + `all dependencies other than 'conditional/*' and 'static/*' should be implicit (${dep})`); t.true(info.dynamicDependencies, `use of dynamic dependencies should have been detected (${dep})`); t.false(info.rawModule, @@ -1017,6 +1059,7 @@ sap.ui.define([], function() { t.is(info.requiresTopLevelScope, false); t.deepEqual(info.subModules, ["foo/bar.js"], "jQuery.sap.declare subModule should be detected"); + t.deepEqual(info.dependencies, ["jquery.sap.global.js"], "Implicit dependency"); }); test("Bundle that contains jQuery.sap.declare (sap.ui.predefine) should not be derived as module name", (t) => { @@ -1033,6 +1076,7 @@ sap.ui.predefine("test1/module1", [], function() { // Note: foo/bar.js is not listed as the predefine body is not analyzed t.deepEqual(info.subModules, ["test1/module1.js"], "subModule via sap.ui.predefine should be detected"); + t.deepEqual(info.dependencies, ["jquery.sap.global.js"], "Implicit dependency"); }); test("Bundle that contains jQuery.sap.declare (sap.ui.require.preload) should not be derived as module name", (t) => { @@ -1054,6 +1098,7 @@ sap.ui.require.preload({ // Note: foo/bar.js is not listed as the sap.ui.define body is not analyzed t.deepEqual(info.subModules, ["test1/module1.js"], "subModule via sap.ui.predefine should be detected"); + t.deepEqual(info.dependencies, ["ui5loader-autoconfig.js"], "Implicit dependency"); }); test("@ui5-bundle comment: Multiple comments", (t) => { diff --git a/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js b/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js index b686025a9..1bcee4421 100644 --- a/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js +++ b/test/lib/lbt/analyzer/XMLTemplateAnalyzer.js @@ -30,10 +30,13 @@ test("integration: Analysis of an xml view", async (t) => { await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "myController.controller.js", "sap/ui/layout/HorizontalLayout.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); }); test("integration: Analysis of an xml view with data binding in properties", async (t) => { @@ -48,9 +51,12 @@ test("integration: Analysis of an xml view with data binding in properties", asy await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "myController.controller.js", "sap/ui/core/ComponentContainer.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); }); test.serial("integration: Analysis of an xml view with core:require from databinding", async (t) => { @@ -94,12 +100,16 @@ test.serial("integration: Analysis of an xml view with core:require from databin await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "my/lib/theController.controller.js", "sap/m/HBox.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); t.true( - !moduleInfo.isConditionalDependency("sap/m/Button.js"), + !moduleInfo.isConditionalDependency("sap/m/Button.js") && + !moduleInfo.isImplicitDependency("sap/m/Button.js"), "A control outside of template:if should become a strict dependency"); t.is(errorLogStub.callCount, 1, "should be called 1 time"); @@ -146,8 +156,11 @@ test.serial("integration: Analysis of an xml view with core:require from databin await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); t.true(moduleInfo.isConditionalDependency("sap/m/Button.js"), "A control within template:if or template:repeat should become a conditional dependency"); @@ -191,8 +204,11 @@ test.serial("integration: Analysis of an xml view with core:require from express await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); t.true(moduleInfo.isConditionalDependency("sap/m/Button.js"), "A control within template:if should become a conditional dependency"); @@ -221,12 +237,15 @@ test("integration: Analysis of an xml view with core:require", async (t) => { await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "myController.controller.js", "sap/ui/Foo.js", "myApp/Bar.js", "sap/m/MessageToast.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); }); test("integration: Analysis of an xml view with core:require (invalid module name)", async (t) => { @@ -247,9 +266,12 @@ test("integration: Analysis of an xml view with core:require (invalid module nam await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "myController.controller.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); }); test("integration: Analysis of an xml view with core:require (missing comma, parsing error)", async (t) => { @@ -270,9 +292,12 @@ test("integration: Analysis of an xml view with core:require (missing comma, par await analyzer.analyzeView(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/mvc/XMLView.js", "myController.controller.js", "sap/m/Button.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "Implicit dependency should be added since an XMLView is analyzed"); }); test("integration: Analysis of an xml view with nested views", async (t) => { @@ -326,6 +351,8 @@ test("integration: Analysis of an xml view with nested views", async (t) => { "myapp/views/MyTemplateView2.view.tmpl", "myapp/views/MyHTMLView2.view.html" ], "Dependencies should come from the XML template"); + t.false(moduleInfo.isImplicitDependency("sap/ui/core/mvc/XMLView.js"), + "XMLView is a strict dependency as the XMLView also has a nested XMLView"); }); test("integration: Analysis of an xml fragment", async (t) => { @@ -345,9 +372,12 @@ test("integration: Analysis of an xml fragment", async (t) => { await analyzer.analyzeFragment(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/Fragment.js", "sap/ui/layout/HorizontalLayout.js", "sap/m/Button.js" ]); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/Fragment.js"), + "Implicit dependency should be added since a fragment is analyzed"); }); test("integration: Analysis of an xml fragment with core:require", async (t) => { @@ -363,8 +393,11 @@ test("integration: Analysis of an xml fragment with core:require", async (t) => await analyzer.analyzeFragment(xml, moduleInfo); t.deepEqual(moduleInfo.dependencies, [ + "sap/ui/core/Fragment.js", "sap/m/MessageToast.js" ], "Dependencies should come from the XML template"); + t.true(moduleInfo.isImplicitDependency("sap/ui/core/Fragment.js"), + "Implicit dependency should be added since an XML Fragment is analyzed"); }); test("integration: Analysis of an empty xml view", async (t) => { @@ -424,7 +457,9 @@ test("_analyze: call twice to simulate busy", async (t) => { sinon.stub(analyzer._parser, "parseString").callsArgWith(1, false, "parse-result"); sinon.stub(analyzer, "_analyzeNode").returns(); - const moduleInfo = {}; + const moduleInfo = { + addImplicitDependency: function() {} + }; // first call sets it to busy const resultPromise = analyzer._analyze(null, moduleInfo, true); @@ -444,13 +479,20 @@ test("_analyze: node", async (t) => { sinon.stub(analyzer._parser, "parseString").callsArgWith(1, false, "parse-result"); const stubAnalyzeNode = sinon.stub(analyzer, "_analyzeNode").returns(); - const moduleInfo = {}; + const moduleInfo = { + addImplicitDependency: function() {} + }; + const stubAddImplicitDependency = sinon.spy(moduleInfo, "addImplicitDependency"); await analyzer._analyze(null, moduleInfo, true); t.true(stubAnalyzeNode.calledOnce, "_analyzeNode was called"); t.is(stubAnalyzeNode.getCall(0).args[0], "parse-result", "_analyzeNode should be called with the result"); + + t.true(stubAddImplicitDependency.calledOnce, "addImplicitDependency was called once"); + t.is(stubAddImplicitDependency.getCall(0).args[0], "sap/ui/core/Fragment.js", + "addImplicitDependency should be called with the dependency name"); }); test("_analyze: viewRootNode", async (t) => { @@ -470,8 +512,10 @@ test("_analyze: viewRootNode", async (t) => { test("_analyzeViewRootNode: process node", async (t) => { const analyzer = new XMLTemplateAnalyzer(); analyzer.info = { + addImplicitDependency: function() {}, addDependency: function() {} }; + const stubAddImplicitDependency = sinon.spy(analyzer.info, "addImplicitDependency"); const stubAddDependency = sinon.spy(analyzer.info, "addDependency"); const stubAnalyzeChildren = sinon.stub(analyzer, "_analyzeChildren").returns(); @@ -492,6 +536,10 @@ test("_analyzeViewRootNode: process node", async (t) => { t.deepEqual(stubAnalyzeChildren.getCall(0).args[0], node, "_analyzeChildren should be called with the result"); + t.true(stubAddImplicitDependency.calledOnce, "addImplicitDependency was called"); + t.is(stubAddImplicitDependency.getCall(0).args[0], "sap/ui/core/mvc/XMLView.js", + "addImplicitDependency should be called with the dependency name"); + t.is(stubAddDependency.callCount, 2, "addDependency was called twice"); t.is(stubAddDependency.getCall(0).args[0], "myController.controller.js", "addDependency should be called with the dependency name"); @@ -502,8 +550,10 @@ test("_analyzeViewRootNode: process node", async (t) => { test("_analyzeCoreRequire: Catches error when attribute can't be parsed", async (t) => { const analyzer = new XMLTemplateAnalyzer(); analyzer.info = { + addImplicitDependency: function() {}, addDependency: function() {} }; + const stubAddImplicitDependency = sinon.spy(analyzer.info, "addImplicitDependency"); const stubAddDependency = sinon.spy(analyzer.info, "addDependency"); const node = { @@ -522,6 +572,7 @@ test("_analyzeCoreRequire: Catches error when attribute can't be parsed", async }; await analyzer._analyzeCoreRequire(node); + t.is(stubAddImplicitDependency.callCount, 0, "addImplicitDependency was never called"); t.is(stubAddDependency.callCount, 0, "addDependency was never called"); }); diff --git a/test/lib/lbt/resources/ResourceCollector.js b/test/lib/lbt/resources/ResourceCollector.js index d5c743987..b7201d7ed 100644 --- a/test/lib/lbt/resources/ResourceCollector.js +++ b/test/lib/lbt/resources/ResourceCollector.js @@ -272,8 +272,11 @@ test.serial("enrichWithDependencyInfo: add infos to resourceinfo", async (t) => isConditionalDependency: (dep) => { return dep.includes("conditional"); }, + isImplicitDependency: (dep) => { + return dep.includes("implicit"); + }, dependencies: [ - "mydependency.conditional", "mydependency" + "mydependency.conditional", "mydependency.implicit", "mydependency" ], subModules: [ "mySubmodule" @@ -431,7 +434,8 @@ test.serial("integration: Analyze debug bundle", async (t) => { t.is(myRawModuleBundle.requiresTopLevelScope, false); t.deepEqual(myRawModuleBundle.included, new Set(["a.js", "b.js"])); - t.is(myRawModuleBundle.required, null); + t.deepEqual(myRawModuleBundle.required, + new Set([])); const myRawModuleBundleDbg = resourceInfoList.resourcesByName.get("myBundle-dbg.js"); t.is(myRawModuleBundleDbg.name, "myBundle-dbg.js"); @@ -440,5 +444,6 @@ test.serial("integration: Analyze debug bundle", async (t) => { t.is(myRawModuleBundleDbg.requiresTopLevelScope, false); t.deepEqual(myRawModuleBundleDbg.included, new Set(["a.js"])); - t.is(myRawModuleBundleDbg.required, null); + t.deepEqual(myRawModuleBundleDbg.required, + new Set()); }); diff --git a/test/lib/lbt/resources/ResourcePool.js b/test/lib/lbt/resources/ResourcePool.js index f0570e8f4..6485dd600 100644 --- a/test/lib/lbt/resources/ResourcePool.js +++ b/test/lib/lbt/resources/ResourcePool.js @@ -325,7 +325,7 @@ test.serial("addResource: library and eval raw module info", async (t) => { const actualResourceB = await resourcePool.findResourceWithInfo("moduleB.js"); t.true(actualResourceB.info instanceof ModuleInfo); - t.deepEqual(actualResourceB.info.dependencies, ["moduleC.js", "456.js"], + t.deepEqual(actualResourceB.info.dependencies, ["moduleC.js", "jquery.sap.global.js", "456.js"], "dependencies from analsyis and raw info should have been merged"); t.true(actualResourceB.info.requiresTopLevelScope); t.deepEqual(actualResourceB.info.exposedGlobals, ["foo", "bar", "some"],