UNPKG

5.08 kBJavaScriptView Raw
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};