Skip to content

Commit

Permalink
[INTERNAL] Create and integrate a new task 'generateLibraryManifest' (#…
Browse files Browse the repository at this point in the history
…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'
  • Loading branch information
codeworrior authored Jun 29, 2018
1 parent e9823f6 commit 000a6fe
Show file tree
Hide file tree
Showing 38 changed files with 3,100 additions and 1,796 deletions.
1 change: 1 addition & 0 deletions lib/builder/builder.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ const definedTasks = {
createDebugFiles: require("../tasks/createDebugFiles"),
uglify: require("../tasks/uglify"),
buildThemes: require("../tasks/buildThemes"),
generateLibraryManifest: require("../tasks/generateLibraryManifest"),
generateVersionInfo: require("../tasks/generateVersionInfo"),
generateManifestBundle: require("../tasks/bundlers/generateManifestBundle"),
generateFlexChangesBundle: require("../tasks/bundlers/generateFlexChangesBundle"),
Expand Down
94 changes: 94 additions & 0 deletions lib/lbt/analyzer/analyzeLibraryJS.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
"use strict";
const esprima = require("esprima");
const {Syntax} = esprima;
const {getPropertyKey, isMethodCall, isIdentifier, getStringArray} = require("../utils/ASTUtils");
const VisitorKeys = require("estraverse").VisitorKeys;

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

/*
* Static Code Analyzer that extracts library information from the sap.ui.getCore().initLibrary()
* call in a library.js module.
*/
async function analyze(resource) {
let libInfo = {
noLibraryCSS: false,
types: [],
controls: [],
elements: [],
interfaces: []
};

function visit(node) {
if ( node.type == Syntax.CallExpression
&& node.callee.type === Syntax.MemberExpression
&& isMethodCall(node.callee.object, CALL__SAP_UI_GETCORE)
&& isIdentifier(node.callee.property, "initLibrary")
&& node.arguments.length === 1
&& node.arguments[0].type === Syntax.ObjectExpression ) {
node.arguments[0].properties.forEach( (prop) => {
let key = getPropertyKey(prop);
let value = prop.value;
if ( key === "noLibraryCSS"
&& (value.type === Syntax.Literal && typeof value.value === "boolean") ) {
libInfo.noLibraryCSS = value.value;
} else if ( key === "types" && value.type == Syntax.ArrayExpression ) {
libInfo.types = getStringArray(value, true);
} else if ( key === "interfaces" && value.type == Syntax.ArrayExpression ) {
libInfo.interfaces = getStringArray(value, true);
} else if ( key === "controls" && value.type == Syntax.ArrayExpression ) {
libInfo.controls = getStringArray(value, true);
} else if ( key === "elements" && value.type == Syntax.ArrayExpression ) {
libInfo.elements = getStringArray(value, true);
}
});

return true; // abort, we're done
}

for ( let key of VisitorKeys[node.type] ) {
let child = node[key];
if ( Array.isArray(child) ) {
if ( child.some(visit) ) {
return true;
}
} else if ( child ) {
if ( visit(child) ) {
return true;
}
}
}

return false;
}

let code = await resource.getBuffer();
visit( esprima.parse(code) );

return libInfo;
}

/**
* Creates a new analyzer and executes it on the given resource.
*
* If the resources exists and can be parsed as JavaScript and if an sap.ui.getCore().initLibrary()
* call is found in the code, then the following information will be set:
* <ul>
* <li>noLibraryCSS: false when the noLibraryCSS property had been set in the initLibrary info object)</li>
* <li>types: string array with the names of the types contained in the library</li>
* <li>controls: string array with the names of the controls defined in the library</li>
* <li>elements: string array with the names of the elements defined in the library</li>
* <li>interfaces: string array with the names of the interfaces defined in the library</li>
* </ul>
*
* Note: only the first initLibrary() call that is found with a DFS on the AST, will be evaluated.
*
* @param {Resource} resource library.js resource whose content should be analyzed
* @returns {Promise<object>} A Promise on the extract info object
*/
module.exports = function(resource) {
if ( resource == null ) {
return Promise.resolve({});
}
return analyze(resource);
};
27 changes: 26 additions & 1 deletion lib/lbt/utils/ASTUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,30 @@ function getValue(obj, names) {
return obj;
}

/**
* Converts an AST node of type 'ArrayExpression' into an array of strings,
* assuming that each item in the array literal is a string literal.
*
* Depending on the parameter skipNonStringLiterals, unexpected items
* in the array are either ignored or cause the method to fail with
* a TypeError.
*
* @param {ESTree} array
* @param {boolean } skipNonStringLiterals
* @throws {TypeError}
* @returns {string[]}
*/
function getStringArray(array, skipNonStringLiterals) {
return array.elements.reduce( (result, item) => {
if ( isString(item) ) {
result.push(item.value);
} else if ( !skipNonStringLiterals ) {
throw new TypeError("array element is not a string literal:" + item.type);
}
return result;
}, []);
}

module.exports = {
isString,
isMethodCall,
Expand All @@ -89,5 +113,6 @@ module.exports = {
},
getPropertyKey,
findOwnProperty,
getValue
getValue,
getStringArray
};
Loading

0 comments on commit 000a6fe

Please sign in to comment.