no-multi-spaces.js 3.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. /**
  2. * @fileoverview This rule warns about the usage of extra whitespaces between attributes
  3. * @author Armano
  4. */
  5. 'use strict'
  6. const path = require('path')
  7. /**
  8. * @param {RuleContext} context
  9. * @param {Token} node
  10. */
  11. const isProperty = (context, node) => {
  12. const sourceCode = context.getSourceCode()
  13. return node.type === 'Punctuator' && sourceCode.getText(node) === ':'
  14. }
  15. module.exports = {
  16. meta: {
  17. type: 'layout',
  18. docs: {
  19. description: 'disallow multiple spaces',
  20. categories: ['vue3-strongly-recommended', 'vue2-strongly-recommended'],
  21. url: 'https://eslint.vuejs.org/rules/no-multi-spaces.html'
  22. },
  23. fixable: 'whitespace',
  24. schema: [
  25. {
  26. type: 'object',
  27. properties: {
  28. ignoreProperties: {
  29. type: 'boolean'
  30. }
  31. },
  32. additionalProperties: false
  33. }
  34. ],
  35. messages: {
  36. multipleSpaces: "Multiple spaces found before '{{displayValue}}'.",
  37. useLatestParser:
  38. 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.'
  39. }
  40. },
  41. /**
  42. * @param {RuleContext} context - The rule context.
  43. * @returns {RuleListener} AST event handlers.
  44. */
  45. create(context) {
  46. const options = context.options[0] || {}
  47. const ignoreProperties = options.ignoreProperties === true
  48. return {
  49. Program(node) {
  50. const sourceCode = context.getSourceCode()
  51. if (sourceCode.parserServices.getTemplateBodyTokenStore == null) {
  52. const filename = context.getFilename()
  53. if (path.extname(filename) === '.vue') {
  54. context.report({
  55. loc: { line: 1, column: 0 },
  56. messageId: 'useLatestParser'
  57. })
  58. }
  59. return
  60. }
  61. if (!node.templateBody) {
  62. return
  63. }
  64. const tokenStore = sourceCode.parserServices.getTemplateBodyTokenStore()
  65. const tokens = tokenStore.getTokens(node.templateBody, {
  66. includeComments: true
  67. })
  68. let prevToken = /** @type {Token} */ (tokens.shift())
  69. for (const token of tokens) {
  70. const spaces = token.range[0] - prevToken.range[1]
  71. const shouldIgnore =
  72. ignoreProperties &&
  73. (isProperty(context, token) || isProperty(context, prevToken))
  74. if (
  75. spaces > 1 &&
  76. token.loc.start.line === prevToken.loc.start.line &&
  77. !shouldIgnore
  78. ) {
  79. context.report({
  80. node: token,
  81. loc: {
  82. start: prevToken.loc.end,
  83. end: token.loc.start
  84. },
  85. messageId: 'multipleSpaces',
  86. fix: (fixer) =>
  87. fixer.replaceTextRange(
  88. [prevToken.range[1], token.range[0]],
  89. ' '
  90. ),
  91. data: {
  92. displayValue: sourceCode.getText(token)
  93. }
  94. })
  95. }
  96. prevToken = token
  97. }
  98. }
  99. }
  100. }
  101. }