no-invalid-this.js 4.3 KB

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