UNPKG

less

Version:
752 lines 32.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var tslib_1 = require("tslib"); var node_1 = tslib_1.__importDefault(require("./node")); var declaration_1 = tslib_1.__importDefault(require("./declaration")); var keyword_1 = tslib_1.__importDefault(require("./keyword")); var comment_1 = tslib_1.__importDefault(require("./comment")); var paren_1 = tslib_1.__importDefault(require("./paren")); var selector_1 = tslib_1.__importDefault(require("./selector")); var element_1 = tslib_1.__importDefault(require("./element")); var anonymous_1 = tslib_1.__importDefault(require("./anonymous")); var contexts_1 = tslib_1.__importDefault(require("../contexts")); var function_registry_1 = tslib_1.__importDefault(require("../functions/function-registry")); var default_1 = tslib_1.__importDefault(require("../functions/default")); var debug_info_1 = tslib_1.__importDefault(require("./debug-info")); var utils = tslib_1.__importStar(require("../utils")); var parser_1 = tslib_1.__importDefault(require("../parser/parser")); var Ruleset = function (selectors, rules, strictImports, visibilityInfo) { this.selectors = selectors; this.rules = rules; this._lookups = {}; this._variables = null; this._properties = null; this.strictImports = strictImports; this.copyVisibilityInfo(visibilityInfo); this.allowRoot = true; this.setParent(this.selectors, this); this.setParent(this.rules, this); }; Ruleset.prototype = Object.assign(new node_1.default(), { type: 'Ruleset', isRuleset: true, isRulesetLike: function () { return true; }, accept: function (visitor) { if (this.paths) { this.paths = visitor.visitArray(this.paths, true); } else if (this.selectors) { this.selectors = visitor.visitArray(this.selectors); } if (this.rules && this.rules.length) { this.rules = visitor.visitArray(this.rules); } }, eval: function (context) { var selectors; var selCnt; var selector; var i; var hasVariable; var hasOnePassingSelector = false; if (this.selectors && (selCnt = this.selectors.length)) { selectors = new Array(selCnt); default_1.default.error({ type: 'Syntax', message: 'it is currently only allowed in parametric mixin guards,' }); for (i = 0; i < selCnt; i++) { selector = this.selectors[i].eval(context); for (var j = 0; j < selector.elements.length; j++) { if (selector.elements[j].isVariable) { hasVariable = true; break; } } selectors[i] = selector; if (selector.evaldCondition) { hasOnePassingSelector = true; } } if (hasVariable) { var toParseSelectors = new Array(selCnt); for (i = 0; i < selCnt; i++) { selector = selectors[i]; toParseSelectors[i] = selector.toCSS(context); } var startingIndex = selectors[0].getIndex(); var selectorFileInfo = selectors[0].fileInfo(); new parser_1.default(context, this.parse.importManager, selectorFileInfo, startingIndex).parseNode(toParseSelectors.join(','), ['selectors'], function (err, result) { if (result) { selectors = utils.flattenArray(result); } }); } default_1.default.reset(); } else { hasOnePassingSelector = true; } var rules = this.rules ? utils.copyArray(this.rules) : null; var ruleset = new Ruleset(selectors, rules, this.strictImports, this.visibilityInfo()); var rule; var subRule; ruleset.originalRuleset = this; ruleset.root = this.root; ruleset.firstRoot = this.firstRoot; ruleset.allowImports = this.allowImports; if (this.debugInfo) { ruleset.debugInfo = this.debugInfo; } if (!hasOnePassingSelector) { rules.length = 0; } // inherit a function registry from the frames stack when possible; // otherwise from the global registry ruleset.functionRegistry = (function (frames) { var i = 0; var n = frames.length; var found; for (; i !== n; ++i) { found = frames[i].functionRegistry; if (found) { return found; } } return function_registry_1.default; }(context.frames)).inherit(); // push the current ruleset to the frames stack var ctxFrames = context.frames; ctxFrames.unshift(ruleset); // currrent selectors var ctxSelectors = context.selectors; if (!ctxSelectors) { context.selectors = ctxSelectors = []; } ctxSelectors.unshift(this.selectors); // Evaluate imports if (ruleset.root || ruleset.allowImports || !ruleset.strictImports) { ruleset.evalImports(context); } // Store the frames around mixin definitions, // so they can be evaluated like closures when the time comes. var rsRules = ruleset.rules; for (i = 0; (rule = rsRules[i]); i++) { if (rule.evalFirst) { rsRules[i] = rule.eval(context); } } var mediaBlockCount = (context.mediaBlocks && context.mediaBlocks.length) || 0; // Evaluate mixin calls. for (i = 0; (rule = rsRules[i]); i++) { if (rule.type === 'MixinCall') { /* jshint loopfunc:true */ rules = rule.eval(context).filter(function (r) { if ((r instanceof declaration_1.default) && r.variable) { // do not pollute the scope if the variable is // already there. consider returning false here // but we need a way to "return" variable from mixins return !(ruleset.variable(r.name)); } return true; }); rsRules.splice.apply(rsRules, [i, 1].concat(rules)); i += rules.length - 1; ruleset.resetCache(); } else if (rule.type === 'VariableCall') { /* jshint loopfunc:true */ rules = rule.eval(context).rules.filter(function (r) { if ((r instanceof declaration_1.default) && r.variable) { // do not pollute the scope at all return false; } return true; }); rsRules.splice.apply(rsRules, [i, 1].concat(rules)); i += rules.length - 1; ruleset.resetCache(); } } // Evaluate everything else for (i = 0; (rule = rsRules[i]); i++) { if (!rule.evalFirst) { rsRules[i] = rule = rule.eval ? rule.eval(context) : rule; } } // Evaluate everything else for (i = 0; (rule = rsRules[i]); i++) { // for rulesets, check if it is a css guard and can be removed if (rule instanceof Ruleset && rule.selectors && rule.selectors.length === 1) { // check if it can be folded in (e.g. & where) if (rule.selectors[0] && rule.selectors[0].isJustParentSelector()) { rsRules.splice(i--, 1); for (var j = 0; (subRule = rule.rules[j]); j++) { if (subRule instanceof node_1.default) { subRule.copyVisibilityInfo(rule.visibilityInfo()); if (!(subRule instanceof declaration_1.default) || !subRule.variable) { rsRules.splice(++i, 0, subRule); } } } } } } // Pop the stack ctxFrames.shift(); ctxSelectors.shift(); if (context.mediaBlocks) { for (i = mediaBlockCount; i < context.mediaBlocks.length; i++) { context.mediaBlocks[i].bubbleSelectors(selectors); } } return ruleset; }, evalImports: function (context) { var rules = this.rules; var i; var importRules; if (!rules) { return; } for (i = 0; i < rules.length; i++) { if (rules[i].type === 'Import') { importRules = rules[i].eval(context); if (importRules && (importRules.length || importRules.length === 0)) { rules.splice.apply(rules, [i, 1].concat(importRules)); i += importRules.length - 1; } else { rules.splice(i, 1, importRules); } this.resetCache(); } } }, makeImportant: function () { var result = new Ruleset(this.selectors, this.rules.map(function (r) { if (r.makeImportant) { return r.makeImportant(); } else { return r; } }), this.strictImports, this.visibilityInfo()); return result; }, matchArgs: function (args) { return !args || args.length === 0; }, // lets you call a css selector with a guard matchCondition: function (args, context) { var lastSelector = this.selectors[this.selectors.length - 1]; if (!lastSelector.evaldCondition) { return false; } if (lastSelector.condition && !lastSelector.condition.eval(new contexts_1.default.Eval(context, context.frames))) { return false; } return true; }, resetCache: function () { this._rulesets = null; this._variables = null; this._properties = null; this._lookups = {}; }, variables: function () { if (!this._variables) { this._variables = !this.rules ? {} : this.rules.reduce(function (hash, r) { if (r instanceof declaration_1.default && r.variable === true) { hash[r.name] = r; } // when evaluating variables in an import statement, imports have not been eval'd // so we need to go inside import statements. // guard against root being a string (in the case of inlined less) if (r.type === 'Import' && r.root && r.root.variables) { var vars = r.root.variables(); for (var name_1 in vars) { // eslint-disable-next-line no-prototype-builtins if (vars.hasOwnProperty(name_1)) { hash[name_1] = r.root.variable(name_1); } } } return hash; }, {}); } return this._variables; }, properties: function () { if (!this._properties) { this._properties = !this.rules ? {} : this.rules.reduce(function (hash, r) { if (r instanceof declaration_1.default && r.variable !== true) { var name_2 = (r.name.length === 1) && (r.name[0] instanceof keyword_1.default) ? r.name[0].value : r.name; // Properties don't overwrite as they can merge if (!hash["$".concat(name_2)]) { hash["$".concat(name_2)] = [r]; } else { hash["$".concat(name_2)].push(r); } } return hash; }, {}); } return this._properties; }, variable: function (name) { var decl = this.variables()[name]; if (decl) { return this.parseValue(decl); } }, property: function (name) { var decl = this.properties()[name]; if (decl) { return this.parseValue(decl); } }, lastDeclaration: function () { for (var i = this.rules.length; i > 0; i--) { var decl = this.rules[i - 1]; if (decl instanceof declaration_1.default) { return this.parseValue(decl); } } }, parseValue: function (toParse) { var self = this; function transformDeclaration(decl) { if (decl.value instanceof anonymous_1.default && !decl.parsed) { if (typeof decl.value.value === 'string') { new parser_1.default(this.parse.context, this.parse.importManager, decl.fileInfo(), decl.value.getIndex()).parseNode(decl.value.value, ['value', 'important'], function (err, result) { if (err) { decl.parsed = true; } if (result) { decl.value = result[0]; decl.important = result[1] || ''; decl.parsed = true; } }); } else { decl.parsed = true; } return decl; } else { return decl; } } if (!Array.isArray(toParse)) { return transformDeclaration.call(self, toParse); } else { var nodes_1 = []; toParse.forEach(function (n) { nodes_1.push(transformDeclaration.call(self, n)); }); return nodes_1; } }, rulesets: function () { if (!this.rules) { return []; } var filtRules = []; var rules = this.rules; var i; var rule; for (i = 0; (rule = rules[i]); i++) { if (rule.isRuleset) { filtRules.push(rule); } } return filtRules; }, prependRule: function (rule) { var rules = this.rules; if (rules) { rules.unshift(rule); } else { this.rules = [rule]; } this.setParent(rule, this); }, find: function (selector, self, filter) { self = self || this; var rules = []; var match; var foundMixins; var key = selector.toCSS(); if (key in this._lookups) { return this._lookups[key]; } this.rulesets().forEach(function (rule) { if (rule !== self) { for (var j = 0; j < rule.selectors.length; j++) { match = selector.match(rule.selectors[j]); if (match) { if (selector.elements.length > match) { if (!filter || filter(rule)) { foundMixins = rule.find(new selector_1.default(selector.elements.slice(match)), self, filter); for (var i = 0; i < foundMixins.length; ++i) { foundMixins[i].path.push(rule); } Array.prototype.push.apply(rules, foundMixins); } } else { rules.push({ rule: rule, path: [] }); } break; } } } }); this._lookups[key] = rules; return rules; }, genCSS: function (context, output) { var i; var j; var charsetRuleNodes = []; var ruleNodes = []; var // Line number debugging debugInfo; var rule; var path; context.tabLevel = (context.tabLevel || 0); if (!this.root) { context.tabLevel++; } var tabRuleStr = context.compress ? '' : Array(context.tabLevel + 1).join(' '); var tabSetStr = context.compress ? '' : Array(context.tabLevel).join(' '); var sep; var charsetNodeIndex = 0; var importNodeIndex = 0; for (i = 0; (rule = this.rules[i]); i++) { if (rule instanceof comment_1.default) { if (importNodeIndex === i) { importNodeIndex++; } ruleNodes.push(rule); } else if (rule.isCharset && rule.isCharset()) { ruleNodes.splice(charsetNodeIndex, 0, rule); charsetNodeIndex++; importNodeIndex++; } else if (rule.type === 'Import') { ruleNodes.splice(importNodeIndex, 0, rule); importNodeIndex++; } else { ruleNodes.push(rule); } } ruleNodes = charsetRuleNodes.concat(ruleNodes); // If this is the root node, we don't render // a selector, or {}. if (!this.root) { debugInfo = (0, debug_info_1.default)(context, this, tabSetStr); if (debugInfo) { output.add(debugInfo); output.add(tabSetStr); } var paths = this.paths; var pathCnt = paths.length; var pathSubCnt = void 0; sep = context.compress ? ',' : (",\n".concat(tabSetStr)); for (i = 0; i < pathCnt; i++) { path = paths[i]; if (!(pathSubCnt = path.length)) { continue; } if (i > 0) { output.add(sep); } context.firstSelector = true; path[0].genCSS(context, output); context.firstSelector = false; for (j = 1; j < pathSubCnt; j++) { path[j].genCSS(context, output); } } output.add((context.compress ? '{' : ' {\n') + tabRuleStr); } // Compile rules and rulesets for (i = 0; (rule = ruleNodes[i]); i++) { if (i + 1 === ruleNodes.length) { context.lastRule = true; } var currentLastRule = context.lastRule; if (rule.isRulesetLike(rule)) { context.lastRule = false; } if (rule.genCSS) { rule.genCSS(context, output); } else if (rule.value) { output.add(rule.value.toString()); } context.lastRule = currentLastRule; if (!context.lastRule && rule.isVisible()) { output.add(context.compress ? '' : ("\n".concat(tabRuleStr))); } else { context.lastRule = false; } } if (!this.root) { output.add((context.compress ? '}' : "\n".concat(tabSetStr, "}"))); context.tabLevel--; } if (!output.isEmpty() && !context.compress && this.firstRoot) { output.add('\n'); } }, joinSelectors: function (paths, context, selectors) { for (var s = 0; s < selectors.length; s++) { this.joinSelector(paths, context, selectors[s]); } }, joinSelector: function (paths, context, selector) { function createParenthesis(elementsToPak, originalElement) { var replacementParen, j; if (elementsToPak.length === 0) { replacementParen = new paren_1.default(elementsToPak[0]); } else { var insideParent = new Array(elementsToPak.length); for (j = 0; j < elementsToPak.length; j++) { insideParent[j] = new element_1.default(null, elementsToPak[j], originalElement.isVariable, originalElement._index, originalElement._fileInfo); } replacementParen = new paren_1.default(new selector_1.default(insideParent)); } return replacementParen; } function createSelector(containedElement, originalElement) { var element, selector; element = new element_1.default(null, containedElement, originalElement.isVariable, originalElement._index, originalElement._fileInfo); selector = new selector_1.default([element]); return selector; } // joins selector path from `beginningPath` with selector path in `addPath` // `replacedElement` contains element that is being replaced by `addPath` // returns concatenated path function addReplacementIntoPath(beginningPath, addPath, replacedElement, originalSelector) { var newSelectorPath, lastSelector, newJoinedSelector; // our new selector path newSelectorPath = []; // construct the joined selector - if & is the first thing this will be empty, // if not newJoinedSelector will be the last set of elements in the selector if (beginningPath.length > 0) { newSelectorPath = utils.copyArray(beginningPath); lastSelector = newSelectorPath.pop(); newJoinedSelector = originalSelector.createDerived(utils.copyArray(lastSelector.elements)); } else { newJoinedSelector = originalSelector.createDerived([]); } if (addPath.length > 0) { // /deep/ is a CSS4 selector - (removed, so should deprecate) // that is valid without anything in front of it // so if the & does not have a combinator that is "" or " " then // and there is a combinator on the parent, then grab that. // this also allows + a { & .b { .a & { ... though not sure why you would want to do that var combinator = replacedElement.combinator; var parentEl = addPath[0].elements[0]; if (combinator.emptyOrWhitespace && !parentEl.combinator.emptyOrWhitespace) { combinator = parentEl.combinator; } // join the elements so far with the first part of the parent newJoinedSelector.elements.push(new element_1.default(combinator, parentEl.value, replacedElement.isVariable, replacedElement._index, replacedElement._fileInfo)); newJoinedSelector.elements = newJoinedSelector.elements.concat(addPath[0].elements.slice(1)); } // now add the joined selector - but only if it is not empty if (newJoinedSelector.elements.length !== 0) { newSelectorPath.push(newJoinedSelector); } // put together the parent selectors after the join (e.g. the rest of the parent) if (addPath.length > 1) { var restOfPath = addPath.slice(1); restOfPath = restOfPath.map(function (selector) { return selector.createDerived(selector.elements, []); }); newSelectorPath = newSelectorPath.concat(restOfPath); } return newSelectorPath; } // joins selector path from `beginningPath` with every selector path in `addPaths` array // `replacedElement` contains element that is being replaced by `addPath` // returns array with all concatenated paths function addAllReplacementsIntoPath(beginningPath, addPaths, replacedElement, originalSelector, result) { var j; for (j = 0; j < beginningPath.length; j++) { var newSelectorPath = addReplacementIntoPath(beginningPath[j], addPaths, replacedElement, originalSelector); result.push(newSelectorPath); } return result; } function mergeElementsOnToSelectors(elements, selectors) { var i, sel; if (elements.length === 0) { return; } if (selectors.length === 0) { selectors.push([new selector_1.default(elements)]); return; } for (i = 0; (sel = selectors[i]); i++) { // if the previous thing in sel is a parent this needs to join on to it if (sel.length > 0) { sel[sel.length - 1] = sel[sel.length - 1].createDerived(sel[sel.length - 1].elements.concat(elements)); } else { sel.push(new selector_1.default(elements)); } } } // replace all parent selectors inside `inSelector` by content of `context` array // resulting selectors are returned inside `paths` array // returns true if `inSelector` contained at least one parent selector function replaceParentSelector(paths, context, inSelector) { // The paths are [[Selector]] // The first list is a list of comma separated selectors // The inner list is a list of inheritance separated selectors // e.g. // .a, .b { // .c { // } // } // == [[.a] [.c]] [[.b] [.c]] // var i, j, k, currentElements, newSelectors, selectorsMultiplied, sel, el, hadParentSelector = false, length, lastSelector; function findNestedSelector(element) { var maybeSelector; if (!(element.value instanceof paren_1.default)) { return null; } maybeSelector = element.value.value; if (!(maybeSelector instanceof selector_1.default)) { return null; } return maybeSelector; } // the elements from the current selector so far currentElements = []; // the current list of new selectors to add to the path. // We will build it up. We initiate it with one empty selector as we "multiply" the new selectors // by the parents newSelectors = [ [] ]; for (i = 0; (el = inSelector.elements[i]); i++) { // non parent reference elements just get added if (el.value !== '&') { var nestedSelector = findNestedSelector(el); if (nestedSelector !== null) { // merge the current list of non parent selector elements // on to the current list of selectors to add mergeElementsOnToSelectors(currentElements, newSelectors); var nestedPaths = []; var replaced = void 0; var replacedNewSelectors = []; replaced = replaceParentSelector(nestedPaths, context, nestedSelector); hadParentSelector = hadParentSelector || replaced; // the nestedPaths array should have only one member - replaceParentSelector does not multiply selectors for (k = 0; k < nestedPaths.length; k++) { var replacementSelector = createSelector(createParenthesis(nestedPaths[k], el), el); addAllReplacementsIntoPath(newSelectors, [replacementSelector], el, inSelector, replacedNewSelectors); } newSelectors = replacedNewSelectors; currentElements = []; } else { currentElements.push(el); } } else { hadParentSelector = true; // the new list of selectors to add selectorsMultiplied = []; // merge the current list of non parent selector elements // on to the current list of selectors to add mergeElementsOnToSelectors(currentElements, newSelectors); // loop through our current selectors for (j = 0; j < newSelectors.length; j++) { sel = newSelectors[j]; // if we don't have any parent paths, the & might be in a mixin so that it can be used // whether there are parents or not if (context.length === 0) { // the combinator used on el should now be applied to the next element instead so that // it is not lost if (sel.length > 0) { sel[0].elements.push(new element_1.default(el.combinator, '', el.isVariable, el._index, el._fileInfo)); } selectorsMultiplied.push(sel); } else { // and the parent selectors for (k = 0; k < context.length; k++) { // We need to put the current selectors // then join the last selector's elements on to the parents selectors var newSelectorPath = addReplacementIntoPath(sel, context[k], el, inSelector); // add that to our new set of selectors selectorsMultiplied.push(newSelectorPath); } } } // our new selectors has been multiplied, so reset the state newSelectors = selectorsMultiplied; currentElements = []; } } // if we have any elements left over (e.g. .a& .b == .b) // add them on to all the current selectors mergeElementsOnToSelectors(currentElements, newSelectors); for (i = 0; i < newSelectors.length; i++) { length = newSelectors[i].length; if (length > 0) { paths.push(newSelectors[i]); lastSelector = newSelectors[i][length - 1]; newSelectors[i][length - 1] = lastSelector.createDerived(lastSelector.elements, inSelector.extendList); } } return hadParentSelector; } function deriveSelector(visibilityInfo, deriveFrom) { var newSelector = deriveFrom.createDerived(deriveFrom.elements, deriveFrom.extendList, deriveFrom.evaldCondition); newSelector.copyVisibilityInfo(visibilityInfo); return newSelector; } // joinSelector code follows var i, newPaths, hadParentSelector; newPaths = []; hadParentSelector = replaceParentSelector(newPaths, context, selector); if (!hadParentSelector) { if (context.length > 0) { newPaths = []; for (i = 0; i < context.length; i++) { var concatenated = context[i].map(deriveSelector.bind(this, selector.visibilityInfo())); concatenated.push(selector); newPaths.push(concatenated); } } else { newPaths = [[selector]]; } } for (i = 0; i < newPaths.length; i++) { paths.push(newPaths[i]); } } }); exports.default = Ruleset; //# sourceMappingURL=ruleset.js.map