f2d4ae843da47d8da8d01d8387a0dea3d566169c
[clean-css.git] / lib / tokenizer / tokenize.js
1 var Marker = require('./marker');
2 var Token = require('./token');
3
4 var formatPosition = require('../utils/format-position');
5
6 var Level = {
7   BLOCK: 'block',
8   COMMENT: 'comment',
9   DOUBLE_QUOTE: 'double-quote',
10   RULE: 'rule',
11   SINGLE_QUOTE: 'single-quote'
12 };
13
14 var AT_RULES = [
15   '@charset',
16   '@import'
17 ];
18
19 var BLOCK_RULES = [
20   '@-moz-document',
21   '@document',
22   '@-moz-keyframes',
23   '@-ms-keyframes',
24   '@-o-keyframes',
25   '@-webkit-keyframes',
26   '@keyframes',
27   '@media',
28   '@supports'
29 ];
30
31 var IGNORE_END_COMMENT_PATTERN = /\/\* clean\-css ignore:end \*\/$/;
32 var IGNORE_START_COMMENT_PATTERN = /^\/\* clean\-css ignore:start \*\//;
33
34 var PAGE_MARGIN_BOXES = [
35   '@bottom-center',
36   '@bottom-left',
37   '@bottom-left-corner',
38   '@bottom-right',
39   '@bottom-right-corner',
40   '@left-bottom',
41   '@left-middle',
42   '@left-top',
43   '@right-bottom',
44   '@right-middle',
45   '@right-top',
46   '@top-center',
47   '@top-left',
48   '@top-left-corner',
49   '@top-right',
50   '@top-right-corner'
51 ];
52
53 var EXTRA_PAGE_BOXES = [
54   '@footnote',
55   '@footnotes',
56   '@left',
57   '@page-float-bottom',
58   '@page-float-top',
59   '@right'
60 ];
61
62 var REPEAT_PATTERN = /^\[\s*\d+\s*\]$/;
63 var RULE_WORD_SEPARATOR_PATTERN = /[\s\(]/;
64 var TAIL_BROKEN_VALUE_PATTERN = /[\s|\}]*$/;
65
66 function tokenize(source, externalContext) {
67   var internalContext = {
68     level: Level.BLOCK,
69     position: {
70       source: externalContext.source || undefined,
71       line: 1,
72       column: 0,
73       index: 0
74     }
75   };
76
77   return intoTokens(source, externalContext, internalContext, false);
78 }
79
80 function intoTokens(source, externalContext, internalContext, isNested) {
81   var allTokens = [];
82   var newTokens = allTokens;
83   var lastToken;
84   var ruleToken;
85   var ruleTokens = [];
86   var propertyToken;
87   var metadata;
88   var metadatas = [];
89   var level = internalContext.level;
90   var levels = [];
91   var buffer = [];
92   var buffers = [];
93   var serializedBuffer;
94   var serializedBufferPart;
95   var roundBracketLevel = 0;
96   var isQuoted;
97   var isSpace;
98   var isNewLineNix;
99   var isNewLineWin;
100   var isCommentStart;
101   var wasCommentStart = false;
102   var isCommentEnd;
103   var wasCommentEnd = false;
104   var isEscaped;
105   var wasEscaped = false;
106   var isRaw = false;
107   var seekingValue = false;
108   var seekingPropertyBlockClosing = false;
109   var position = internalContext.position;
110   var lastCommentStartAt;
111
112   for (; position.index < source.length; position.index++) {
113     var character = source[position.index];
114
115     isQuoted = level == Level.SINGLE_QUOTE || level == Level.DOUBLE_QUOTE;
116     isSpace = character == Marker.SPACE || character == Marker.TAB;
117     isNewLineNix = character == Marker.NEW_LINE_NIX;
118     isNewLineWin = character == Marker.NEW_LINE_NIX && source[position.index - 1] == Marker.NEW_LINE_WIN;
119     isCommentStart = !wasCommentEnd && level != Level.COMMENT && !isQuoted && character == Marker.ASTERISK && source[position.index - 1] == Marker.FORWARD_SLASH;
120     isCommentEnd = !wasCommentStart && level == Level.COMMENT && character == Marker.FORWARD_SLASH && source[position.index - 1] == Marker.ASTERISK;
121
122     metadata = buffer.length === 0 ?
123       [position.line, position.column, position.source] :
124       metadata;
125
126     if (isEscaped) {
127       // previous character was a backslash
128       buffer.push(character);
129     } else if (!isCommentEnd && level == Level.COMMENT) {
130       buffer.push(character);
131     } else if (!isCommentStart && !isCommentEnd && isRaw) {
132       buffer.push(character);
133     } else if (isCommentStart && (level == Level.BLOCK || level == Level.RULE) && buffer.length > 1) {
134       // comment start within block preceded by some content, e.g. div/*<--
135       metadatas.push(metadata);
136       buffer.push(character);
137       buffers.push(buffer.slice(0, buffer.length - 2));
138
139       buffer = buffer.slice(buffer.length - 2);
140       metadata = [position.line, position.column - 1, position.source];
141
142       levels.push(level);
143       level = Level.COMMENT;
144     } else if (isCommentStart) {
145       // comment start, e.g. /*<--
146       levels.push(level);
147       level = Level.COMMENT;
148       buffer.push(character);
149     } else if (isCommentEnd && isIgnoreStartComment(buffer)) {
150       // ignore:start comment end, e.g. /* clean-css ignore:start */<--
151       serializedBuffer = buffer.join('').trim() + character;
152       lastToken = [Token.COMMENT, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]];
153       newTokens.push(lastToken);
154
155       isRaw = true;
156       metadata = metadatas.pop() || null;
157       buffer = buffers.pop() || [];
158     } else if (isCommentEnd && isIgnoreEndComment(buffer)) {
159       // ignore:start comment end, e.g. /* clean-css ignore:end */<--
160       serializedBuffer = buffer.join('') + character;
161       lastCommentStartAt = serializedBuffer.lastIndexOf(Marker.FORWARD_SLASH + Marker.ASTERISK);
162
163       serializedBufferPart = serializedBuffer.substring(0, lastCommentStartAt);
164       lastToken = [Token.RAW, serializedBufferPart, [originalMetadata(metadata, serializedBufferPart, externalContext)]];
165       newTokens.push(lastToken);
166
167       serializedBufferPart = serializedBuffer.substring(lastCommentStartAt);
168       metadata = [position.line, position.column - serializedBufferPart.length + 1, position.source];
169       lastToken = [Token.COMMENT, serializedBufferPart, [originalMetadata(metadata, serializedBufferPart, externalContext)]];
170       newTokens.push(lastToken);
171
172       isRaw = false;
173       level = levels.pop();
174       metadata = metadatas.pop() || null;
175       buffer = buffers.pop() || [];
176     } else if (isCommentEnd) {
177       // comment end, e.g. /* comment */<--
178       serializedBuffer = buffer.join('').trim() + character;
179       lastToken = [Token.COMMENT, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]];
180       newTokens.push(lastToken);
181
182       level = levels.pop();
183       metadata = metadatas.pop() || null;
184       buffer = buffers.pop() || [];
185     } else if (character == Marker.SINGLE_QUOTE && !isQuoted) {
186       // single quotation start, e.g. a[href^='https<--
187       levels.push(level);
188       level = Level.SINGLE_QUOTE;
189       buffer.push(character);
190     } else if (character == Marker.SINGLE_QUOTE && level == Level.SINGLE_QUOTE) {
191       // single quotation end, e.g. a[href^='https'<--
192       level = levels.pop();
193       buffer.push(character);
194     } else if (character == Marker.DOUBLE_QUOTE && !isQuoted) {
195       // double quotation start, e.g. a[href^="<--
196       levels.push(level);
197       level = Level.DOUBLE_QUOTE;
198       buffer.push(character);
199     } else if (character == Marker.DOUBLE_QUOTE && level == Level.DOUBLE_QUOTE) {
200       // double quotation end, e.g. a[href^="https"<--
201       level = levels.pop();
202       buffer.push(character);
203     } else if (!isCommentStart && !isCommentEnd && character != Marker.CLOSE_ROUND_BRACKET && character != Marker.OPEN_ROUND_BRACKET && level != Level.COMMENT && !isQuoted && roundBracketLevel > 0) {
204       // character inside any function, e.g. hsla(.<--
205       buffer.push(character);
206     } else if (character == Marker.OPEN_ROUND_BRACKET && !isQuoted && level != Level.COMMENT && !seekingValue) {
207       // round open bracket, e.g. @import url(<--
208       buffer.push(character);
209
210       roundBracketLevel++;
211     } else if (character == Marker.CLOSE_ROUND_BRACKET && !isQuoted && level != Level.COMMENT && !seekingValue) {
212       // round open bracket, e.g. @import url(test.css)<--
213       buffer.push(character);
214
215       roundBracketLevel--;
216     } else if (character == Marker.SEMICOLON && level == Level.BLOCK && buffer[0] == Marker.AT) {
217       // semicolon ending rule at block level, e.g. @import '...';<--
218       serializedBuffer = buffer.join('').trim();
219       allTokens.push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
220
221       buffer = [];
222     } else if (character == Marker.COMMA && level == Level.BLOCK && ruleToken) {
223       // comma separator at block level, e.g. a,div,<--
224       serializedBuffer = buffer.join('').trim();
225       ruleToken[1].push([tokenScopeFrom(ruleToken[0]), serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext, ruleToken[1].length)]]);
226
227       buffer = [];
228     } else if (character == Marker.COMMA && level == Level.BLOCK && tokenTypeFrom(buffer) == Token.AT_RULE) {
229       // comma separator at block level, e.g. @import url(...) screen,<--
230       // keep iterating as end semicolon will create the token
231       buffer.push(character);
232     } else if (character == Marker.COMMA && level == Level.BLOCK) {
233       // comma separator at block level, e.g. a,<--
234       ruleToken = [tokenTypeFrom(buffer), [], []];
235       serializedBuffer = buffer.join('').trim();
236       ruleToken[1].push([tokenScopeFrom(ruleToken[0]), serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext, 0)]]);
237
238       buffer = [];
239     } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.BLOCK && ruleToken && ruleToken[0] == Token.NESTED_BLOCK) {
240       // open brace opening at-rule at block level, e.g. @media{<--
241       serializedBuffer = buffer.join('').trim();
242       ruleToken[1].push([Token.NESTED_BLOCK_SCOPE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
243       allTokens.push(ruleToken);
244
245       levels.push(level);
246       position.column++;
247       position.index++;
248       buffer = [];
249
250       ruleToken[2] = intoTokens(source, externalContext, internalContext, true);
251       ruleToken = null;
252     } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.BLOCK && tokenTypeFrom(buffer) == Token.NESTED_BLOCK) {
253       // open brace opening at-rule at block level, e.g. @media{<--
254       serializedBuffer = buffer.join('').trim();
255       ruleToken = ruleToken || [Token.NESTED_BLOCK, [], []];
256       ruleToken[1].push([Token.NESTED_BLOCK_SCOPE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
257       allTokens.push(ruleToken);
258
259       levels.push(level);
260       position.column++;
261       position.index++;
262       buffer = [];
263
264       ruleToken[2] = intoTokens(source, externalContext, internalContext, true);
265       ruleToken = null;
266     } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.BLOCK) {
267       // open brace opening rule at block level, e.g. div{<--
268       serializedBuffer = buffer.join('').trim();
269       ruleToken = ruleToken || [tokenTypeFrom(buffer), [], []];
270       ruleToken[1].push([tokenScopeFrom(ruleToken[0]), serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext, ruleToken[1].length)]]);
271       newTokens = ruleToken[2];
272       allTokens.push(ruleToken);
273
274       levels.push(level);
275       level = Level.RULE;
276       buffer = [];
277     } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.RULE && seekingValue) {
278       // open brace opening rule at rule level, e.g. div{--variable:{<--
279       ruleTokens.push(ruleToken);
280       ruleToken = [Token.PROPERTY_BLOCK, []];
281       propertyToken.push(ruleToken);
282       newTokens = ruleToken[1];
283
284       levels.push(level);
285       level = Level.RULE;
286       seekingValue = false;
287     } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.RULE && isPageMarginBox(buffer)) {
288       // open brace opening page-margin box at rule level, e.g. @page{@top-center{<--
289       serializedBuffer = buffer.join('').trim();
290       ruleTokens.push(ruleToken);
291       ruleToken = [Token.AT_RULE_BLOCK, [], []];
292       ruleToken[1].push([Token.AT_RULE_BLOCK_SCOPE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
293       newTokens.push(ruleToken);
294       newTokens = ruleToken[2];
295
296       levels.push(level);
297       level = Level.RULE;
298       buffer = [];
299     } else if (character == Marker.COLON && level == Level.RULE && !seekingValue) {
300       // colon at rule level, e.g. a{color:<--
301       serializedBuffer = buffer.join('').trim();
302       propertyToken = [Token.PROPERTY, [Token.PROPERTY_NAME, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]];
303       newTokens.push(propertyToken);
304
305       seekingValue = true;
306       buffer = [];
307     } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && ruleTokens.length > 0 && buffer.length > 0 && buffer[0] == Marker.AT) {
308       // semicolon at rule level for at-rule, e.g. a{--color:{@apply(--other-color);<--
309       serializedBuffer = buffer.join('').trim();
310       ruleToken[1].push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
311
312       buffer = [];
313     } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && buffer.length > 0) {
314       // semicolon at rule level, e.g. a{color:red;<--
315       serializedBuffer = buffer.join('').trim();
316       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
317
318       propertyToken = null;
319       seekingValue = false;
320       buffer = [];
321     } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && buffer.length === 0) {
322       // semicolon after bracketed value at rule level, e.g. a{color:rgb(...);<--
323       propertyToken = null;
324       seekingValue = false;
325     } else if (character == Marker.SEMICOLON && level == Level.RULE && buffer.length > 0 && buffer[0] == Marker.AT) {
326       // semicolon for at-rule at rule level, e.g. a{@apply(--variable);<--
327       serializedBuffer = buffer.join('');
328       newTokens.push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
329
330       seekingValue = false;
331       buffer = [];
332     } else if (character == Marker.SEMICOLON && level == Level.RULE && seekingPropertyBlockClosing) {
333       // close brace after a property block at rule level, e.g. a{--custom:{color:red;};<--
334       seekingPropertyBlockClosing = false;
335       buffer = [];
336     } else if (character == Marker.SEMICOLON && level == Level.RULE && buffer.length === 0) {
337       // stray semicolon at rule level, e.g. a{;<--
338       // noop
339     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE && propertyToken && seekingValue && buffer.length > 0 && ruleTokens.length > 0) {
340       // close brace at rule level, e.g. a{--color:{color:red}<--
341       serializedBuffer = buffer.join('');
342       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
343       propertyToken = null;
344       ruleToken = ruleTokens.pop();
345       newTokens = ruleToken[2];
346
347       level = levels.pop();
348       seekingValue = false;
349       buffer = [];
350     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE && propertyToken && buffer.length > 0 && buffer[0] == Marker.AT && ruleTokens.length > 0) {
351       // close brace at rule level for at-rule, e.g. a{--color:{@apply(--other-color)}<--
352       serializedBuffer = buffer.join('');
353       ruleToken[1].push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
354       propertyToken = null;
355       ruleToken = ruleTokens.pop();
356       newTokens = ruleToken[2];
357
358       level = levels.pop();
359       seekingValue = false;
360       buffer = [];
361     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE && propertyToken && ruleTokens.length > 0) {
362       // close brace at rule level after space, e.g. a{--color:{color:red }<--
363       propertyToken = null;
364       ruleToken = ruleTokens.pop();
365       newTokens = ruleToken[2];
366
367       level = levels.pop();
368       seekingValue = false;
369     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE && propertyToken && buffer.length > 0) {
370       // close brace at rule level, e.g. a{color:red}<--
371       serializedBuffer = buffer.join('');
372       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
373       propertyToken = null;
374       ruleToken = ruleTokens.pop();
375       newTokens = allTokens;
376
377       level = levels.pop();
378       seekingValue = false;
379       buffer = [];
380     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE && buffer.length > 0 && buffer[0] == Marker.AT) {
381       // close brace after at-rule at rule level, e.g. a{@apply(--variable)}<--
382       propertyToken = null;
383       ruleToken = null;
384       serializedBuffer = buffer.join('').trim();
385       newTokens.push([Token.AT_RULE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
386       newTokens = allTokens;
387
388       level = levels.pop();
389       seekingValue = false;
390       buffer = [];
391     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE && levels[levels.length - 1] == Level.RULE) {
392       // close brace after a property block at rule level, e.g. a{--custom:{color:red;}<--
393       propertyToken = null;
394       ruleToken = ruleTokens.pop();
395       newTokens = ruleToken[2];
396
397       level = levels.pop();
398       seekingValue = false;
399       seekingPropertyBlockClosing = true;
400       buffer = [];
401     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE) {
402       // close brace after a rule, e.g. a{color:red;}<--
403       propertyToken = null;
404       ruleToken = null;
405       newTokens = allTokens;
406
407       level = levels.pop();
408       seekingValue = false;
409     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.BLOCK && !isNested && position.index <= source.length - 1) {
410       // stray close brace at block level, e.g. a{color:red}color:blue}<--
411       externalContext.warnings.push('Unexpected \'}\' at ' + formatPosition([position.line, position.column, position.source]) + '.');
412       buffer.push(character);
413     } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.BLOCK) {
414       // close brace at block level, e.g. @media screen {...}<--
415       break;
416     } else if (character == Marker.OPEN_ROUND_BRACKET && level == Level.RULE && seekingValue) {
417       // round open bracket, e.g. a{color:hsla(<--
418       buffer.push(character);
419       roundBracketLevel++;
420     } else if (character == Marker.CLOSE_ROUND_BRACKET && level == Level.RULE && seekingValue && roundBracketLevel == 1) {
421       // round close bracket, e.g. a{color:hsla(0,0%,0%)<--
422       buffer.push(character);
423       serializedBuffer = buffer.join('').trim();
424       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
425
426       roundBracketLevel--;
427       buffer = [];
428     } else if (character == Marker.CLOSE_ROUND_BRACKET && level == Level.RULE && seekingValue) {
429       // round close bracket within other brackets, e.g. a{width:calc((10rem / 2)<--
430       buffer.push(character);
431       roundBracketLevel--;
432     } else if (character == Marker.FORWARD_SLASH && source[position.index + 1] != Marker.ASTERISK && level == Level.RULE && seekingValue && buffer.length > 0) {
433       // forward slash within a property, e.g. a{background:url(image.png) 0 0/<--
434       serializedBuffer = buffer.join('').trim();
435       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
436       propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
437
438       buffer = [];
439     } else if (character == Marker.FORWARD_SLASH && source[position.index + 1] != Marker.ASTERISK && level == Level.RULE && seekingValue) {
440       // forward slash within a property after space, e.g. a{background:url(image.png) 0 0 /<--
441       propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
442
443       buffer = [];
444     } else if (character == Marker.COMMA && level == Level.RULE && seekingValue && buffer.length > 0) {
445       // comma within a property, e.g. a{background:url(image.png),<--
446       serializedBuffer = buffer.join('').trim();
447       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
448       propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
449
450       buffer = [];
451     } else if (character == Marker.COMMA && level == Level.RULE && seekingValue) {
452       // comma within a property after space, e.g. a{background:url(image.png) ,<--
453       propertyToken.push([Token.PROPERTY_VALUE, character, [[position.line, position.column, position.source]]]);
454
455       buffer = [];
456     } else if (character == Marker.CLOSE_SQUARE_BRACKET && propertyToken && propertyToken.length > 1 && buffer.length > 0 && isRepeatToken(buffer)) {
457       buffer.push(character);
458       serializedBuffer = buffer.join('').trim();
459       propertyToken[propertyToken.length - 1][1] += serializedBuffer;
460
461       buffer = [];
462     } else if ((isSpace || (isNewLineNix && !isNewLineWin)) && level == Level.RULE && seekingValue && propertyToken && buffer.length > 0) {
463       // space or *nix newline within property, e.g. a{margin:0 <--
464       serializedBuffer = buffer.join('').trim();
465       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
466
467       buffer = [];
468     } else if (isNewLineWin && level == Level.RULE && seekingValue && propertyToken && buffer.length > 1) {
469       // win newline within property, e.g. a{margin:0\r\n<--
470       serializedBuffer = buffer.join('').trim();
471       propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
472
473       buffer = [];
474     } else if (isNewLineWin && level == Level.RULE && seekingValue) {
475       // win newline
476       buffer = [];
477     } else if (buffer.length == 1 && isNewLineWin) {
478       // ignore windows newline which is composed of two characters
479       buffer.pop();
480     } else if (buffer.length > 0 || !isSpace && !isNewLineNix && !isNewLineWin) {
481       // any character
482       buffer.push(character);
483     }
484
485     wasEscaped = isEscaped;
486     isEscaped = !wasEscaped && character == Marker.BACK_SLASH;
487     wasCommentStart = isCommentStart;
488     wasCommentEnd = isCommentEnd;
489
490     position.line = (isNewLineWin || isNewLineNix) ? position.line + 1 : position.line;
491     position.column = (isNewLineWin || isNewLineNix) ? 0 : position.column + 1;
492   }
493
494   if (seekingValue) {
495     externalContext.warnings.push('Missing \'}\' at ' + formatPosition([position.line, position.column, position.source]) + '.');
496   }
497
498   if (seekingValue && buffer.length > 0) {
499     serializedBuffer = buffer.join('').replace(TAIL_BROKEN_VALUE_PATTERN, '');
500     propertyToken.push([Token.PROPERTY_VALUE, serializedBuffer, [originalMetadata(metadata, serializedBuffer, externalContext)]]);
501
502     buffer = [];
503   }
504
505   if (buffer.length > 0) {
506     externalContext.warnings.push('Invalid character(s) \'' + buffer.join('') + '\' at ' + formatPosition(metadata) + '. Ignoring.');
507   }
508
509   return allTokens;
510 }
511
512 function isIgnoreStartComment(buffer) {
513   return IGNORE_START_COMMENT_PATTERN.test(buffer.join('') + Marker.FORWARD_SLASH);
514 }
515
516 function isIgnoreEndComment(buffer) {
517   return IGNORE_END_COMMENT_PATTERN.test(buffer.join('') + Marker.FORWARD_SLASH);
518 }
519
520 function originalMetadata(metadata, value, externalContext, selectorFallbacks) {
521   var source = metadata[2];
522
523   return externalContext.inputSourceMapTracker.isTracking(source) ?
524     externalContext.inputSourceMapTracker.originalPositionFor(metadata, value.length, selectorFallbacks) :
525     metadata;
526 }
527
528 function tokenTypeFrom(buffer) {
529   var isAtRule = buffer[0] == Marker.AT || buffer[0] == Marker.UNDERSCORE;
530   var ruleWord = buffer.join('').split(RULE_WORD_SEPARATOR_PATTERN)[0];
531
532   if (isAtRule && BLOCK_RULES.indexOf(ruleWord) > -1) {
533     return Token.NESTED_BLOCK;
534   } else if (isAtRule && AT_RULES.indexOf(ruleWord) > -1) {
535     return Token.AT_RULE;
536   } else if (isAtRule) {
537     return Token.AT_RULE_BLOCK;
538   } else {
539     return Token.RULE;
540   }
541 }
542
543 function tokenScopeFrom(tokenType) {
544   if (tokenType == Token.RULE) {
545     return Token.RULE_SCOPE;
546   } else if (tokenType == Token.NESTED_BLOCK) {
547     return Token.NESTED_BLOCK_SCOPE;
548   } else if (tokenType == Token.AT_RULE_BLOCK) {
549     return Token.AT_RULE_BLOCK_SCOPE;
550   }
551 }
552
553 function isPageMarginBox(buffer) {
554   var serializedBuffer = buffer.join('').trim();
555
556   return PAGE_MARGIN_BOXES.indexOf(serializedBuffer) > -1 || EXTRA_PAGE_BOXES.indexOf(serializedBuffer) > -1;
557 }
558
559 function isRepeatToken(buffer) {
560   return REPEAT_PATTERN.test(buffer.join('') + Marker.CLOSE_SQUARE_BRACKET);
561 }
562
563 module.exports = tokenize;