first-attribute-linebreak.js 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. /**
  2. * @fileoverview Enforce the location of first attribute
  3. * @author Yosuke Ota
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. module.exports = {
  8. meta: {
  9. type: 'layout',
  10. docs: {
  11. description: 'enforce the location of first attribute',
  12. categories: ['vue3-strongly-recommended', 'strongly-recommended'],
  13. url: 'https://eslint.vuejs.org/rules/first-attribute-linebreak.html'
  14. },
  15. fixable: 'whitespace',
  16. schema: [
  17. {
  18. type: 'object',
  19. properties: {
  20. multiline: { enum: ['below', 'beside', 'ignore'] },
  21. singleline: { enum: ['below', 'beside', 'ignore'] }
  22. },
  23. additionalProperties: false
  24. }
  25. ],
  26. messages: {
  27. expected: 'Expected a linebreak before this attribute.',
  28. unexpected: 'Expected no linebreak before this attribute.'
  29. }
  30. },
  31. /** @param {RuleContext} context */
  32. create(context) {
  33. /** @type {"below" | "beside" | "ignore"} */
  34. const singleline =
  35. (context.options[0] && context.options[0].singleline) || 'ignore'
  36. /** @type {"below" | "beside" | "ignore"} */
  37. const multiline =
  38. (context.options[0] && context.options[0].multiline) || 'below'
  39. const template =
  40. context.parserServices.getTemplateBodyTokenStore &&
  41. context.parserServices.getTemplateBodyTokenStore()
  42. /**
  43. * Report attribute
  44. * @param {VAttribute | VDirective} firstAttribute
  45. * @param { "below" | "beside"} location
  46. */
  47. function report(firstAttribute, location) {
  48. context.report({
  49. node: firstAttribute,
  50. messageId: location === 'beside' ? 'unexpected' : 'expected',
  51. fix(fixer) {
  52. const prevToken = template.getTokenBefore(firstAttribute, {
  53. includeComments: true
  54. })
  55. return fixer.replaceTextRange(
  56. [prevToken.range[1], firstAttribute.range[0]],
  57. location === 'beside' ? ' ' : '\n'
  58. )
  59. }
  60. })
  61. }
  62. return utils.defineTemplateBodyVisitor(context, {
  63. VStartTag(node) {
  64. const firstAttribute = node.attributes[0]
  65. if (!firstAttribute) return
  66. const lastAttribute = node.attributes[node.attributes.length - 1]
  67. const location =
  68. firstAttribute.loc.start.line === lastAttribute.loc.end.line
  69. ? singleline
  70. : multiline
  71. if (location === 'ignore') {
  72. return
  73. }
  74. if (location === 'beside') {
  75. if (node.loc.start.line === firstAttribute.loc.start.line) {
  76. return
  77. }
  78. } else {
  79. if (node.loc.start.line < firstAttribute.loc.start.line) {
  80. return
  81. }
  82. }
  83. report(firstAttribute, location)
  84. }
  85. })
  86. }
  87. }