2 * @fileoverview Source code for spaced-comments rule
3 * @author Gyandeep Singh
7 const escapeRegExp
= require("escape-string-regexp");
8 const astUtils
= require("./utils/ast-utils");
10 //------------------------------------------------------------------------------
12 //------------------------------------------------------------------------------
15 * Escapes the control characters of a given string.
16 * @param {string} s A string to escape.
17 * @returns {string} An escaped string.
20 return `(?:${escapeRegExp(s)})`;
24 * Escapes the control characters of a given string.
25 * And adds a repeat flag.
26 * @param {string} s A string to escape.
27 * @returns {string} An escaped string.
29 function escapeAndRepeat(s
) {
30 return `${escape(s)}+`;
34 * Parses `markers` option.
35 * If markers don't include `"*"`, this adds `"*"` to allow JSDoc comments.
36 * @param {string[]} [markers] A marker list.
37 * @returns {string[]} A marker list.
39 function parseMarkersOption(markers
) {
41 // `*` is a marker for JSDoc comments.
42 if (!markers
.includes("*")) {
43 return markers
.concat("*");
50 * Creates string pattern for exceptions.
53 * 1. A space or an exception pattern sequence.
54 * @param {string[]} exceptions An exception pattern list.
55 * @returns {string} A regular expression string for exceptions.
57 function createExceptionsPattern(exceptions
) {
61 * A space or an exception pattern sequence.
63 * ["-"] ==> "(?:\s|\-+$)"
64 * ["-", "="] ==> "(?:\s|(?:\-+|=+)$)"
65 * ["-", "=", "--=="] ==> "(?:\s|(?:\-+|=+|(?:\-\-==)+)$)" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5Cs%7C(%3F%3A%5C-%2B%7C%3D%2B%7C(%3F%3A%5C-%5C-%3D%3D)%2B)%24)
67 if (exceptions
.length
=== 0) {
76 if (exceptions
.length
=== 1) {
78 // a sequence of the exception pattern.
79 pattern
+= escapeAndRepeat(exceptions
[0]);
82 // a sequence of one of the exception patterns.
84 pattern
+= exceptions
.map(escapeAndRepeat
).join("|");
87 pattern
+= `(?:$|[${Array.from(astUtils.LINEBREAKS).join("")}]))`;
94 * Creates RegExp object for `always` mode.
95 * Generated pattern for beginning of comment:
97 * 1. First, a marker or nothing.
98 * 2. Next, a space or an exception pattern sequence.
99 * @param {string[]} markers A marker list.
100 * @param {string[]} exceptions An exception pattern list.
101 * @returns {RegExp} A RegExp object for the beginning of a comment in `always` mode.
103 function createAlwaysStylePattern(markers
, exceptions
) {
107 * A marker or nothing.
109 * ["*", "!"] ==> "(?:\*|!)?"
110 * ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F
112 if (markers
.length
=== 1) {
115 pattern
+= escape(markers
[0]);
120 pattern
+= markers
.map(escape
).join("|");
124 pattern
+= "?"; // or nothing.
125 pattern
+= createExceptionsPattern(exceptions
);
127 return new RegExp(pattern
, "u");
131 * Creates RegExp object for `never` mode.
132 * Generated pattern for beginning of comment:
134 * 1. First, a marker or nothing (captured).
135 * 2. Next, a space or a tab.
136 * @param {string[]} markers A marker list.
137 * @returns {RegExp} A RegExp object for `never` mode.
139 function createNeverStylePattern(markers
) {
140 const pattern
= `^(${markers.map(escape).join("|")})?[ \t]+`;
142 return new RegExp(pattern
, "u");
145 //------------------------------------------------------------------------------
147 //------------------------------------------------------------------------------
149 /** @type {import('../shared/types').Rule} */
155 description
: "Enforce consistent spacing after the `//` or `/*` in a comment",
157 url
: "https://eslint.org/docs/rules/spaced-comment"
160 fixable
: "whitespace",
164 enum: ["always", "never"]
197 additionalProperties
: false
219 additionalProperties
: false
222 additionalProperties
: false
227 unexpectedSpaceAfterMarker
: "Unexpected space or tab after marker ({{refChar}}) in comment.",
228 expectedExceptionAfter
: "Expected exception block, space or tab after '{{refChar}}' in comment.",
229 unexpectedSpaceBefore
: "Unexpected space or tab before '*/' in comment.",
230 unexpectedSpaceAfter
: "Unexpected space or tab after '{{refChar}}' in comment.",
231 expectedSpaceBefore
: "Expected space or tab before '*/' in comment.",
232 expectedSpaceAfter
: "Expected space or tab after '{{refChar}}' in comment."
238 const sourceCode
= context
.getSourceCode();
240 // Unless the first option is never, require a space
241 const requireSpace
= context
.options
[0] !== "never";
244 * Parse the second options.
245 * If markers don't include `"*"`, it's added automatically for JSDoc
248 const config
= context
.options
[1] || {};
249 const balanced
= config
.block
&& config
.block
.balanced
;
251 const styleRules
= ["block", "line"].reduce((rule
, type
) => {
252 const markers
= parseMarkersOption(config
[type
] && config
[type
].markers
|| config
.markers
|| []);
253 const exceptions
= config
[type
] && config
[type
].exceptions
|| config
.exceptions
|| [];
254 const endNeverPattern
= "[ \t]+$";
256 // Create RegExp object for valid patterns.
258 beginRegex
: requireSpace
? createAlwaysStylePattern(markers
, exceptions
) : createNeverStylePattern(markers
),
259 endRegex
: balanced
&& requireSpace
? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern
, "u"),
260 hasExceptions
: exceptions
.length
> 0,
261 captureMarker
: new RegExp(`^(${markers.map(escape).join("|")})`, "u"),
262 markers
: new Set(markers
)
269 * Reports a beginning spacing error with an appropriate message.
270 * @param {ASTNode} node A comment node to check.
271 * @param {string} messageId An error message to report.
272 * @param {Array} match An array of match results for markers.
273 * @param {string} refChar Character used for reference in the error message.
276 function reportBegin(node
, messageId
, match
, refChar
) {
277 const type
= node
.type
.toLowerCase(),
278 commentIdentifier
= type
=== "block" ? "/*" : "//";
283 const start
= node
.range
[0];
288 end
+= match
[0].length
;
290 return fixer
.insertTextAfterRange([start
, end
], " ");
292 end
+= match
[0].length
;
293 return fixer
.replaceTextRange([start
, end
], commentIdentifier
+ (match
[1] ? match
[1] : ""));
302 * Reports an ending spacing error with an appropriate message.
303 * @param {ASTNode} node A comment node to check.
304 * @param {string} messageId An error message to report.
305 * @param {string} match An array of the matched whitespace characters.
308 function reportEnd(node
, messageId
, match
) {
313 return fixer
.insertTextAfterRange([node
.range
[0], node
.range
[1] - 2], " ");
315 const end
= node
.range
[1] - 2,
316 start
= end
- match
[0].length
;
318 return fixer
.replaceTextRange([start
, end
], "");
326 * Reports a given comment if it's invalid.
327 * @param {ASTNode} node a comment node to check.
330 function checkCommentForSpace(node
) {
331 const type
= node
.type
.toLowerCase(),
332 rule
= styleRules
[type
],
333 commentIdentifier
= type
=== "block" ? "/*" : "//";
335 // Ignores empty comments and comments that consist only of a marker.
336 if (node
.value
.length
=== 0 || rule
.markers
.has(node
.value
)) {
340 const beginMatch
= rule
.beginRegex
.exec(node
.value
);
341 const endMatch
= rule
.endRegex
.exec(node
.value
);
346 const hasMarker
= rule
.captureMarker
.exec(node
.value
);
347 const marker
= hasMarker
? commentIdentifier
+ hasMarker
[0] : commentIdentifier
;
349 if (rule
.hasExceptions
) {
350 reportBegin(node
, "expectedExceptionAfter", hasMarker
, marker
);
352 reportBegin(node
, "expectedSpaceAfter", hasMarker
, marker
);
356 if (balanced
&& type
=== "block" && !endMatch
) {
357 reportEnd(node
, "expectedSpaceBefore");
361 if (!beginMatch
[1]) {
362 reportBegin(node
, "unexpectedSpaceAfter", beginMatch
, commentIdentifier
);
364 reportBegin(node
, "unexpectedSpaceAfterMarker", beginMatch
, beginMatch
[1]);
368 if (balanced
&& type
=== "block" && endMatch
) {
369 reportEnd(node
, "unexpectedSpaceBefore", endMatch
);
376 const comments
= sourceCode
.getAllComments();
378 comments
.filter(token
=> token
.type
!== "Shebang").forEach(checkCommentForSpace
);