UNPKG

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