no-irregular-whitespace.js 8.1 KB

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