no-invalid-this.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  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. },
  35. create(context) {
  36. const options = context.options[0] || {};
  37. const capIsConstructor = options.capIsConstructor !== false;
  38. const stack = [],
  39. sourceCode = context.getSourceCode();
  40. /**
  41. * Gets the current checking context.
  42. *
  43. * The return value has a flag that whether or not `this` keyword is valid.
  44. * The flag is initialized when got at the first time.
  45. * @returns {{valid: boolean}}
  46. * an object which has a flag that whether or not `this` keyword is valid.
  47. */
  48. stack.getCurrent = function() {
  49. const current = this[this.length - 1];
  50. if (!current.init) {
  51. current.init = true;
  52. current.valid = !astUtils.isDefaultThisBinding(
  53. current.node,
  54. sourceCode,
  55. { capIsConstructor }
  56. );
  57. }
  58. return current;
  59. };
  60. /**
  61. * Pushs new checking context into the stack.
  62. *
  63. * The checking context is not initialized yet.
  64. * Because most functions don't have `this` keyword.
  65. * When `this` keyword was found, the checking context is initialized.
  66. * @param {ASTNode} node A function node that was entered.
  67. * @returns {void}
  68. */
  69. function enterFunction(node) {
  70. // `this` can be invalid only under strict mode.
  71. stack.push({
  72. init: !context.getScope().isStrict,
  73. node,
  74. valid: true
  75. });
  76. }
  77. /**
  78. * Pops the current checking context from the stack.
  79. * @returns {void}
  80. */
  81. function exitFunction() {
  82. stack.pop();
  83. }
  84. return {
  85. /*
  86. * `this` is invalid only under strict mode.
  87. * Modules is always strict mode.
  88. */
  89. Program(node) {
  90. const scope = context.getScope(),
  91. features = context.parserOptions.ecmaFeatures || {};
  92. stack.push({
  93. init: true,
  94. node,
  95. valid: !(
  96. scope.isStrict ||
  97. node.sourceType === "module" ||
  98. (features.globalReturn && scope.childScopes[0].isStrict)
  99. )
  100. });
  101. },
  102. "Program:exit"() {
  103. stack.pop();
  104. },
  105. FunctionDeclaration: enterFunction,
  106. "FunctionDeclaration:exit": exitFunction,
  107. FunctionExpression: enterFunction,
  108. "FunctionExpression:exit": exitFunction,
  109. // Reports if `this` of the current context is invalid.
  110. ThisExpression(node) {
  111. const current = stack.getCurrent();
  112. if (current && !current.valid) {
  113. context.report({ node, message: "Unexpected 'this'." });
  114. }
  115. }
  116. };
  117. }
  118. };