‘liusuyi’
2023-10-21 94023628bd9c5e6bf724c37371a19b60d338b291
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
/**
 * @author Yosuke Ota
 * See LICENSE file in root directory for full license.
 */
'use strict'
 
// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------
 
const utils = require('../utils')
 
// ------------------------------------------------------------------------------
// Helpers
// ------------------------------------------------------------------------------
 
// https://github.com/vuejs/vue-next/blob/64e2f4643602c5980361e66674141e61ba60ef70/packages/compiler-core/src/parse.ts#L405
const SPECIAL_TEMPLATE_DIRECTIVES = new Set([
  'if',
  'else',
  'else-if',
  'for',
  'slot'
])
 
// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------
 
module.exports = {
  meta: {
    type: 'problem',
    docs: {
      description: 'disallow unnecessary `<template>`',
      categories: ['vue3-recommended', 'recommended'],
      url: 'https://eslint.vuejs.org/rules/no-lone-template.html'
    },
    fixable: null,
    schema: [
      {
        type: 'object',
        properties: {
          ignoreAccessible: {
            type: 'boolean'
          }
        },
        additionalProperties: false
      }
    ],
    messages: {
      requireDirective: '`<template>` require directive.'
    }
  },
  /** @param {RuleContext} context */
  create(context) {
    const options = context.options[0] || {}
    const ignoreAccessible = options.ignoreAccessible === true
 
    /**
     * @param {VAttribute | VDirective} attr
     */
    function getKeyName(attr) {
      if (attr.directive) {
        if (attr.key.name.name !== 'bind') {
          // no v-bind
          return null
        }
        if (
          !attr.key.argument ||
          attr.key.argument.type === 'VExpressionContainer'
        ) {
          // unknown
          return null
        }
        return attr.key.argument.name
      }
      return attr.key.name
    }
 
    return utils.defineTemplateBodyVisitor(context, {
      /** @param {VStartTag} node */
      "VElement[name='template'][parent.type='VElement'] > VStartTag"(node) {
        if (
          node.attributes.some((attr) => {
            if (attr.directive) {
              const directiveName = attr.key.name.name
              if (SPECIAL_TEMPLATE_DIRECTIVES.has(directiveName)) {
                return true
              }
              if (directiveName === 'slot-scope') {
                // `slot-scope` is deprecated in Vue.js 2.6
                return true
              }
              if (directiveName === 'scope') {
                // `scope` is deprecated in Vue.js 2.5
                return true
              }
            }
 
            const keyName = getKeyName(attr)
            if (keyName === 'slot') {
              // `slot` is deprecated in Vue.js 2.6
              return true
            }
 
            return false
          })
        ) {
          return
        }
 
        if (
          ignoreAccessible &&
          node.attributes.some((attr) => {
            const keyName = getKeyName(attr)
            return keyName === 'id' || keyName === 'ref'
          })
        ) {
          return
        }
 
        context.report({
          node,
          messageId: 'requireDirective'
        })
      }
    })
  }
}