no-useless-v-bind.js 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150
  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. }
  32. ],
  33. type: 'suggestion'
  34. },
  35. /** @param {RuleContext} context */
  36. create(context) {
  37. const opts = context.options[0] || {}
  38. const ignoreIncludesComment = opts.ignoreIncludesComment
  39. const ignoreStringEscape = opts.ignoreStringEscape
  40. const sourceCode = context.getSourceCode()
  41. /**
  42. * Report if the value expression is string literals
  43. * @param {VDirective} node the node to check
  44. */
  45. function verify(node) {
  46. const { value } = node
  47. if (!value || node.key.modifiers.length) {
  48. return
  49. }
  50. const { expression } = value
  51. if (!expression) {
  52. return
  53. }
  54. /** @type {string} */
  55. let strValue
  56. /** @type {string} */
  57. let rawValue
  58. if (expression.type === 'Literal') {
  59. if (typeof expression.value !== 'string') {
  60. return
  61. }
  62. strValue = expression.value
  63. rawValue = sourceCode.getText(expression).slice(1, -1)
  64. } else if (expression.type === 'TemplateLiteral') {
  65. if (expression.expressions.length > 0) {
  66. return
  67. }
  68. strValue = expression.quasis[0].value.cooked
  69. rawValue = expression.quasis[0].value.raw
  70. } else {
  71. return
  72. }
  73. const tokenStore = context.parserServices.getTemplateBodyTokenStore()
  74. const hasComment = tokenStore
  75. .getTokens(value, { includeComments: true })
  76. .some((t) => t.type === 'Block' || t.type === 'Line')
  77. if (ignoreIncludesComment && hasComment) {
  78. return
  79. }
  80. let hasEscape = false
  81. if (rawValue !== strValue) {
  82. // check escapes
  83. const chars = [...rawValue]
  84. let c = chars.shift()
  85. while (c) {
  86. if (c === '\\') {
  87. c = chars.shift()
  88. if (
  89. c == null ||
  90. // ignore "\\", '"', "'", "`" and "$"
  91. 'nrvtbfux'.includes(c)
  92. ) {
  93. // has useful escape.
  94. hasEscape = true
  95. break
  96. }
  97. }
  98. c = chars.shift()
  99. }
  100. }
  101. if (ignoreStringEscape && hasEscape) {
  102. return
  103. }
  104. context.report({
  105. node,
  106. messageId: 'unexpected',
  107. *fix(fixer) {
  108. if (hasComment || hasEscape) {
  109. // cannot fix
  110. return
  111. }
  112. const text = sourceCode.getText(value)
  113. const quoteChar = text[0]
  114. const shorthand = node.key.name.rawName === ':'
  115. /** @type {Range} */
  116. const keyDirectiveRange = [
  117. node.key.name.range[0],
  118. node.key.name.range[1] + (shorthand ? 0 : 1)
  119. ]
  120. yield fixer.removeRange(keyDirectiveRange)
  121. let attrValue
  122. if (quoteChar === '"') {
  123. attrValue = strValue.replace(DOUBLE_QUOTES_RE, '"')
  124. } else if (quoteChar === "'") {
  125. attrValue = strValue.replace(SINGLE_QUOTES_RE, ''')
  126. } else {
  127. attrValue = strValue
  128. .replace(DOUBLE_QUOTES_RE, '"')
  129. .replace(SINGLE_QUOTES_RE, ''')
  130. }
  131. yield fixer.replaceText(expression, attrValue)
  132. }
  133. })
  134. }
  135. return utils.defineTemplateBodyVisitor(context, {
  136. "VAttribute[directive=true][key.name.name='bind'][key.argument!=null]": verify
  137. })
  138. }
  139. }