echo-fecs
Version:
Front End Code Style Suite
197 lines (164 loc) • 6.08 kB
JavaScript
/**
* @file Rule to enforce to use async and await.
* @author chris<wfsr@foxmail.com>
*/
;
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = {
meta: {
docs: {
description: 'enforce to use async and await',
category: 'ECMAScript 6',
recommended: false
},
schema: [{
type: 'object',
properties: {
co: {
type: 'boolean'
}
},
additionalProperties: false
}]
},
create: function (context) {
var options = context.options[0] || {};
var useCO = options.co === true;
var sourceCode = context.getSourceCode();
function validateCO(node) {
var callee = node.callee;
var args = node.arguments;
if (callee.name === 'require'
&& args[0]
&& args[0].value === 'co'
|| callee.name === 'co'
&& args[0]
&& args[0].generator
&& args[0].type === 'FunctionExpression'
) {
context.report(node, 'Expected to use `async` and `await`.');
}
}
var scopes = [];
function pushScope(node) {
scopes.push({});
}
function popScope(node) {
scopes.pop();
}
function hasAwaitVariable(node) {
var scope = scopes[scopes.length - 1];
var name;
switch (node.type) {
case 'Identifier':
return scope[node.name];
case 'MemberExpression':
name = sourceCode.getText(node).split('.').shift();
return scope[name];
case 'NewExpression':
case 'CallExpression':
name = sourceCode.getText(node.callee).split('.').shift();
return scope[name] || node.arguments.some(hasAwaitVariable);
}
}
function tagIdentifier(node, scope) {
switch (node.type) {
case 'Identifier':
scope[node.name] = true;
break;
case 'MemberExpression':
scope[sourceCode.getText(node).split('.').shift()] = true;
break;
case 'ObjectPattern':
node.properties.forEach(function (property) {
tagIdentifier(property.value, scope);
});
break;
case 'ArrayPattern':
node.elements.forEach(function (element) {
tagIdentifier(element, scope);
});
break;
}
}
function analyse(node) {
var scope = scopes[scopes.length - 1];
if (!scope
|| node.type === 'VariableDeclarator'
&& (!node.init || node.init.type === 'YieldExpression')
|| node.type === 'AssignmentExpression'
&& node.right.type === 'YieldExpression'
) {
return;
}
if (hasAwaitVariable(node.init || node.right)) {
tagIdentifier(node.id || node.left, scope);
}
}
function analyseDeps(node) {
var scope = scopes[scopes.length - 1];
var ignoreTypes = {
AssignmentExpression: true,
VariableDeclarator: true,
YieldExpression: true
};
if (!scope || ignoreTypes[node.parent.type] || node.callee.type !== 'MemberExpression') {
return;
}
if (hasAwaitVariable(node)) {
tagIdentifier(node.callee, scope);
}
}
function validate(node) {
var parent = node.parent;
if (node._babelType !== 'AwaitExpression'
|| parent.type !== 'VariableDeclarator'
&& parent.type !== 'AssignmentExpression'
) {
return;
}
var scope = scopes[scopes.length - 1];
var name = parent.id && parent.id.name || parent.left && parent.left.name;
if (scope[name]) {
return;
}
var earlyAwaits = Object.keys(scope);
parent = parent.parent.parent;
scope[name] = {parent: parent};
var argument = node.argument;
var argumentType = argument.type;
if ((argumentType === 'Identifier' || argumentType === 'MemberExpression')
&& hasAwaitVariable(argument)
|| argumentType === 'CallExpression'
&& (hasAwaitVariable(argument.callee) || argument.arguments.some(hasAwaitVariable))
) {
return;
}
var hasSameParentAwait = function (name) {
return parent === scope[name].parent;
};
if (earlyAwaits.length && earlyAwaits.some(hasSameParentAwait)) {
context.report(node, 'Expected to excute in parallel.');
}
}
var listeners = {
'ArrowFunctionExpression': pushScope,
'FunctionExpression': pushScope,
'FunctionDeclaration': pushScope,
'ArrowFunctionExpression:exit': popScope,
'FunctionExpression:exit': popScope,
'FunctionDeclaration:exit': popScope,
'VariableDeclarator': analyse,
'AssignmentExpression': analyse,
'CallExpression:exit': analyseDeps,
'AwaitExpression': validate,
'YieldExpression': validate
};
if (!useCO) {
listeners.CallExpression = validateCO;
}
return listeners;
}
};