no-ref-as-operand.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const { findVariable } = require('@eslint-community/eslint-utils')
  7. const { extractRefObjectReferences } = require('../utils/ref-object-references')
  8. const utils = require('../utils')
  9. /**
  10. * @typedef {import('../utils/ref-object-references').RefObjectReferences} RefObjectReferences
  11. * @typedef {import('../utils/ref-object-references').RefObjectReferenceForIdentifier} RefObjectReferenceForIdentifier
  12. */
  13. /**
  14. * Checks whether the given identifier reference has been initialized with a ref object.
  15. * @param {RefObjectReferenceForIdentifier | null} data
  16. * @returns {data is RefObjectReferenceForIdentifier}
  17. */
  18. function isRefInit(data) {
  19. const init = data && data.variableDeclarator && data.variableDeclarator.init
  20. if (!init) {
  21. return false
  22. }
  23. return data.defineChain.includes(/** @type {any} */ (init))
  24. }
  25. /**
  26. * Get the callee member node from the given CallExpression
  27. * @param {CallExpression} node CallExpression
  28. */
  29. function getNameParamNode(node) {
  30. const nameLiteralNode = node.arguments[0]
  31. if (nameLiteralNode && utils.isStringLiteral(nameLiteralNode)) {
  32. const name = utils.getStringLiteralValue(nameLiteralNode)
  33. if (name != null) {
  34. return { name, loc: nameLiteralNode.loc }
  35. }
  36. }
  37. // cannot check
  38. return null
  39. }
  40. /**
  41. * Get the callee member node from the given CallExpression
  42. * @param {CallExpression} node CallExpression
  43. */
  44. function getCalleeMemberNode(node) {
  45. const callee = utils.skipChainExpression(node.callee)
  46. if (callee.type === 'MemberExpression') {
  47. const name = utils.getStaticPropertyName(callee)
  48. if (name) {
  49. return { name, member: callee }
  50. }
  51. }
  52. return null
  53. }
  54. module.exports = {
  55. meta: {
  56. type: 'suggestion',
  57. docs: {
  58. description:
  59. 'disallow use of value wrapped by `ref()` (Composition API) as an operand',
  60. categories: ['vue3-essential', 'vue2-essential'],
  61. url: 'https://eslint.vuejs.org/rules/no-ref-as-operand.html'
  62. },
  63. fixable: 'code',
  64. schema: [],
  65. messages: {
  66. requireDotValue:
  67. 'Must use `.value` to read or write the value wrapped by `{{method}}()`.'
  68. }
  69. },
  70. /** @param {RuleContext} context */
  71. create(context) {
  72. /** @type {RefObjectReferences} */
  73. let refReferences
  74. const setupContexts = new Map()
  75. /**
  76. * Collect identifier id
  77. * @param {Identifier} node
  78. * @param {Set<Identifier>} referenceIds
  79. */
  80. function collectReferenceIds(node, referenceIds) {
  81. const variable = findVariable(utils.getScope(context, node), node)
  82. if (!variable) {
  83. return
  84. }
  85. for (const reference of variable.references) {
  86. referenceIds.add(reference.identifier)
  87. }
  88. }
  89. /**
  90. * @param {Identifier} node
  91. */
  92. function reportIfRefWrapped(node) {
  93. const data = refReferences.get(node)
  94. if (!isRefInit(data)) {
  95. return
  96. }
  97. context.report({
  98. node,
  99. messageId: 'requireDotValue',
  100. data: {
  101. method: data.method
  102. },
  103. fix(fixer) {
  104. return fixer.insertTextAfter(node, '.value')
  105. }
  106. })
  107. }
  108. /**
  109. * @param {CallExpression} node
  110. */
  111. function reportWrappedIdentifiers(node) {
  112. const nodes = node.arguments.filter((node) => node.type === 'Identifier')
  113. for (const node of nodes) {
  114. reportIfRefWrapped(node)
  115. }
  116. }
  117. const programNode = context.getSourceCode().ast
  118. const callVisitor = {
  119. /**
  120. * @param {CallExpression} node
  121. * @param {import('../utils').VueObjectData} [info]
  122. */
  123. CallExpression(node, info) {
  124. const nameWithLoc = getNameParamNode(node)
  125. if (!nameWithLoc) {
  126. // cannot check
  127. return
  128. }
  129. // verify setup context
  130. const setupContext = setupContexts.get(info ? info.node : programNode)
  131. if (!setupContext) {
  132. return
  133. }
  134. const { contextReferenceIds, emitReferenceIds } = setupContext
  135. if (
  136. node.callee.type === 'Identifier' &&
  137. emitReferenceIds.has(node.callee)
  138. ) {
  139. // verify setup(props,{emit}) {emit()}
  140. reportWrappedIdentifiers(node)
  141. } else {
  142. const emit = getCalleeMemberNode(node)
  143. if (
  144. emit &&
  145. emit.name === 'emit' &&
  146. emit.member.object.type === 'Identifier' &&
  147. contextReferenceIds.has(emit.member.object)
  148. ) {
  149. // verify setup(props,context) {context.emit()}
  150. reportWrappedIdentifiers(node)
  151. }
  152. }
  153. }
  154. }
  155. return utils.compositingVisitors(
  156. {
  157. Program() {
  158. refReferences = extractRefObjectReferences(context)
  159. },
  160. // if (refValue)
  161. /** @param {Identifier} node */
  162. 'IfStatement>Identifier'(node) {
  163. reportIfRefWrapped(node)
  164. },
  165. // switch (refValue)
  166. /** @param {Identifier} node */
  167. 'SwitchStatement>Identifier'(node) {
  168. reportIfRefWrapped(node)
  169. },
  170. // -refValue, +refValue, !refValue, ~refValue, typeof refValue
  171. /** @param {Identifier} node */
  172. 'UnaryExpression>Identifier'(node) {
  173. reportIfRefWrapped(node)
  174. },
  175. // refValue++, refValue--
  176. /** @param {Identifier} node */
  177. 'UpdateExpression>Identifier'(node) {
  178. reportIfRefWrapped(node)
  179. },
  180. // refValue+1, refValue-1
  181. /** @param {Identifier} node */
  182. 'BinaryExpression>Identifier'(node) {
  183. reportIfRefWrapped(node)
  184. },
  185. // refValue+=1, refValue-=1, foo+=refValue, foo-=refValue
  186. /** @param {Identifier & {parent: AssignmentExpression}} node */
  187. 'AssignmentExpression>Identifier'(node) {
  188. if (node.parent.operator === '=' && node.parent.left !== node) {
  189. return
  190. }
  191. reportIfRefWrapped(node)
  192. },
  193. // refValue || other, refValue && other. ignore: other || refValue
  194. /** @param {Identifier & {parent: LogicalExpression}} node */
  195. 'LogicalExpression>Identifier'(node) {
  196. if (node.parent.left !== node) {
  197. return
  198. }
  199. // Report only constants.
  200. const data = refReferences.get(node)
  201. if (
  202. !data ||
  203. !data.variableDeclaration ||
  204. data.variableDeclaration.kind !== 'const'
  205. ) {
  206. return
  207. }
  208. reportIfRefWrapped(node)
  209. },
  210. // refValue ? x : y
  211. /** @param {Identifier & {parent: ConditionalExpression}} node */
  212. 'ConditionalExpression>Identifier'(node) {
  213. if (node.parent.test !== node) {
  214. return
  215. }
  216. reportIfRefWrapped(node)
  217. },
  218. // `${refValue}`
  219. /** @param {Identifier} node */
  220. 'TemplateLiteral>Identifier'(node) {
  221. reportIfRefWrapped(node)
  222. },
  223. // refValue.x
  224. /** @param {Identifier & {parent: MemberExpression}} node */
  225. 'MemberExpression>Identifier'(node) {
  226. if (node.parent.object !== node) {
  227. return
  228. }
  229. const name = utils.getStaticPropertyName(node.parent)
  230. if (
  231. name === 'value' ||
  232. name == null ||
  233. // WritableComputedRef
  234. name === 'effect'
  235. ) {
  236. return
  237. }
  238. reportIfRefWrapped(node)
  239. }
  240. },
  241. utils.defineScriptSetupVisitor(context, {
  242. onDefineEmitsEnter(node) {
  243. if (
  244. !node.parent ||
  245. node.parent.type !== 'VariableDeclarator' ||
  246. node.parent.init !== node
  247. ) {
  248. return
  249. }
  250. const emitParam = node.parent.id
  251. if (emitParam.type !== 'Identifier') {
  252. return
  253. }
  254. // const emit = defineEmits()
  255. const emitReferenceIds = new Set()
  256. collectReferenceIds(emitParam, emitReferenceIds)
  257. setupContexts.set(programNode, {
  258. contextReferenceIds: new Set(),
  259. emitReferenceIds
  260. })
  261. },
  262. ...callVisitor
  263. }),
  264. utils.defineVueVisitor(context, {
  265. onSetupFunctionEnter(node, { node: vueNode }) {
  266. const contextParam = utils.skipDefaultParamValue(node.params[1])
  267. if (!contextParam) {
  268. // no arguments
  269. return
  270. }
  271. if (
  272. contextParam.type === 'RestElement' ||
  273. contextParam.type === 'ArrayPattern'
  274. ) {
  275. // cannot check
  276. return
  277. }
  278. const contextReferenceIds = new Set()
  279. const emitReferenceIds = new Set()
  280. if (contextParam.type === 'ObjectPattern') {
  281. const emitProperty = utils.findAssignmentProperty(
  282. contextParam,
  283. 'emit'
  284. )
  285. if (!emitProperty || emitProperty.value.type !== 'Identifier') {
  286. return
  287. }
  288. // `setup(props, {emit})`
  289. collectReferenceIds(emitProperty.value, emitReferenceIds)
  290. } else {
  291. // `setup(props, context)`
  292. collectReferenceIds(contextParam, contextReferenceIds)
  293. }
  294. setupContexts.set(vueNode, {
  295. contextReferenceIds,
  296. emitReferenceIds
  297. })
  298. },
  299. ...callVisitor,
  300. onVueObjectExit(node) {
  301. setupContexts.delete(node)
  302. }
  303. })
  304. )
  305. }
  306. }