UNPKG

12.4 kBJavaScriptView Raw
1"use strict";
2Object.defineProperty(exports, "__esModule", { value: true });
3var tslib_1 = require("tslib");
4var tree_1 = tslib_1.__importDefault(require("../tree"));
5var visitor_1 = tslib_1.__importDefault(require("./visitor"));
6var CSSVisitorUtils = /** @class */ (function () {
7 function CSSVisitorUtils(context) {
8 this._visitor = new visitor_1.default(this);
9 this._context = context;
10 }
11 CSSVisitorUtils.prototype.containsSilentNonBlockedChild = function (bodyRules) {
12 var rule;
13 if (!bodyRules) {
14 return false;
15 }
16 for (var r = 0; r < bodyRules.length; r++) {
17 rule = bodyRules[r];
18 if (rule.isSilent && rule.isSilent(this._context) && !rule.blocksVisibility()) {
19 // the atrule contains something that was referenced (likely by extend)
20 // therefore it needs to be shown in output too
21 return true;
22 }
23 }
24 return false;
25 };
26 CSSVisitorUtils.prototype.keepOnlyVisibleChilds = function (owner) {
27 if (owner && owner.rules) {
28 owner.rules = owner.rules.filter(function (thing) { return thing.isVisible(); });
29 }
30 };
31 CSSVisitorUtils.prototype.isEmpty = function (owner) {
32 return (owner && owner.rules)
33 ? (owner.rules.length === 0) : true;
34 };
35 CSSVisitorUtils.prototype.hasVisibleSelector = function (rulesetNode) {
36 return (rulesetNode && rulesetNode.paths)
37 ? (rulesetNode.paths.length > 0) : false;
38 };
39 CSSVisitorUtils.prototype.resolveVisibility = function (node, originalRules) {
40 if (!node.blocksVisibility()) {
41 if (this.isEmpty(node) && !this.containsSilentNonBlockedChild(originalRules)) {
42 return;
43 }
44 return node;
45 }
46 var compiledRulesBody = node.rules[0];
47 this.keepOnlyVisibleChilds(compiledRulesBody);
48 if (this.isEmpty(compiledRulesBody)) {
49 return;
50 }
51 node.ensureVisibility();
52 node.removeVisibilityBlock();
53 return node;
54 };
55 CSSVisitorUtils.prototype.isVisibleRuleset = function (rulesetNode) {
56 if (rulesetNode.firstRoot) {
57 return true;
58 }
59 if (this.isEmpty(rulesetNode)) {
60 return false;
61 }
62 if (!rulesetNode.root && !this.hasVisibleSelector(rulesetNode)) {
63 return false;
64 }
65 return true;
66 };
67 return CSSVisitorUtils;
68}());
69var ToCSSVisitor = function (context) {
70 this._visitor = new visitor_1.default(this);
71 this._context = context;
72 this.utils = new CSSVisitorUtils(context);
73};
74ToCSSVisitor.prototype = {
75 isReplacing: true,
76 run: function (root) {
77 return this._visitor.visit(root);
78 },
79 visitDeclaration: function (declNode, visitArgs) {
80 if (declNode.blocksVisibility() || declNode.variable) {
81 return;
82 }
83 return declNode;
84 },
85 visitMixinDefinition: function (mixinNode, visitArgs) {
86 // mixin definitions do not get eval'd - this means they keep state
87 // so we have to clear that state here so it isn't used if toCSS is called twice
88 mixinNode.frames = [];
89 },
90 visitExtend: function (extendNode, visitArgs) {
91 },
92 visitComment: function (commentNode, visitArgs) {
93 if (commentNode.blocksVisibility() || commentNode.isSilent(this._context)) {
94 return;
95 }
96 return commentNode;
97 },
98 visitMedia: function (mediaNode, visitArgs) {
99 var originalRules = mediaNode.rules[0].rules;
100 mediaNode.accept(this._visitor);
101 visitArgs.visitDeeper = false;
102 return this.utils.resolveVisibility(mediaNode, originalRules);
103 },
104 visitImport: function (importNode, visitArgs) {
105 if (importNode.blocksVisibility()) {
106 return;
107 }
108 return importNode;
109 },
110 visitAtRule: function (atRuleNode, visitArgs) {
111 if (atRuleNode.rules && atRuleNode.rules.length) {
112 return this.visitAtRuleWithBody(atRuleNode, visitArgs);
113 }
114 else {
115 return this.visitAtRuleWithoutBody(atRuleNode, visitArgs);
116 }
117 },
118 visitAnonymous: function (anonymousNode, visitArgs) {
119 if (!anonymousNode.blocksVisibility()) {
120 anonymousNode.accept(this._visitor);
121 return anonymousNode;
122 }
123 },
124 visitAtRuleWithBody: function (atRuleNode, visitArgs) {
125 // if there is only one nested ruleset and that one has no path, then it is
126 // just fake ruleset
127 function hasFakeRuleset(atRuleNode) {
128 var bodyRules = atRuleNode.rules;
129 return bodyRules.length === 1 && (!bodyRules[0].paths || bodyRules[0].paths.length === 0);
130 }
131 function getBodyRules(atRuleNode) {
132 var nodeRules = atRuleNode.rules;
133 if (hasFakeRuleset(atRuleNode)) {
134 return nodeRules[0].rules;
135 }
136 return nodeRules;
137 }
138 // it is still true that it is only one ruleset in array
139 // this is last such moment
140 // process childs
141 var originalRules = getBodyRules(atRuleNode);
142 atRuleNode.accept(this._visitor);
143 visitArgs.visitDeeper = false;
144 if (!this.utils.isEmpty(atRuleNode)) {
145 this._mergeRules(atRuleNode.rules[0].rules);
146 }
147 return this.utils.resolveVisibility(atRuleNode, originalRules);
148 },
149 visitAtRuleWithoutBody: function (atRuleNode, visitArgs) {
150 if (atRuleNode.blocksVisibility()) {
151 return;
152 }
153 if (atRuleNode.name === '@charset') {
154 // Only output the debug info together with subsequent @charset definitions
155 // a comment (or @media statement) before the actual @charset atrule would
156 // be considered illegal css as it has to be on the first line
157 if (this.charset) {
158 if (atRuleNode.debugInfo) {
159 var comment = new tree_1.default.Comment("/* " + atRuleNode.toCSS(this._context).replace(/\n/g, '') + " */\n");
160 comment.debugInfo = atRuleNode.debugInfo;
161 return this._visitor.visit(comment);
162 }
163 return;
164 }
165 this.charset = true;
166 }
167 return atRuleNode;
168 },
169 checkValidNodes: function (rules, isRoot) {
170 if (!rules) {
171 return;
172 }
173 for (var i = 0; i < rules.length; i++) {
174 var ruleNode = rules[i];
175 if (isRoot && ruleNode instanceof tree_1.default.Declaration && !ruleNode.variable) {
176 throw { message: 'Properties must be inside selector blocks. They cannot be in the root',
177 index: ruleNode.getIndex(), filename: ruleNode.fileInfo() && ruleNode.fileInfo().filename };
178 }
179 if (ruleNode instanceof tree_1.default.Call) {
180 throw { message: "Function '" + ruleNode.name + "' did not return a root node", index: ruleNode.getIndex(), filename: ruleNode.fileInfo() && ruleNode.fileInfo().filename };
181 }
182 if (ruleNode.type && !ruleNode.allowRoot) {
183 throw { message: ruleNode.type + " node returned by a function is not valid here", index: ruleNode.getIndex(), filename: ruleNode.fileInfo() && ruleNode.fileInfo().filename };
184 }
185 }
186 },
187 visitRuleset: function (rulesetNode, visitArgs) {
188 // at this point rulesets are nested into each other
189 var rule;
190 var rulesets = [];
191 this.checkValidNodes(rulesetNode.rules, rulesetNode.firstRoot);
192 if (!rulesetNode.root) {
193 // remove invisible paths
194 this._compileRulesetPaths(rulesetNode);
195 // remove rulesets from this ruleset body and compile them separately
196 var nodeRules = rulesetNode.rules;
197 var nodeRuleCnt = nodeRules ? nodeRules.length : 0;
198 for (var i = 0; i < nodeRuleCnt;) {
199 rule = nodeRules[i];
200 if (rule && rule.rules) {
201 // visit because we are moving them out from being a child
202 rulesets.push(this._visitor.visit(rule));
203 nodeRules.splice(i, 1);
204 nodeRuleCnt--;
205 continue;
206 }
207 i++;
208 }
209 // accept the visitor to remove rules and refactor itself
210 // then we can decide nogw whether we want it or not
211 // compile body
212 if (nodeRuleCnt > 0) {
213 rulesetNode.accept(this._visitor);
214 }
215 else {
216 rulesetNode.rules = null;
217 }
218 visitArgs.visitDeeper = false;
219 }
220 else { // if (! rulesetNode.root) {
221 rulesetNode.accept(this._visitor);
222 visitArgs.visitDeeper = false;
223 }
224 if (rulesetNode.rules) {
225 this._mergeRules(rulesetNode.rules);
226 this._removeDuplicateRules(rulesetNode.rules);
227 }
228 // now decide whether we keep the ruleset
229 if (this.utils.isVisibleRuleset(rulesetNode)) {
230 rulesetNode.ensureVisibility();
231 rulesets.splice(0, 0, rulesetNode);
232 }
233 if (rulesets.length === 1) {
234 return rulesets[0];
235 }
236 return rulesets;
237 },
238 _compileRulesetPaths: function (rulesetNode) {
239 if (rulesetNode.paths) {
240 rulesetNode.paths = rulesetNode.paths
241 .filter(function (p) {
242 var i;
243 if (p[0].elements[0].combinator.value === ' ') {
244 p[0].elements[0].combinator = new (tree_1.default.Combinator)('');
245 }
246 for (i = 0; i < p.length; i++) {
247 if (p[i].isVisible() && p[i].getIsOutput()) {
248 return true;
249 }
250 }
251 return false;
252 });
253 }
254 },
255 _removeDuplicateRules: function (rules) {
256 if (!rules) {
257 return;
258 }
259 // remove duplicates
260 var ruleCache = {};
261 var ruleList;
262 var rule;
263 var i;
264 for (i = rules.length - 1; i >= 0; i--) {
265 rule = rules[i];
266 if (rule instanceof tree_1.default.Declaration) {
267 if (!ruleCache[rule.name]) {
268 ruleCache[rule.name] = rule;
269 }
270 else {
271 ruleList = ruleCache[rule.name];
272 if (ruleList instanceof tree_1.default.Declaration) {
273 ruleList = ruleCache[rule.name] = [ruleCache[rule.name].toCSS(this._context)];
274 }
275 var ruleCSS = rule.toCSS(this._context);
276 if (ruleList.indexOf(ruleCSS) !== -1) {
277 rules.splice(i, 1);
278 }
279 else {
280 ruleList.push(ruleCSS);
281 }
282 }
283 }
284 }
285 },
286 _mergeRules: function (rules) {
287 if (!rules) {
288 return;
289 }
290 var groups = {};
291 var groupsArr = [];
292 for (var i = 0; i < rules.length; i++) {
293 var rule = rules[i];
294 if (rule.merge) {
295 var key = rule.name;
296 groups[key] ? rules.splice(i--, 1) :
297 groupsArr.push(groups[key] = []);
298 groups[key].push(rule);
299 }
300 }
301 groupsArr.forEach(function (group) {
302 if (group.length > 0) {
303 var result_1 = group[0];
304 var space_1 = [];
305 var comma_1 = [new tree_1.default.Expression(space_1)];
306 group.forEach(function (rule) {
307 if ((rule.merge === '+') && (space_1.length > 0)) {
308 comma_1.push(new tree_1.default.Expression(space_1 = []));
309 }
310 space_1.push(rule.value);
311 result_1.important = result_1.important || rule.important;
312 });
313 result_1.value = new tree_1.default.Value(comma_1);
314 }
315 });
316 }
317};
318exports.default = ToCSSVisitor;
319//# sourceMappingURL=to-css-visitor.js.map
\No newline at end of file