no-invalid-this.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. /**
  2. * @fileoverview A rule to disallow `this` keywords in contexts where the value of `this` is `undefined`.
  3. * @author Toru Nagashima
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Requirements
  8. //------------------------------------------------------------------------------
  9. const astUtils = require("./utils/ast-utils");
  10. //------------------------------------------------------------------------------
  11. // Helpers
  12. //------------------------------------------------------------------------------
  13. /**
  14. * Determines if the given code path is a code path with lexical `this` binding.
  15. * That is, if `this` within the code path refers to `this` of surrounding code path.
  16. * @param {CodePath} codePath Code path.
  17. * @param {ASTNode} node Node that started the code path.
  18. * @returns {boolean} `true` if it is a code path with lexical `this` binding.
  19. */
  20. function isCodePathWithLexicalThis(codePath, node) {
  21. return (
  22. codePath.origin === "function" &&
  23. node.type === "ArrowFunctionExpression"
  24. );
  25. }
  26. //------------------------------------------------------------------------------
  27. // Rule Definition
  28. //------------------------------------------------------------------------------
  29. /** @type {import('../types').Rule.RuleModule} */
  30. module.exports = {
  31. meta: {
  32. dialects: ["javascript", "typescript"],
  33. language: "javascript",
  34. type: "suggestion",
  35. defaultOptions: [{ capIsConstructor: true }],
  36. docs: {
  37. description:
  38. "Disallow use of `this` in contexts where the value of `this` is `undefined`",
  39. recommended: false,
  40. url: "https://eslint.org/docs/latest/rules/no-invalid-this",
  41. },
  42. schema: [
  43. {
  44. type: "object",
  45. properties: {
  46. capIsConstructor: {
  47. type: "boolean",
  48. },
  49. },
  50. additionalProperties: false,
  51. },
  52. ],
  53. messages: {
  54. unexpectedThis: "Unexpected 'this'.",
  55. },
  56. },
  57. create(context) {
  58. const [{ capIsConstructor }] = context.options;
  59. const stack = [],
  60. sourceCode = context.sourceCode;
  61. /**
  62. * Gets the current checking context.
  63. *
  64. * The return value has a flag that whether or not `this` keyword is valid.
  65. * The flag is initialized when got at the first time.
  66. * @returns {{valid: boolean}}
  67. * an object which has a flag that whether or not `this` keyword is valid.
  68. */
  69. stack.getCurrent = function () {
  70. const current = this.at(-1);
  71. if (!current.init) {
  72. current.init = true;
  73. current.valid = !astUtils.isDefaultThisBinding(
  74. current.node,
  75. sourceCode,
  76. { capIsConstructor },
  77. );
  78. }
  79. return current;
  80. };
  81. return {
  82. onCodePathStart(codePath, node) {
  83. if (isCodePathWithLexicalThis(codePath, node)) {
  84. return;
  85. }
  86. if (codePath.origin === "program") {
  87. const scope = sourceCode.getScope(node);
  88. const features =
  89. context.languageOptions.parserOptions.ecmaFeatures ||
  90. {};
  91. // `this` at the top level of scripts always refers to the global object
  92. stack.push({
  93. init: true,
  94. node,
  95. valid: !(
  96. node.sourceType === "module" ||
  97. (features.globalReturn &&
  98. scope.childScopes[0].isStrict)
  99. ),
  100. });
  101. return;
  102. }
  103. /*
  104. * `init: false` means that `valid` isn't determined yet.
  105. * Most functions don't use `this`, and the calculation for `valid`
  106. * is relatively costly, so we'll calculate it lazily when the first
  107. * `this` within the function is traversed. A special case are non-strict
  108. * functions, because `this` refers to the global object and therefore is
  109. * always valid, so we can set `init: true` right away.
  110. */
  111. stack.push({
  112. init: !sourceCode.getScope(node).isStrict,
  113. node,
  114. valid: true,
  115. });
  116. },
  117. onCodePathEnd(codePath, node) {
  118. if (isCodePathWithLexicalThis(codePath, node)) {
  119. return;
  120. }
  121. stack.pop();
  122. },
  123. "AccessorProperty > *.value"(node) {
  124. stack.push({
  125. init: true,
  126. node,
  127. valid: true,
  128. });
  129. },
  130. "AccessorProperty:exit"() {
  131. stack.pop();
  132. },
  133. // Reports if `this` of the current context is invalid.
  134. ThisExpression(node) {
  135. // Special case: skip `this` if it's the value of an AccessorProperty
  136. if (
  137. node.parent.type === "AccessorProperty" &&
  138. node.parent.value === node
  139. ) {
  140. return;
  141. }
  142. const current = stack.getCurrent();
  143. if (current && !current.valid) {
  144. context.report({
  145. node,
  146. messageId: "unexpectedThis",
  147. });
  148. }
  149. },
  150. };
  151. },
  152. };