html-comment-content-newline.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. /**
  2. * @author Yosuke ota
  3. * See LICENSE file in root directory for full license.
  4. */
  5. 'use strict'
  6. const htmlComments = require('../utils/html-comments')
  7. /**
  8. * @typedef { import('../utils/html-comments').ParsedHTMLComment } ParsedHTMLComment
  9. */
  10. /**
  11. * @param {any} param
  12. */
  13. function parseOption(param) {
  14. if (param && typeof param === 'string') {
  15. return {
  16. singleline: param,
  17. multiline: param
  18. }
  19. }
  20. return Object.assign(
  21. {
  22. singleline: 'never',
  23. multiline: 'always'
  24. },
  25. param
  26. )
  27. }
  28. module.exports = {
  29. meta: {
  30. type: 'layout',
  31. docs: {
  32. description: 'enforce unified line brake in HTML comments',
  33. categories: undefined,
  34. url: 'https://eslint.vuejs.org/rules/html-comment-content-newline.html'
  35. },
  36. fixable: 'whitespace',
  37. schema: [
  38. {
  39. anyOf: [
  40. {
  41. enum: ['always', 'never']
  42. },
  43. {
  44. type: 'object',
  45. properties: {
  46. singleline: { enum: ['always', 'never', 'ignore'] },
  47. multiline: { enum: ['always', 'never', 'ignore'] }
  48. },
  49. additionalProperties: false
  50. }
  51. ]
  52. },
  53. {
  54. type: 'object',
  55. properties: {
  56. exceptions: {
  57. type: 'array',
  58. items: {
  59. type: 'string'
  60. }
  61. }
  62. },
  63. additionalProperties: false
  64. }
  65. ],
  66. messages: {
  67. expectedAfterHTMLCommentOpen: "Expected line break after '<!--'.",
  68. expectedBeforeHTMLCommentOpen: "Expected line break before '-->'.",
  69. expectedAfterExceptionBlock: 'Expected line break after exception block.',
  70. expectedBeforeExceptionBlock:
  71. 'Expected line break before exception block.',
  72. unexpectedAfterHTMLCommentOpen: "Unexpected line breaks after '<!--'.",
  73. unexpectedBeforeHTMLCommentOpen: "Unexpected line breaks before '-->'."
  74. }
  75. },
  76. /** @param {RuleContext} context */
  77. create(context) {
  78. const option = parseOption(context.options[0])
  79. return htmlComments.defineVisitor(
  80. context,
  81. context.options[1],
  82. (comment) => {
  83. const { value, openDecoration, closeDecoration } = comment
  84. if (!value) {
  85. return
  86. }
  87. const startLine = openDecoration
  88. ? openDecoration.loc.end.line
  89. : value.loc.start.line
  90. const endLine = closeDecoration
  91. ? closeDecoration.loc.start.line
  92. : value.loc.end.line
  93. const newlineType =
  94. startLine === endLine ? option.singleline : option.multiline
  95. if (newlineType === 'ignore') {
  96. return
  97. }
  98. checkCommentOpen(comment, newlineType !== 'never')
  99. checkCommentClose(comment, newlineType !== 'never')
  100. }
  101. )
  102. /**
  103. * Reports the newline before the contents of a given comment if it's invalid.
  104. * @param {ParsedHTMLComment} comment - comment data.
  105. * @param {boolean} requireNewline - `true` if line breaks are required.
  106. * @returns {void}
  107. */
  108. function checkCommentOpen(comment, requireNewline) {
  109. const { value, openDecoration, open } = comment
  110. if (!value) {
  111. return
  112. }
  113. const beforeToken = openDecoration || open
  114. if (requireNewline) {
  115. if (beforeToken.loc.end.line < value.loc.start.line) {
  116. // Is valid
  117. return
  118. }
  119. context.report({
  120. loc: {
  121. start: beforeToken.loc.end,
  122. end: value.loc.start
  123. },
  124. messageId: openDecoration
  125. ? 'expectedAfterExceptionBlock'
  126. : 'expectedAfterHTMLCommentOpen',
  127. fix: openDecoration
  128. ? undefined
  129. : (fixer) => fixer.insertTextAfter(beforeToken, '\n')
  130. })
  131. } else {
  132. if (beforeToken.loc.end.line === value.loc.start.line) {
  133. // Is valid
  134. return
  135. }
  136. context.report({
  137. loc: {
  138. start: beforeToken.loc.end,
  139. end: value.loc.start
  140. },
  141. messageId: 'unexpectedAfterHTMLCommentOpen',
  142. fix: (fixer) =>
  143. fixer.replaceTextRange([beforeToken.range[1], value.range[0]], ' ')
  144. })
  145. }
  146. }
  147. /**
  148. * Reports the space after the contents of a given comment if it's invalid.
  149. * @param {ParsedHTMLComment} comment - comment data.
  150. * @param {boolean} requireNewline - `true` if line breaks are required.
  151. * @returns {void}
  152. */
  153. function checkCommentClose(comment, requireNewline) {
  154. const { value, closeDecoration, close } = comment
  155. if (!value) {
  156. return
  157. }
  158. const afterToken = closeDecoration || close
  159. if (requireNewline) {
  160. if (value.loc.end.line < afterToken.loc.start.line) {
  161. // Is valid
  162. return
  163. }
  164. context.report({
  165. loc: {
  166. start: value.loc.end,
  167. end: afterToken.loc.start
  168. },
  169. messageId: closeDecoration
  170. ? 'expectedBeforeExceptionBlock'
  171. : 'expectedBeforeHTMLCommentOpen',
  172. fix: closeDecoration
  173. ? undefined
  174. : (fixer) => fixer.insertTextBefore(afterToken, '\n')
  175. })
  176. } else {
  177. if (value.loc.end.line === afterToken.loc.start.line) {
  178. // Is valid
  179. return
  180. }
  181. context.report({
  182. loc: {
  183. start: value.loc.end,
  184. end: afterToken.loc.start
  185. },
  186. messageId: 'unexpectedBeforeHTMLCommentOpen',
  187. fix: (fixer) =>
  188. fixer.replaceTextRange([value.range[1], afterToken.range[0]], ' ')
  189. })
  190. }
  191. }
  192. }
  193. }