html-comment-content-newline.js 6.0 KB

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