no-v-for-template-key-on-child.js 3.1 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /**
  2. * @author Yosuke Ota
  3. * This rule is based on X_V_FOR_TEMPLATE_KEY_PLACEMENT error of Vue 3.
  4. * see https://github.com/vuejs/vue-next/blob/b0d01e9db9ffe5781cce5a2d62c8552db3d615b0/packages/compiler-core/src/errors.ts#L70
  5. */
  6. 'use strict'
  7. // ------------------------------------------------------------------------------
  8. // Requirements
  9. // ------------------------------------------------------------------------------
  10. const utils = require('../utils')
  11. // ------------------------------------------------------------------------------
  12. // Helpers
  13. // ------------------------------------------------------------------------------
  14. /**
  15. * Check whether the given attribute is using the variables which are defined by `v-for` directives.
  16. * @param {VDirective} vFor The attribute node of `v-for` to check.
  17. * @param {VDirective} vBindKey The attribute node of `v-bind:key` to check.
  18. * @returns {boolean} `true` if the node is using the variables which are defined by `v-for` directives.
  19. */
  20. function isUsingIterationVar(vFor, vBindKey) {
  21. if (vBindKey.value == null) {
  22. return false
  23. }
  24. const references = vBindKey.value.references
  25. const variables = vFor.parent.parent.variables
  26. return references.some((reference) =>
  27. variables.some(
  28. (variable) =>
  29. variable.id.name === reference.id.name && variable.kind === 'v-for'
  30. )
  31. )
  32. }
  33. // ------------------------------------------------------------------------------
  34. // Rule Definition
  35. // ------------------------------------------------------------------------------
  36. module.exports = {
  37. meta: {
  38. type: 'problem',
  39. docs: {
  40. description:
  41. 'disallow key of `<template v-for>` placed on child elements',
  42. categories: ['vue3-essential'],
  43. url: 'https://eslint.vuejs.org/rules/no-v-for-template-key-on-child.html'
  44. },
  45. fixable: null,
  46. schema: [],
  47. messages: {
  48. vForTemplateKeyPlacement:
  49. '`<template v-for>` key should be placed on the `<template>` tag.'
  50. }
  51. },
  52. /** @param {RuleContext} context */
  53. create(context) {
  54. return utils.defineTemplateBodyVisitor(context, {
  55. /** @param {VDirective} node */
  56. "VElement[name='template'] > VStartTag > VAttribute[directive=true][key.name.name='for']"(
  57. node
  58. ) {
  59. const template = node.parent.parent
  60. const vBindKeyOnTemplate = utils.getDirective(template, 'bind', 'key')
  61. if (
  62. vBindKeyOnTemplate &&
  63. isUsingIterationVar(node, vBindKeyOnTemplate)
  64. ) {
  65. return
  66. }
  67. for (const child of template.children.filter(utils.isVElement)) {
  68. if (
  69. utils.hasDirective(child, 'if') ||
  70. utils.hasDirective(child, 'else-if') ||
  71. utils.hasDirective(child, 'else') ||
  72. utils.hasDirective(child, 'for')
  73. ) {
  74. continue
  75. }
  76. const vBindKeyOnChild = utils.getDirective(child, 'bind', 'key')
  77. if (vBindKeyOnChild && isUsingIterationVar(node, vBindKeyOnChild)) {
  78. context.report({
  79. node: vBindKeyOnChild,
  80. loc: vBindKeyOnChild.loc,
  81. messageId: 'vForTemplateKeyPlacement'
  82. })
  83. }
  84. }
  85. }
  86. })
  87. }
  88. }