cd8ac023241006981a3decc6ea47346b8d648052
[platform/framework/web/crosswalk-tizen.git] /
1 'use strict';
2
3 // Line break helpers
4
5 var _tk = require('rocambole-token');
6 var debug = require('debug');
7 var debugBefore = debug('rocambole:br:before');
8 var debugAfter = debug('rocambole:br:after');
9 var debugBetween = debug('rocambole:br:between');
10
11 // yeah, we use semver to parse integers. it's lame but works and will give
12 // more flexibility while still keeping a format that is easy to read
13 var semver = require('semver');
14
15 // fallback in case plugin author forgets to call setOptions
16 var _curOpts = {
17   value: '\n'
18 };
19
20
21 // ---
22
23
24 exports.setOptions = setOptions;
25 function setOptions(opts) {
26   _curOpts = opts;
27 }
28
29
30 exports.limit = limit;
31 function limit(token, typeOrValue) {
32   limitBefore(token, typeOrValue);
33   limitAfter(token, typeOrValue);
34 }
35
36
37 exports.limitBefore = limitBefore;
38 function limitBefore(token, typeOrValue) {
39   var expected = expectedBefore(typeOrValue);
40   debugBefore(
41     'typeOrValue: %s, expected: %s, value: %s',
42     typeOrValue, expected, token && token.value
43   );
44   if (expected < 0) return; // noop
45   var start = getStartToken(token);
46   limitInBetween('before', start, token, expected);
47 }
48
49
50 exports.limitAfter = limitAfter;
51 function limitAfter(token, typeOrValue) {
52   var expected = expectedAfter(typeOrValue);
53   debugAfter(
54     'typeOrValue: %s, expected: %s, value: %s',
55     typeOrValue, expected, token && token.value
56   );
57   if (expected < 0) return; // noop
58   var end = getEndToken(token);
59   limitInBetween('after', token, end, expected);
60 }
61
62 exports.expectedBefore = expectedBefore;
63 function expectedBefore(typeOrValue) {
64   return getExpect('before', typeOrValue);
65 }
66
67 exports.expectedAfter = expectedAfter;
68 function expectedAfter(typeOrValue) {
69   return getExpect('after', typeOrValue);
70 }
71
72
73 function getExpect(location, typeOrValue) {
74   var expected;
75
76   // we allow expected value (number) as 2nd argument or the node type (string)
77   if (typeof typeOrValue === 'string') {
78     expected = _curOpts[location][typeOrValue];
79   } else {
80     expected = typeOrValue;
81   }
82
83   // default is noop, explicit is better than implicit
84   expected = expected != null ? expected : -1;
85
86   if (typeof expected === 'boolean') {
87     // if user sets booleans by mistake we simply add one if missing (true)
88     // or remove all if false
89     expected = expected ? '>=1' : 0;
90   }
91
92   if (expected < 0) {
93     // noop
94     return expected;
95   } else if (typeof expected === 'number') {
96     return String(expected);
97   } else {
98     return expected;
99   }
100 }
101
102
103 function limitInBetween(location, start, end, expected) {
104   var n = getDiff(start, end, expected);
105   debugBetween('diff: %d', n);
106   if (n) {
107     _tk.removeInBetween(start, end, 'WhiteSpace');
108   }
109   if (n < 0) {
110     _tk.removeInBetween(start, end, function(token) {
111       return token.type === 'LineBreak' && n++ < 0 &&
112         !siblingIsComment(location, token);
113     });
114   } else if (n > 0) {
115     var target = location === 'after' ? start : end;
116     var insertNextTo = _tk[location];
117     while (n-- > 0) {
118       insertNextTo(target, {
119         type: 'LineBreak',
120         value: _curOpts.value
121       });
122     }
123   }
124 }
125
126
127 function siblingIsComment(location, token) {
128   var prop = location === 'before' ? 'prev' : 'next';
129   return _tk.isComment(token[prop]);
130 }
131
132
133 function getDiff(start, end, expected) {
134   // start will only be equal to end if it's start or file
135   if (start === end) return 0;
136   var count = countBrInBetween(start, end);
137   // yeah, it's ugly to strings to compare integers but was quickest solution
138   var vCount = String(count) + '.0.0';
139   if (semver.satisfies(vCount, expected)) {
140     return 0;
141   } else {
142     return getSatisfyingMatch(count, vCount, expected) - count;
143   }
144 }
145
146
147 function getSatisfyingMatch(count, vCount, expected) {
148   var result;
149   var diff = semver.gtr(vCount, expected) ? -1 : 1;
150   count += diff;
151   while (result == null && count >= 0 && count < 100) {
152     if (semver.satisfies(String(count) + '.0.0', expected)) {
153       result = count;
154     }
155     count += diff;
156   }
157   return parseInt(result, 10);
158 }
159
160
161 function countBrInBetween(start, end) {
162   var count = 0;
163   _tk.eachInBetween(start, end, function(token) {
164     if (_tk.isBr(token)) count++;
165   });
166   return count;
167 }
168
169
170 function getEndToken(token) {
171   var end = _tk.findNextNonEmpty(token);
172   if (shouldSkipToken(end)) {
173     end = _tk.findNextNonEmpty(end);
174   }
175   return end ? end : token.root.endToken;
176 }
177
178
179 function shouldSkipToken(token) {
180   // if comment is at same line we skip it unless it has a specific rule that
181   // would add line breaks
182   var result = _tk.isComment(token) && !isOnSeparateLine(token);
183   return result && getExpect('before', token.type) <= 0;
184 }
185
186
187 function isOnSeparateLine(token) {
188   return _tk.isBr(token.prev) || (
189     _tk.isEmpty(token.prev) && _tk.isBr(token.prev.prev)
190     );
191 }
192
193
194 function getStartToken(token) {
195   var end = _tk.findPrevNonEmpty(token);
196   return end ? end : token.root.startToken;
197 }
198
199
200 exports.limitBeforeEndOfFile = function(ast, amount) {
201   var typeOrValue = amount != null ? amount : 'EndOfFile';
202   var expected = getExpect('before', typeOrValue);
203
204   if (expected < 0) return; // noop
205
206   var lastNonEmpty = _tk.isEmpty(ast.endToken) ?
207     _tk.findPrevNonEmpty(ast.endToken) :
208     ast.endToken;
209
210   if (lastNonEmpty) {
211     limitInBetween('after', lastNonEmpty, null, expected);
212   } else {
213     do {
214       var br = {
215         type: 'LineBreak',
216         value: _curOpts.value
217       };
218       if (ast.startToken) {
219         _tk.after(ast.startToken, br);
220       } else {
221         ast.startToken = ast.endToken = br;
222       }
223     } while (--expected);
224   }
225 };