tokenize.js 25 KB

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