1 | /**
|
2 | * @fileoverview Rule to check for "block scoped" variables by binding context
|
3 | * @author Matt DuVall <http://www.mattduvall.com>
|
4 | */
|
5 | ;
|
6 |
|
7 | //------------------------------------------------------------------------------
|
8 | // Rule Definition
|
9 | //------------------------------------------------------------------------------
|
10 |
|
11 | module.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 | };
|