tokenize.js 30 KB

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