You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

144 lines
4.7 KiB

/**
* @fileoverview A rule to disallow using `this`/`super` before `super()`.
* @author Toru Nagashima
* @copyright 2015 Toru Nagashima. All rights reserved.
*/
"use strict";
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
module.exports = function(context) {
/**
* Searches a class node that a node is belonging to.
* @param {Node} node - A node to start searching.
* @returns {ClassDeclaration|ClassExpression|null} the found class node, or `null`.
*/
function getClassInAncestor(node) {
while (node != null) {
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
return node;
}
node = node.parent;
}
/* istanbul ignore next */
return null;
}
/**
* Checks whether or not a node is the null literal.
* @param {Node} node - A node to check.
* @returns {boolean} whether or not a node is the null literal.
*/
function isNullLiteral(node) {
return node != null && node.type === "Literal" && node.value === null;
}
/**
* Checks whether or not a node is the callee of a call expression.
* @param {Node} node - A node to check.
* @returns {boolean} whether or not a node is the callee of a call expression.
*/
function isCallee(node) {
return node != null && node.parent.type === "CallExpression" && node.parent.callee === node;
}
/**
* Checks whether or not the current traversal context is before `super()`.
* @param {object} item - A checking context.
* @returns {boolean} whether or not the current traversal context is before `super()`.
*/
function isBeforeSuperCalling(item) {
return (
item != null &&
item.scope === context.getScope().variableScope.upper.variableScope &&
item.superCalled === false
);
}
var stack = [];
return {
/**
* Start checking.
* @param {MethodDefinition} node - A target node.
* @returns {void}
*/
"MethodDefinition": function(node) {
if (node.kind !== "constructor") {
return;
}
stack.push({
thisOrSuperBeforeSuperCalled: [],
superCalled: false,
scope: context.getScope().variableScope
});
},
/**
* Treats the result of checking and reports invalid `this`/`super`.
* @param {MethodDefinition} node - A target node.
* @returns {void}
*/
"MethodDefinition:exit": function(node) {
if (node.kind !== "constructor") {
return;
}
var result = stack.pop();
// Skip if it has no extends or `extends null`.
var classNode = getClassInAncestor(node);
if (classNode == null || classNode.superClass == null || isNullLiteral(classNode.superClass)) {
return;
}
// Reports.
result.thisOrSuperBeforeSuperCalled.forEach(function(thisOrSuper) {
var type = (thisOrSuper.type === "Super" ? "super" : "this");
context.report(thisOrSuper, "\"{{type}}\" is not allowed before super()", {type: type});
});
},
/**
* Marks the node if is before `super()`.
* @param {ThisExpression} node - A target node.
* @returns {void}
*/
"ThisExpression": function(node) {
var item = stack[stack.length - 1];
if (isBeforeSuperCalling(item)) {
item.thisOrSuperBeforeSuperCalled.push(node);
}
},
/**
* Marks the node if is before `super()`. (exclude `super()` itself)
* @param {Super} node - A target node.
* @returns {void}
*/
"Super": function(node) {
var item = stack[stack.length - 1];
if (isBeforeSuperCalling(item) && isCallee(node) === false) {
item.thisOrSuperBeforeSuperCalled.push(node);
}
},
/**
* Marks `super()` called.
* To catch `super(this.a);`, marks on `CallExpression:exit`.
* @param {CallExpression} node - A target node.
* @returns {void}
*/
"CallExpression:exit": function(node) {
var item = stack[stack.length - 1];
if (isBeforeSuperCalling(item) && node.callee.type === "Super") {
item.superCalled = true;
}
}
};
};
module.exports.schema = [];