parser.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. 'use strict';
  2. exports.__esModule = true;
  3. var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  4. var _flatten = require('flatten');
  5. var _flatten2 = _interopRequireDefault(_flatten);
  6. var _indexesOf = require('indexes-of');
  7. var _indexesOf2 = _interopRequireDefault(_indexesOf);
  8. var _uniq = require('uniq');
  9. var _uniq2 = _interopRequireDefault(_uniq);
  10. var _root = require('./selectors/root');
  11. var _root2 = _interopRequireDefault(_root);
  12. var _selector = require('./selectors/selector');
  13. var _selector2 = _interopRequireDefault(_selector);
  14. var _className = require('./selectors/className');
  15. var _className2 = _interopRequireDefault(_className);
  16. var _comment = require('./selectors/comment');
  17. var _comment2 = _interopRequireDefault(_comment);
  18. var _id = require('./selectors/id');
  19. var _id2 = _interopRequireDefault(_id);
  20. var _tag = require('./selectors/tag');
  21. var _tag2 = _interopRequireDefault(_tag);
  22. var _string = require('./selectors/string');
  23. var _string2 = _interopRequireDefault(_string);
  24. var _pseudo = require('./selectors/pseudo');
  25. var _pseudo2 = _interopRequireDefault(_pseudo);
  26. var _attribute = require('./selectors/attribute');
  27. var _attribute2 = _interopRequireDefault(_attribute);
  28. var _universal = require('./selectors/universal');
  29. var _universal2 = _interopRequireDefault(_universal);
  30. var _combinator = require('./selectors/combinator');
  31. var _combinator2 = _interopRequireDefault(_combinator);
  32. var _nesting = require('./selectors/nesting');
  33. var _nesting2 = _interopRequireDefault(_nesting);
  34. var _sortAscending = require('./sortAscending');
  35. var _sortAscending2 = _interopRequireDefault(_sortAscending);
  36. var _tokenize = require('./tokenize');
  37. var _tokenize2 = _interopRequireDefault(_tokenize);
  38. var _types = require('./selectors/types');
  39. var types = _interopRequireWildcard(_types);
  40. function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }
  41. function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  42. function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  43. var Parser = function () {
  44. function Parser(input) {
  45. _classCallCheck(this, Parser);
  46. this.input = input;
  47. this.lossy = input.options.lossless === false;
  48. this.position = 0;
  49. this.root = new _root2.default();
  50. var selectors = new _selector2.default();
  51. this.root.append(selectors);
  52. this.current = selectors;
  53. if (this.lossy) {
  54. this.tokens = (0, _tokenize2.default)({ safe: input.safe, css: input.css.trim() });
  55. } else {
  56. this.tokens = (0, _tokenize2.default)(input);
  57. }
  58. return this.loop();
  59. }
  60. Parser.prototype.attribute = function attribute() {
  61. var str = '';
  62. var attr = void 0;
  63. var startingToken = this.currToken;
  64. this.position++;
  65. while (this.position < this.tokens.length && this.currToken[0] !== ']') {
  66. str += this.tokens[this.position][1];
  67. this.position++;
  68. }
  69. if (this.position === this.tokens.length && !~str.indexOf(']')) {
  70. this.error('Expected a closing square bracket.');
  71. }
  72. var parts = str.split(/((?:[*~^$|]?=))([^]*)/);
  73. var namespace = parts[0].split(/(\|)/g);
  74. var attributeProps = {
  75. operator: parts[1],
  76. value: parts[2],
  77. source: {
  78. start: {
  79. line: startingToken[2],
  80. column: startingToken[3]
  81. },
  82. end: {
  83. line: this.currToken[2],
  84. column: this.currToken[3]
  85. }
  86. },
  87. sourceIndex: startingToken[4]
  88. };
  89. if (namespace.length > 1) {
  90. if (namespace[0] === '') {
  91. namespace[0] = true;
  92. }
  93. attributeProps.attribute = this.parseValue(namespace[2]);
  94. attributeProps.namespace = this.parseNamespace(namespace[0]);
  95. } else {
  96. attributeProps.attribute = this.parseValue(parts[0]);
  97. }
  98. attr = new _attribute2.default(attributeProps);
  99. if (parts[2]) {
  100. var insensitive = parts[2].split(/(\s+i\s*?)$/);
  101. var trimmedValue = insensitive[0].trim();
  102. attr.value = this.lossy ? trimmedValue : insensitive[0];
  103. if (insensitive[1]) {
  104. attr.insensitive = true;
  105. if (!this.lossy) {
  106. attr.raws.insensitive = insensitive[1];
  107. }
  108. }
  109. attr.quoted = trimmedValue[0] === '\'' || trimmedValue[0] === '"';
  110. attr.raws.unquoted = attr.quoted ? trimmedValue.slice(1, -1) : trimmedValue;
  111. }
  112. this.newNode(attr);
  113. this.position++;
  114. };
  115. Parser.prototype.combinator = function combinator() {
  116. if (this.currToken[1] === '|') {
  117. return this.namespace();
  118. }
  119. var node = new _combinator2.default({
  120. value: '',
  121. source: {
  122. start: {
  123. line: this.currToken[2],
  124. column: this.currToken[3]
  125. },
  126. end: {
  127. line: this.currToken[2],
  128. column: this.currToken[3]
  129. }
  130. },
  131. sourceIndex: this.currToken[4]
  132. });
  133. while (this.position < this.tokens.length && this.currToken && (this.currToken[0] === 'space' || this.currToken[0] === 'combinator')) {
  134. if (this.nextToken && this.nextToken[0] === 'combinator') {
  135. node.spaces.before = this.parseSpace(this.currToken[1]);
  136. node.source.start.line = this.nextToken[2];
  137. node.source.start.column = this.nextToken[3];
  138. node.source.end.column = this.nextToken[3];
  139. node.source.end.line = this.nextToken[2];
  140. node.sourceIndex = this.nextToken[4];
  141. } else if (this.prevToken && this.prevToken[0] === 'combinator') {
  142. node.spaces.after = this.parseSpace(this.currToken[1]);
  143. } else if (this.currToken[0] === 'combinator') {
  144. node.value = this.currToken[1];
  145. } else if (this.currToken[0] === 'space') {
  146. node.value = this.parseSpace(this.currToken[1], ' ');
  147. }
  148. this.position++;
  149. }
  150. return this.newNode(node);
  151. };
  152. Parser.prototype.comma = function comma() {
  153. if (this.position === this.tokens.length - 1) {
  154. this.root.trailingComma = true;
  155. this.position++;
  156. return;
  157. }
  158. var selectors = new _selector2.default();
  159. this.current.parent.append(selectors);
  160. this.current = selectors;
  161. this.position++;
  162. };
  163. Parser.prototype.comment = function comment() {
  164. var node = new _comment2.default({
  165. value: this.currToken[1],
  166. source: {
  167. start: {
  168. line: this.currToken[2],
  169. column: this.currToken[3]
  170. },
  171. end: {
  172. line: this.currToken[4],
  173. column: this.currToken[5]
  174. }
  175. },
  176. sourceIndex: this.currToken[6]
  177. });
  178. this.newNode(node);
  179. this.position++;
  180. };
  181. Parser.prototype.error = function error(message) {
  182. throw new this.input.error(message); // eslint-disable-line new-cap
  183. };
  184. Parser.prototype.missingBackslash = function missingBackslash() {
  185. return this.error('Expected a backslash preceding the semicolon.');
  186. };
  187. Parser.prototype.missingParenthesis = function missingParenthesis() {
  188. return this.error('Expected opening parenthesis.');
  189. };
  190. Parser.prototype.missingSquareBracket = function missingSquareBracket() {
  191. return this.error('Expected opening square bracket.');
  192. };
  193. Parser.prototype.namespace = function namespace() {
  194. var before = this.prevToken && this.prevToken[1] || true;
  195. if (this.nextToken[0] === 'word') {
  196. this.position++;
  197. return this.word(before);
  198. } else if (this.nextToken[0] === '*') {
  199. this.position++;
  200. return this.universal(before);
  201. }
  202. };
  203. Parser.prototype.nesting = function nesting() {
  204. this.newNode(new _nesting2.default({
  205. value: this.currToken[1],
  206. source: {
  207. start: {
  208. line: this.currToken[2],
  209. column: this.currToken[3]
  210. },
  211. end: {
  212. line: this.currToken[2],
  213. column: this.currToken[3]
  214. }
  215. },
  216. sourceIndex: this.currToken[4]
  217. }));
  218. this.position++;
  219. };
  220. Parser.prototype.parentheses = function parentheses() {
  221. var last = this.current.last;
  222. if (last && last.type === types.PSEUDO) {
  223. var selector = new _selector2.default();
  224. var cache = this.current;
  225. last.append(selector);
  226. this.current = selector;
  227. var balanced = 1;
  228. this.position++;
  229. while (this.position < this.tokens.length && balanced) {
  230. if (this.currToken[0] === '(') {
  231. balanced++;
  232. }
  233. if (this.currToken[0] === ')') {
  234. balanced--;
  235. }
  236. if (balanced) {
  237. this.parse();
  238. } else {
  239. selector.parent.source.end.line = this.currToken[2];
  240. selector.parent.source.end.column = this.currToken[3];
  241. this.position++;
  242. }
  243. }
  244. if (balanced) {
  245. this.error('Expected closing parenthesis.');
  246. }
  247. this.current = cache;
  248. } else {
  249. var _balanced = 1;
  250. this.position++;
  251. last.value += '(';
  252. while (this.position < this.tokens.length && _balanced) {
  253. if (this.currToken[0] === '(') {
  254. _balanced++;
  255. }
  256. if (this.currToken[0] === ')') {
  257. _balanced--;
  258. }
  259. last.value += this.parseParenthesisToken(this.currToken);
  260. this.position++;
  261. }
  262. if (_balanced) {
  263. this.error('Expected closing parenthesis.');
  264. }
  265. }
  266. };
  267. Parser.prototype.pseudo = function pseudo() {
  268. var _this = this;
  269. var pseudoStr = '';
  270. var startingToken = this.currToken;
  271. while (this.currToken && this.currToken[0] === ':') {
  272. pseudoStr += this.currToken[1];
  273. this.position++;
  274. }
  275. if (!this.currToken) {
  276. return this.error('Expected pseudo-class or pseudo-element');
  277. }
  278. if (this.currToken[0] === 'word') {
  279. var pseudo = void 0;
  280. this.splitWord(false, function (first, length) {
  281. pseudoStr += first;
  282. pseudo = new _pseudo2.default({
  283. value: pseudoStr,
  284. source: {
  285. start: {
  286. line: startingToken[2],
  287. column: startingToken[3]
  288. },
  289. end: {
  290. line: _this.currToken[4],
  291. column: _this.currToken[5]
  292. }
  293. },
  294. sourceIndex: startingToken[4]
  295. });
  296. _this.newNode(pseudo);
  297. if (length > 1 && _this.nextToken && _this.nextToken[0] === '(') {
  298. _this.error('Misplaced parenthesis.');
  299. }
  300. });
  301. } else {
  302. this.error('Unexpected "' + this.currToken[0] + '" found.');
  303. }
  304. };
  305. Parser.prototype.space = function space() {
  306. var token = this.currToken;
  307. // Handle space before and after the selector
  308. if (this.position === 0 || this.prevToken[0] === ',' || this.prevToken[0] === '(') {
  309. this.spaces = this.parseSpace(token[1]);
  310. this.position++;
  311. } else if (this.position === this.tokens.length - 1 || this.nextToken[0] === ',' || this.nextToken[0] === ')') {
  312. this.current.last.spaces.after = this.parseSpace(token[1]);
  313. this.position++;
  314. } else {
  315. this.combinator();
  316. }
  317. };
  318. Parser.prototype.string = function string() {
  319. var token = this.currToken;
  320. this.newNode(new _string2.default({
  321. value: this.currToken[1],
  322. source: {
  323. start: {
  324. line: token[2],
  325. column: token[3]
  326. },
  327. end: {
  328. line: token[4],
  329. column: token[5]
  330. }
  331. },
  332. sourceIndex: token[6]
  333. }));
  334. this.position++;
  335. };
  336. Parser.prototype.universal = function universal(namespace) {
  337. var nextToken = this.nextToken;
  338. if (nextToken && nextToken[1] === '|') {
  339. this.position++;
  340. return this.namespace();
  341. }
  342. this.newNode(new _universal2.default({
  343. value: this.currToken[1],
  344. source: {
  345. start: {
  346. line: this.currToken[2],
  347. column: this.currToken[3]
  348. },
  349. end: {
  350. line: this.currToken[2],
  351. column: this.currToken[3]
  352. }
  353. },
  354. sourceIndex: this.currToken[4]
  355. }), namespace);
  356. this.position++;
  357. };
  358. Parser.prototype.splitWord = function splitWord(namespace, firstCallback) {
  359. var _this2 = this;
  360. var nextToken = this.nextToken;
  361. var word = this.currToken[1];
  362. while (nextToken && nextToken[0] === 'word') {
  363. this.position++;
  364. var current = this.currToken[1];
  365. word += current;
  366. if (current.lastIndexOf('\\') === current.length - 1) {
  367. var next = this.nextToken;
  368. if (next && next[0] === 'space') {
  369. word += this.parseSpace(next[1], ' ');
  370. this.position++;
  371. }
  372. }
  373. nextToken = this.nextToken;
  374. }
  375. var hasClass = (0, _indexesOf2.default)(word, '.');
  376. var hasId = (0, _indexesOf2.default)(word, '#');
  377. // Eliminate Sass interpolations from the list of id indexes
  378. var interpolations = (0, _indexesOf2.default)(word, '#{');
  379. if (interpolations.length) {
  380. hasId = hasId.filter(function (hashIndex) {
  381. return !~interpolations.indexOf(hashIndex);
  382. });
  383. }
  384. var indices = (0, _sortAscending2.default)((0, _uniq2.default)((0, _flatten2.default)([[0], hasClass, hasId])));
  385. indices.forEach(function (ind, i) {
  386. var index = indices[i + 1] || word.length;
  387. var value = word.slice(ind, index);
  388. if (i === 0 && firstCallback) {
  389. return firstCallback.call(_this2, value, indices.length);
  390. }
  391. var node = void 0;
  392. if (~hasClass.indexOf(ind)) {
  393. node = new _className2.default({
  394. value: value.slice(1),
  395. source: {
  396. start: {
  397. line: _this2.currToken[2],
  398. column: _this2.currToken[3] + ind
  399. },
  400. end: {
  401. line: _this2.currToken[4],
  402. column: _this2.currToken[3] + (index - 1)
  403. }
  404. },
  405. sourceIndex: _this2.currToken[6] + indices[i]
  406. });
  407. } else if (~hasId.indexOf(ind)) {
  408. node = new _id2.default({
  409. value: value.slice(1),
  410. source: {
  411. start: {
  412. line: _this2.currToken[2],
  413. column: _this2.currToken[3] + ind
  414. },
  415. end: {
  416. line: _this2.currToken[4],
  417. column: _this2.currToken[3] + (index - 1)
  418. }
  419. },
  420. sourceIndex: _this2.currToken[6] + indices[i]
  421. });
  422. } else {
  423. node = new _tag2.default({
  424. value: value,
  425. source: {
  426. start: {
  427. line: _this2.currToken[2],
  428. column: _this2.currToken[3] + ind
  429. },
  430. end: {
  431. line: _this2.currToken[4],
  432. column: _this2.currToken[3] + (index - 1)
  433. }
  434. },
  435. sourceIndex: _this2.currToken[6] + indices[i]
  436. });
  437. }
  438. _this2.newNode(node, namespace);
  439. });
  440. this.position++;
  441. };
  442. Parser.prototype.word = function word(namespace) {
  443. var nextToken = this.nextToken;
  444. if (nextToken && nextToken[1] === '|') {
  445. this.position++;
  446. return this.namespace();
  447. }
  448. return this.splitWord(namespace);
  449. };
  450. Parser.prototype.loop = function loop() {
  451. while (this.position < this.tokens.length) {
  452. this.parse(true);
  453. }
  454. return this.root;
  455. };
  456. Parser.prototype.parse = function parse(throwOnParenthesis) {
  457. switch (this.currToken[0]) {
  458. case 'space':
  459. this.space();
  460. break;
  461. case 'comment':
  462. this.comment();
  463. break;
  464. case '(':
  465. this.parentheses();
  466. break;
  467. case ')':
  468. if (throwOnParenthesis) {
  469. this.missingParenthesis();
  470. }
  471. break;
  472. case '[':
  473. this.attribute();
  474. break;
  475. case ']':
  476. this.missingSquareBracket();
  477. break;
  478. case 'at-word':
  479. case 'word':
  480. this.word();
  481. break;
  482. case ':':
  483. this.pseudo();
  484. break;
  485. case ';':
  486. this.missingBackslash();
  487. break;
  488. case ',':
  489. this.comma();
  490. break;
  491. case '*':
  492. this.universal();
  493. break;
  494. case '&':
  495. this.nesting();
  496. break;
  497. case 'combinator':
  498. this.combinator();
  499. break;
  500. case 'string':
  501. this.string();
  502. break;
  503. }
  504. };
  505. /**
  506. * Helpers
  507. */
  508. Parser.prototype.parseNamespace = function parseNamespace(namespace) {
  509. if (this.lossy && typeof namespace === 'string') {
  510. var trimmed = namespace.trim();
  511. if (!trimmed.length) {
  512. return true;
  513. }
  514. return trimmed;
  515. }
  516. return namespace;
  517. };
  518. Parser.prototype.parseSpace = function parseSpace(space, replacement) {
  519. return this.lossy ? replacement || '' : space;
  520. };
  521. Parser.prototype.parseValue = function parseValue(value) {
  522. return this.lossy && value && typeof value === 'string' ? value.trim() : value;
  523. };
  524. Parser.prototype.parseParenthesisToken = function parseParenthesisToken(token) {
  525. if (!this.lossy) {
  526. return token[1];
  527. }
  528. if (token[0] === 'space') {
  529. return this.parseSpace(token[1], ' ');
  530. }
  531. return this.parseValue(token[1]);
  532. };
  533. Parser.prototype.newNode = function newNode(node, namespace) {
  534. if (namespace) {
  535. node.namespace = this.parseNamespace(namespace);
  536. }
  537. if (this.spaces) {
  538. node.spaces.before = this.spaces;
  539. this.spaces = '';
  540. }
  541. return this.current.append(node);
  542. };
  543. _createClass(Parser, [{
  544. key: 'currToken',
  545. get: function get() {
  546. return this.tokens[this.position];
  547. }
  548. }, {
  549. key: 'nextToken',
  550. get: function get() {
  551. return this.tokens[this.position + 1];
  552. }
  553. }, {
  554. key: 'prevToken',
  555. get: function get() {
  556. return this.tokens[this.position - 1];
  557. }
  558. }]);
  559. return Parser;
  560. }();
  561. exports.default = Parser;
  562. module.exports = exports['default'];