ruleset.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749
  1. "use strict";
  2. Object.defineProperty(exports, "__esModule", { value: true });
  3. var tslib_1 = require("tslib");
  4. var node_1 = tslib_1.__importDefault(require("./node"));
  5. var declaration_1 = tslib_1.__importDefault(require("./declaration"));
  6. var keyword_1 = tslib_1.__importDefault(require("./keyword"));
  7. var comment_1 = tslib_1.__importDefault(require("./comment"));
  8. var paren_1 = tslib_1.__importDefault(require("./paren"));
  9. var selector_1 = tslib_1.__importDefault(require("./selector"));
  10. var element_1 = tslib_1.__importDefault(require("./element"));
  11. var anonymous_1 = tslib_1.__importDefault(require("./anonymous"));
  12. var contexts_1 = tslib_1.__importDefault(require("../contexts"));
  13. var function_registry_1 = tslib_1.__importDefault(require("../functions/function-registry"));
  14. var default_1 = tslib_1.__importDefault(require("../functions/default"));
  15. var debug_info_1 = tslib_1.__importDefault(require("./debug-info"));
  16. var utils = tslib_1.__importStar(require("../utils"));
  17. var Ruleset = function (selectors, rules, strictImports, visibilityInfo) {
  18. this.selectors = selectors;
  19. this.rules = rules;
  20. this._lookups = {};
  21. this._variables = null;
  22. this._properties = null;
  23. this.strictImports = strictImports;
  24. this.copyVisibilityInfo(visibilityInfo);
  25. this.allowRoot = true;
  26. this.setParent(this.selectors, this);
  27. this.setParent(this.rules, this);
  28. };
  29. Ruleset.prototype = Object.assign(new node_1.default(), {
  30. type: 'Ruleset',
  31. isRuleset: true,
  32. isRulesetLike: function () { return true; },
  33. accept: function (visitor) {
  34. if (this.paths) {
  35. this.paths = visitor.visitArray(this.paths, true);
  36. }
  37. else if (this.selectors) {
  38. this.selectors = visitor.visitArray(this.selectors);
  39. }
  40. if (this.rules && this.rules.length) {
  41. this.rules = visitor.visitArray(this.rules);
  42. }
  43. },
  44. eval: function (context) {
  45. var that = this;
  46. var selectors;
  47. var selCnt;
  48. var selector;
  49. var i;
  50. var hasVariable;
  51. var hasOnePassingSelector = false;
  52. if (this.selectors && (selCnt = this.selectors.length)) {
  53. selectors = new Array(selCnt);
  54. default_1.default.error({
  55. type: 'Syntax',
  56. message: 'it is currently only allowed in parametric mixin guards,'
  57. });
  58. for (i = 0; i < selCnt; i++) {
  59. selector = this.selectors[i].eval(context);
  60. for (var j = 0; j < selector.elements.length; j++) {
  61. if (selector.elements[j].isVariable) {
  62. hasVariable = true;
  63. break;
  64. }
  65. }
  66. selectors[i] = selector;
  67. if (selector.evaldCondition) {
  68. hasOnePassingSelector = true;
  69. }
  70. }
  71. if (hasVariable) {
  72. var toParseSelectors = new Array(selCnt);
  73. for (i = 0; i < selCnt; i++) {
  74. selector = selectors[i];
  75. toParseSelectors[i] = selector.toCSS(context);
  76. }
  77. this.parse.parseNode(toParseSelectors.join(','), ["selectors"], selectors[0].getIndex(), selectors[0].fileInfo(), function (err, result) {
  78. if (result) {
  79. selectors = utils.flattenArray(result);
  80. }
  81. });
  82. }
  83. default_1.default.reset();
  84. }
  85. else {
  86. hasOnePassingSelector = true;
  87. }
  88. var rules = this.rules ? utils.copyArray(this.rules) : null;
  89. var ruleset = new Ruleset(selectors, rules, this.strictImports, this.visibilityInfo());
  90. var rule;
  91. var subRule;
  92. ruleset.originalRuleset = this;
  93. ruleset.root = this.root;
  94. ruleset.firstRoot = this.firstRoot;
  95. ruleset.allowImports = this.allowImports;
  96. if (this.debugInfo) {
  97. ruleset.debugInfo = this.debugInfo;
  98. }
  99. if (!hasOnePassingSelector) {
  100. rules.length = 0;
  101. }
  102. // inherit a function registry from the frames stack when possible;
  103. // otherwise from the global registry
  104. ruleset.functionRegistry = (function (frames) {
  105. var i = 0;
  106. var n = frames.length;
  107. var found;
  108. for (; i !== n; ++i) {
  109. found = frames[i].functionRegistry;
  110. if (found) {
  111. return found;
  112. }
  113. }
  114. return function_registry_1.default;
  115. }(context.frames)).inherit();
  116. // push the current ruleset to the frames stack
  117. var ctxFrames = context.frames;
  118. ctxFrames.unshift(ruleset);
  119. // currrent selectors
  120. var ctxSelectors = context.selectors;
  121. if (!ctxSelectors) {
  122. context.selectors = ctxSelectors = [];
  123. }
  124. ctxSelectors.unshift(this.selectors);
  125. // Evaluate imports
  126. if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) {
  127. ruleset.evalImports(context);
  128. }
  129. // Store the frames around mixin definitions,
  130. // so they can be evaluated like closures when the time comes.
  131. var rsRules = ruleset.rules;
  132. for (i = 0; (rule = rsRules[i]); i++) {
  133. if (rule.evalFirst) {
  134. rsRules[i] = rule.eval(context);
  135. }
  136. }
  137. var mediaBlockCount = (context.mediaBlocks && context.mediaBlocks.length) || 0;
  138. // Evaluate mixin calls.
  139. for (i = 0; (rule = rsRules[i]); i++) {
  140. if (rule.type === 'MixinCall') {
  141. /* jshint loopfunc:true */
  142. rules = rule.eval(context).filter(function (r) {
  143. if ((r instanceof declaration_1.default) && r.variable) {
  144. // do not pollute the scope if the variable is
  145. // already there. consider returning false here
  146. // but we need a way to "return" variable from mixins
  147. return !(ruleset.variable(r.name));
  148. }
  149. return true;
  150. });
  151. rsRules.splice.apply(rsRules, [i, 1].concat(rules));
  152. i += rules.length - 1;
  153. ruleset.resetCache();
  154. }
  155. else if (rule.type === 'VariableCall') {
  156. /* jshint loopfunc:true */
  157. rules = rule.eval(context).rules.filter(function (r) {
  158. if ((r instanceof declaration_1.default) && r.variable) {
  159. // do not pollute the scope at all
  160. return false;
  161. }
  162. return true;
  163. });
  164. rsRules.splice.apply(rsRules, [i, 1].concat(rules));
  165. i += rules.length - 1;
  166. ruleset.resetCache();
  167. }
  168. }
  169. // Evaluate everything else
  170. for (i = 0; (rule = rsRules[i]); i++) {
  171. if (!rule.evalFirst) {
  172. rsRules[i] = rule = rule.eval ? rule.eval(context) : rule;
  173. }
  174. }
  175. // Evaluate everything else
  176. for (i = 0; (rule = rsRules[i]); i++) {
  177. // for rulesets, check if it is a css guard and can be removed
  178. if (rule instanceof Ruleset && rule.selectors && rule.selectors.length === 1) {
  179. // check if it can be folded in (e.g. & where)
  180. if (rule.selectors[0] && rule.selectors[0].isJustParentSelector()) {
  181. rsRules.splice(i--, 1);
  182. for (var j = 0; (subRule = rule.rules[j]); j++) {
  183. if (subRule instanceof node_1.default) {
  184. subRule.copyVisibilityInfo(rule.visibilityInfo());
  185. if (!(subRule instanceof declaration_1.default) || !subRule.variable) {
  186. rsRules.splice(++i, 0, subRule);
  187. }
  188. }
  189. }
  190. }
  191. }
  192. }
  193. // Pop the stack
  194. ctxFrames.shift();
  195. ctxSelectors.shift();
  196. if (context.mediaBlocks) {
  197. for (i = mediaBlockCount; i < context.mediaBlocks.length; i++) {
  198. context.mediaBlocks[i].bubbleSelectors(selectors);
  199. }
  200. }
  201. return ruleset;
  202. },
  203. evalImports: function (context) {
  204. var rules = this.rules;
  205. var i;
  206. var importRules;
  207. if (!rules) {
  208. return;
  209. }
  210. for (i = 0; i < rules.length; i++) {
  211. if (rules[i].type === 'Import') {
  212. importRules = rules[i].eval(context);
  213. if (importRules && (importRules.length || importRules.length === 0)) {
  214. rules.splice.apply(rules, [i, 1].concat(importRules));
  215. i += importRules.length - 1;
  216. }
  217. else {
  218. rules.splice(i, 1, importRules);
  219. }
  220. this.resetCache();
  221. }
  222. }
  223. },
  224. makeImportant: function () {
  225. var result = new Ruleset(this.selectors, this.rules.map(function (r) {
  226. if (r.makeImportant) {
  227. return r.makeImportant();
  228. }
  229. else {
  230. return r;
  231. }
  232. }), this.strictImports, this.visibilityInfo());
  233. return result;
  234. },
  235. matchArgs: function (args) {
  236. return !args || args.length === 0;
  237. },
  238. // lets you call a css selector with a guard
  239. matchCondition: function (args, context) {
  240. var lastSelector = this.selectors[this.selectors.length - 1];
  241. if (!lastSelector.evaldCondition) {
  242. return false;
  243. }
  244. if (lastSelector.condition &&
  245. !lastSelector.condition.eval(new contexts_1.default.Eval(context, context.frames))) {
  246. return false;
  247. }
  248. return true;
  249. },
  250. resetCache: function () {
  251. this._rulesets = null;
  252. this._variables = null;
  253. this._properties = null;
  254. this._lookups = {};
  255. },
  256. variables: function () {
  257. if (!this._variables) {
  258. this._variables = !this.rules ? {} : this.rules.reduce(function (hash, r) {
  259. if (r instanceof declaration_1.default && r.variable === true) {
  260. hash[r.name] = r;
  261. }
  262. // when evaluating variables in an import statement, imports have not been eval'd
  263. // so we need to go inside import statements.
  264. // guard against root being a string (in the case of inlined less)
  265. if (r.type === 'Import' && r.root && r.root.variables) {
  266. var vars = r.root.variables();
  267. for (var name_1 in vars) {
  268. if (vars.hasOwnProperty(name_1)) {
  269. hash[name_1] = r.root.variable(name_1);
  270. }
  271. }
  272. }
  273. return hash;
  274. }, {});
  275. }
  276. return this._variables;
  277. },
  278. properties: function () {
  279. if (!this._properties) {
  280. this._properties = !this.rules ? {} : this.rules.reduce(function (hash, r) {
  281. if (r instanceof declaration_1.default && r.variable !== true) {
  282. var name_2 = (r.name.length === 1) && (r.name[0] instanceof keyword_1.default) ?
  283. r.name[0].value : r.name;
  284. // Properties don't overwrite as they can merge
  285. if (!hash["$" + name_2]) {
  286. hash["$" + name_2] = [r];
  287. }
  288. else {
  289. hash["$" + name_2].push(r);
  290. }
  291. }
  292. return hash;
  293. }, {});
  294. }
  295. return this._properties;
  296. },
  297. variable: function (name) {
  298. var decl = this.variables()[name];
  299. if (decl) {
  300. return this.parseValue(decl);
  301. }
  302. },
  303. property: function (name) {
  304. var decl = this.properties()[name];
  305. if (decl) {
  306. return this.parseValue(decl);
  307. }
  308. },
  309. lastDeclaration: function () {
  310. for (var i = this.rules.length; i > 0; i--) {
  311. var decl = this.rules[i - 1];
  312. if (decl instanceof declaration_1.default) {
  313. return this.parseValue(decl);
  314. }
  315. }
  316. },
  317. parseValue: function (toParse) {
  318. var self = this;
  319. function transformDeclaration(decl) {
  320. if (decl.value instanceof anonymous_1.default && !decl.parsed) {
  321. if (typeof decl.value.value === 'string') {
  322. this.parse.parseNode(decl.value.value, ['value', 'important'], decl.value.getIndex(), decl.fileInfo(), function (err, result) {
  323. if (err) {
  324. decl.parsed = true;
  325. }
  326. if (result) {
  327. decl.value = result[0];
  328. decl.important = result[1] || '';
  329. decl.parsed = true;
  330. }
  331. });
  332. }
  333. else {
  334. decl.parsed = true;
  335. }
  336. return decl;
  337. }
  338. else {
  339. return decl;
  340. }
  341. }
  342. if (!Array.isArray(toParse)) {
  343. return transformDeclaration.call(self, toParse);
  344. }
  345. else {
  346. var nodes_1 = [];
  347. toParse.forEach(function (n) {
  348. nodes_1.push(transformDeclaration.call(self, n));
  349. });
  350. return nodes_1;
  351. }
  352. },
  353. rulesets: function () {
  354. if (!this.rules) {
  355. return [];
  356. }
  357. var filtRules = [];
  358. var rules = this.rules;
  359. var i;
  360. var rule;
  361. for (i = 0; (rule = rules[i]); i++) {
  362. if (rule.isRuleset) {
  363. filtRules.push(rule);
  364. }
  365. }
  366. return filtRules;
  367. },
  368. prependRule: function (rule) {
  369. var rules = this.rules;
  370. if (rules) {
  371. rules.unshift(rule);
  372. }
  373. else {
  374. this.rules = [rule];
  375. }
  376. this.setParent(rule, this);
  377. },
  378. find: function (selector, self, filter) {
  379. self = self || this;
  380. var rules = [];
  381. var match;
  382. var foundMixins;
  383. var key = selector.toCSS();
  384. if (key in this._lookups) {
  385. return this._lookups[key];
  386. }
  387. this.rulesets().forEach(function (rule) {
  388. if (rule !== self) {
  389. for (var j = 0; j < rule.selectors.length; j++) {
  390. match = selector.match(rule.selectors[j]);
  391. if (match) {
  392. if (selector.elements.length > match) {
  393. if (!filter || filter(rule)) {
  394. foundMixins = rule.find(new selector_1.default(selector.elements.slice(match)), self, filter);
  395. for (var i = 0; i < foundMixins.length; ++i) {
  396. foundMixins[i].path.push(rule);
  397. }
  398. Array.prototype.push.apply(rules, foundMixins);
  399. }
  400. }
  401. else {
  402. rules.push({ rule: rule, path: [] });
  403. }
  404. break;
  405. }
  406. }
  407. }
  408. });
  409. this._lookups[key] = rules;
  410. return rules;
  411. },
  412. genCSS: function (context, output) {
  413. var i;
  414. var j;
  415. var charsetRuleNodes = [];
  416. var ruleNodes = [];
  417. var // Line number debugging
  418. debugInfo;
  419. var rule;
  420. var path;
  421. context.tabLevel = (context.tabLevel || 0);
  422. if (!this.root) {
  423. context.tabLevel++;
  424. }
  425. var tabRuleStr = context.compress ? '' : Array(context.tabLevel + 1).join(' ');
  426. var tabSetStr = context.compress ? '' : Array(context.tabLevel).join(' ');
  427. var sep;
  428. var charsetNodeIndex = 0;
  429. var importNodeIndex = 0;
  430. for (i = 0; (rule = this.rules[i]); i++) {
  431. if (rule instanceof comment_1.default) {
  432. if (importNodeIndex === i) {
  433. importNodeIndex++;
  434. }
  435. ruleNodes.push(rule);
  436. }
  437. else if (rule.isCharset && rule.isCharset()) {
  438. ruleNodes.splice(charsetNodeIndex, 0, rule);
  439. charsetNodeIndex++;
  440. importNodeIndex++;
  441. }
  442. else if (rule.type === 'Import') {
  443. ruleNodes.splice(importNodeIndex, 0, rule);
  444. importNodeIndex++;
  445. }
  446. else {
  447. ruleNodes.push(rule);
  448. }
  449. }
  450. ruleNodes = charsetRuleNodes.concat(ruleNodes);
  451. // If this is the root node, we don't render
  452. // a selector, or {}.
  453. if (!this.root) {
  454. debugInfo = debug_info_1.default(context, this, tabSetStr);
  455. if (debugInfo) {
  456. output.add(debugInfo);
  457. output.add(tabSetStr);
  458. }
  459. var paths = this.paths;
  460. var pathCnt = paths.length;
  461. var pathSubCnt = void 0;
  462. sep = context.compress ? ',' : (",\n" + tabSetStr);
  463. for (i = 0; i < pathCnt; i++) {
  464. path = paths[i];
  465. if (!(pathSubCnt = path.length)) {
  466. continue;
  467. }
  468. if (i > 0) {
  469. output.add(sep);
  470. }
  471. context.firstSelector = true;
  472. path[0].genCSS(context, output);
  473. context.firstSelector = false;
  474. for (j = 1; j < pathSubCnt; j++) {
  475. path[j].genCSS(context, output);
  476. }
  477. }
  478. output.add((context.compress ? '{' : ' {\n') + tabRuleStr);
  479. }
  480. // Compile rules and rulesets
  481. for (i = 0; (rule = ruleNodes[i]); i++) {
  482. if (i + 1 === ruleNodes.length) {
  483. context.lastRule = true;
  484. }
  485. var currentLastRule = context.lastRule;
  486. if (rule.isRulesetLike(rule)) {
  487. context.lastRule = false;
  488. }
  489. if (rule.genCSS) {
  490. rule.genCSS(context, output);
  491. }
  492. else if (rule.value) {
  493. output.add(rule.value.toString());
  494. }
  495. context.lastRule = currentLastRule;
  496. if (!context.lastRule && rule.isVisible()) {
  497. output.add(context.compress ? '' : ("\n" + tabRuleStr));
  498. }
  499. else {
  500. context.lastRule = false;
  501. }
  502. }
  503. if (!this.root) {
  504. output.add((context.compress ? '}' : "\n" + tabSetStr + "}"));
  505. context.tabLevel--;
  506. }
  507. if (!output.isEmpty() && !context.compress && this.firstRoot) {
  508. output.add('\n');
  509. }
  510. },
  511. joinSelectors: function (paths, context, selectors) {
  512. for (var s = 0; s < selectors.length; s++) {
  513. this.joinSelector(paths, context, selectors[s]);
  514. }
  515. },
  516. joinSelector: function (paths, context, selector) {
  517. function createParenthesis(elementsToPak, originalElement) {
  518. var replacementParen, j;
  519. if (elementsToPak.length === 0) {
  520. replacementParen = new paren_1.default(elementsToPak[0]);
  521. }
  522. else {
  523. var insideParent = new Array(elementsToPak.length);
  524. for (j = 0; j < elementsToPak.length; j++) {
  525. insideParent[j] = new element_1.default(null, elementsToPak[j], originalElement.isVariable, originalElement._index, originalElement._fileInfo);
  526. }
  527. replacementParen = new paren_1.default(new selector_1.default(insideParent));
  528. }
  529. return replacementParen;
  530. }
  531. function createSelector(containedElement, originalElement) {
  532. var element, selector;
  533. element = new element_1.default(null, containedElement, originalElement.isVariable, originalElement._index, originalElement._fileInfo);
  534. selector = new selector_1.default([element]);
  535. return selector;
  536. }
  537. // joins selector path from `beginningPath` with selector path in `addPath`
  538. // `replacedElement` contains element that is being replaced by `addPath`
  539. // returns concatenated path
  540. function addReplacementIntoPath(beginningPath, addPath, replacedElement, originalSelector) {
  541. var newSelectorPath, lastSelector, newJoinedSelector;
  542. // our new selector path
  543. newSelectorPath = [];
  544. // construct the joined selector - if & is the first thing this will be empty,
  545. // if not newJoinedSelector will be the last set of elements in the selector
  546. if (beginningPath.length > 0) {
  547. newSelectorPath = utils.copyArray(beginningPath);
  548. lastSelector = newSelectorPath.pop();
  549. newJoinedSelector = originalSelector.createDerived(utils.copyArray(lastSelector.elements));
  550. }
  551. else {
  552. newJoinedSelector = originalSelector.createDerived([]);
  553. }
  554. if (addPath.length > 0) {
  555. // /deep/ is a CSS4 selector - (removed, so should deprecate)
  556. // that is valid without anything in front of it
  557. // so if the & does not have a combinator that is "" or " " then
  558. // and there is a combinator on the parent, then grab that.
  559. // this also allows + a { & .b { .a & { ... though not sure why you would want to do that
  560. var combinator = replacedElement.combinator;
  561. var parentEl = addPath[0].elements[0];
  562. if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) {
  563. combinator = parentEl.combinator;
  564. }
  565. // join the elements so far with the first part of the parent
  566. newJoinedSelector.elements.push(new element_1.default(combinator, parentEl.value, replacedElement.isVariable, replacedElement._index, replacedElement._fileInfo));
  567. newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1));
  568. }
  569. // now add the joined selector - but only if it is not empty
  570. if (newJoinedSelector.elements.length !== 0) {
  571. newSelectorPath.push(newJoinedSelector);
  572. }
  573. // put together the parent selectors after the join (e.g. the rest of the parent)
  574. if (addPath.length > 1) {
  575. var restOfPath = addPath.slice(1);
  576. restOfPath = restOfPath.map(function (selector) {
  577. return selector.createDerived(selector.elements, []);
  578. });
  579. newSelectorPath = newSelectorPath.concat(restOfPath);
  580. }
  581. return newSelectorPath;
  582. }
  583. // joins selector path from `beginningPath` with every selector path in `addPaths` array
  584. // `replacedElement` contains element that is being replaced by `addPath`
  585. // returns array with all concatenated paths
  586. function addAllReplacementsIntoPath(beginningPath, addPaths, replacedElement, originalSelector, result) {
  587. var j;
  588. for (j = 0; j < beginningPath.length; j++) {
  589. var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector);
  590. result.push(newSelectorPath);
  591. }
  592. return result;
  593. }
  594. function mergeElementsOnToSelectors(elements, selectors) {
  595. var i, sel;
  596. if (elements.length === 0) {
  597. return;
  598. }
  599. if (selectors.length === 0) {
  600. selectors.push([new selector_1.default(elements)]);
  601. return;
  602. }
  603. for (i = 0; (sel = selectors[i]); i++) {
  604. // if the previous thing in sel is a parent this needs to join on to it
  605. if (sel.length > 0) {
  606. sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements));
  607. }
  608. else {
  609. sel.push(new selector_1.default(elements));
  610. }
  611. }
  612. }
  613. // replace all parent selectors inside `inSelector` by content of `context` array
  614. // resulting selectors are returned inside `paths` array
  615. // returns true if `inSelector` contained at least one parent selector
  616. function replaceParentSelector(paths, context, inSelector) {
  617. // The paths are [[Selector]]
  618. // The first list is a list of comma separated selectors
  619. // The inner list is a list of inheritance separated selectors
  620. // e.g.
  621. // .a, .b {
  622. // .c {
  623. // }
  624. // }
  625. // == [[.a] [.c]] [[.b] [.c]]
  626. //
  627. var i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false, length, lastSelector;
  628. function findNestedSelector(element) {
  629. var maybeSelector;
  630. if (!(element.value instanceof paren_1.default)) {
  631. return null;
  632. }
  633. maybeSelector = element.value.value;
  634. if (!(maybeSelector instanceof selector_1.default)) {
  635. return null;
  636. }
  637. return maybeSelector;
  638. }
  639. // the elements from the current selector so far
  640. currentElements = [];
  641. // the current list of new selectors to add to the path.
  642. // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors
  643. // by the parents
  644. newSelectors = [
  645. []
  646. ];
  647. for (i = 0; (el = inSelector.elements[i]); i++) {
  648. // non parent reference elements just get added
  649. if (el.value !== '&') {
  650. var nestedSelector = findNestedSelector(el);
  651. if (nestedSelector != null) {
  652. // merge the current list of non parent selector elements
  653. // on to the current list of selectors to add
  654. mergeElementsOnToSelectors(currentElements, newSelectors);
  655. var nestedPaths = [];
  656. var replaced = void 0;
  657. var replacedNewSelectors = [];
  658. replaced = replaceParentSelector(nestedPaths, context, nestedSelector);
  659. hadParentSelector = hadParentSelector || replaced;
  660. // the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors
  661. for (k = 0; k < nestedPaths.length; k++) {
  662. var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el);
  663. addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors);
  664. }
  665. newSelectors = replacedNewSelectors;
  666. currentElements = [];
  667. }
  668. else {
  669. currentElements.push(el);
  670. }
  671. }
  672. else {
  673. hadParentSelector = true;
  674. // the new list of selectors to add
  675. selectorsMultiplied = [];
  676. // merge the current list of non parent selector elements
  677. // on to the current list of selectors to add
  678. mergeElementsOnToSelectors(currentElements, newSelectors);
  679. // loop through our current selectors
  680. for (j = 0; j < newSelectors.length; j++) {
  681. sel = newSelectors[j];
  682. // if we don't have any parent paths, the & might be in a mixin so that it can be used
  683. // whether there are parents or not
  684. if (context.length === 0) {
  685. // the combinator used on el should now be applied to the next element instead so that
  686. // it is not lost
  687. if (sel.length > 0) {
  688. sel[0].elements.push(new element_1.default(el.combinator, '', el.isVariable, el._index, el._fileInfo));
  689. }
  690. selectorsMultiplied.push(sel);
  691. }
  692. else {
  693. // and the parent selectors
  694. for (k = 0; k < context.length; k++) {
  695. // We need to put the current selectors
  696. // then join the last selector's elements on to the parents selectors
  697. var newSelectorPath = addReplacementIntoPath(sel, context[k], el, inSelector);
  698. // add that to our new set of selectors
  699. selectorsMultiplied.push(newSelectorPath);
  700. }
  701. }
  702. }
  703. // our new selectors has been multiplied, so reset the state
  704. newSelectors = selectorsMultiplied;
  705. currentElements = [];
  706. }
  707. }
  708. // if we have any elements left over (e.g. .a& .b == .b)
  709. // add them on to all the current selectors
  710. mergeElementsOnToSelectors(currentElements, newSelectors);
  711. for (i = 0; i < newSelectors.length; i++) {
  712. length = newSelectors[i].length;
  713. if (length > 0) {
  714. paths.push(newSelectors[i]);
  715. lastSelector = newSelectors[i][length - 1];
  716. newSelectors[i][length - 1] = lastSelector.createDerived(lastSelector.elements, inSelector.extendList);
  717. }
  718. }
  719. return hadParentSelector;
  720. }
  721. function deriveSelector(visibilityInfo, deriveFrom) {
  722. var newSelector = deriveFrom.createDerived(deriveFrom.elements, deriveFrom.extendList, deriveFrom.evaldCondition);
  723. newSelector.copyVisibilityInfo(visibilityInfo);
  724. return newSelector;
  725. }
  726. // joinSelector code follows
  727. var i, newPaths, hadParentSelector;
  728. newPaths = [];
  729. hadParentSelector = replaceParentSelector(newPaths, context, selector);
  730. if (!hadParentSelector) {
  731. if (context.length > 0) {
  732. newPaths = [];
  733. for (i = 0; i < context.length; i++) {
  734. var concatenated = context[i].map(deriveSelector.bind(this, selector.visibilityInfo()));
  735. concatenated.push(selector);
  736. newPaths.push(concatenated);
  737. }
  738. }
  739. else {
  740. newPaths = [[selector]];
  741. }
  742. }
  743. for (i = 0; i < newPaths.length; i++) {
  744. paths.push(newPaths[i]);
  745. }
  746. }
  747. });
  748. exports.default = Ruleset;
  749. //# sourceMappingURL=ruleset.js.map