no-useless-v-bind.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /**
  2. * @author Yosuke Ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const DOUBLE_QUOTES_RE = /"/gu
  8. const SINGLE_QUOTES_RE = /'/gu
  9. module.exports = {
  10. meta: {
  11. docs: {
  12. description: 'disallow unnecessary `v-bind` directives',
  13. categories: undefined,
  14. url: 'https://eslint.vuejs.org/rules/no-useless-v-bind.html'
  15. },
  16. fixable: 'code',
  17. messages: {
  18. unexpected: 'Unexpected `v-bind` with a string literal value.'
  19. },
  20. schema: [
  21. {
  22. type: 'object',
  23. properties: {
  24. ignoreIncludesComment: {
  25. type: 'boolean'
  26. },
  27. ignoreStringEscape: {
  28. type: 'boolean'
  29. }
  30. },
  31. additionalProperties: false
  32. }
  33. ],
  34. type: 'suggestion'
  35. },
  36. /** @param {RuleContext} context */
  37. create(context) {
  38. const opts = context.options[0] || {}
  39. const ignoreIncludesComment = opts.ignoreIncludesComment
  40. const ignoreStringEscape = opts.ignoreStringEscape
  41. const sourceCode = context.getSourceCode()
  42. /**
  43. * Report if the value expression is string literals
  44. * @param {VDirective} node the node to check
  45. */
  46. function verify(node) {
  47. const { value } = node
  48. if (!value || node.key.modifiers.length) {
  49. return
  50. }
  51. const { expression } = value
  52. if (!expression) {
  53. return
  54. }
  55. /** @type {string} */
  56. let strValue
  57. /** @type {string} */
  58. let rawValue
  59. if (expression.type === 'Literal') {
  60. if (typeof expression.value !== 'string') {
  61. return
  62. }
  63. strValue = expression.value
  64. rawValue = sourceCode.getText(expression).slice(1, -1)
  65. } else if (expression.type === 'TemplateLiteral') {
  66. if (expression.expressions.length > 0) {
  67. return
  68. }
  69. strValue = expression.quasis[0].value.cooked
  70. rawValue = expression.quasis[0].value.raw
  71. } else {
  72. return
  73. }
  74. const tokenStore = context.parserServices.getTemplateBodyTokenStore()
  75. const hasComment = tokenStore
  76. .getTokens(value, { includeComments: true })
  77. .some((t) => t.type === 'Block' || t.type === 'Line')
  78. if (ignoreIncludesComment && hasComment) {
  79. return
  80. }
  81. let hasEscape = false
  82. if (rawValue !== strValue) {
  83. // check escapes
  84. const chars = [...rawValue]
  85. let c = chars.shift()
  86. while (c) {
  87. if (c === '\\') {
  88. c = chars.shift()
  89. if (
  90. c == null ||
  91. // ignore "\\", '"', "'", "`" and "$"
  92. 'nrvtbfux'.includes(c)
  93. ) {
  94. // has useful escape.
  95. hasEscape = true
  96. break
  97. }
  98. }
  99. c = chars.shift()
  100. }
  101. }
  102. if (ignoreStringEscape && hasEscape) {
  103. return
  104. }
  105. context.report({
  106. node,
  107. messageId: 'unexpected',
  108. *fix(fixer) {
  109. if (hasComment || hasEscape) {
  110. // cannot fix
  111. return
  112. }
  113. const text = sourceCode.getText(value)
  114. const quoteChar = text[0]
  115. const shorthand = node.key.name.rawName === ':'
  116. /** @type {Range} */
  117. const keyDirectiveRange = [
  118. node.key.name.range[0],
  119. node.key.name.range[1] + (shorthand ? 0 : 1)
  120. ]
  121. yield fixer.removeRange(keyDirectiveRange)
  122. let attrValue
  123. if (quoteChar === '"') {
  124. attrValue = strValue.replace(DOUBLE_QUOTES_RE, '"')
  125. } else if (quoteChar === "'") {
  126. attrValue = strValue.replace(SINGLE_QUOTES_RE, ''')
  127. } else {
  128. attrValue = strValue
  129. .replace(DOUBLE_QUOTES_RE, '"')
  130. .replace(SINGLE_QUOTES_RE, ''')
  131. }
  132. yield fixer.replaceText(expression, attrValue)
  133. }
  134. })
  135. }
  136. return utils.defineTemplateBodyVisitor(context, {
  137. "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]":
  138. verify
  139. })
  140. }
  141. }