612 lines
18 KiB
JavaScript
612 lines
18 KiB
JavaScript
'use strict';
|
|
var falseConfigValues = require('./false-values').config;
|
|
var falseRunValues = require('./false-values').run;
|
|
|
|
var scopeProperties = [
|
|
'$id',
|
|
'$parent',
|
|
'$root',
|
|
'$destroy',
|
|
'$broadcast',
|
|
'$emit',
|
|
'$on',
|
|
'$applyAsync',
|
|
'$apply',
|
|
'$evalAsync',
|
|
'$eval',
|
|
'$digest',
|
|
'$watchCollection',
|
|
'$watchGroup',
|
|
'$watch',
|
|
'$new'
|
|
];
|
|
|
|
|
|
module.exports = {
|
|
// Properties
|
|
scopeProperties: scopeProperties,
|
|
|
|
// Functions
|
|
convertPrefixToRegex: convertPrefixToRegex,
|
|
convertStringToRegex: convertStringToRegex,
|
|
isTypeOfStatement: isTypeOfStatement,
|
|
isToStringStatement: isToStringStatement,
|
|
isArrayType: isArrayType,
|
|
isFunctionType: isFunctionType,
|
|
isNamedInlineFunction: isNamedInlineFunction,
|
|
isIdentifierType: isIdentifierType,
|
|
isMemberExpression: isMemberExpression,
|
|
isLiteralType: isLiteralType,
|
|
isCallExpression: isCallExpression,
|
|
isEmptyFunction: isEmptyFunction,
|
|
isStringRegexp: isStringRegexp,
|
|
isAngularComponent: isAngularComponent,
|
|
isAngularComponentDeclaration: isAngularComponentDeclaration,
|
|
isAngularControllerDeclaration: isAngularControllerDeclaration,
|
|
isAngularFilterDeclaration: isAngularFilterDeclaration,
|
|
isAngularDirectiveDeclaration: isAngularDirectiveDeclaration,
|
|
isAngularServiceDeclarationDeprecated: isAngularServiceDeclarationDeprecated,
|
|
isAngularServiceDeclaration: isAngularServiceDeclaration,
|
|
isAngularProviderDeclaration: isAngularProviderDeclaration,
|
|
isAngularFactoryDeclaration: isAngularFactoryDeclaration,
|
|
isAngularConstantDeclaration: isAngularConstantDeclaration,
|
|
isAngularValueDeclaration: isAngularValueDeclaration,
|
|
isAngularModuleDeclaration: isAngularModuleDeclaration,
|
|
isAngularModuleGetter: isAngularModuleGetter,
|
|
isAngularRunSection: isAngularRunSection,
|
|
isAngularConfigSection: isAngularConfigSection,
|
|
isRouteDefinition: isRouteDefinition,
|
|
isUIRouterStateDefinition: isUIRouterStateDefinition,
|
|
findIdentiferInScope: findIdentiferInScope,
|
|
getControllerDefinition: getControllerDefinition,
|
|
isAngularServiceImport: isAngularServiceImport,
|
|
getToStringTagType: getToStringTagType
|
|
};
|
|
|
|
|
|
/**
|
|
* Recursively grab the callee until an Identifier is found.
|
|
*
|
|
* @todo Needs better documentation.
|
|
*/
|
|
function getCallingIdentifier(calleeObject) {
|
|
if (calleeObject.type && calleeObject.type === 'Identifier') {
|
|
return calleeObject;
|
|
}
|
|
if (calleeObject.callee && calleeObject.callee.object) {
|
|
return getCallingIdentifier(calleeObject.callee.object);
|
|
}
|
|
return null;
|
|
}
|
|
|
|
/**
|
|
* Convert a prefix string to a RegExp.
|
|
*
|
|
* `'/app/'` → `/app.*\/`
|
|
*
|
|
* @param {string} prefix
|
|
* @returns {RegExp}
|
|
*/
|
|
function convertPrefixToRegex(prefix) {
|
|
if (typeof prefix !== 'string') {
|
|
return prefix;
|
|
}
|
|
|
|
if (prefix[0] === '/' && prefix[prefix.length - 1] === '/') {
|
|
prefix = prefix.substring(1, prefix.length - 1);
|
|
}
|
|
|
|
return new RegExp(prefix + '.*');
|
|
}
|
|
|
|
/**
|
|
* Convert a string to a RegExp.
|
|
*
|
|
* `'app'` → `/app/`
|
|
* `'/app/'` → `/app/`
|
|
*
|
|
* @param {string} prefix
|
|
* @returns {RegExp}
|
|
*/
|
|
function convertStringToRegex(string) {
|
|
if (string[0] === '/' && string[string.length - 1] === '/') {
|
|
string = string.substring(1, string.length - 1);
|
|
}
|
|
return new RegExp(string);
|
|
}
|
|
|
|
/**
|
|
* @todo Missing documentation
|
|
*/
|
|
function isTypeOfStatement(node) {
|
|
return node.type === 'Identifier' || (node.type === 'UnaryExpression' && node.operator === 'typeof');
|
|
}
|
|
|
|
/**
|
|
* @todo Missing documentation
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is a `toString` statement.
|
|
*/
|
|
function isToStringStatement(node) {
|
|
return node.type === 'CallExpression' &&
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.object.type === 'MemberExpression' &&
|
|
node.callee.object.property.name === 'toString' &&
|
|
node.callee.property.name === 'call' &&
|
|
node.callee.object.object.type === 'MemberExpression' &&
|
|
node.callee.object.object.object.name === 'Object' &&
|
|
node.callee.object.object.property.name === 'prototype';
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an ArrayExpression.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an ArrayExpression.
|
|
*/
|
|
function isArrayType(node) {
|
|
return node !== undefined && node.type === 'ArrayExpression';
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an FunctionExpression.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an FunctionExpression.
|
|
*/
|
|
function isFunctionType(node) {
|
|
return node !== undefined && (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression');
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an named FunctionExpression.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an named FunctionExpression.
|
|
*/
|
|
function isNamedInlineFunction(node) {
|
|
return this.isFunctionType(node) && node.id && node.id.name && node.id.name.length > 0;
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an Identifier.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an Identifier.
|
|
*/
|
|
function isIdentifierType(node) {
|
|
return node !== undefined && node.type === 'Identifier';
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an MemberExpression.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an MemberExpression.
|
|
*/
|
|
function isMemberExpression(node) {
|
|
return node !== undefined && node.type === 'MemberExpression';
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an Literal.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an Literal.
|
|
*/
|
|
function isLiteralType(node) {
|
|
return node !== undefined && node.type === 'Literal';
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is a CallExpression.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is a CallExpression.
|
|
*/
|
|
function isCallExpression(node) {
|
|
return node !== undefined && node.type === 'CallExpression';
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a node is an isEmptyFunction.
|
|
*
|
|
* @param {Object} node The node to check.
|
|
* @returns {boolean} Whether or not the node is an isEmptyFunction.
|
|
*/
|
|
function isEmptyFunction(fn) {
|
|
return fn.body.body.length === 0;
|
|
}
|
|
|
|
/**
|
|
* Check whether or not a string resembles a regular expression.
|
|
*
|
|
* A string is considered a regular expression if it starts and ends with `/`.
|
|
*
|
|
* @param {string} The string to check.
|
|
* @returns {boolean} Whether or not a string resembles a regular expression.
|
|
*/
|
|
function isStringRegexp(string) {
|
|
return string[0] === '/' && string[string.length - 1] === '/';
|
|
}
|
|
|
|
/**
|
|
* Check if a CallExpression node somewhat resembles an Angular component.
|
|
*
|
|
* The following are considered Angular components
|
|
* ```js
|
|
* app.factory('kittenService', function() {})
|
|
* ^^^^^^^
|
|
* app.factory('kittenService', kittenService)
|
|
* ^^^^^^^
|
|
* app.factory('kittenService', [])
|
|
* ^^^^^^^
|
|
* app.factory('kittenService', require(""))
|
|
* ^^^^^^^
|
|
* asyncFn('value', callback)
|
|
* ^^^^^^^
|
|
* ```
|
|
*
|
|
* @todo FIXME
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node somewhat resembles an Angular component.
|
|
*/
|
|
function isAngularComponent(node) {
|
|
return node.arguments !== undefined &&
|
|
node.arguments.length === 2 &&
|
|
(isLiteralType(node.arguments[0]) || isIdentifierType(node.arguments[0])) &&
|
|
(isIdentifierType(node.arguments[1]) ||
|
|
isFunctionType(node.arguments[1]) ||
|
|
isArrayType(node.arguments[1]) ||
|
|
isCallExpression(node.arguments[1]));
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines an Angular component.
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular component.
|
|
*/
|
|
function isAngularComponentDeclaration(node) {
|
|
return node.arguments !== undefined &&
|
|
node.arguments.length === 2 &&
|
|
isLiteralType(node.arguments[0]) &&
|
|
(node.arguments[1].type === 'ObjectExpression' || isIdentifierType(node.arguments[1])) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.property.name === 'component';
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines an Angular controller.
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular controller.
|
|
*/
|
|
function isAngularControllerDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.property.name === 'controller';
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines an Angular filter.
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular filter.
|
|
*/
|
|
function isAngularFilterDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.property.name === 'filter';
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines an Angular directive.
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular directive.
|
|
*/
|
|
function isAngularDirectiveDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.property.name === 'directive';
|
|
}
|
|
|
|
/**
|
|
* Check whether a node defines an Angular service.
|
|
*
|
|
* The following are considered services
|
|
* ```js
|
|
* app.provider('kittenServiceProvider', function() {})
|
|
* ^^^^^^^^
|
|
* app.factory('kittenService', function() {})
|
|
* ^^^^^^^
|
|
* app.service('kittenService', function() {})
|
|
* ^^^^^^^
|
|
* app.constant('KITTENS', function() {})
|
|
* ^^^^^^^^
|
|
* app.value('KITTENS', function() {})
|
|
* ^^^^^
|
|
* ```
|
|
*
|
|
* The following are not considered services
|
|
* ```js
|
|
* $provide.factory('kittenService', function() {})
|
|
* app.constant('KITTENS', 'meow')
|
|
* app.value('KITTENS', 'purr')
|
|
* this.$get = function() {}
|
|
* ```
|
|
*
|
|
* @todo FIXME
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular controller.
|
|
*/
|
|
function isAngularServiceDeclarationDeprecated(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.object.name !== '$provide' &&
|
|
(node.callee.property.name === 'provider' ||
|
|
node.callee.property.name === 'service' ||
|
|
node.callee.property.name === 'factory' ||
|
|
node.callee.property.name === 'constant' ||
|
|
node.callee.property.name === 'value');
|
|
}
|
|
|
|
/*
|
|
* @param {Object}
|
|
* @returns {boolean}
|
|
*/
|
|
function isAngularServiceDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.object.name !== '$provide' &&
|
|
node.callee.property.name === 'service';
|
|
}
|
|
|
|
/*
|
|
* @param {Object}
|
|
* @returns {boolean}
|
|
*/
|
|
function isAngularProviderDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.object.name !== '$provide' &&
|
|
node.callee.property.name === 'provider';
|
|
}
|
|
|
|
/*
|
|
* @param {Object}
|
|
* @returns {boolean}
|
|
*/
|
|
function isAngularFactoryDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.object.name !== '$provide' &&
|
|
node.callee.property.name === 'factory';
|
|
}
|
|
|
|
/*
|
|
* @param {Object}
|
|
* @returns {boolean}
|
|
*/
|
|
function isAngularConstantDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.object.name !== '$provide' &&
|
|
node.callee.property.name === 'constant';
|
|
}
|
|
|
|
function isAngularValueDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.object.name !== '$provide' &&
|
|
node.callee.property.name === 'value';
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node declares an Angular module.
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node declares an Angular module.
|
|
*/
|
|
function isAngularModuleDeclaration(node) {
|
|
return isAngularComponent(node) &&
|
|
isMemberExpression(node.callee) &&
|
|
node.callee.property.name === 'module';
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node gets or declares an Angular module.
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node gets or declares an Angular module.
|
|
*/
|
|
function isAngularModuleGetter(node) {
|
|
return node.arguments !== undefined &&
|
|
node.arguments.length > 0 &&
|
|
(isLiteralType(node.arguments[0]) || isIdentifierType(node.arguments[0])) &&
|
|
node.callee.type === 'MemberExpression' &&
|
|
node.callee.property.name === 'module';
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines an Angular run function.
|
|
*
|
|
* The following are considered run functions
|
|
* ```js
|
|
* app.run()
|
|
* ^^^
|
|
* app.run(function() {})
|
|
* ^^^
|
|
* ```
|
|
*
|
|
* The following are not considered run functions
|
|
* ```js
|
|
* angular.module('myApp').run(function() {})
|
|
* angular.module('myApp', []).run(function() {})
|
|
* mocha.run()
|
|
* ```
|
|
*
|
|
* @todo FIXME
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular run function.
|
|
*/
|
|
function isAngularRunSection(node) {
|
|
return isMemberExpression(node.callee) &&
|
|
node.callee.property.type === 'Identifier' &&
|
|
node.callee.property.name === 'run' &&
|
|
!falseRunValues.find(pattern => new RegExp(pattern).test(node.callee.object.name));
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines an Angular config function.
|
|
*
|
|
* The following are considered config functions
|
|
* ```js
|
|
* app.config()
|
|
* ^^^^^^
|
|
* app.config(function() {})
|
|
* ^^^^^^
|
|
* ```
|
|
*
|
|
* The following are not considered run functions
|
|
* ```js
|
|
* angular.module('myApp').config(function() {})
|
|
* angular.module('myApp', []).config(function() {})
|
|
* ```
|
|
*
|
|
* @todo FIXME
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines an Angular config function.
|
|
*/
|
|
function isAngularConfigSection(node) {
|
|
return isMemberExpression(node.callee) &&
|
|
node.callee.property.type === 'Identifier' &&
|
|
node.callee.property.name === 'config' &&
|
|
!falseConfigValues.find(pattern => new RegExp(pattern).test(node.callee.object.name));
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines a route using $routeProvider.
|
|
*
|
|
* The following are considered routes:
|
|
* ```js
|
|
* $routeProvider.when()
|
|
* ^^^^
|
|
* ```
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines a route.
|
|
*/
|
|
function isRouteDefinition(node) {
|
|
// the route def function is .when(), so when we find that, go up through the chain and make sure
|
|
// $routeProvider is the calling object
|
|
if (node.callee.property && node.callee.property.name === 'when') {
|
|
var callObject = getCallingIdentifier(node.callee.object);
|
|
return callObject && callObject.name === '$routeProvider';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Check whether a CallExpression node defines a state using $stateProvider.
|
|
*
|
|
* The following are considered states:
|
|
* ```js
|
|
* $stateProvider.state()
|
|
* ^^^^^
|
|
* ```
|
|
*
|
|
* @param {Object} node The CallExpression node to check.
|
|
* @returns {boolean} Whether or not the node defines a state.
|
|
*/
|
|
function isUIRouterStateDefinition(node) {
|
|
// the state def function is .state(), so when we find that, go up through the chain and make sure
|
|
// $stateProvider is the calling object
|
|
if (node.callee.property && node.callee.property.name === 'state') {
|
|
var callObject = getCallingIdentifier(node.callee.object);
|
|
return callObject && callObject.name === '$stateProvider';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Find an identifier node in the current scope.
|
|
*
|
|
* @param {Object} context The context to use to get the scope.
|
|
* @param {Object} identifier The identifier node to look up.
|
|
*
|
|
* @returns {Object} The node declaring the identifier.
|
|
*/
|
|
function findIdentiferInScope(context, identifier) {
|
|
var identifierNode = null;
|
|
context.getScope().variables.forEach(function(variable) {
|
|
if (variable.name === identifier.name) {
|
|
identifierNode = variable.defs[0].node;
|
|
if (identifierNode.type === 'VariableDeclarator') {
|
|
identifierNode = identifierNode.init;
|
|
}
|
|
}
|
|
});
|
|
return identifierNode;
|
|
}
|
|
|
|
/**
|
|
* Find the function definition of a controller in the current context.
|
|
*
|
|
* @param {Object} context The context to use to find the controller declaration.
|
|
* @param {Object} node The Angular controller call to look up the declaration for.
|
|
*
|
|
* @returns {Object} The identifier declaring the controller function.
|
|
*/
|
|
function getControllerDefinition(context, node) {
|
|
var controllerArg = node.arguments[1];
|
|
|
|
// Three ways of creating a controller function: function expression,
|
|
// variable name that references a function, and an array with a function
|
|
// as the last item
|
|
if (isFunctionType(controllerArg)) {
|
|
return controllerArg;
|
|
}
|
|
if (isArrayType(controllerArg)) {
|
|
controllerArg = controllerArg.elements[controllerArg.elements.length - 1];
|
|
|
|
if (isIdentifierType(controllerArg)) {
|
|
return findIdentiferInScope(context, controllerArg);
|
|
}
|
|
return controllerArg;
|
|
}
|
|
if (isIdentifierType(controllerArg)) {
|
|
return findIdentiferInScope(context, controllerArg);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Check if the imported service support these two syntaxes : serviceName and _serviceName_
|
|
*
|
|
* @param {string} parameterName The label of the parameter.
|
|
* @param {string} serviceName The name of the service.
|
|
*
|
|
* @returns {boolean} True if the service use on of these previous syntaxes.
|
|
*/
|
|
function isAngularServiceImport(parameterName, serviceName) {
|
|
var r = new RegExp('^\_?' + serviceName.replace(/[!@#$%^&*()+=\-[\]\\';,./{}|":<>?~_]/g, '\\$&') + '\_?$', 'i');
|
|
return r.test(parameterName);
|
|
}
|
|
|
|
/**
|
|
* Return the value of the given param that retrieved by Object#toString()
|
|
*
|
|
* @param {*} obj
|
|
* @return {string}
|
|
*/
|
|
function getToStringTagType(obj) {
|
|
return Object.prototype.toString.apply(obj)
|
|
.match(/^\[object\s(.+)]$/)[1];
|
|
}
|