echo-fecs
Version:
Front End Code Style Suite
197 lines (163 loc) • 6.37 kB
JavaScript
/**
* @file Rule to enforce using Map or Set.
* @author chris<wfsr@foxmail.com>
*/
;
var util = require('../../util');
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'Enforce using Map or Set.',
category: 'ECMAScript 6',
recommended: false
},
schema: []
},
create: function (context) {
/**
* Find variable and it's write expression
*
* @param {string} name variable name
* @param {function(ASTNode):boolean} checkType type of the write expression node
* @return {?ASTNode}
*/
function findVariableReference(name, checkType) {
var variable;
util.variablesInScope(context).some(function (item) {
if (item.name === name) {
variable = item;
return true;
}
});
var result;
var checker = function (item) {
if (item.isWrite() && checkType(item.writeExpr)) {
result = item.writeExpr;
return true;
}
};
if (variable) {
variable.references.some(checker);
}
else {
context.getScope().through.some(function (item) {
if (item.identifier.name === name) {
checker(item);
return true;
}
});
}
return result;
}
function isPureObject(name) {
var check = function (node) {
switch (node.type) {
case 'NewExpression':
return node.callee.name === 'Object'
&& (!node.arguments.length || node.arguments[0].type === 'ObjectExpression');
case 'ObjectExpression':
return true;
case 'CallExpression':
var callee = node.callee;
return callee.type === 'MemberExpression'
&& callee.object.name === 'Object'
&& callee.property.name === 'create'
&& node.arguments[0]
&& node.arguments[0].value === null
|| callee.name === 'Object'
&& (!node.arguments.length || node.arguments[0].type === 'ObjectExpression');
}
};
return typeof name === 'string' ? findVariableReference(name, check) : check(name);
}
function isNotString(name) {
var result = name && findVariableReference(name, function (node) {
if (isPureObject(node) || node.regex) {
return true;
}
switch (node.type) {
case 'ArrayExpression':
return true;
case 'NewExpression':
return node.callee.name !== 'String';
}
return false;
});
return !!result;
}
function isBoolean(node) {
var value = node.value;
switch (node.type) {
case 'Literal':
return value === 1
|| value === 0
|| value === true
|| value === false;
case 'UnaryExpression':
return node.operator === '!';
}
}
var refMap = {};
function validate(node) {
var left = node.left;
if (left.type !== 'MemberExpression') {
return;
}
var object = left.object;
var property = left.property;
if (property.type !== 'Identifier' || !left.computed || object.type !== 'Identifier') {
return;
}
var id = left.object.name;
var ref = isPureObject(id);
if (!ref) {
return;
}
var key = [id, ref.loc.start.line, ref.loc.start.column].join('-');
if (refMap[key]) {
return;
}
if (property.type === 'Identifier' && isNotString(property.name)) {
refMap[key] = true;
context.report(
node,
'Expected to use ' + (isBoolean(node.right) ? 'Set' : 'Map') + ' but found Object.'
);
}
}
return {
AssignmentExpression: validate,
ForInStatement: function (node) {
var right = node.right;
if (isPureObject(right.type === 'Identifier' ? right.name : right)) {
context.report(node, 'Expected to use Map or Set with interation.');
}
},
UnaryExpression: function (node) {
if (node.operator === 'delete'
&& node.argument.type === 'MemberExpression'
&& node.argument.object.type === 'Identifier'
&& isPureObject(node.argument.object.name)
) {
context.report(node, 'Expected to use Map or Set if you need to add or remove items.');
}
},
ObjectExpression: function (node) {
var effectiveType = node.parent.type;
if (effectiveType !== 'AssignmentExpression' && effectiveType !== 'VariableDeclarator') {
return;
}
var hasAllTrue = node.properties.length > 1 && !node.properties.some(function (property) {
var value = property.value && property.value.value;
return value !== true && value !== 1;
});
if (hasAllTrue) {
context.report(node, 'Expected to use Set if you need an identify collection.');
}
}
};
}
};