/* * Copyright 2011 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.Predicates; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; import com.google.common.css.JobDescription; import com.google.common.css.PrefixingSubstitutionMap; import com.google.common.css.RecordingSubstitutionMap; import com.google.common.css.SubstitutionMap; import com.google.common.css.compiler.ast.CssCompilerPass; import com.google.common.css.compiler.ast.CssTree; import com.google.common.css.compiler.ast.ErrorManager; import com.google.common.css.compiler.ast.GssFunction; import java.util.Map; import javax.annotation.Nullable; /** * {@link PassRunner} runs applies a sequence of {@link CssCompilerPass}es to a * {@link CssTree}. * * @author bolinfest@google.com (Michael Bolin) */ public class PassRunner { private static final ImmutableMap<String, GssFunction> EMPTY_GSS_FUNCTION_MAP = ImmutableMap.of(); private final JobDescription job; private final ErrorManager errorManager; private final RecordingSubstitutionMap recordingSubstitutionMap; public PassRunner(JobDescription job, ErrorManager errorManager) { this(job, errorManager, createSubstitutionMap(job)); } public PassRunner(JobDescription job, ErrorManager errorManager, RecordingSubstitutionMap recordingSubstitutionMap) { this.job = job; this.errorManager = errorManager; this.recordingSubstitutionMap = recordingSubstitutionMap; } /** * Runs the passes on the specified {@link CssTree}. This method may be * invoked multiple times, as one compilation job may have one {@link CssTree} * per input file. */ public void runPasses(CssTree cssTree) { new CheckDependencyNodes(cssTree.getMutatingVisitController(), errorManager, job.suppressDependencyCheck).runPass(); new CreateStandardAtRuleNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateMixins(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateDefinitionNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateConstantReferences(cssTree.getMutatingVisitController()) .runPass(); new CreateConditionalNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateForLoopNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new CreateComponentNodes(cssTree.getMutatingVisitController(), errorManager).runPass(); new ValidatePropertyValues(cssTree.getVisitController(), errorManager).runPass(); new HandleUnknownAtRuleNodes(cssTree.getMutatingVisitController(), errorManager, job.allowedAtRules, true /* report */, false /* remove */).runPass(); new ProcessKeyframes(cssTree.getMutatingVisitController(), errorManager, job.allowKeyframes || job.allowWebkitKeyframes, job.simplifyCss).runPass(); new CreateVendorPrefixedKeyframes(cssTree.getMutatingVisitController(), errorManager).runPass(); new EvaluateCompileConstants(cssTree.getMutatingVisitController(), job.compileConstants).runPass(); new UnrollLoops(cssTree.getMutatingVisitController(), errorManager).runPass(); new ProcessRefiners(cssTree.getMutatingVisitController(), errorManager, job.simplifyCss).runPass(); // Eliminate conditional nodes. new EliminateConditionalNodes( cssTree.getMutatingVisitController(), ImmutableSet.copyOf(job.trueConditionNames)).runPass(); // Collect mixin definitions and replace mixins CollectMixinDefinitions collectMixinDefinitions = new CollectMixinDefinitions(cssTree.getMutatingVisitController(), errorManager); collectMixinDefinitions.runPass(); new ReplaceMixins(cssTree.getMutatingVisitController(), errorManager, collectMixinDefinitions.getDefinitions()).runPass(); new ProcessComponents<Object>(cssTree.getMutatingVisitController(), errorManager).runPass(); // Collect constant definitions. CollectConstantDefinitions collectConstantDefinitionsPass = new CollectConstantDefinitions(cssTree); collectConstantDefinitionsPass.runPass(); // Replace constant references. ReplaceConstantReferences replaceConstantReferences = new ReplaceConstantReferences(cssTree, collectConstantDefinitionsPass.getConstantDefinitions(), true /* removeDefs */, errorManager, job.allowUndefinedConstants); replaceConstantReferences.runPass(); Map<String, GssFunction> gssFunctionMap = getGssFunctionMap(); new ResolveCustomFunctionNodes( cssTree.getMutatingVisitController(), errorManager, gssFunctionMap, job.allowUnrecognizedFunctions, job.allowedNonStandardFunctions) .runPass(); if (job.simplifyCss) { // Eliminate empty rules. new EliminateEmptyRulesetNodes(cssTree.getMutatingVisitController()) .runPass(); // Eliminating units for zero values. new EliminateUnitsFromZeroNumericValues( cssTree.getMutatingVisitController()).runPass(); // Optimize color values. new ColorValueOptimizer( cssTree.getMutatingVisitController()).runPass(); // Compress redundant top-right-bottom-left value lists. new AbbreviatePositionalValues( cssTree.getMutatingVisitController()).runPass(); } if (job.eliminateDeadStyles) { // Report errors for duplicate declarations new DisallowDuplicateDeclarations( cssTree.getVisitController(), errorManager).runPass(); // Split rules by selector and declaration. new SplitRulesetNodes(cssTree.getMutatingVisitController()).runPass(); // Dead code elimination. new MarkRemovableRulesetNodes(cssTree).runPass(); new EliminateUselessRulesetNodes(cssTree).runPass(); // Merge of rules with same selector. new MergeAdjacentRulesetNodesWithSameSelector(cssTree).runPass(); new EliminateUselessRulesetNodes(cssTree).runPass(); // Merge of rules with same styles. new MergeAdjacentRulesetNodesWithSameDeclarations(cssTree).runPass(); new EliminateUselessRulesetNodes(cssTree).runPass(); } // Perform BiDi flipping if required. if (job.needsBiDiFlipping()) { new MarkNonFlippableNodes(cssTree.getVisitController(), errorManager).runPass(); new BiDiFlipper(cssTree.getMutatingVisitController(), job.swapLtrRtlInUrl, job.swapLeftRightInUrl).runPass(); } // If specified, remove all vendor-specific properties except for the // whitelisted vendor. if (job.vendor != null) { new RemoveVendorSpecificProperties(job.vendor, cssTree.getMutatingVisitController()).runPass(); } // Unless all unrecognized properties are allowed, check for unrecognized // properties. if (!job.allowUnrecognizedProperties) { new VerifyRecognizedProperties(job.allowedUnrecognizedProperties, cssTree.getVisitController(), errorManager).runPass(); } // Rename class names if (recordingSubstitutionMap != null) { new CssClassRenaming( cssTree.getMutatingVisitController(), recordingSubstitutionMap, null).runPass(); } } @Nullable public RecordingSubstitutionMap getRecordingSubstitutionMap() { return recordingSubstitutionMap; } /** * Creates the CSS class substitution map from the provider, if any. * Wraps it in a substitution map that optionally prefixes all of the renamed * classes. Additionaly wraps in a recording substituion map which excludes a * blacklist of classnames and allows the map to produced as an output. */ private static RecordingSubstitutionMap createSubstitutionMap( JobDescription job) { if (job.cssSubstitutionMapProvider != null) { SubstitutionMap baseMap = job.cssSubstitutionMapProvider.get(); if (baseMap != null) { SubstitutionMap map = baseMap; if (!job.cssRenamingPrefix.isEmpty()) { map = new PrefixingSubstitutionMap(baseMap, job.cssRenamingPrefix); } return new RecordingSubstitutionMap(map, Predicates.not(Predicates.in(job.excludedClassesFromRenaming))); } } return null; } /** * Gets the GSS function map from the provider, if any. * * @return the provided map or an empty map if none is provided */ private Map<String, GssFunction> getGssFunctionMap() { if (job.gssFunctionMapProvider == null) { return EMPTY_GSS_FUNCTION_MAP; } Map<String, GssFunction> map = job.gssFunctionMapProvider.get(GssFunction.class); if (map == null) { return EMPTY_GSS_FUNCTION_MAP; } return map; } }