1/** 2 * @fileoverview Disallow use of multiple spaces. 3 * @author Nicholas C. Zakas 4 */ 5 6"use strict"; 7 8const astUtils = require("./utils/ast-utils"); 9 10//------------------------------------------------------------------------------ 11// Rule Definition 12//------------------------------------------------------------------------------ 13 14module.exports = { 15 meta: { 16 type: "layout", 17 18 docs: { 19 description: "disallow multiple spaces", 20 category: "Best Practices", 21 recommended: false, 22 url: "https://eslint.org/docs/rules/no-multi-spaces" 23 }, 24 25 fixable: "whitespace", 26 27 schema: [ 28 { 29 type: "object", 30 properties: { 31 exceptions: { 32 type: "object", 33 patternProperties: { 34 "^([A-Z][a-z]*)+$": { 35 type: "boolean" 36 } 37 }, 38 additionalProperties: false 39 }, 40 ignoreEOLComments: { 41 type: "boolean", 42 default: false 43 } 44 }, 45 additionalProperties: false 46 } 47 ], 48 49 messages: { 50 multipleSpaces: "Multiple spaces found before '{{displayValue}}'." 51 } 52 }, 53 54 create(context) { 55 const sourceCode = context.getSourceCode(); 56 const options = context.options[0] || {}; 57 const ignoreEOLComments = options.ignoreEOLComments; 58 const exceptions = Object.assign({ Property: true }, options.exceptions); 59 const hasExceptions = Object.keys(exceptions).filter(key => exceptions[key]).length > 0; 60 61 /** 62 * Formats value of given comment token for error message by truncating its length. 63 * @param {Token} token comment token 64 * @returns {string} formatted value 65 * @private 66 */ 67 function formatReportedCommentValue(token) { 68 const valueLines = token.value.split("\n"); 69 const value = valueLines[0]; 70 const formattedValue = `${value.slice(0, 12)}...`; 71 72 return valueLines.length === 1 && value.length <= 12 ? value : formattedValue; 73 } 74 75 //-------------------------------------------------------------------------- 76 // Public 77 //-------------------------------------------------------------------------- 78 79 return { 80 Program() { 81 sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => { 82 if (leftIndex === tokensAndComments.length - 1) { 83 return; 84 } 85 const rightToken = tokensAndComments[leftIndex + 1]; 86 87 // Ignore tokens that don't have 2 spaces between them or are on different lines 88 if ( 89 !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes(" ") || 90 leftToken.loc.end.line < rightToken.loc.start.line 91 ) { 92 return; 93 } 94 95 // Ignore comments that are the last token on their line if `ignoreEOLComments` is active. 96 if ( 97 ignoreEOLComments && 98 astUtils.isCommentToken(rightToken) && 99 ( 100 leftIndex === tokensAndComments.length - 2 || 101 rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line 102 ) 103 ) { 104 return; 105 } 106 107 // Ignore tokens that are in a node in the "exceptions" object 108 if (hasExceptions) { 109 const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1); 110 111 if (parentNode && exceptions[parentNode.type]) { 112 return; 113 } 114 } 115 116 let displayValue; 117 118 if (rightToken.type === "Block") { 119 displayValue = `/*${formatReportedCommentValue(rightToken)}*/`; 120 } else if (rightToken.type === "Line") { 121 displayValue = `//${formatReportedCommentValue(rightToken)}`; 122 } else { 123 displayValue = rightToken.value; 124 } 125 126 context.report({ 127 node: rightToken, 128 loc: { start: leftToken.loc.end, end: rightToken.loc.start }, 129 messageId: "multipleSpaces", 130 data: { displayValue }, 131 fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ") 132 }); 133 }); 134 } 135 }; 136 137 } 138}; 139