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.
157 lines
5.2 KiB
157 lines
5.2 KiB
/** |
|
* @fileoverview Rule to enforce var declarations are only at the top of a function. |
|
* @author Danny Fritz |
|
* @author Gyandeep Singh |
|
*/ |
|
"use strict"; |
|
|
|
//------------------------------------------------------------------------------ |
|
// Rule Definition |
|
//------------------------------------------------------------------------------ |
|
|
|
/** @type {import('../shared/types').Rule} */ |
|
module.exports = { |
|
meta: { |
|
type: "suggestion", |
|
|
|
docs: { |
|
description: "Require `var` declarations be placed at the top of their containing scope", |
|
recommended: false, |
|
url: "https://eslint.org/docs/rules/vars-on-top" |
|
}, |
|
|
|
schema: [], |
|
messages: { |
|
top: "All 'var' declarations must be at the top of the function scope." |
|
} |
|
}, |
|
|
|
create(context) { |
|
|
|
//-------------------------------------------------------------------------- |
|
// Helpers |
|
//-------------------------------------------------------------------------- |
|
|
|
/** |
|
* Has AST suggesting a directive. |
|
* @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 and imports. Static blocks don't have either. |
|
if (node.parent.type !== "StaticBlock") { |
|
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, messageId: "top" }); |
|
} |
|
} |
|
|
|
/** |
|
* Checks whether variable is on top at functional block scope level |
|
* @param {ASTNode} node The node to check |
|
* @returns {void} |
|
*/ |
|
function blockScopeVarCheck(node) { |
|
const { parent } = node; |
|
|
|
if ( |
|
parent.type === "BlockStatement" && |
|
/Function/u.test(parent.parent.type) && |
|
isVarOnTop(node, parent.body) |
|
) { |
|
return; |
|
} |
|
|
|
if ( |
|
parent.type === "StaticBlock" && |
|
isVarOnTop(node, parent.body) |
|
) { |
|
return; |
|
} |
|
|
|
context.report({ node, messageId: "top" }); |
|
} |
|
|
|
//-------------------------------------------------------------------------- |
|
// Public API |
|
//-------------------------------------------------------------------------- |
|
|
|
return { |
|
"VariableDeclaration[kind='var']"(node) { |
|
if (node.parent.type === "ExportNamedDeclaration") { |
|
globalVarCheck(node.parent, node.parent.parent); |
|
} else if (node.parent.type === "Program") { |
|
globalVarCheck(node, node.parent); |
|
} else { |
|
blockScopeVarCheck(node); |
|
} |
|
} |
|
}; |
|
|
|
} |
|
};
|
|
|