2 * @fileoverview Disallows or enforces spaces inside of parentheses.
3 * @author Jonathan Rajavuori
4 * @copyright 2014 David Clark. All rights reserved.
5 * @copyright 2014 Jonathan Rajavuori. All rights reserved.
9 //------------------------------------------------------------------------------
11 //------------------------------------------------------------------------------
13 module.exports = function(context) {
15 var MISSING_SPACE_MESSAGE = "There must be a space inside this paren.",
16 REJECTED_SPACE_MESSAGE = "There should be no spaces inside this paren.",
17 exceptionsArray = (context.options.length === 2) ? context.options[1].exceptions : [],
23 if (exceptionsArray && exceptionsArray.length) {
24 options.braceException = exceptionsArray.indexOf("{}") !== -1 || false;
25 options.bracketException = exceptionsArray.indexOf("[]") !== -1 || false;
26 options.parenException = exceptionsArray.indexOf("()") !== -1 || false;
27 options.empty = exceptionsArray.indexOf("empty") !== -1 || false;
31 * Used with the `never` option to produce, given the exception options,
32 * two regular expressions to check for missing and rejected spaces.
33 * @param {Object} opts The exception options
34 * @returns {Object} `missingSpace` and `rejectedSpace` regular expressions
37 function getNeverChecks(opts) {
38 var missingSpaceOpeners = [],
39 missingSpaceClosers = [],
40 rejectedSpaceOpeners = [" ", "\\n", "\\r"],
41 rejectedSpaceClosers = [" ", "\\n", "\\r"],
45 // Populate openers and closers
46 if (opts.braceException) {
47 missingSpaceOpeners.push("\\{");
48 missingSpaceClosers.push("\\}");
49 rejectedSpaceOpeners.push("\\{");
50 rejectedSpaceClosers.push("\\}");
52 if (opts.bracketException) {
53 missingSpaceOpeners.push("\\[");
54 missingSpaceClosers.push("\\]");
55 rejectedSpaceOpeners.push("\\[");
56 rejectedSpaceClosers.push("\\]");
58 if (opts.parenException) {
59 missingSpaceOpeners.push("\\(");
60 missingSpaceClosers.push("\\)");
61 rejectedSpaceOpeners.push("\\(");
62 rejectedSpaceClosers.push("\\)");
65 missingSpaceOpeners.push("\\)");
66 missingSpaceClosers.push("\\(");
67 rejectedSpaceOpeners.push("\\)");
68 rejectedSpaceClosers.push("\\(");
71 if (missingSpaceOpeners.length) {
72 missingSpaceCheck = "\\((" + missingSpaceOpeners.join("|") + ")";
73 if (missingSpaceClosers.length) {
74 missingSpaceCheck += "|";
77 if (missingSpaceClosers.length) {
78 missingSpaceCheck += "(" + missingSpaceClosers.join("|") + ")\\)";
81 // compose the rejected regexp
82 rejectedSpaceCheck = "\\( +[^" + rejectedSpaceOpeners.join("") + "]";
83 rejectedSpaceCheck += "|[^" + rejectedSpaceClosers.join("") + "] +\\)";
86 // e.g. \((\{)|(\})\) --- where {} is an exception
87 missingSpace: missingSpaceCheck || ".^",
88 // e.g. \( +[^ \n\r\{]|[^ \n\r\}] +\) --- where {} is an exception
89 rejectedSpace: rejectedSpaceCheck
94 * Used with the `always` option to produce, given the exception options,
95 * two regular expressions to check for missing and rejected spaces.
96 * @param {Object} opts The exception options
97 * @returns {Object} `missingSpace` and `rejectedSpace` regular expressions
100 function getAlwaysChecks(opts) {
101 var missingSpaceOpeners = [" ", "\\)", "\\r", "\\n"],
102 missingSpaceClosers = [" ", "\\(", "\\r", "\\n"],
103 rejectedSpaceOpeners = [],
104 rejectedSpaceClosers = [],
108 // Populate openers and closers
109 if (opts.braceException) {
110 missingSpaceOpeners.push("\\{");
111 missingSpaceClosers.push("\\}");
112 rejectedSpaceOpeners.push(" \\{");
113 rejectedSpaceClosers.push("\\} ");
115 if (opts.bracketException) {
116 missingSpaceOpeners.push("\\[");
117 missingSpaceClosers.push("\\]");
118 rejectedSpaceOpeners.push(" \\[");
119 rejectedSpaceClosers.push("\\] ");
121 if (opts.parenException) {
122 missingSpaceOpeners.push("\\(");
123 missingSpaceClosers.push("\\)");
124 rejectedSpaceOpeners.push(" \\(");
125 rejectedSpaceClosers.push("\\) ");
128 rejectedSpaceOpeners.push(" \\)");
129 rejectedSpaceClosers.push("\\( ");
132 // compose the allowed regexp
133 missingSpaceCheck = "\\([^" + missingSpaceOpeners.join("") + "]";
134 missingSpaceCheck += "|[^" + missingSpaceClosers.join("") + "]\\)";
136 // compose the rejected regexp
137 if (rejectedSpaceOpeners.length) {
138 rejectedSpaceCheck = "\\((" + rejectedSpaceOpeners.join("|") + ")";
139 if (rejectedSpaceClosers.length) {
140 rejectedSpaceCheck += "|";
143 if (rejectedSpaceClosers.length) {
144 rejectedSpaceCheck += "(" + rejectedSpaceClosers.join("|") + ")\\)";
148 // e.g. \([^ \)\r\n\{]|[^ \(\r\n\}]\) --- where {} is an exception
149 missingSpace: missingSpaceCheck,
150 // e.g. \(( \{})|(\} )\) --- where {} is an excpetion
151 rejectedSpace: rejectedSpaceCheck || ".^"
155 spaceChecks = (context.options[0] === "always") ? getAlwaysChecks(options) : getNeverChecks(options);
156 missingSpaceRegExp = new RegExp(spaceChecks.missingSpace, "mg");
157 rejectedSpaceRegExp = new RegExp(spaceChecks.rejectedSpace, "mg");
160 //--------------------------------------------------------------------------
162 //--------------------------------------------------------------------------
167 * Adds the range of a node to the set to be skipped when checking parens
168 * @param {ASTNode} node The node to skip
172 function addSkipRange(node) {
173 skipRanges.push(node.range);
177 * Sorts the skipRanges array. Must be called before shouldSkip
181 function sortSkipRanges() {
182 skipRanges.sort(function (a, b) {
188 * Checks if a certain position in the source should be skipped
189 * @param {Number} pos The 0-based index in the source
190 * @returns {boolean} whether the position should be skipped
193 function shouldSkip(pos) {
195 for (i = 0, len = skipRanges.length; i < len; i += 1) {
196 range = skipRanges[i];
197 if (pos < range[0]) {
199 } else if (pos < range[1]) {
207 //--------------------------------------------------------------------------
209 //--------------------------------------------------------------------------
213 "Program:exit": function checkParenSpaces(node) {
219 source = context.getSource(),
222 function checkMatch(match, message) {
223 if (source.charAt(match.index) !== "(") {
224 // Matched a closing paren pattern
228 if (!shouldSkip(match.index)) {
229 while ((nextLine = source.indexOf("\n", pos)) !== -1 && nextLine < match.index) {
233 column = match.index - pos;
235 context.report(node, { line: line, column: column }, message);
241 while ((nextMatch = rejectedSpaceRegExp.exec(source)) !== null) {
242 checkMatch(nextMatch, REJECTED_SPACE_MESSAGE);
245 while ((nextMatch = missingSpaceRegExp.exec(source)) !== null) {
246 checkMatch(nextMatch, MISSING_SPACE_MESSAGE);
252 // These nodes can contain parentheses that this rule doesn't care about
254 LineComment: addSkipRange,
256 BlockComment: addSkipRange,
258 Literal: addSkipRange