consistent-this.js 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /**
  2. * @fileoverview Rule to enforce consistent naming of "this" context variables
  3. * @author Raphael Pigulla
  4. */
  5. "use strict";
  6. //------------------------------------------------------------------------------
  7. // Rule Definition
  8. //------------------------------------------------------------------------------
  9. /** @type {import('../types').Rule.RuleModule} */
  10. module.exports = {
  11. meta: {
  12. type: "suggestion",
  13. docs: {
  14. description:
  15. "Enforce consistent naming when capturing the current execution context",
  16. recommended: false,
  17. frozen: true,
  18. url: "https://eslint.org/docs/latest/rules/consistent-this",
  19. },
  20. schema: {
  21. type: "array",
  22. items: {
  23. type: "string",
  24. minLength: 1,
  25. },
  26. uniqueItems: true,
  27. },
  28. defaultOptions: ["that"],
  29. messages: {
  30. aliasNotAssignedToThis:
  31. "Designated alias '{{name}}' is not assigned to 'this'.",
  32. unexpectedAlias: "Unexpected alias '{{name}}' for 'this'.",
  33. },
  34. },
  35. create(context) {
  36. const aliases = context.options;
  37. const sourceCode = context.sourceCode;
  38. /**
  39. * Reports that a variable declarator or assignment expression is assigning
  40. * a non-'this' value to the specified alias.
  41. * @param {ASTNode} node The assigning node.
  42. * @param {string} name the name of the alias that was incorrectly used.
  43. * @returns {void}
  44. */
  45. function reportBadAssignment(node, name) {
  46. context.report({
  47. node,
  48. messageId: "aliasNotAssignedToThis",
  49. data: { name },
  50. });
  51. }
  52. /**
  53. * Checks that an assignment to an identifier only assigns 'this' to the
  54. * appropriate alias, and the alias is only assigned to 'this'.
  55. * @param {ASTNode} node The assigning node.
  56. * @param {Identifier} name The name of the variable assigned to.
  57. * @param {Expression} value The value of the assignment.
  58. * @returns {void}
  59. */
  60. function checkAssignment(node, name, value) {
  61. const isThis = value.type === "ThisExpression";
  62. if (aliases.includes(name)) {
  63. if (!isThis || (node.operator && node.operator !== "=")) {
  64. reportBadAssignment(node, name);
  65. }
  66. } else if (isThis) {
  67. context.report({
  68. node,
  69. messageId: "unexpectedAlias",
  70. data: { name },
  71. });
  72. }
  73. }
  74. /**
  75. * Ensures that a variable declaration of the alias in a program or function
  76. * is assigned to the correct value.
  77. * @param {string} alias alias the check the assignment of.
  78. * @param {Object} scope scope of the current code we are checking.
  79. * @private
  80. * @returns {void}
  81. */
  82. function checkWasAssigned(alias, scope) {
  83. const variable = scope.set.get(alias);
  84. if (!variable) {
  85. return;
  86. }
  87. if (
  88. variable.defs.some(
  89. def =>
  90. def.node.type === "VariableDeclarator" &&
  91. def.node.init !== null,
  92. )
  93. ) {
  94. return;
  95. }
  96. /*
  97. * The alias has been declared and not assigned: check it was
  98. * assigned later in the same scope.
  99. */
  100. if (
  101. !variable.references.some(reference => {
  102. const write = reference.writeExpr;
  103. return (
  104. reference.from === scope &&
  105. write &&
  106. write.type === "ThisExpression" &&
  107. write.parent.operator === "="
  108. );
  109. })
  110. ) {
  111. variable.defs
  112. .map(def => def.node)
  113. .forEach(node => {
  114. reportBadAssignment(node, alias);
  115. });
  116. }
  117. }
  118. /**
  119. * Check each alias to ensure that is was assigned to the correct value.
  120. * @param {ASTNode} node The node that represents the scope to check.
  121. * @returns {void}
  122. */
  123. function ensureWasAssigned(node) {
  124. const scope = sourceCode.getScope(node);
  125. // if this is program scope we also need to check module scope
  126. const extraScope =
  127. node.type === "Program" && node.sourceType === "module"
  128. ? scope.childScopes[0]
  129. : null;
  130. aliases.forEach(alias => {
  131. checkWasAssigned(alias, scope);
  132. if (extraScope) {
  133. checkWasAssigned(alias, extraScope);
  134. }
  135. });
  136. }
  137. return {
  138. "Program:exit": ensureWasAssigned,
  139. "FunctionExpression:exit": ensureWasAssigned,
  140. "FunctionDeclaration:exit": ensureWasAssigned,
  141. VariableDeclarator(node) {
  142. const id = node.id;
  143. const isDestructuring =
  144. id.type === "ArrayPattern" || id.type === "ObjectPattern";
  145. if (node.init !== null && !isDestructuring) {
  146. checkAssignment(node, id.name, node.init);
  147. }
  148. },
  149. AssignmentExpression(node) {
  150. if (node.left.type === "Identifier") {
  151. checkAssignment(node, node.left.name, node.right);
  152. }
  153. },
  154. };
  155. },
  156. };