ruleset.js 31 KB

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