123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144 |
- /**
- * @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: {
- type: "suggestion",
-
- docs: {
- description: "require `var` declarations be placed at the top of their containing scope",
- category: "Best Practices",
- 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
- //--------------------------------------------------------------------------
-
- // eslint-disable-next-line jsdoc/require-description
- /**
- * @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, messageId: "top" });
- }
- }
-
- /**
- * 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/u.test(grandParent.type) &&
- parent.type === "BlockStatement" &&
- isVarOnTop(node, parent.body))) {
- 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, node.parent, node.parent.parent);
- }
- }
- };
-
- }
- };
|