1/** 2 * @fileoverview Rule to check for "block scoped" variables by binding context 3 * @author Matt DuVall <http://www.mattduvall.com> 4 */ 5"use strict"; 6 7//------------------------------------------------------------------------------ 8// Rule Definition 9//------------------------------------------------------------------------------ 10 11module.exports = { 12 meta: { 13 type: "suggestion", 14 15 docs: { 16 description: "enforce the use of variables within the scope they are defined", 17 category: "Best Practices", 18 recommended: false, 19 url: "https://eslint.org/docs/rules/block-scoped-var" 20 }, 21 22 schema: [], 23 24 messages: { 25 outOfScope: "'{{name}}' used outside of binding context." 26 } 27 }, 28 29 create(context) { 30 let stack = []; 31 32 /** 33 * Makes a block scope. 34 * @param {ASTNode} node A node of a scope. 35 * @returns {void} 36 */ 37 function enterScope(node) { 38 stack.push(node.range); 39 } 40 41 /** 42 * Pops the last block scope. 43 * @returns {void} 44 */ 45 function exitScope() { 46 stack.pop(); 47 } 48 49 /** 50 * Reports a given reference. 51 * @param {eslint-scope.Reference} reference A reference to report. 52 * @returns {void} 53 */ 54 function report(reference) { 55 const identifier = reference.identifier; 56 57 context.report({ node: identifier, messageId: "outOfScope", data: { name: identifier.name } }); 58 } 59 60 /** 61 * Finds and reports references which are outside of valid scopes. 62 * @param {ASTNode} node A node to get variables. 63 * @returns {void} 64 */ 65 function checkForVariables(node) { 66 if (node.kind !== "var") { 67 return; 68 } 69 70 // Defines a predicate to check whether or not a given reference is outside of valid scope. 71 const scopeRange = stack[stack.length - 1]; 72 73 /** 74 * Check if a reference is out of scope 75 * @param {ASTNode} reference node to examine 76 * @returns {boolean} True is its outside the scope 77 * @private 78 */ 79 function isOutsideOfScope(reference) { 80 const idRange = reference.identifier.range; 81 82 return idRange[0] < scopeRange[0] || idRange[1] > scopeRange[1]; 83 } 84 85 // Gets declared variables, and checks its references. 86 const variables = context.getDeclaredVariables(node); 87 88 for (let i = 0; i < variables.length; ++i) { 89 90 // Reports. 91 variables[i] 92 .references 93 .filter(isOutsideOfScope) 94 .forEach(report); 95 } 96 } 97 98 return { 99 Program(node) { 100 stack = [node.range]; 101 }, 102 103 // Manages scopes. 104 BlockStatement: enterScope, 105 "BlockStatement:exit": exitScope, 106 ForStatement: enterScope, 107 "ForStatement:exit": exitScope, 108 ForInStatement: enterScope, 109 "ForInStatement:exit": exitScope, 110 ForOfStatement: enterScope, 111 "ForOfStatement:exit": exitScope, 112 SwitchStatement: enterScope, 113 "SwitchStatement:exit": exitScope, 114 CatchClause: enterScope, 115 "CatchClause:exit": exitScope, 116 117 // Finds and reports references which are outside of valid scope. 118 VariableDeclaration: checkForVariables 119 }; 120 121 } 122}; 123