no-ref-as-operand.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  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: 'code',
  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. fix(fixer) {
  49. return fixer.insertTextAfter(node, '.value')
  50. }
  51. })
  52. }
  53. return {
  54. Program() {
  55. const tracker = new ReferenceTracker(context.getScope())
  56. const traceMap = utils.createCompositionApiTraceMap({
  57. [ReferenceTracker.ESM]: true,
  58. ref: {
  59. [ReferenceTracker.CALL]: true
  60. },
  61. computed: {
  62. [ReferenceTracker.CALL]: true
  63. },
  64. toRef: {
  65. [ReferenceTracker.CALL]: true
  66. },
  67. customRef: {
  68. [ReferenceTracker.CALL]: true
  69. },
  70. shallowRef: {
  71. [ReferenceTracker.CALL]: true
  72. }
  73. })
  74. for (const { node, path } of tracker.iterateEsmReferences(traceMap)) {
  75. const variableDeclarator = node.parent
  76. if (
  77. !variableDeclarator ||
  78. variableDeclarator.type !== 'VariableDeclarator' ||
  79. variableDeclarator.id.type !== 'Identifier'
  80. ) {
  81. continue
  82. }
  83. const variable = findVariable(
  84. context.getScope(),
  85. variableDeclarator.id
  86. )
  87. if (!variable) {
  88. continue
  89. }
  90. const variableDeclaration =
  91. (variableDeclarator.parent &&
  92. variableDeclarator.parent.type === 'VariableDeclaration' &&
  93. variableDeclarator.parent) ||
  94. null
  95. for (const reference of variable.references) {
  96. if (!reference.isRead()) {
  97. continue
  98. }
  99. refReferenceIds.set(reference.identifier, {
  100. variableDeclarator,
  101. variableDeclaration,
  102. method: path[1]
  103. })
  104. }
  105. }
  106. },
  107. // if (refValue)
  108. /** @param {Identifier} node */
  109. 'IfStatement>Identifier'(node) {
  110. reportIfRefWrapped(node)
  111. },
  112. // switch (refValue)
  113. /** @param {Identifier} node */
  114. 'SwitchStatement>Identifier'(node) {
  115. reportIfRefWrapped(node)
  116. },
  117. // -refValue, +refValue, !refValue, ~refValue, typeof refValue
  118. /** @param {Identifier} node */
  119. 'UnaryExpression>Identifier'(node) {
  120. reportIfRefWrapped(node)
  121. },
  122. // refValue++, refValue--
  123. /** @param {Identifier} node */
  124. 'UpdateExpression>Identifier'(node) {
  125. reportIfRefWrapped(node)
  126. },
  127. // refValue+1, refValue-1
  128. /** @param {Identifier} node */
  129. 'BinaryExpression>Identifier'(node) {
  130. reportIfRefWrapped(node)
  131. },
  132. // refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
  133. /** @param {Identifier & {parent: AssignmentExpression}} node */
  134. 'AssignmentExpression>Identifier'(node) {
  135. if (node.parent.operator === '=' && node.parent.left !== node) {
  136. return
  137. }
  138. reportIfRefWrapped(node)
  139. },
  140. // refValue || other, refValue && other. ignore: other || refValue
  141. /** @param {Identifier & {parent: LogicalExpression}} node */
  142. 'LogicalExpression>Identifier'(node) {
  143. if (node.parent.left !== node) {
  144. return
  145. }
  146. // Report only constants.
  147. const data = refReferenceIds.get(node)
  148. if (!data) {
  149. return
  150. }
  151. if (
  152. !data.variableDeclaration ||
  153. data.variableDeclaration.kind !== 'const'
  154. ) {
  155. return
  156. }
  157. reportIfRefWrapped(node)
  158. },
  159. // refValue ? x : y
  160. /** @param {Identifier & {parent: ConditionalExpression}} node */
  161. 'ConditionalExpression>Identifier'(node) {
  162. if (node.parent.test !== node) {
  163. return
  164. }
  165. reportIfRefWrapped(node)
  166. },
  167. // `${refValue}`
  168. /** @param {Identifier} node */
  169. 'TemplateLiteral>Identifier'(node) {
  170. reportIfRefWrapped(node)
  171. },
  172. // refValue.x
  173. /** @param {Identifier & {parent: MemberExpression}} node */
  174. 'MemberExpression>Identifier'(node) {
  175. if (node.parent.object !== node) {
  176. return
  177. }
  178. const name = utils.getStaticPropertyName(node.parent)
  179. if (
  180. name === 'value' ||
  181. name == null ||
  182. // WritableComputedRef
  183. name === 'effect'
  184. ) {
  185. return
  186. }
  187. reportIfRefWrapped(node)
  188. }
  189. }
  190. }
  191. }