tokenize.js 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864
  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 TAIL_BROKEN_VALUE_PATTERN = /([^}])\}*$/;
  56. var RULE_WORD_SEPARATOR_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 isBufferEmpty = true;
  83. var serializedBuffer;
  84. var serializedBufferPart;
  85. var roundBracketLevel = 0;
  86. var isQuoted;
  87. var isSpace;
  88. var isNewLineNix;
  89. var isNewLineWin;
  90. var isCarriageReturn;
  91. var isCommentStart;
  92. var wasCommentStart = false;
  93. var isCommentEnd;
  94. var wasCommentEnd = false;
  95. var isCommentEndMarker;
  96. var isEscaped;
  97. var wasEscaped = false;
  98. var characterWithNoSpecialMeaning;
  99. var isPreviousDash = false;
  100. var isVariable = false;
  101. var isRaw = false;
  102. var seekingValue = false;
  103. var seekingPropertyBlockClosing = false;
  104. var position = internalContext.position;
  105. var lastCommentStartAt;
  106. for (; position.index < source.length; position.index++) {
  107. var character = source[position.index];
  108. isQuoted = level == Level.SINGLE_QUOTE || level == Level.DOUBLE_QUOTE;
  109. isSpace = character == Marker.SPACE || character == Marker.TAB;
  110. isNewLineNix = character == Marker.NEW_LINE_NIX;
  111. isNewLineWin = character == Marker.NEW_LINE_NIX
  112. && source[position.index - 1] == Marker.CARRIAGE_RETURN;
  113. isCarriageReturn = character == Marker.CARRIAGE_RETURN
  114. && source[position.index + 1] && source[position.index + 1] != Marker.NEW_LINE_NIX;
  115. isCommentStart = !wasCommentEnd
  116. && level != Level.COMMENT && !isQuoted
  117. && character == Marker.ASTERISK && source[position.index - 1] == Marker.FORWARD_SLASH;
  118. isCommentEndMarker = !wasCommentStart
  119. && !isQuoted && character == Marker.FORWARD_SLASH
  120. && source[position.index - 1] == Marker.ASTERISK;
  121. isCommentEnd = level == Level.COMMENT && isCommentEndMarker;
  122. characterWithNoSpecialMeaning = !isSpace && !isCarriageReturn && (character >= 'A' && character <= 'Z' || character >= 'a' && character <= 'z' || character >= '0' && character <= '9' || character == '-');
  123. isVariable = isVariable || (level != Level.COMMENT && !seekingValue && isPreviousDash && character === '-');
  124. isPreviousDash = character === '-';
  125. roundBracketLevel = Math.max(roundBracketLevel, 0);
  126. metadata = isBufferEmpty
  127. ? [position.line, position.column, position.source]
  128. : metadata;
  129. if (isEscaped) {
  130. // previous character was a backslash
  131. buffer.push(character);
  132. isBufferEmpty = false;
  133. } else if (characterWithNoSpecialMeaning) {
  134. // it's just an alphanumeric character or a hyphen (part of any rule or property name) so let's end it quickly
  135. buffer.push(character);
  136. isBufferEmpty = false;
  137. } else if ((isSpace || isNewLineNix && !isNewLineWin) && (isQuoted || level == Level.COMMENT)) {
  138. buffer.push(character);
  139. isBufferEmpty = false;
  140. } else if ((isSpace || isNewLineNix && !isNewLineWin) && isBufferEmpty) {
  141. // noop
  142. } else if (!isCommentEnd && level == Level.COMMENT) {
  143. buffer.push(character);
  144. isBufferEmpty = false;
  145. } else if (!isCommentStart && !isCommentEnd && isRaw) {
  146. buffer.push(character);
  147. isBufferEmpty = false;
  148. } else if (isCommentStart
  149. && isVariable
  150. && (level == Level.BLOCK || level == Level.RULE) && buffer.length > 1) {
  151. // comment start within a variable, e.g. var(/*<--
  152. buffer.push(character);
  153. isBufferEmpty = false;
  154. levels.push(level);
  155. level = Level.COMMENT;
  156. } else if (isCommentStart && (level == Level.BLOCK || level == Level.RULE) && buffer.length > 1) {
  157. // comment start within block preceded by some content, e.g. div/*<--
  158. metadatas.push(metadata);
  159. buffer.push(character);
  160. buffers.push(buffer.slice(0, -2));
  161. isBufferEmpty = false;
  162. buffer = buffer.slice(-2);
  163. metadata = [position.line, position.column - 1, position.source];
  164. levels.push(level);
  165. level = Level.COMMENT;
  166. } else if (isCommentStart) {
  167. // comment start, e.g. /*<--
  168. levels.push(level);
  169. level = Level.COMMENT;
  170. buffer.push(character);
  171. isBufferEmpty = false;
  172. } else if (isCommentEnd && isVariable) {
  173. // comment end within a variable, e.g. var(/*!*/<--
  174. buffer.push(character);
  175. level = levels.pop();
  176. } else if (isCommentEnd && isIgnoreStartComment(buffer)) {
  177. // ignore:start comment end, e.g. /* clean-css ignore:start */<--
  178. serializedBuffer = buffer.join('').trim() + character;
  179. lastToken = [
  180. Token.COMMENT,
  181. serializedBuffer,
  182. [originalMetadata(metadata, serializedBuffer, externalContext)]
  183. ];
  184. newTokens.push(lastToken);
  185. isRaw = true;
  186. metadata = metadatas.pop() || null;
  187. buffer = buffers.pop() || [];
  188. isBufferEmpty = buffer.length === 0;
  189. } else if (isCommentEnd && isIgnoreEndComment(buffer)) {
  190. // ignore:start comment end, e.g. /* clean-css ignore:end */<--
  191. serializedBuffer = buffer.join('') + character;
  192. lastCommentStartAt = serializedBuffer.lastIndexOf(Marker.FORWARD_SLASH + Marker.ASTERISK);
  193. serializedBufferPart = serializedBuffer.substring(0, lastCommentStartAt);
  194. lastToken = [
  195. Token.RAW,
  196. serializedBufferPart,
  197. [originalMetadata(metadata, serializedBufferPart, externalContext)]
  198. ];
  199. newTokens.push(lastToken);
  200. serializedBufferPart = serializedBuffer.substring(lastCommentStartAt);
  201. metadata = [position.line, position.column - serializedBufferPart.length + 1, position.source];
  202. lastToken = [
  203. Token.COMMENT,
  204. serializedBufferPart,
  205. [originalMetadata(metadata, serializedBufferPart, externalContext)]
  206. ];
  207. newTokens.push(lastToken);
  208. isRaw = false;
  209. level = levels.pop();
  210. metadata = metadatas.pop() || null;
  211. buffer = buffers.pop() || [];
  212. isBufferEmpty = buffer.length === 0;
  213. } else if (isCommentEnd) {
  214. // comment end, e.g. /* comment */<--
  215. serializedBuffer = buffer.join('').trim() + character;
  216. lastToken = [
  217. Token.COMMENT,
  218. serializedBuffer,
  219. [originalMetadata(metadata, serializedBuffer, externalContext)]
  220. ];
  221. newTokens.push(lastToken);
  222. level = levels.pop();
  223. metadata = metadatas.pop() || null;
  224. buffer = buffers.pop() || [];
  225. isBufferEmpty = buffer.length === 0;
  226. } else if (isCommentEndMarker && source[position.index + 1] != Marker.ASTERISK) {
  227. externalContext.warnings.push('Unexpected \'*/\' at ' + formatPosition([position.line, position.column, position.source]) + '.');
  228. buffer = [];
  229. isBufferEmpty = true;
  230. } else if (character == Marker.SINGLE_QUOTE && !isQuoted) {
  231. // single quotation start, e.g. a[href^='https<--
  232. levels.push(level);
  233. level = Level.SINGLE_QUOTE;
  234. buffer.push(character);
  235. isBufferEmpty = false;
  236. } else if (character == Marker.SINGLE_QUOTE && level == Level.SINGLE_QUOTE) {
  237. // single quotation end, e.g. a[href^='https'<--
  238. level = levels.pop();
  239. buffer.push(character);
  240. isBufferEmpty = false;
  241. } else if (character == Marker.DOUBLE_QUOTE && !isQuoted) {
  242. // double quotation start, e.g. a[href^="<--
  243. levels.push(level);
  244. level = Level.DOUBLE_QUOTE;
  245. buffer.push(character);
  246. isBufferEmpty = false;
  247. } else if (character == Marker.DOUBLE_QUOTE && level == Level.DOUBLE_QUOTE) {
  248. // double quotation end, e.g. a[href^="https"<--
  249. level = levels.pop();
  250. buffer.push(character);
  251. isBufferEmpty = false;
  252. } else if (character != Marker.CLOSE_ROUND_BRACKET
  253. && character != Marker.OPEN_ROUND_BRACKET
  254. && level != Level.COMMENT && !isQuoted && roundBracketLevel > 0) {
  255. // character inside any function, e.g. hsla(.<--
  256. buffer.push(character);
  257. isBufferEmpty = false;
  258. } else if (character == Marker.OPEN_ROUND_BRACKET
  259. && !isQuoted && level != Level.COMMENT
  260. && !seekingValue) {
  261. // round open bracket, e.g. @import url(<--
  262. buffer.push(character);
  263. isBufferEmpty = false;
  264. roundBracketLevel++;
  265. } else if (character == Marker.CLOSE_ROUND_BRACKET
  266. && !isQuoted
  267. && level != Level.COMMENT
  268. && !seekingValue) {
  269. // round open bracket, e.g. @import url(test.css)<--
  270. buffer.push(character);
  271. isBufferEmpty = false;
  272. roundBracketLevel--;
  273. } else if (character == Marker.SEMICOLON && level == Level.BLOCK && buffer[0] == Marker.AT) {
  274. // semicolon ending rule at block level, e.g. @import '...';<--
  275. serializedBuffer = buffer.join('').trim();
  276. allTokens.push([
  277. Token.AT_RULE,
  278. serializedBuffer,
  279. [originalMetadata(metadata, serializedBuffer, externalContext)]
  280. ]);
  281. buffer = [];
  282. isBufferEmpty = true;
  283. } else if (character == Marker.COMMA && level == Level.BLOCK && ruleToken) {
  284. // comma separator at block level, e.g. a,div,<--
  285. serializedBuffer = buffer.join('').trim();
  286. ruleToken[1].push([
  287. tokenScopeFrom(ruleToken[0]),
  288. serializedBuffer,
  289. [originalMetadata(metadata, serializedBuffer, externalContext, ruleToken[1].length)]
  290. ]);
  291. buffer = [];
  292. isBufferEmpty = true;
  293. } else if (character == Marker.COMMA && level == Level.BLOCK && tokenTypeFrom(buffer) == Token.AT_RULE) {
  294. // comma separator at block level, e.g. @import url(...) screen,<--
  295. // keep iterating as end semicolon will create the token
  296. buffer.push(character);
  297. isBufferEmpty = false;
  298. } else if (character == Marker.COMMA && level == Level.BLOCK) {
  299. // comma separator at block level, e.g. a,<--
  300. ruleToken = [tokenTypeFrom(buffer), [], []];
  301. serializedBuffer = buffer.join('').trim();
  302. ruleToken[1].push([
  303. tokenScopeFrom(ruleToken[0]),
  304. serializedBuffer,
  305. [originalMetadata(metadata, serializedBuffer, externalContext, 0)]
  306. ]);
  307. buffer = [];
  308. isBufferEmpty = true;
  309. } else if (character == Marker.OPEN_CURLY_BRACKET
  310. && level == Level.BLOCK
  311. && ruleToken
  312. && ruleToken[0] == Token.NESTED_BLOCK) {
  313. // open brace opening at-rule at block level, e.g. @media{<--
  314. serializedBuffer = buffer.join('').trim();
  315. ruleToken[1].push([
  316. Token.NESTED_BLOCK_SCOPE,
  317. serializedBuffer,
  318. [originalMetadata(metadata, serializedBuffer, externalContext)]
  319. ]);
  320. allTokens.push(ruleToken);
  321. levels.push(level);
  322. position.column++;
  323. position.index++;
  324. buffer = [];
  325. isBufferEmpty = true;
  326. ruleToken[2] = intoTokens(source, externalContext, internalContext, true);
  327. ruleToken = null;
  328. } else if (character == Marker.OPEN_CURLY_BRACKET
  329. && level == Level.BLOCK
  330. && tokenTypeFrom(buffer) == Token.NESTED_BLOCK) {
  331. // open brace opening at-rule at block level, e.g. @media{<--
  332. serializedBuffer = buffer.join('').trim();
  333. ruleToken = ruleToken || [Token.NESTED_BLOCK, [], []];
  334. ruleToken[1].push([
  335. Token.NESTED_BLOCK_SCOPE,
  336. serializedBuffer,
  337. [originalMetadata(metadata, serializedBuffer, externalContext)]
  338. ]);
  339. allTokens.push(ruleToken);
  340. levels.push(level);
  341. position.column++;
  342. position.index++;
  343. buffer = [];
  344. isBufferEmpty = true;
  345. ruleToken[2] = intoTokens(source, externalContext, internalContext, true);
  346. ruleToken = null;
  347. } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.BLOCK) {
  348. // open brace opening rule at block level, e.g. div{<--
  349. serializedBuffer = buffer.join('').trim();
  350. ruleToken = ruleToken || [tokenTypeFrom(buffer), [], []];
  351. ruleToken[1].push([
  352. tokenScopeFrom(ruleToken[0]),
  353. serializedBuffer,
  354. [originalMetadata(metadata, serializedBuffer, externalContext, ruleToken[1].length)]
  355. ]);
  356. newTokens = ruleToken[2];
  357. allTokens.push(ruleToken);
  358. levels.push(level);
  359. level = Level.RULE;
  360. buffer = [];
  361. isBufferEmpty = true;
  362. } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.RULE && seekingValue) {
  363. // open brace opening rule at rule level, e.g. div{--variable:{<--
  364. ruleTokens.push(ruleToken);
  365. ruleToken = [Token.PROPERTY_BLOCK, []];
  366. propertyToken.push(ruleToken);
  367. newTokens = ruleToken[1];
  368. levels.push(level);
  369. level = Level.RULE;
  370. seekingValue = false;
  371. } else if (character == Marker.OPEN_CURLY_BRACKET && level == Level.RULE && isPageMarginBox(buffer)) {
  372. // open brace opening page-margin box at rule level, e.g. @page{@top-center{<--
  373. serializedBuffer = buffer.join('').trim();
  374. ruleTokens.push(ruleToken);
  375. ruleToken = [Token.AT_RULE_BLOCK, [], []];
  376. ruleToken[1].push([
  377. Token.AT_RULE_BLOCK_SCOPE,
  378. serializedBuffer,
  379. [originalMetadata(metadata, serializedBuffer, externalContext)]
  380. ]);
  381. newTokens.push(ruleToken);
  382. newTokens = ruleToken[2];
  383. levels.push(level);
  384. level = Level.RULE;
  385. buffer = [];
  386. isBufferEmpty = true;
  387. } else if (character == Marker.COLON && level == Level.RULE && !seekingValue) {
  388. // colon at rule level, e.g. a{color:<--
  389. serializedBuffer = buffer.join('').trim();
  390. propertyToken = [
  391. Token.PROPERTY,
  392. [
  393. Token.PROPERTY_NAME,
  394. serializedBuffer,
  395. [originalMetadata(metadata, serializedBuffer, externalContext)]
  396. ]
  397. ];
  398. newTokens.push(propertyToken);
  399. seekingValue = true;
  400. buffer = [];
  401. isBufferEmpty = true;
  402. } else if (character == Marker.SEMICOLON
  403. && level == Level.RULE
  404. && propertyToken
  405. && ruleTokens.length > 0
  406. && !isBufferEmpty
  407. && buffer[0] == Marker.AT) {
  408. // semicolon at rule level for at-rule, e.g. a{--color:{@apply(--other-color);<--
  409. serializedBuffer = buffer.join('').trim();
  410. ruleToken[1].push([
  411. Token.AT_RULE,
  412. serializedBuffer,
  413. [originalMetadata(metadata, serializedBuffer, externalContext)]
  414. ]);
  415. buffer = [];
  416. isBufferEmpty = true;
  417. } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && !isBufferEmpty) {
  418. // semicolon at rule level, e.g. a{color:red;<--
  419. serializedBuffer = buffer.join('').trim();
  420. propertyToken.push([
  421. Token.PROPERTY_VALUE,
  422. serializedBuffer,
  423. [originalMetadata(metadata, serializedBuffer, externalContext)]
  424. ]);
  425. propertyToken = null;
  426. seekingValue = false;
  427. buffer = [];
  428. isBufferEmpty = true;
  429. isVariable = false;
  430. } else if (character == Marker.SEMICOLON
  431. && level == Level.RULE
  432. && propertyToken
  433. && isBufferEmpty
  434. && isVariable
  435. && !propertyToken[2]) {
  436. // semicolon after empty variable value at rule level, e.g. a{--color: ;<--
  437. propertyToken.push([Token.PROPERTY_VALUE, ' ', [originalMetadata(metadata, ' ', externalContext)]]);
  438. isVariable = false;
  439. propertyToken = null;
  440. seekingValue = false;
  441. } else if (character == Marker.SEMICOLON && level == Level.RULE && propertyToken && isBufferEmpty) {
  442. // semicolon after bracketed value at rule level, e.g. a{color:rgb(...);<--
  443. propertyToken = null;
  444. seekingValue = false;
  445. } else if (character == Marker.SEMICOLON
  446. && level == Level.RULE
  447. && !isBufferEmpty
  448. && buffer[0] == Marker.AT) {
  449. // semicolon for at-rule at rule level, e.g. a{@apply(--variable);<--
  450. serializedBuffer = buffer.join('');
  451. newTokens.push([
  452. Token.AT_RULE,
  453. serializedBuffer,
  454. [originalMetadata(metadata, serializedBuffer, externalContext)]
  455. ]);
  456. seekingValue = false;
  457. buffer = [];
  458. isBufferEmpty = true;
  459. } else if (character == Marker.SEMICOLON && level == Level.RULE && seekingPropertyBlockClosing) {
  460. // close brace after a property block at rule level, e.g. a{--custom:{color:red;};<--
  461. seekingPropertyBlockClosing = false;
  462. buffer = [];
  463. isBufferEmpty = true;
  464. } else if (character == Marker.SEMICOLON && level == Level.RULE && isBufferEmpty) {
  465. // stray semicolon at rule level, e.g. a{;<--
  466. // noop
  467. } else if (character == Marker.CLOSE_CURLY_BRACKET
  468. && level == Level.RULE
  469. && propertyToken
  470. && seekingValue
  471. && !isBufferEmpty && ruleTokens.length > 0) {
  472. // close brace at rule level, e.g. a{--color:{color:red}<--
  473. serializedBuffer = buffer.join('');
  474. propertyToken.push([
  475. Token.PROPERTY_VALUE,
  476. serializedBuffer,
  477. [originalMetadata(metadata, serializedBuffer, externalContext)]
  478. ]);
  479. propertyToken = null;
  480. ruleToken = ruleTokens.pop();
  481. newTokens = ruleToken[2];
  482. level = levels.pop();
  483. seekingValue = false;
  484. buffer = [];
  485. isBufferEmpty = true;
  486. } else if (character == Marker.CLOSE_CURLY_BRACKET
  487. && level == Level.RULE
  488. && propertyToken
  489. && !isBufferEmpty
  490. && buffer[0] == Marker.AT
  491. && ruleTokens.length > 0) {
  492. // close brace at rule level for at-rule, e.g. a{--color:{@apply(--other-color)}<--
  493. serializedBuffer = buffer.join('');
  494. ruleToken[1].push([
  495. Token.AT_RULE,
  496. serializedBuffer,
  497. [originalMetadata(metadata, serializedBuffer, externalContext)]
  498. ]);
  499. propertyToken = null;
  500. ruleToken = ruleTokens.pop();
  501. newTokens = ruleToken[2];
  502. level = levels.pop();
  503. seekingValue = false;
  504. buffer = [];
  505. isBufferEmpty = true;
  506. } else if (character == Marker.CLOSE_CURLY_BRACKET
  507. && level == Level.RULE
  508. && propertyToken
  509. && ruleTokens.length > 0) {
  510. // close brace at rule level after space, e.g. a{--color:{color:red }<--
  511. propertyToken = null;
  512. ruleToken = ruleTokens.pop();
  513. newTokens = ruleToken[2];
  514. level = levels.pop();
  515. seekingValue = false;
  516. } else if (character == Marker.CLOSE_CURLY_BRACKET
  517. && level == Level.RULE
  518. && propertyToken
  519. && !isBufferEmpty) {
  520. // close brace at rule level, e.g. a{color:red}<--
  521. serializedBuffer = buffer.join('');
  522. propertyToken.push([
  523. Token.PROPERTY_VALUE,
  524. serializedBuffer,
  525. [originalMetadata(metadata, serializedBuffer, externalContext)]
  526. ]);
  527. propertyToken = null;
  528. ruleToken = ruleTokens.pop();
  529. newTokens = allTokens;
  530. level = levels.pop();
  531. seekingValue = false;
  532. buffer = [];
  533. isBufferEmpty = true;
  534. } else if (character == Marker.CLOSE_CURLY_BRACKET
  535. && level == Level.RULE
  536. && !isBufferEmpty
  537. && buffer[0] == Marker.AT) {
  538. // close brace after at-rule at rule level, e.g. a{@apply(--variable)}<--
  539. propertyToken = null;
  540. ruleToken = null;
  541. serializedBuffer = buffer.join('').trim();
  542. newTokens.push([
  543. Token.AT_RULE,
  544. serializedBuffer,
  545. [originalMetadata(metadata, serializedBuffer, externalContext)]
  546. ]);
  547. newTokens = allTokens;
  548. level = levels.pop();
  549. seekingValue = false;
  550. buffer = [];
  551. isBufferEmpty = true;
  552. } else if (character == Marker.CLOSE_CURLY_BRACKET
  553. && level == Level.RULE
  554. && levels[levels.length - 1] == Level.RULE) {
  555. // close brace after a property block at rule level, e.g. a{--custom:{color:red;}<--
  556. propertyToken = null;
  557. ruleToken = ruleTokens.pop();
  558. newTokens = ruleToken[2];
  559. level = levels.pop();
  560. seekingValue = false;
  561. seekingPropertyBlockClosing = true;
  562. buffer = [];
  563. isBufferEmpty = true;
  564. } else if (character == Marker.CLOSE_CURLY_BRACKET
  565. && level == Level.RULE
  566. && isVariable
  567. && propertyToken
  568. && !propertyToken[2]) {
  569. // close brace after an empty variable declaration inside a rule, e.g. a{--color: }<--
  570. propertyToken.push([Token.PROPERTY_VALUE, ' ', [originalMetadata(metadata, ' ', externalContext)]]);
  571. isVariable = false;
  572. propertyToken = null;
  573. ruleToken = null;
  574. newTokens = allTokens;
  575. level = levels.pop();
  576. seekingValue = false;
  577. isVariable = false;
  578. } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.RULE) {
  579. // close brace after a rule, e.g. a{color:red;}<--
  580. propertyToken = null;
  581. ruleToken = null;
  582. newTokens = allTokens;
  583. level = levels.pop();
  584. seekingValue = false;
  585. isVariable = false;
  586. } else if (character == Marker.CLOSE_CURLY_BRACKET
  587. && level == Level.BLOCK
  588. && !isNested
  589. && position.index <= source.length - 1) {
  590. // stray close brace at block level, e.g. a{color:red}color:blue}<--
  591. externalContext.warnings.push('Unexpected \'}\' at ' + formatPosition([position.line, position.column, position.source]) + '.');
  592. buffer.push(character);
  593. isBufferEmpty = false;
  594. } else if (character == Marker.CLOSE_CURLY_BRACKET && level == Level.BLOCK) {
  595. // close brace at block level, e.g. @media screen {...}<--
  596. break;
  597. } else if (character == Marker.OPEN_ROUND_BRACKET && level == Level.RULE && seekingValue) {
  598. // round open bracket, e.g. a{color:hsla(<--
  599. buffer.push(character);
  600. isBufferEmpty = false;
  601. roundBracketLevel++;
  602. } else if (character == Marker.CLOSE_ROUND_BRACKET
  603. && level == Level.RULE
  604. && seekingValue
  605. && roundBracketLevel == 1) {
  606. // round close bracket, e.g. a{color:hsla(0,0%,0%)<--
  607. buffer.push(character);
  608. isBufferEmpty = false;
  609. serializedBuffer = buffer.join('').trim();
  610. propertyToken.push([
  611. Token.PROPERTY_VALUE,
  612. serializedBuffer,
  613. [originalMetadata(metadata, serializedBuffer, externalContext)]
  614. ]);
  615. roundBracketLevel--;
  616. buffer = [];
  617. isBufferEmpty = true;
  618. isVariable = false;
  619. } else if (character == Marker.CLOSE_ROUND_BRACKET && level == Level.RULE && seekingValue) {
  620. // round close bracket within other brackets, e.g. a{width:calc((10rem / 2)<--
  621. buffer.push(character);
  622. isBufferEmpty = false;
  623. isVariable = false;
  624. roundBracketLevel--;
  625. } else if (character == Marker.FORWARD_SLASH
  626. && source[position.index + 1] != Marker.ASTERISK
  627. && level == Level.RULE
  628. && seekingValue
  629. && !isBufferEmpty) {
  630. // forward slash within a property, e.g. a{background:url(image.png) 0 0/<--
  631. serializedBuffer = buffer.join('').trim();
  632. propertyToken.push([
  633. Token.PROPERTY_VALUE,
  634. serializedBuffer,
  635. [originalMetadata(metadata, serializedBuffer, externalContext)]
  636. ]);
  637. propertyToken.push([
  638. Token.PROPERTY_VALUE,
  639. character,
  640. [[position.line, position.column, position.source]]
  641. ]);
  642. buffer = [];
  643. isBufferEmpty = true;
  644. } else if (character == Marker.FORWARD_SLASH
  645. && source[position.index + 1] != Marker.ASTERISK
  646. && level == Level.RULE
  647. && seekingValue) {
  648. // forward slash within a property after space, e.g. a{background:url(image.png) 0 0 /<--
  649. propertyToken.push([
  650. Token.PROPERTY_VALUE,
  651. character,
  652. [[position.line, position.column, position.source]]
  653. ]);
  654. buffer = [];
  655. isBufferEmpty = true;
  656. } else if (character == Marker.COMMA && level == Level.RULE && seekingValue && !isBufferEmpty) {
  657. // comma within a property, e.g. a{background:url(image.png),<--
  658. serializedBuffer = buffer.join('').trim();
  659. propertyToken.push([
  660. Token.PROPERTY_VALUE,
  661. serializedBuffer,
  662. [originalMetadata(metadata, serializedBuffer, externalContext)]
  663. ]);
  664. propertyToken.push([
  665. Token.PROPERTY_VALUE,
  666. character,
  667. [[position.line, position.column, position.source]]
  668. ]);
  669. buffer = [];
  670. isBufferEmpty = true;
  671. } else if (character == Marker.COMMA && level == Level.RULE && seekingValue) {
  672. // comma within a property after space, e.g. a{background:url(image.png) ,<--
  673. propertyToken.push([
  674. Token.PROPERTY_VALUE,
  675. character,
  676. [[position.line, position.column, position.source]]
  677. ]);
  678. buffer = [];
  679. isBufferEmpty = true;
  680. } else if (character == Marker.CLOSE_SQUARE_BRACKET
  681. && propertyToken
  682. && propertyToken.length > 1
  683. && !isBufferEmpty
  684. && isRepeatToken(buffer)) {
  685. buffer.push(character);
  686. serializedBuffer = buffer.join('').trim();
  687. propertyToken[propertyToken.length - 1][1] += serializedBuffer;
  688. buffer = [];
  689. isBufferEmpty = true;
  690. } else if ((isSpace || (isNewLineNix && !isNewLineWin))
  691. && level == Level.RULE
  692. && seekingValue
  693. && propertyToken
  694. && !isBufferEmpty) {
  695. // space or *nix newline within property, e.g. a{margin:0 <--
  696. serializedBuffer = buffer.join('').trim();
  697. propertyToken.push([
  698. Token.PROPERTY_VALUE,
  699. serializedBuffer,
  700. [originalMetadata(metadata, serializedBuffer, externalContext)]
  701. ]);
  702. buffer = [];
  703. isBufferEmpty = true;
  704. } else if (isNewLineWin && level == Level.RULE && seekingValue && propertyToken && buffer.length > 1) {
  705. // win newline within property, e.g. a{margin:0\r\n<--
  706. serializedBuffer = buffer.join('').trim();
  707. propertyToken.push([
  708. Token.PROPERTY_VALUE,
  709. serializedBuffer,
  710. [originalMetadata(metadata, serializedBuffer, externalContext)]
  711. ]);
  712. buffer = [];
  713. isBufferEmpty = true;
  714. } else if (isNewLineWin && level == Level.RULE && seekingValue) {
  715. // win newline
  716. buffer = [];
  717. isBufferEmpty = true;
  718. } else if (isNewLineWin && buffer.length == 1) {
  719. // ignore windows newline which is composed of two characters
  720. buffer.pop();
  721. isBufferEmpty = buffer.length === 0;
  722. } else if (!isBufferEmpty || !isSpace && !isNewLineNix && !isNewLineWin && !isCarriageReturn) {
  723. // any character
  724. buffer.push(character);
  725. isBufferEmpty = false;
  726. }
  727. wasEscaped = isEscaped;
  728. isEscaped = !wasEscaped && character == Marker.BACK_SLASH;
  729. wasCommentStart = isCommentStart;
  730. wasCommentEnd = isCommentEnd;
  731. position.line = (isNewLineWin || isNewLineNix || isCarriageReturn) ? position.line + 1 : position.line;
  732. position.column = (isNewLineWin || isNewLineNix || isCarriageReturn) ? 0 : position.column + 1;
  733. }
  734. if (seekingValue) {
  735. externalContext.warnings.push('Missing \'}\' at ' + formatPosition([position.line, position.column, position.source]) + '.');
  736. }
  737. if (seekingValue && buffer.length > 0) {
  738. serializedBuffer = buffer.join('').trimRight().replace(TAIL_BROKEN_VALUE_PATTERN, '$1').trimRight();
  739. propertyToken.push([
  740. Token.PROPERTY_VALUE,
  741. serializedBuffer,
  742. [originalMetadata(metadata, serializedBuffer, externalContext)]
  743. ]);
  744. buffer = [];
  745. }
  746. if (buffer.length > 0) {
  747. externalContext.warnings.push('Invalid character(s) \'' + buffer.join('') + '\' at ' + formatPosition(metadata) + '. Ignoring.');
  748. }
  749. return allTokens;
  750. }
  751. function isIgnoreStartComment(buffer) {
  752. return IGNORE_START_COMMENT_PATTERN.test(buffer.join('') + Marker.FORWARD_SLASH);
  753. }
  754. function isIgnoreEndComment(buffer) {
  755. return IGNORE_END_COMMENT_PATTERN.test(buffer.join('') + Marker.FORWARD_SLASH);
  756. }
  757. function originalMetadata(metadata, value, externalContext, selectorFallbacks) {
  758. var source = metadata[2];
  759. return externalContext.inputSourceMapTracker.isTracking(source)
  760. ? externalContext.inputSourceMapTracker.originalPositionFor(metadata, value.length, selectorFallbacks)
  761. : metadata;
  762. }
  763. function tokenTypeFrom(buffer) {
  764. var isAtRule = buffer[0] == Marker.AT || buffer[0] == Marker.UNDERSCORE;
  765. var ruleWord = buffer.join('').split(RULE_WORD_SEPARATOR_PATTERN)[0];
  766. if (isAtRule && BLOCK_RULES.indexOf(ruleWord) > -1) {
  767. return Token.NESTED_BLOCK;
  768. } if (isAtRule && AT_RULES.indexOf(ruleWord) > -1) {
  769. return Token.AT_RULE;
  770. } if (isAtRule) {
  771. return Token.AT_RULE_BLOCK;
  772. }
  773. return Token.RULE;
  774. }
  775. function tokenScopeFrom(tokenType) {
  776. if (tokenType == Token.RULE) {
  777. return Token.RULE_SCOPE;
  778. } if (tokenType == Token.NESTED_BLOCK) {
  779. return Token.NESTED_BLOCK_SCOPE;
  780. } if (tokenType == Token.AT_RULE_BLOCK) {
  781. return Token.AT_RULE_BLOCK_SCOPE;
  782. }
  783. }
  784. function isPageMarginBox(buffer) {
  785. var serializedBuffer = buffer.join('').trim();
  786. return PAGE_MARGIN_BOXES.indexOf(serializedBuffer) > -1 || EXTRA_PAGE_BOXES.indexOf(serializedBuffer) > -1;
  787. }
  788. function isRepeatToken(buffer) {
  789. return REPEAT_PATTERN.test(buffer.join('') + Marker.CLOSE_SQUARE_BRACKET);
  790. }
  791. module.exports = tokenize;