All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.google.common.css.compiler.passes.MarkRemovableRulesetNodes Maven / Gradle / Ivy

Go to download

Closure Stylesheets is an extension to CSS that adds variables, functions, conditionals, and mixins to standard CSS. The tool also supports minification, linting, RTL flipping, and CSS class renaming.

There is a newer version: 20160212
Show newest version
/*
 * Copyright 2009 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.google.common.css.compiler.passes;

import com.google.common.base.Preconditions;
import com.google.common.base.Predicates;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import com.google.common.collect.HashBasedTable;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterables;
import com.google.common.collect.Table;
import com.google.common.css.compiler.ast.CssBlockNode;
import com.google.common.css.compiler.ast.CssClassSelectorNode;
import com.google.common.css.compiler.ast.CssCompilerPass;
import com.google.common.css.compiler.ast.CssDeclarationNode;
import com.google.common.css.compiler.ast.CssPriorityNode;
import com.google.common.css.compiler.ast.CssPropertyNode;
import com.google.common.css.compiler.ast.CssRefinerNode;
import com.google.common.css.compiler.ast.CssRulesetNode;
import com.google.common.css.compiler.ast.CssSelectorNode;
import com.google.common.css.compiler.ast.CssTree;
import com.google.common.css.compiler.ast.CssValueNode;
import com.google.common.css.compiler.ast.SkippingTreeVisitor;
import com.google.common.css.compiler.ast.VisitController;

import java.util.Set;

/**
 * Compiler pass that marks the ruleset nodes that should be removed from the
 * tree.
 *
 * 

This pass assumes that each ruleset node contains exactly one declaration * and one selector. This pass should be preceded by SplitRulesetNodes * pass. This pass skips over nodes in the body that are not ruleset nodes and * ignores all their children including ruleset nodes. So, in particular, all * ruleset nodes contained within {@code @media} rules will be ignored. * * @author [email protected] (Oana Florescu) */ // TODO(oana): Split this pass into two, but only after running some performance // tests to figure out the time it takes for a pass that does not change the // tree to run. public class MarkRemovableRulesetNodes extends SkippingTreeVisitor implements CssCompilerPass { private final CssTree tree; private final VisitController visitController; /** * Property names not be to checked while traversing the tree to find * overridden declarations. */ static final Set PROPERTIES_NOT_TO_BE_CHECKED = ImmutableSet.of("display", "cursor"); /** The set of rules known to be referenced. */ private Set referencedRules = null; /** The prefix of the class names. TODO(oana): This should be a namespace. */ private String prefixOfReferencedRules = ""; /** * Creates a new pass over the specified tree. */ public MarkRemovableRulesetNodes(CssTree tree) { this(tree, false); } /** * Creates a new pass over the specified tree. * * @param skipping whether to skip over rulesets containing properties that * might make them unsafe to modify (see {@link SkippingTreeVisitor}) */ public MarkRemovableRulesetNodes(CssTree tree, boolean skipping) { super(skipping); this.tree = tree; this.visitController = tree.getVisitController(); } /** * Sets the reference rules. * * @param referencedRules the set of referenced rules, which, since it is * aliased, is expected not to change while this pass is in progress * @param prefixOfReferencedRules the prefix to match when looking for * unreferenced rules */ public void setReferencedRules( Set referencedRules, String prefixOfReferencedRules) { this.referencedRules = referencedRules; this.prefixOfReferencedRules = prefixOfReferencedRules; } @Override public boolean enterBlock(CssBlockNode block) { // All the children of the block, which are ruleset nodes, are looked at // in reverse order, from the last one to the first. We mark as removable // those nodes that we are found as overridden already. // Collect the already-seen pairs of selectors and property names in this // table, save the CssRulesetNode also. Table rules = HashBasedTable.create(); for (int i = block.numChildren() - 1; i >= 0; i--) { if (block.getChildAt(i) instanceof CssRulesetNode) { CssRulesetNode ruleset = (CssRulesetNode) block.getChildAt(i); // Filter out unreferenced rules. We do this regardless of whether the // rule in question has a "display" property, is in the chunk currently // being processed or any of the other criteria that canModifyRuleset // checks for because there's no reason not to remove unreferenced // rules. if (referencedRules != null && !referencedRules.isEmpty() && isSelectorUnreferenced(ruleset.getSelectors().getChildAt(0))) { tree.getRulesetNodesToRemove().addRulesetNode(ruleset); continue; } // If skipping is on and the rule contains a property from a // pre-defined set then we skip processing this ruleset // (in that case "canModifyRuleset()" will return false). if (canModifyRuleset(ruleset)) { // Make sure the node has only one selector. Preconditions.checkArgument(isSkipping() || (ruleset.getSelectors().numChildren() == 1)); processRuleset(rules, ruleset); } } } return false; } @Override public void runPass() { visitController.startVisit(this); } /** * Processes the given ruleset, deciding whether it should be kept * or removed by looking at the given previous rules. */ private void processRuleset( Table rules, CssRulesetNode ruleset) { if ((referencedRules != null) && !referencedRules.isEmpty()) { // If this rule is not referenced to in the code we remove it. if (isSelectorUnreferenced(ruleset.getSelectors().getChildAt(0))) { // TODO(henrywong, dgajda): Storing the set of things to clean up // in the tree is pretty brittle - better would be to have the pass // return the rules it finds and then manually pass them in to the // EliminateUselessRulesets pass. tree.getRulesetNodesToRemove().addRulesetNode(ruleset); return; } } // Make sure the node has only one declaration. Preconditions.checkArgument(ruleset.getDeclarations().numChildren() == 1); Preconditions.checkArgument( ruleset.getDeclarations().getChildAt(0) instanceof CssDeclarationNode); CssDeclarationNode declaration = (CssDeclarationNode) ruleset.getDeclarations().getChildAt(0); CssPropertyNode propertyNode = declaration.getPropertyName(); String propertyName = propertyNode.getPropertyName(); if (PROPERTIES_NOT_TO_BE_CHECKED.contains(propertyName)) { return; } // If the declaration is star-hacked then we make the star be part of // the property name to ensure that we do not consider hacked // declarations as overridden by the non-hacked ones. if (declaration.hasStarHack()) { propertyName = "*" + propertyName; } String selector = PassUtil.printSelector( ruleset.getSelectors().getChildAt(0)); CssRulesetNode previousRuleset = rules.get(selector, propertyName); if (previousRuleset != null) { // If the new rule is important and the saved was not, then remove the saved one. if (isImportantRule(ruleset) && !isImportantRule(previousRuleset)) { tree.getRulesetNodesToRemove().addRulesetNode(previousRuleset); // Replace the non-important ruleset in the map, keep the important one. rules.put(selector, propertyName, ruleset); } else { tree.getRulesetNodesToRemove().addRulesetNode(ruleset); } } else if (hasOverridingShorthand(propertyNode, selector, rules, ruleset)) { tree.getRulesetNodesToRemove().addRulesetNode(ruleset); } else if (PassUtil.hasAlternateAnnotation(declaration)) { // The declaration has @alternate, so do not let it mask other // declarations that precede it. However, @alternate rules may be masked // by succeeding non-@alternate rules. } else { rules.put(selector, propertyName, ruleset); } } private boolean isSelectorUnreferenced(CssSelectorNode selector) { return okToRemoveSelector(selector) && (atLeastOneUnreferencedRefiner(selector) || unreferencedSelectorCombinator(selector)); } /** * Returns whether it's OK to remove the specified selector, i.e. it is not a * tag, like: body, html, etc, as tags should not be removed unless they are * in combination with other CSS classes. */ private boolean okToRemoveSelector(CssSelectorNode selector) { return (selector.getSelectorName() == null) || !selector.getRefiners().isEmpty() || (selector.getCombinator() != null); } /** * Returns whether at least one of the refiners of the specified selector is * not in the list of referenced rules. */ private boolean atLeastOneUnreferencedRefiner(CssSelectorNode selector) { for (CssRefinerNode ref : selector.getRefiners().childIterable()) { if (ref instanceof CssClassSelectorNode) { String refiner = ref.getRefinerName(); if (refiner.startsWith(prefixOfReferencedRules) && isRefinerUnreferenced(refiner)) { return true; } } } return false; } /** * Returns whether the combinator of the specified selector is unreferenced. */ private boolean unreferencedSelectorCombinator(CssSelectorNode selector) { return (selector.getCombinator() != null && isSelectorUnreferenced(selector.getCombinator().getSelector())); } private boolean isRefinerUnreferenced(String refiner) { String[] splits = refiner.split("-"); for (String s : splits) { if (!referencedRules.contains(s)) { return true; } } return false; } private boolean isImportantRule(CssRulesetNode ruleset) { CssDeclarationNode decl = (CssDeclarationNode) ruleset.getDeclarations().getChildAt(0); Iterable propertyValues = decl.getPropertyValue().childIterable(); return Iterables.any(propertyValues, Predicates.instanceOf(CssPriorityNode.class)); } /** * Computes whether the given rule is overridden by another rule with some * related shorthand property of equal or higher importance. * * @param propertyNode the property node of the rule to check * @param selector the printed representation of the selector of the rule * @param rules rulesets occurring after the ruleset to check (represented as * a map from selector/property pairs to rulesets for easy searching) * @param ruleset the ruleset to check (assumed to contain one rule) * @return whether the given ruleset has an overriding ruleset which uses a * related shorthand property */ private boolean hasOverridingShorthand( CssPropertyNode propertyNode, String selector, Table rules, final CssRulesetNode ruleset) { Supplier rulesetIsImportant = Suppliers.memoize( new Supplier() { @Override public Boolean get() { return isImportantRule(ruleset); } }); for (String shorthand : propertyNode.getProperty().getShorthands()) { CssRulesetNode shorthandRuleset = rules.get(selector, shorthand); if ((shorthandRuleset != null) && (!rulesetIsImportant.get() || isImportantRule(shorthandRuleset))) { return true; } } return false; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy