no-irregular-whitespace.js 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249
  1. /**
  2. * @author Yosuke Ota
  3. * @fileoverview Rule to disalow whitespace that is not a tab or space, whitespace inside strings and comments are allowed
  4. */
  5. 'use strict'
  6. const utils = require('../utils')
  7. const ALL_IRREGULARS =
  8. /[\f\v\u0085\uFEFF\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000\u2028\u2029]/u
  9. const IRREGULAR_WHITESPACE =
  10. /[\f\v\u0085\uFEFF\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u200B\u202F\u205F\u3000]+/gmu
  11. const IRREGULAR_LINE_TERMINATORS = /[\u2028\u2029]/gmu
  12. module.exports = {
  13. meta: {
  14. type: 'problem',
  15. docs: {
  16. description: 'disallow irregular whitespace in `.vue` files',
  17. categories: undefined,
  18. url: 'https://eslint.vuejs.org/rules/no-irregular-whitespace.html',
  19. extensionSource: {
  20. url: 'https://eslint.org/docs/rules/no-irregular-whitespace',
  21. name: 'ESLint core'
  22. }
  23. },
  24. schema: [
  25. {
  26. type: 'object',
  27. properties: {
  28. skipComments: {
  29. type: 'boolean',
  30. default: false
  31. },
  32. skipStrings: {
  33. type: 'boolean',
  34. default: true
  35. },
  36. skipTemplates: {
  37. type: 'boolean',
  38. default: false
  39. },
  40. skipRegExps: {
  41. type: 'boolean',
  42. default: false
  43. },
  44. skipHTMLAttributeValues: {
  45. type: 'boolean',
  46. default: false
  47. },
  48. skipHTMLTextContents: {
  49. type: 'boolean',
  50. default: false
  51. }
  52. },
  53. additionalProperties: false
  54. }
  55. ],
  56. messages: {
  57. disallow: 'Irregular whitespace not allowed.'
  58. }
  59. },
  60. /**
  61. * @param {RuleContext} context - The rule context.
  62. * @returns {RuleListener} AST event handlers.
  63. */
  64. create(context) {
  65. // Module store of error indexes that we have found
  66. /** @type {number[]} */
  67. let errorIndexes = []
  68. // Lookup the `skipComments` option, which defaults to `false`.
  69. const options = context.options[0] || {}
  70. const skipComments = !!options.skipComments
  71. const skipStrings = options.skipStrings !== false
  72. const skipRegExps = !!options.skipRegExps
  73. const skipTemplates = !!options.skipTemplates
  74. const skipHTMLAttributeValues = !!options.skipHTMLAttributeValues
  75. const skipHTMLTextContents = !!options.skipHTMLTextContents
  76. const sourceCode = context.getSourceCode()
  77. /**
  78. * Removes errors that occur inside a string node
  79. * @param {ASTNode | Token} node to check for matching errors.
  80. * @returns {void}
  81. * @private
  82. */
  83. function removeWhitespaceError(node) {
  84. const [startIndex, endIndex] = node.range
  85. errorIndexes = errorIndexes.filter(
  86. (errorIndex) => errorIndex < startIndex || endIndex <= errorIndex
  87. )
  88. }
  89. /**
  90. * Checks literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
  91. * @param {Literal} node to check for matching errors.
  92. * @returns {void}
  93. * @private
  94. */
  95. function removeInvalidNodeErrorsInLiteral(node) {
  96. const shouldCheckStrings = skipStrings && typeof node.value === 'string'
  97. const shouldCheckRegExps = skipRegExps && Boolean(node.regex)
  98. // If we have irregular characters, remove them from the errors list
  99. if (
  100. (shouldCheckStrings || shouldCheckRegExps) &&
  101. ALL_IRREGULARS.test(sourceCode.getText(node))
  102. ) {
  103. removeWhitespaceError(node)
  104. }
  105. }
  106. /**
  107. * Checks template string literal nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
  108. * @param {TemplateElement} node to check for matching errors.
  109. * @returns {void}
  110. * @private
  111. */
  112. function removeInvalidNodeErrorsInTemplateLiteral(node) {
  113. if (ALL_IRREGULARS.test(node.value.raw)) {
  114. removeWhitespaceError(node)
  115. }
  116. }
  117. /**
  118. * Checks HTML attribute value nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
  119. * @param {VLiteral} node to check for matching errors.
  120. * @returns {void}
  121. * @private
  122. */
  123. function removeInvalidNodeErrorsInHTMLAttributeValue(node) {
  124. if (ALL_IRREGULARS.test(sourceCode.getText(node))) {
  125. removeWhitespaceError(node)
  126. }
  127. }
  128. /**
  129. * Checks HTML text content nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
  130. * @param {VText} node to check for matching errors.
  131. * @returns {void}
  132. * @private
  133. */
  134. function removeInvalidNodeErrorsInHTMLTextContent(node) {
  135. if (ALL_IRREGULARS.test(sourceCode.getText(node))) {
  136. removeWhitespaceError(node)
  137. }
  138. }
  139. /**
  140. * Checks comment nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
  141. * @param {Comment | HTMLComment | HTMLBogusComment} node to check for matching errors.
  142. * @returns {void}
  143. * @private
  144. */
  145. function removeInvalidNodeErrorsInComment(node) {
  146. if (ALL_IRREGULARS.test(node.value)) {
  147. removeWhitespaceError(node)
  148. }
  149. }
  150. /**
  151. * Checks the program source for irregular whitespaces and irregular line terminators
  152. * @returns {void}
  153. * @private
  154. */
  155. function checkForIrregularWhitespace() {
  156. const source = sourceCode.getText()
  157. let match
  158. while ((match = IRREGULAR_WHITESPACE.exec(source)) !== null) {
  159. errorIndexes.push(match.index)
  160. }
  161. while ((match = IRREGULAR_LINE_TERMINATORS.exec(source)) !== null) {
  162. errorIndexes.push(match.index)
  163. }
  164. }
  165. checkForIrregularWhitespace()
  166. if (errorIndexes.length === 0) {
  167. return {}
  168. }
  169. const bodyVisitor = utils.defineTemplateBodyVisitor(context, {
  170. ...(skipHTMLAttributeValues
  171. ? {
  172. 'VAttribute[directive=false] > VLiteral':
  173. removeInvalidNodeErrorsInHTMLAttributeValue
  174. }
  175. : {}),
  176. ...(skipHTMLTextContents
  177. ? { VText: removeInvalidNodeErrorsInHTMLTextContent }
  178. : {}),
  179. // inline scripts
  180. Literal: removeInvalidNodeErrorsInLiteral,
  181. ...(skipTemplates
  182. ? { TemplateElement: removeInvalidNodeErrorsInTemplateLiteral }
  183. : {})
  184. })
  185. return {
  186. ...bodyVisitor,
  187. Literal: removeInvalidNodeErrorsInLiteral,
  188. ...(skipTemplates
  189. ? { TemplateElement: removeInvalidNodeErrorsInTemplateLiteral }
  190. : {}),
  191. 'Program:exit'(node) {
  192. if (bodyVisitor['Program:exit']) {
  193. bodyVisitor['Program:exit'](node)
  194. }
  195. const templateBody = node.templateBody
  196. if (skipComments) {
  197. // First strip errors occurring in comment nodes.
  198. for (const node of sourceCode.getAllComments()) {
  199. removeInvalidNodeErrorsInComment(node)
  200. }
  201. if (templateBody) {
  202. for (const node of templateBody.comments) {
  203. removeInvalidNodeErrorsInComment(node)
  204. }
  205. }
  206. }
  207. // Removes errors that occur outside script and template
  208. const [scriptStart, scriptEnd] = node.range
  209. const [templateStart, templateEnd] = templateBody
  210. ? templateBody.range
  211. : [0, 0]
  212. errorIndexes = errorIndexes.filter(
  213. (errorIndex) =>
  214. (scriptStart <= errorIndex && errorIndex < scriptEnd) ||
  215. (templateStart <= errorIndex && errorIndex < templateEnd)
  216. )
  217. // If we have any errors remaining, report on them
  218. for (const errorIndex of errorIndexes) {
  219. context.report({
  220. loc: sourceCode.getLocFromIndex(errorIndex),
  221. messageId: 'disallow'
  222. })
  223. }
  224. }
  225. }
  226. }
  227. }