no-irregular-whitespace.js 8.1 KB

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