Skip to content

Commit 000a6fe

Browse files
authored
[INTERNAL] Create and integrate a new task 'generateLibraryManifest' (#26)
The task creates a manifest.json file for libraries that don't have one. It collects the necessary information from the project structure (supported themes, versions of dependencies), from the .library file (name, dependencies, supported themes, thirdparty components, i18n information...) and from an initLibrary call inside the library.js file (implemented controls, elements, types and interfaces, noLibraryCSS flag). An existing manifest.json won't be modified or overwritten. - refactor test scenario 'library.h' so that the sub-components reside in the namespace of the library - add new test scenario 'library.i' to test manifest creation with data from .library and library.js file - add the newly generated manifest.json to the expected content of the other library test scenarios - enhance all library test scenarios with a dependency to a fake 'sap.ui.core' lib. For UI5 libs, this dependency is mandatory and manifest generation needs it to determine the UI5 version - re-classify log message for missing .library file as 'verbose'
1 parent e9823f6 commit 000a6fe

File tree

38 files changed

+3100
-1796
lines changed

38 files changed

+3100
-1796
lines changed

lib/builder/builder.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const definedTasks = {
88
createDebugFiles: require("../tasks/createDebugFiles"),
99
uglify: require("../tasks/uglify"),
1010
buildThemes: require("../tasks/buildThemes"),
11+
generateLibraryManifest: require("../tasks/generateLibraryManifest"),
1112
generateVersionInfo: require("../tasks/generateVersionInfo"),
1213
generateManifestBundle: require("../tasks/bundlers/generateManifestBundle"),
1314
generateFlexChangesBundle: require("../tasks/bundlers/generateFlexChangesBundle"),
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
"use strict";
2+
const esprima = require("esprima");
3+
const {Syntax} = esprima;
4+
const {getPropertyKey, isMethodCall, isIdentifier, getStringArray} = require("../utils/ASTUtils");
5+
const VisitorKeys = require("estraverse").VisitorKeys;
6+
7+
const CALL__SAP_UI_GETCORE = ["sap", "ui", "getCore"];
8+
9+
/*
10+
* Static Code Analyzer that extracts library information from the sap.ui.getCore().initLibrary()
11+
* call in a library.js module.
12+
*/
13+
async function analyze(resource) {
14+
let libInfo = {
15+
noLibraryCSS: false,
16+
types: [],
17+
controls: [],
18+
elements: [],
19+
interfaces: []
20+
};
21+
22+
function visit(node) {
23+
if ( node.type == Syntax.CallExpression
24+
&& node.callee.type === Syntax.MemberExpression
25+
&& isMethodCall(node.callee.object, CALL__SAP_UI_GETCORE)
26+
&& isIdentifier(node.callee.property, "initLibrary")
27+
&& node.arguments.length === 1
28+
&& node.arguments[0].type === Syntax.ObjectExpression ) {
29+
node.arguments[0].properties.forEach( (prop) => {
30+
let key = getPropertyKey(prop);
31+
let value = prop.value;
32+
if ( key === "noLibraryCSS"
33+
&& (value.type === Syntax.Literal && typeof value.value === "boolean") ) {
34+
libInfo.noLibraryCSS = value.value;
35+
} else if ( key === "types" && value.type == Syntax.ArrayExpression ) {
36+
libInfo.types = getStringArray(value, true);
37+
} else if ( key === "interfaces" && value.type == Syntax.ArrayExpression ) {
38+
libInfo.interfaces = getStringArray(value, true);
39+
} else if ( key === "controls" && value.type == Syntax.ArrayExpression ) {
40+
libInfo.controls = getStringArray(value, true);
41+
} else if ( key === "elements" && value.type == Syntax.ArrayExpression ) {
42+
libInfo.elements = getStringArray(value, true);
43+
}
44+
});
45+
46+
return true; // abort, we're done
47+
}
48+
49+
for ( let key of VisitorKeys[node.type] ) {
50+
let child = node[key];
51+
if ( Array.isArray(child) ) {
52+
if ( child.some(visit) ) {
53+
return true;
54+
}
55+
} else if ( child ) {
56+
if ( visit(child) ) {
57+
return true;
58+
}
59+
}
60+
}
61+
62+
return false;
63+
}
64+
65+
let code = await resource.getBuffer();
66+
visit( esprima.parse(code) );
67+
68+
return libInfo;
69+
}
70+
71+
/**
72+
* Creates a new analyzer and executes it on the given resource.
73+
*
74+
* If the resources exists and can be parsed as JavaScript and if an sap.ui.getCore().initLibrary()
75+
* call is found in the code, then the following information will be set:
76+
* <ul>
77+
* <li>noLibraryCSS: false when the noLibraryCSS property had been set in the initLibrary info object)</li>
78+
* <li>types: string array with the names of the types contained in the library</li>
79+
* <li>controls: string array with the names of the controls defined in the library</li>
80+
* <li>elements: string array with the names of the elements defined in the library</li>
81+
* <li>interfaces: string array with the names of the interfaces defined in the library</li>
82+
* </ul>
83+
*
84+
* Note: only the first initLibrary() call that is found with a DFS on the AST, will be evaluated.
85+
*
86+
* @param {Resource} resource library.js resource whose content should be analyzed
87+
* @returns {Promise<object>} A Promise on the extract info object
88+
*/
89+
module.exports = function(resource) {
90+
if ( resource == null ) {
91+
return Promise.resolve({});
92+
}
93+
return analyze(resource);
94+
};

lib/lbt/utils/ASTUtils.js

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,30 @@ function getValue(obj, names) {
7979
return obj;
8080
}
8181

82+
/**
83+
* Converts an AST node of type 'ArrayExpression' into an array of strings,
84+
* assuming that each item in the array literal is a string literal.
85+
*
86+
* Depending on the parameter skipNonStringLiterals, unexpected items
87+
* in the array are either ignored or cause the method to fail with
88+
* a TypeError.
89+
*
90+
* @param {ESTree} array
91+
* @param {boolean } skipNonStringLiterals
92+
* @throws {TypeError}
93+
* @returns {string[]}
94+
*/
95+
function getStringArray(array, skipNonStringLiterals) {
96+
return array.elements.reduce( (result, item) => {
97+
if ( isString(item) ) {
98+
result.push(item.value);
99+
} else if ( !skipNonStringLiterals ) {
100+
throw new TypeError("array element is not a string literal:" + item.type);
101+
}
102+
return result;
103+
}, []);
104+
}
105+
82106
module.exports = {
83107
isString,
84108
isMethodCall,
@@ -89,5 +113,6 @@ module.exports = {
89113
},
90114
getPropertyKey,
91115
findOwnProperty,
92-
getValue
116+
getValue,
117+
getStringArray
93118
};

0 commit comments

Comments
 (0)