c7f7f5c12fba5b4b3e87dc00583e60ce8f793898
[platform/framework/web/crosswalk-tizen.git] /
1 /**
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.
6  */
7 "use strict";
8
9 //------------------------------------------------------------------------------
10 // Rule Definition
11 //------------------------------------------------------------------------------
12
13 module.exports = function(context) {
14
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 : [],
18         options = {},
19         rejectedSpaceRegExp,
20         missingSpaceRegExp,
21         spaceChecks;
22
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;
28     }
29
30     /**
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
35      * @private
36      */
37     function getNeverChecks(opts) {
38         var missingSpaceOpeners = [],
39             missingSpaceClosers = [],
40             rejectedSpaceOpeners = [" ", "\\n", "\\r"],
41             rejectedSpaceClosers = [" ", "\\n", "\\r"],
42             missingSpaceCheck,
43             rejectedSpaceCheck;
44
45         // Populate openers and closers
46         if (opts.braceException) {
47             missingSpaceOpeners.push("\\{");
48             missingSpaceClosers.push("\\}");
49             rejectedSpaceOpeners.push("\\{");
50             rejectedSpaceClosers.push("\\}");
51         }
52         if (opts.bracketException) {
53             missingSpaceOpeners.push("\\[");
54             missingSpaceClosers.push("\\]");
55             rejectedSpaceOpeners.push("\\[");
56             rejectedSpaceClosers.push("\\]");
57         }
58         if (opts.parenException) {
59             missingSpaceOpeners.push("\\(");
60             missingSpaceClosers.push("\\)");
61             rejectedSpaceOpeners.push("\\(");
62             rejectedSpaceClosers.push("\\)");
63         }
64         if (opts.empty) {
65             missingSpaceOpeners.push("\\)");
66             missingSpaceClosers.push("\\(");
67             rejectedSpaceOpeners.push("\\)");
68             rejectedSpaceClosers.push("\\(");
69         }
70
71         if (missingSpaceOpeners.length) {
72             missingSpaceCheck = "\\((" + missingSpaceOpeners.join("|") + ")";
73             if (missingSpaceClosers.length) {
74                 missingSpaceCheck += "|";
75             }
76         }
77         if (missingSpaceClosers.length) {
78             missingSpaceCheck += "(" + missingSpaceClosers.join("|") + ")\\)";
79         }
80
81         // compose the rejected regexp
82         rejectedSpaceCheck = "\\( +[^" + rejectedSpaceOpeners.join("") + "]";
83         rejectedSpaceCheck += "|[^" + rejectedSpaceClosers.join("") + "] +\\)";
84
85         return {
86             // e.g. \((\{)|(\})\) --- where {} is an exception
87             missingSpace: missingSpaceCheck || ".^",
88             // e.g. \( +[^ \n\r\{]|[^ \n\r\}] +\) --- where {} is an exception
89             rejectedSpace: rejectedSpaceCheck
90         };
91     }
92
93     /**
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
98      * @private
99      */
100     function getAlwaysChecks(opts) {
101         var missingSpaceOpeners = [" ", "\\)", "\\r", "\\n"],
102             missingSpaceClosers = [" ", "\\(", "\\r", "\\n"],
103             rejectedSpaceOpeners = [],
104             rejectedSpaceClosers = [],
105             missingSpaceCheck,
106             rejectedSpaceCheck;
107
108         // Populate openers and closers
109         if (opts.braceException) {
110             missingSpaceOpeners.push("\\{");
111             missingSpaceClosers.push("\\}");
112             rejectedSpaceOpeners.push(" \\{");
113             rejectedSpaceClosers.push("\\} ");
114         }
115         if (opts.bracketException) {
116             missingSpaceOpeners.push("\\[");
117             missingSpaceClosers.push("\\]");
118             rejectedSpaceOpeners.push(" \\[");
119             rejectedSpaceClosers.push("\\] ");
120         }
121         if (opts.parenException) {
122             missingSpaceOpeners.push("\\(");
123             missingSpaceClosers.push("\\)");
124             rejectedSpaceOpeners.push(" \\(");
125             rejectedSpaceClosers.push("\\) ");
126         }
127         if (opts.empty) {
128             rejectedSpaceOpeners.push(" \\)");
129             rejectedSpaceClosers.push("\\( ");
130         }
131
132         // compose the allowed regexp
133         missingSpaceCheck = "\\([^" + missingSpaceOpeners.join("") + "]";
134         missingSpaceCheck += "|[^" + missingSpaceClosers.join("") + "]\\)";
135
136         // compose the rejected regexp
137         if (rejectedSpaceOpeners.length) {
138             rejectedSpaceCheck = "\\((" + rejectedSpaceOpeners.join("|") + ")";
139             if (rejectedSpaceClosers.length) {
140                 rejectedSpaceCheck += "|";
141             }
142         }
143         if (rejectedSpaceClosers.length) {
144             rejectedSpaceCheck += "(" + rejectedSpaceClosers.join("|") + ")\\)";
145         }
146
147         return {
148             // e.g. \([^ \)\r\n\{]|[^ \(\r\n\}]\) --- where {} is an exception
149             missingSpace: missingSpaceCheck,
150             // e.g. \(( \{})|(\} )\) --- where {} is an excpetion
151             rejectedSpace: rejectedSpaceCheck || ".^"
152         };
153     }
154
155     spaceChecks = (context.options[0] === "always") ? getAlwaysChecks(options) : getNeverChecks(options);
156     missingSpaceRegExp = new RegExp(spaceChecks.missingSpace, "mg");
157     rejectedSpaceRegExp = new RegExp(spaceChecks.rejectedSpace, "mg");
158
159
160     //--------------------------------------------------------------------------
161     // Helpers
162     //--------------------------------------------------------------------------
163
164     var skipRanges = [];
165
166     /**
167      * Adds the range of a node to the set to be skipped when checking parens
168      * @param {ASTNode} node The node to skip
169      * @returns {void}
170      * @private
171      */
172     function addSkipRange(node) {
173         skipRanges.push(node.range);
174     }
175
176     /**
177      * Sorts the skipRanges array. Must be called before shouldSkip
178      * @returns {void}
179      * @private
180      */
181     function sortSkipRanges() {
182         skipRanges.sort(function (a, b) {
183             return a[0] - b[0];
184         });
185     }
186
187     /**
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
191      * @private
192      */
193     function shouldSkip(pos) {
194         var i, len, range;
195         for (i = 0, len = skipRanges.length; i < len; i += 1) {
196             range = skipRanges[i];
197             if (pos < range[0]) {
198                 break;
199             } else if (pos < range[1]) {
200                 return true;
201             }
202         }
203         return false;
204     }
205
206
207     //--------------------------------------------------------------------------
208     // Public
209     //--------------------------------------------------------------------------
210
211     return {
212
213         "Program:exit": function checkParenSpaces(node) {
214
215             var nextMatch,
216                 nextLine,
217                 column,
218                 line = 1,
219                 source = context.getSource(),
220                 pos = 0;
221
222             function checkMatch(match, message) {
223                 if (source.charAt(match.index) !== "(") {
224                     // Matched a closing paren pattern
225                     match.index += 1;
226                 }
227
228                 if (!shouldSkip(match.index)) {
229                     while ((nextLine = source.indexOf("\n", pos)) !== -1 && nextLine < match.index) {
230                         pos = nextLine + 1;
231                         line += 1;
232                     }
233                     column = match.index - pos;
234
235                     context.report(node, { line: line, column: column }, message);
236                 }
237             }
238
239             sortSkipRanges();
240
241             while ((nextMatch = rejectedSpaceRegExp.exec(source)) !== null) {
242                 checkMatch(nextMatch, REJECTED_SPACE_MESSAGE);
243             }
244
245             while ((nextMatch = missingSpaceRegExp.exec(source)) !== null) {
246                 checkMatch(nextMatch, MISSING_SPACE_MESSAGE);
247             }
248
249         },
250
251
252         // These nodes can contain parentheses that this rule doesn't care about
253
254         LineComment: addSkipRange,
255
256         BlockComment: addSkipRange,
257
258         Literal: addSkipRange
259
260     };
261
262 };