UNPKG

4.96 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
50 create(context) {
51 const sourceCode = context.getSourceCode();
52 const options = context.options[0] || {};
53 const ignoreEOLComments = options.ignoreEOLComments;
54 const exceptions = Object.assign({ Property: true }, options.exceptions);
55 const hasExceptions = Object.keys(exceptions).filter(key => exceptions[key]).length > 0;
56
57 /**
58 * Formats value of given comment token for error message by truncating its length.
59 * @param {Token} token comment token
60 * @returns {string} formatted value
61 * @private
62 */
63 function formatReportedCommentValue(token) {
64 const valueLines = token.value.split("\n");
65 const value = valueLines[0];
66 const formattedValue = `${value.slice(0, 12)}...`;
67
68 return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
69 }
70
71 //--------------------------------------------------------------------------
72 // Public
73 //--------------------------------------------------------------------------
74
75 return {
76 Program() {
77 sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => {
78 if (leftIndex === tokensAndComments.length - 1) {
79 return;
80 }
81 const rightToken = tokensAndComments[leftIndex + 1];
82
83 // Ignore tokens that don't have 2 spaces between them or are on different lines
84 if (
85 !sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes(" ") ||
86 leftToken.loc.end.line < rightToken.loc.start.line
87 ) {
88 return;
89 }
90
91 // Ignore comments that are the last token on their line if `ignoreEOLComments` is active.
92 if (
93 ignoreEOLComments &&
94 astUtils.isCommentToken(rightToken) &&
95 (
96 leftIndex === tokensAndComments.length - 2 ||
97 rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line
98 )
99 ) {
100 return;
101 }
102
103 // Ignore tokens that are in a node in the "exceptions" object
104 if (hasExceptions) {
105 const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1);
106
107 if (parentNode && exceptions[parentNode.type]) {
108 return;
109 }
110 }
111
112 let displayValue;
113
114 if (rightToken.type === "Block") {
115 displayValue = `/*${formatReportedCommentValue(rightToken)}*/`;
116 } else if (rightToken.type === "Line") {
117 displayValue = `//${formatReportedCommentValue(rightToken)}`;
118 } else {
119 displayValue = rightToken.value;
120 }
121
122 context.report({
123 node: rightToken,
124 loc: rightToken.loc.start,
125 message: "Multiple spaces found before '{{displayValue}}'.",
126 data: { displayValue },
127 fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ")
128 });
129 });
130 }
131 };
132
133 }
134};