Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New options to better check Aria Templates dependencies. #4

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 61 additions & 21 deletions lib/ATGetDependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

var UglifyJS = require("uglify-js");
var grunt = require('./grunt').grunt();
var findGlobals = require('./findGlobals');

var acceptedAriaMethods = {
'classDefinition' : 1,
Expand Down Expand Up @@ -43,25 +44,45 @@ var getLogicalPath = {
TXT : createGetLogicalPathFn('.tpl.txt')
};

var appendString = function (stringLitteral, mapFunction, array) {
var removeResolvedGlobals = function (globals, classpath) {
var useful = false;
for (var i = globals.length - 1; i >= 0; i--) {
var curGlobal = globals[i];
if ((curGlobal.length == classpath.length || curGlobal.charAt(classpath.length) == ".") &&
curGlobal.substring(0, classpath.length) == classpath) {
useful = true;
globals.splice(i, 1);
}
}
return useful;
};

var appendString = function (stringLitteral, mapFunction, state, alwaysUseful) {
if (stringLitteral instanceof UglifyJS.AST_String) {
var value = mapFunction(stringLitteral.value);
array.push(value);
var classpath = stringLitteral.value;
var value = mapFunction(classpath);
state.declaredDependencies.push(value);
if (state.unresolvedGlobals) {
var useful = removeResolvedGlobals(state.unresolvedGlobals, classpath) || alwaysUseful;
if (!useful) {
state.uselessDependencies.push(value);
}
}
} else {
reportError('Expected a string litteral.', stringLitteral);
}
};

var appendMappedStringLitterals = function (stringLitterals, mapFunction, array) {
var appendMappedStringLitterals = function (stringLitterals, mapFunction, state, alwaysUseful) {
for (var i = 0, l = stringLitterals.length; i < l; i++) {
appendString(stringLitterals[i], mapFunction, array);
appendString(stringLitterals[i], mapFunction, state, alwaysUseful);
}
};

var appendMappedMapValueStringLitterals = function (object, mapFunction, array) {
var appendMappedMapValueStringLitterals = function (object, mapFunction, state, alwaysUseful) {
var properties = object.properties;
for (var i = 0, l = properties.length; i < l; i++) {
appendString(properties[i].value, mapFunction, array);
appendString(properties[i].value, mapFunction, state, alwaysUseful);
}
};

Expand Down Expand Up @@ -99,29 +120,34 @@ var checkAriaDefinition = function (walker) {
return false;
};

var handleArray = function (mapFunction) {
return function (value, res, walker) {
var handleArray = function (mapFunction, alwaysUseful) {
return function (value, state, walker) {
if (value instanceof UglifyJS.AST_Array) {
appendMappedStringLitterals(value.elements, mapFunction, res);
appendMappedStringLitterals(value.elements, mapFunction, state, alwaysUseful);
} else {
reportError('Expected an array litteral', walker.self());
}
};
};

var handleMap = function (mapFunction) {
return function (value, res, walker) {
return function (value, state, walker) {
if (value instanceof UglifyJS.AST_Object) {
appendMappedMapValueStringLitterals(value, mapFunction, res);
appendMappedMapValueStringLitterals(value, mapFunction, state, true);
} else {
reportError('Expected an object litteral', walker.self());
}
};
};

var processNames = {
'$classpath' : function (value, state) {
if (value instanceof UglifyJS.AST_String && state.unresolvedGlobals) {
removeResolvedGlobals(state.unresolvedGlobals, value.value);
}
},
'$dependencies' : handleArray(getLogicalPath.JS),
'$extends' : function (value, res, walker) {
'$extends' : function (value, state, walker) {
var extendsType;
var extendsTypeValue = findMapValue(walker.parent(0), '$extendsType');
if (extendsTypeValue && extendsTypeValue instanceof UglifyJS.AST_String) {
Expand All @@ -130,12 +156,12 @@ var processNames = {
if (!getLogicalPath.hasOwnProperty(extendsType)) {
extendsType = "JS";
}
appendString(value, getLogicalPath[extendsType], res);
appendString(value, getLogicalPath[extendsType], state, true);
},
'$implements' : handleArray(getLogicalPath.JS),
'$implements' : handleArray(getLogicalPath.JS, true),
'$namespaces' : handleMap(getLogicalPath.JS),
'$css' : handleArray(getLogicalPath.CSS),
'$templates' : handleArray(getLogicalPath.TPL),
'$css' : handleArray(getLogicalPath.CSS, true),
'$templates' : handleArray(getLogicalPath.TPL, true),
'$texts' : handleMap(getLogicalPath.TXT)
};

Expand All @@ -144,17 +170,31 @@ var processNames = {
* @param {Object} ast uglify-js abstract syntax tree
* @return {Array} array of dependencies
*/
var getATDependencies = function (ast) {
var res = [];
var getATDependencies = function (ast, options) {
options = options || {};
var state = {
declaredDependencies : []
};
if (options.checkGlobals !== false) {
state.unresolvedGlobals = findGlobals(ast, {
ignoreBuiltin : options.ignoreBuiltinGlobals,
includesWith : options.includesWithGlobals
});
state.uselessDependencies = [];
var resolvedGlobals = (options.resolvedGlobals || []).concat("Aria");
for (var i = 0, l = resolvedGlobals.length; i < l; i++) {
removeResolvedGlobals(state.unresolvedGlobals, resolvedGlobals[i]);
}
}
var walker = new UglifyJS.TreeWalker(function (node) {
if (node instanceof UglifyJS.AST_ObjectProperty && processNames.hasOwnProperty(node.key)) {
if (checkAriaDefinition(walker)) {
processNames[node.key](node.value, res, walker);
processNames[node.key](node.value, state, walker);
}
}
});
ast.walk(walker);
return res;
return state;
};

module.exports = getATDependencies;
92 changes: 92 additions & 0 deletions lib/findGlobals.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright 2013 Amadeus s.a.s.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

var UglifyJS = require("uglify-js");

var inContext = function () {
var global = this;
var undef;
return function (name) {
return global[name] !== undef;
};
};

var isVarBuiltin = function (name) {
var vm = require("vm");
// run in an empty context to know which are the built-in globals
isVarBuiltin = vm.runInNewContext("(" + inContext + ")()");
return isVarBuiltin(name);
};

var isUndeclaredSymbolRef = function (node, walker, options) {
if (!(node instanceof UglifyJS.AST_SymbolRef && node.thedef.undeclared)) {
return false;
}
if (node.name == "arguments" && walker.find_parent(UglifyJS.AST_Lambda)) {
return false;
}
if (options.ignoreBuiltin && isVarBuiltin(node.name)) {
return false;
}
return true;
};

var matchStart = function (testName) {
return function (acceptedPrefix) {
return (testName.length == acceptedPrefix.length || testName.charAt(acceptedPrefix.length) == ".") &&
testName.substring(0, acceptedPrefix.length) == acceptedPrefix;
};
};

var checkWith = function (name, walker, i, options) {
if (options.includesWith === true) {
return true;
}
var stack = walker.stack;
for (var j = i; j >= 0; j--) {
var curItem = stack[j];
if (curItem instanceof UglifyJS.AST_With && curItem.body === stack[j + 1]) {
if (Array.isArray(options.includesWith)) {
return options.includesWith.some(matchStart(name));
}
return false;
}
}
// not in a with (...) {...} structure
return true;
};

module.exports = function (ast, options) {
options = options || {};
var res = {};
ast.figure_out_scope();
var walker = new UglifyJS.TreeWalker(function (node) {
if (isUndeclaredSymbolRef(node, walker, options)) {
var stack = walker.stack;
var i = stack.length - 2;
while (i >= 0 && stack[i] instanceof UglifyJS.AST_Dot) {
i--;
}
i++;
var wholeProperty = stack[i];
var name = wholeProperty.print_to_string();
if (checkWith(name, walker, i, options)) {
res[name] = true;
}
}
});
ast.walk(walker);
return Object.keys(res).sort();
};
28 changes: 26 additions & 2 deletions lib/visitors/ATDependencies.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,18 @@ var ATDependencies = function (cfg) {
this.files = cfg.files || ['**/*'];
this.mustExist = cfg.hasOwnProperty('mustExist') ? cfg.mustExist : true;
this.externalDependencies = cfg.hasOwnProperty('externalDependencies') ? cfg.externalDependencies : [];
this.unresolvedGlobalError = cfg.hasOwnProperty('unresolvedGlobalError') ? cfg.unresolvedGlobalError : false;
this.unresolvedGlobalWarning = cfg.hasOwnProperty('unresolvedGlobalWarning') ? cfg.unresolvedGlobalWarning : true;
this.uselessDependencyError = cfg.hasOwnProperty('uselessDependencyError') ? cfg.uselessDependencyError : false;
this.uselessDependencyWarning = cfg.hasOwnProperty('uselessDependencyWarning') ? cfg.uselessDependencyWarning
: false;
this.atDependenciesOptions = {
ignoreBuiltinGlobals : cfg.hasOwnProperty('ignoreBuiltinGlobals') ? cfg.ignoreBuiltinGlobals : true,
includesWithGlobals : cfg.hasOwnProperty('includesWithGlobals') ? cfg.includesWithGlobals : false,
resolvedGlobals : cfg.hasOwnProperty('resolvedGlobals') ? cfg.resolvedGlobals : [],
checkGlobals : this.unresolvedGlobalError || this.unresolvedGlobalWarning || this.uselessDependencyError ||
this.uselessDependencyWarning
};
};

ATDependencies.prototype.computeDependencies = function (packaging, inputFile) {
Expand All @@ -46,8 +58,20 @@ ATDependencies.prototype.computeDependencies = function (packaging, inputFile) {
var externalDependencies = this.externalDependencies;
var ast = uglifyContentProvider.getAST(inputFile, jsStringContent);
if (ast) {
var dependencies = atGetDependencies(ast);
dependencies.forEach(function (dependency) {
var logMethod;
var depInfo = atGetDependencies(ast, this.atDependenciesOptions);
if ((this.unresolvedGlobalWarning || this.unresolvedGlobalError) && depInfo.unresolvedGlobals.length > 0) {
logMethod = this.unresolvedGlobalError ? "error" : "warn";
grunt.log[logMethod](inputFile.logicalPath.yellow + " uses the following unresolved globals:\n - " +
depInfo.unresolvedGlobals.join("\n - "));
}
if ((this.uselessDependencyError || this.uselessDependencyWarning) && depInfo.uselessDependencies.length > 0) {
logMethod = this.unresolvedGlobalError ? "error" : "warn";
grunt.log[logMethod](inputFile.logicalPath.yellow +
" depends on the following files without using them (apparently):\n - " +
depInfo.uselessDependencies.join("\n - "));
}
depInfo.declaredDependencies.forEach(function (dependency) {
var correspondingFile = packaging.getSourceFile(dependency);
if (correspondingFile) {
inputFile.addDependency(correspondingFile);
Expand Down