We have been stalled on ESLint 3.8.0 for some time. Current ESLint is 3.13.0. We have been unable to upgrade because of more aggressive reporting on some rules, including indentation. ESLint configuration options and bugfixes are now such that we can reasonably upgrade. PR-URL: https://github.com/nodejs/node/pull/10561 Reviewed-By: Teddy Katz <teddy.katz@gmail.com> Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Sam Roberts <vieuxtech@gmail.com>
149 lines
5.2 KiB
JavaScript
149 lines
5.2 KiB
JavaScript
/**
|
|
* @fileoverview Rule to enforce var declarations are only at the top of a function.
|
|
* @author Danny Fritz
|
|
* @author Gyandeep Singh
|
|
*/
|
|
"use strict";
|
|
|
|
//------------------------------------------------------------------------------
|
|
// Rule Definition
|
|
//------------------------------------------------------------------------------
|
|
|
|
module.exports = {
|
|
meta: {
|
|
docs: {
|
|
description: "require `var` declarations be placed at the top of their containing scope",
|
|
category: "Best Practices",
|
|
recommended: false
|
|
},
|
|
|
|
schema: []
|
|
},
|
|
|
|
create(context) {
|
|
const errorMessage = "All 'var' declarations must be at the top of the function scope.";
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Helpers
|
|
//--------------------------------------------------------------------------
|
|
|
|
/**
|
|
* @param {ASTNode} node - any node
|
|
* @returns {boolean} whether the given node structurally represents a directive
|
|
*/
|
|
function looksLikeDirective(node) {
|
|
return node.type === "ExpressionStatement" &&
|
|
node.expression.type === "Literal" && typeof node.expression.value === "string";
|
|
}
|
|
|
|
/**
|
|
* Check to see if its a ES6 import declaration
|
|
* @param {ASTNode} node - any node
|
|
* @returns {boolean} whether the given node represents a import declaration
|
|
*/
|
|
function looksLikeImport(node) {
|
|
return node.type === "ImportDeclaration" || node.type === "ImportSpecifier" ||
|
|
node.type === "ImportDefaultSpecifier" || node.type === "ImportNamespaceSpecifier";
|
|
}
|
|
|
|
/**
|
|
* Checks whether a given node is a variable declaration or not.
|
|
*
|
|
* @param {ASTNode} node - any node
|
|
* @returns {boolean} `true` if the node is a variable declaration.
|
|
*/
|
|
function isVariableDeclaration(node) {
|
|
return (
|
|
node.type === "VariableDeclaration" ||
|
|
(
|
|
node.type === "ExportNamedDeclaration" &&
|
|
node.declaration &&
|
|
node.declaration.type === "VariableDeclaration"
|
|
)
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Checks whether this variable is on top of the block body
|
|
* @param {ASTNode} node - The node to check
|
|
* @param {ASTNode[]} statements - collection of ASTNodes for the parent node block
|
|
* @returns {boolean} True if var is on top otherwise false
|
|
*/
|
|
function isVarOnTop(node, statements) {
|
|
const l = statements.length;
|
|
let i = 0;
|
|
|
|
// skip over directives
|
|
for (; i < l; ++i) {
|
|
if (!looksLikeDirective(statements[i]) && !looksLikeImport(statements[i])) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (; i < l; ++i) {
|
|
if (!isVariableDeclaration(statements[i])) {
|
|
return false;
|
|
}
|
|
if (statements[i] === node) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Checks whether variable is on top at the global level
|
|
* @param {ASTNode} node - The node to check
|
|
* @param {ASTNode} parent - Parent of the node
|
|
* @returns {void}
|
|
*/
|
|
function globalVarCheck(node, parent) {
|
|
if (!isVarOnTop(node, parent.body)) {
|
|
context.report({ node, message: errorMessage });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks whether variable is on top at functional block scope level
|
|
* @param {ASTNode} node - The node to check
|
|
* @param {ASTNode} parent - Parent of the node
|
|
* @param {ASTNode} grandParent - Parent of the node's parent
|
|
* @returns {void}
|
|
*/
|
|
function blockScopeVarCheck(node, parent, grandParent) {
|
|
if (!(/Function/.test(grandParent.type) &&
|
|
parent.type === "BlockStatement" &&
|
|
isVarOnTop(node, parent.body))) {
|
|
context.report({ node, message: errorMessage });
|
|
}
|
|
}
|
|
|
|
//--------------------------------------------------------------------------
|
|
// Public API
|
|
//--------------------------------------------------------------------------
|
|
|
|
return {
|
|
VariableDeclaration(node) {
|
|
const ancestors = context.getAncestors();
|
|
let parent = ancestors.pop();
|
|
let grandParent = ancestors.pop();
|
|
|
|
if (node.kind === "var") { // check variable is `var` type and not `let` or `const`
|
|
if (parent.type === "ExportNamedDeclaration") {
|
|
node = parent;
|
|
parent = grandParent;
|
|
grandParent = ancestors.pop();
|
|
}
|
|
|
|
if (parent.type === "Program") { // That means its a global variable
|
|
globalVarCheck(node, parent);
|
|
} else {
|
|
blockScopeVarCheck(node, parent, grandParent);
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
}
|
|
};
|