UNPKG

9.2 kBJavaScriptView Raw
1/**
2 * @fileoverview Source code for spaced-comments rule
3 * @author Gyandeep Singh
4 * @copyright 2015 Toru Nagashima. All rights reserved.
5 * @copyright 2015 Gyandeep Singh. All rights reserved.
6 * @copyright 2014 Greg Cochard. All rights reserved.
7 */
8"use strict";
9
10var escapeStringRegexp = require("escape-string-regexp");
11
12//------------------------------------------------------------------------------
13// Helpers
14//------------------------------------------------------------------------------
15
16/**
17 * Escapes the control characters of a given string.
18 * @param {string} s - A string to escape.
19 * @returns {string} An escaped string.
20 */
21function escape(s) {
22 var isOneChar = s.length === 1;
23 s = escapeStringRegexp(s);
24 return isOneChar ? s : "(?:" + s + ")";
25}
26
27/**
28 * Escapes the control characters of a given string.
29 * And adds a repeat flag.
30 * @param {string} s - A string to escape.
31 * @returns {string} An escaped string.
32 */
33function escapeAndRepeat(s) {
34 return escape(s) + "+";
35}
36
37/**
38 * Parses `markers` option.
39 * If markers don't include `"*"`, this adds `"*"` to allow JSDoc comments.
40 * @param {string[]} [markers] - A marker list.
41 * @returns {string[]} A marker list.
42 */
43function parseMarkersOption(markers) {
44 markers = markers ? markers.slice(0) : [];
45
46 // `*` is a marker for JSDoc comments.
47 if (markers.indexOf("*") === -1) {
48 markers.push("*");
49 }
50
51 return markers;
52}
53
54/**
55 * Creates RegExp object for `always` mode.
56 * Generated pattern is below:
57 *
58 * 1. First, a marker or nothing.
59 * 2. Next, a space or an exception pattern sequence.
60 *
61 * @param {string[]} markers - A marker list.
62 * @param {string[]} exceptions - A exception pattern list.
63 * @returns {RegExp} A RegExp object for `always` mode.
64 */
65function createAlwaysStylePattern(markers, exceptions) {
66 var pattern = "^";
67
68 // A marker or nothing.
69 // ["*"] ==> "\*?"
70 // ["*", "!"] ==> "(?:\*|!)?"
71 // ["*", "/", "!<"] ==> "(?:\*|\/|(?:!<))?" ==> https://jex.im/regulex/#!embed=false&flags=&re=(%3F%3A%5C*%7C%5C%2F%7C(%3F%3A!%3C))%3F
72 if (markers.length === 1) {
73 // the marker.
74 pattern += escape(markers[0]);
75 } else {
76 // one of markers.
77 pattern += "(?:";
78 pattern += markers.map(escape).join("|");
79 pattern += ")";
80 }
81 pattern += "?"; // or nothing.
82
83 // A space or an exception pattern sequence.
84 // [] ==> "\s"
85 // ["-"] ==> "(?:\s|\-+$)"
86 // ["-", "="] ==> "(?:\s|(?:\-+|=+)$)"
87 // ["-", "=", "--=="] ==> "(?:\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)
88 if (exceptions.length === 0) {
89 // a space.
90 pattern += "\\s";
91 } else {
92 // a space or...
93 pattern += "(?:\\s|";
94 if (exceptions.length === 1) {
95 // a sequence of the exception pattern.
96 pattern += escapeAndRepeat(exceptions[0]);
97 } else {
98 // a sequence of one of exception patterns.
99 pattern += "(?:";
100 pattern += exceptions.map(escapeAndRepeat).join("|");
101 pattern += ")";
102 }
103 pattern += "(?:$|[\n\r]))"; // the sequence continues until the end.
104 }
105 return new RegExp(pattern);
106}
107
108/**
109 * Creates RegExp object for `never` mode.
110 * Generated pattern is below:
111 *
112 * 1. First, a marker or nothing (captured).
113 * 2. Next, a space or a tab.
114 *
115 * @param {string[]} markers - A marker list.
116 * @returns {RegExp} A RegExp object for `never` mode.
117 */
118function createNeverStylePattern(markers) {
119 var pattern = "^(" + markers.map(escape).join("|") + ")?[ \t]+";
120 return new RegExp(pattern);
121}
122
123//------------------------------------------------------------------------------
124// Rule Definition
125//------------------------------------------------------------------------------
126
127module.exports = function(context) {
128 // Unless the first option is never, require a space
129 var requireSpace = context.options[0] !== "never";
130
131 // Parse the second options.
132 // If markers don't include `"*"`, it's added automatically for JSDoc comments.
133 var config = context.options[1] || {};
134 var styleRules = ["block", "line"].reduce(function(rule, type) {
135 var markers = parseMarkersOption(config[type] && config[type].markers || config.markers);
136 var exceptions = config[type] && config[type].exceptions || config.exceptions || [];
137
138 // Create RegExp object for valid patterns.
139 rule[type] = {
140 regex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers),
141 hasExceptions: exceptions.length > 0,
142 markers: new RegExp("^(" + markers.map(escape).join("|") + ")")
143 };
144
145 return rule;
146 }, {});
147
148 /**
149 * Reports a spacing error with an appropriate message.
150 * @param {ASTNode} node - A comment node to check.
151 * @param {string} message - An error message to report
152 * @param {Array} match - An array of match results for markers.
153 * @returns {void}
154 */
155 function report(node, message, match) {
156 var type = node.type.toLowerCase(),
157 commentIdentifier = type === "block" ? "/*" : "//";
158
159 context.report({
160 node: node,
161 fix: function(fixer) {
162 var start = node.range[0],
163 end = start + 2;
164
165 if (requireSpace) {
166 if (match) {
167 end += match[0].length;
168 }
169 return fixer.insertTextAfterRange([start, end], " ");
170 } else {
171 end += match[0].length;
172 return fixer.replaceTextRange([start, end], commentIdentifier + (match[1] ? match[1] : ""));
173 }
174 },
175 message: message
176 });
177 }
178
179 /**
180 * Reports a given comment if it's invalid.
181 * @param {ASTNode} node - a comment node to check.
182 * @returns {void}
183 */
184 function checkCommentForSpace(node) {
185 var type = node.type.toLowerCase(),
186 rule = styleRules[type],
187 commentIdentifier = type === "block" ? "/*" : "//";
188
189 // Ignores empty comments.
190 if (node.value.length === 0) {
191 return;
192 }
193
194 // Checks.
195 if (requireSpace) {
196 if (!rule.regex.test(node.value)) {
197 var hasMarker = rule.markers.exec(node.value);
198 var marker = hasMarker ? commentIdentifier + hasMarker[0] : commentIdentifier;
199 if (rule.hasExceptions) {
200 report(node, "Expected exception block, space or tab after '" + marker + "' in comment.", hasMarker);
201 } else {
202 report(node, "Expected space or tab after '" + marker + "' in comment.", hasMarker);
203 }
204 }
205 } else {
206 var matched = rule.regex.exec(node.value);
207 if (matched) {
208 if (!matched[1]) {
209 report(node, "Unexpected space or tab after '" + commentIdentifier + "' in comment.", matched);
210 } else {
211 report(node, "Unexpected space or tab after marker (" + matched[1] + ") in comment.", matched);
212 }
213 }
214 }
215 }
216
217 return {
218
219 "LineComment": checkCommentForSpace,
220 "BlockComment": checkCommentForSpace
221
222 };
223};
224
225module.exports.schema = [
226 {
227 "enum": ["always", "never"]
228 },
229 {
230 "type": "object",
231 "properties": {
232 "exceptions": {
233 "type": "array",
234 "items": {
235 "type": "string"
236 }
237 },
238 "markers": {
239 "type": "array",
240 "items": {
241 "type": "string"
242 }
243 },
244 "line": {
245 "type": "object",
246 "properties": {
247 "exceptions": {
248 "type": "array",
249 "items": {
250 "type": "string"
251 }
252 },
253 "markers": {
254 "type": "array",
255 "items": {
256 "type": "string"
257 }
258 }
259 },
260 "additionalProperties": false
261 },
262 "block": {
263 "type": "object",
264 "properties": {
265 "exceptions": {
266 "type": "array",
267 "items": {
268 "type": "string"
269 }
270 },
271 "markers": {
272 "type": "array",
273 "items": {
274 "type": "string"
275 }
276 }
277 },
278 "additionalProperties": false
279 }
280 },
281 "additionalProperties": false
282 }
283];