no-ref-as-operand.js 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { ReferenceTracker, findVariable } = require('eslint-utils')
  7. const utils = require('../utils')
  8. module.exports = {
  9. meta: {
  10. type: 'suggestion',
  11. docs: {
  12. description:
  13. 'disallow use of value wrapped by `ref()` (Composition API) as an operand',
  14. categories: ['vue3-essential'],
  15. url: 'https://eslint.vuejs.org/rules/no-ref-as-operand.html'
  16. },
  17. fixable: null,
  18. schema: [],
  19. messages: {
  20. requireDotValue:
  21. 'Must use `.value` to read or write the value wrapped by `{{method}}()`.'
  22. }
  23. },
  24. /** @param {RuleContext} context */
  25. create(context) {
  26. /**
  27. * @typedef {object} ReferenceData
  28. * @property {VariableDeclarator} variableDeclarator
  29. * @property {VariableDeclaration | null} variableDeclaration
  30. * @property {string} method
  31. */
  32. /** @type {Map<Identifier, ReferenceData>} */
  33. const refReferenceIds = new Map()
  34. /**
  35. * @param {Identifier} node
  36. */
  37. function reportIfRefWrapped(node) {
  38. const data = refReferenceIds.get(node)
  39. if (!data) {
  40. return
  41. }
  42. context.report({
  43. node,
  44. messageId: 'requireDotValue',
  45. data: {
  46. method: data.method
  47. }
  48. })
  49. }
  50. return {
  51. Program() {
  52. const tracker = new ReferenceTracker(context.getScope())
  53. const traceMap = utils.createCompositionApiTraceMap({
  54. [ReferenceTracker.ESM]: true,
  55. ref: {
  56. [ReferenceTracker.CALL]: true
  57. },
  58. computed: {
  59. [ReferenceTracker.CALL]: true
  60. },
  61. toRef: {
  62. [ReferenceTracker.CALL]: true
  63. },
  64. customRef: {
  65. [ReferenceTracker.CALL]: true
  66. },
  67. shallowRef: {
  68. [ReferenceTracker.CALL]: true
  69. }
  70. })
  71. for (const { node, path } of tracker.iterateEsmReferences(traceMap)) {
  72. const variableDeclarator = node.parent
  73. if (
  74. !variableDeclarator ||
  75. variableDeclarator.type !== 'VariableDeclarator' ||
  76. variableDeclarator.id.type !== 'Identifier'
  77. ) {
  78. continue
  79. }
  80. const variable = findVariable(
  81. context.getScope(),
  82. variableDeclarator.id
  83. )
  84. if (!variable) {
  85. continue
  86. }
  87. const variableDeclaration =
  88. (variableDeclarator.parent &&
  89. variableDeclarator.parent.type === 'VariableDeclaration' &&
  90. variableDeclarator.parent) ||
  91. null
  92. for (const reference of variable.references) {
  93. if (!reference.isRead()) {
  94. continue
  95. }
  96. refReferenceIds.set(reference.identifier, {
  97. variableDeclarator,
  98. variableDeclaration,
  99. method: path[1]
  100. })
  101. }
  102. }
  103. },
  104. // if (refValue)
  105. /** @param {Identifier} node */
  106. 'IfStatement>Identifier'(node) {
  107. reportIfRefWrapped(node)
  108. },
  109. // switch (refValue)
  110. /** @param {Identifier} node */
  111. 'SwitchStatement>Identifier'(node) {
  112. reportIfRefWrapped(node)
  113. },
  114. // -refValue, +refValue, !refValue, ~refValue, typeof refValue
  115. /** @param {Identifier} node */
  116. 'UnaryExpression>Identifier'(node) {
  117. reportIfRefWrapped(node)
  118. },
  119. // refValue++, refValue--
  120. /** @param {Identifier} node */
  121. 'UpdateExpression>Identifier'(node) {
  122. reportIfRefWrapped(node)
  123. },
  124. // refValue+1, refValue-1
  125. /** @param {Identifier} node */
  126. 'BinaryExpression>Identifier'(node) {
  127. reportIfRefWrapped(node)
  128. },
  129. // refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
  130. /** @param {Identifier} node */
  131. 'AssignmentExpression>Identifier'(node) {
  132. reportIfRefWrapped(node)
  133. },
  134. // refValue || other, refValue && other. ignore: other || refValue
  135. /** @param {Identifier & {parent: LogicalExpression}} node */
  136. 'LogicalExpression>Identifier'(node) {
  137. if (node.parent.left !== node) {
  138. return
  139. }
  140. // Report only constants.
  141. const data = refReferenceIds.get(node)
  142. if (!data) {
  143. return
  144. }
  145. if (
  146. !data.variableDeclaration ||
  147. data.variableDeclaration.kind !== 'const'
  148. ) {
  149. return
  150. }
  151. reportIfRefWrapped(node)
  152. },
  153. // refValue ? x : y
  154. /** @param {Identifier & {parent: ConditionalExpression}} node */
  155. 'ConditionalExpression>Identifier'(node) {
  156. if (node.parent.test !== node) {
  157. return
  158. }
  159. reportIfRefWrapped(node)
  160. },
  161. // `${refValue}`
  162. /** @param {Identifier} node */
  163. 'TemplateLiteral>Identifier'(node) {
  164. reportIfRefWrapped(node)
  165. },
  166. // refValue.x
  167. /** @param {Identifier & {parent: MemberExpression}} node */
  168. 'MemberExpression>Identifier'(node) {
  169. if (node.parent.object !== node) {
  170. return
  171. }
  172. const name = utils.getStaticPropertyName(node.parent)
  173. if (
  174. name === 'value' ||
  175. name == null ||
  176. // WritableComputedRef
  177. name === 'effect'
  178. ) {
  179. return
  180. }
  181. reportIfRefWrapped(node)
  182. }
  183. }
  184. }
  185. }