1/** 2 * @fileoverview A rule to disallow `this` keywords outside of classes or class-like objects. 3 * @author Toru Nagashima 4 */ 5 6"use strict"; 7 8//------------------------------------------------------------------------------ 9// Requirements 10//------------------------------------------------------------------------------ 11 12const astUtils = require("./utils/ast-utils"); 13 14//------------------------------------------------------------------------------ 15// Rule Definition 16//------------------------------------------------------------------------------ 17 18module.exports = { 19 meta: { 20 type: "suggestion", 21 22 docs: { 23 description: "disallow `this` keywords outside of classes or class-like objects", 24 category: "Best Practices", 25 recommended: false, 26 url: "https://eslint.org/docs/rules/no-invalid-this" 27 }, 28 29 schema: [ 30 { 31 type: "object", 32 properties: { 33 capIsConstructor: { 34 type: "boolean", 35 default: true 36 } 37 }, 38 additionalProperties: false 39 } 40 ], 41 42 messages: { 43 unexpectedThis: "Unexpected 'this'." 44 } 45 }, 46 47 create(context) { 48 const options = context.options[0] || {}; 49 const capIsConstructor = options.capIsConstructor !== false; 50 const stack = [], 51 sourceCode = context.getSourceCode(); 52 53 /** 54 * Gets the current checking context. 55 * 56 * The return value has a flag that whether or not `this` keyword is valid. 57 * The flag is initialized when got at the first time. 58 * @returns {{valid: boolean}} 59 * an object which has a flag that whether or not `this` keyword is valid. 60 */ 61 stack.getCurrent = function() { 62 const current = this[this.length - 1]; 63 64 if (!current.init) { 65 current.init = true; 66 current.valid = !astUtils.isDefaultThisBinding( 67 current.node, 68 sourceCode, 69 { capIsConstructor } 70 ); 71 } 72 return current; 73 }; 74 75 /** 76 * Pushs new checking context into the stack. 77 * 78 * The checking context is not initialized yet. 79 * Because most functions don't have `this` keyword. 80 * When `this` keyword was found, the checking context is initialized. 81 * @param {ASTNode} node A function node that was entered. 82 * @returns {void} 83 */ 84 function enterFunction(node) { 85 86 // `this` can be invalid only under strict mode. 87 stack.push({ 88 init: !context.getScope().isStrict, 89 node, 90 valid: true 91 }); 92 } 93 94 /** 95 * Pops the current checking context from the stack. 96 * @returns {void} 97 */ 98 function exitFunction() { 99 stack.pop(); 100 } 101 102 return { 103 104 /* 105 * `this` is invalid only under strict mode. 106 * Modules is always strict mode. 107 */ 108 Program(node) { 109 const scope = context.getScope(), 110 features = context.parserOptions.ecmaFeatures || {}; 111 112 stack.push({ 113 init: true, 114 node, 115 valid: !( 116 scope.isStrict || 117 node.sourceType === "module" || 118 (features.globalReturn && scope.childScopes[0].isStrict) 119 ) 120 }); 121 }, 122 123 "Program:exit"() { 124 stack.pop(); 125 }, 126 127 FunctionDeclaration: enterFunction, 128 "FunctionDeclaration:exit": exitFunction, 129 FunctionExpression: enterFunction, 130 "FunctionExpression:exit": exitFunction, 131 132 // Reports if `this` of the current context is invalid. 133 ThisExpression(node) { 134 const current = stack.getCurrent(); 135 136 if (current && !current.valid) { 137 context.report({ 138 node, 139 messageId: "unexpectedThis" 140 }); 141 } 142 } 143 }; 144 } 145}; 146