| /* |
| * |
| * Licensed to the Apache Software Foundation (ASF) under one or more |
| * contributor license agreements. See the NOTICE file distributed with |
| * this work for additional information regarding copyright ownership. |
| * The ASF licenses this file to You 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 org.apache.royale.compiler.internal.css.semantics; |
| |
| import static org.apache.royale.compiler.internal.css.CSSStringPropertyValue.stripQuotes; |
| import static com.google.common.collect.Collections2.filter; |
| import static com.google.common.collect.Collections2.transform; |
| |
| import java.util.Collection; |
| import java.util.HashMap; |
| import java.util.Map; |
| import java.util.Set; |
| |
| import org.apache.royale.compiler.common.XMLName; |
| import org.apache.royale.compiler.constants.IASLanguageConstants; |
| import org.apache.royale.compiler.css.ICSSDocument; |
| import org.apache.royale.compiler.css.ICSSNamespaceDefinition; |
| import org.apache.royale.compiler.css.ICSSProperty; |
| import org.apache.royale.compiler.css.ICSSPropertyValue; |
| import org.apache.royale.compiler.css.ICSSRule; |
| import org.apache.royale.compiler.css.ICSSSelector; |
| import org.apache.royale.compiler.css.ICSSSelectorCondition; |
| import org.apache.royale.compiler.definitions.IClassDefinition; |
| import org.apache.royale.compiler.definitions.IDefinition; |
| import org.apache.royale.compiler.definitions.metadata.IMetaTag; |
| import org.apache.royale.compiler.internal.css.CSSFunctionCallPropertyValue; |
| import org.apache.royale.compiler.internal.css.CSSManager; |
| import org.apache.royale.compiler.internal.css.CSSSelector; |
| import org.apache.royale.compiler.internal.css.CSSStringPropertyValue; |
| import org.apache.royale.compiler.internal.css.codegen.CSSCompilationSession; |
| import org.apache.royale.compiler.internal.mxml.MXMLDialect; |
| import org.apache.royale.compiler.internal.parsing.as.ASParser; |
| import org.apache.royale.compiler.internal.projects.ASProject; |
| import org.apache.royale.compiler.internal.targets.Target; |
| import org.apache.royale.compiler.internal.tree.as.metadata.MetaTagsNode; |
| import org.apache.royale.compiler.internal.units.EmbedCompilationUnit; |
| import org.apache.royale.compiler.internal.units.EmbedCompilationUnitFactory; |
| import org.apache.royale.compiler.mxml.IXMLNameResolver; |
| import org.apache.royale.compiler.problems.CSSEmbedAssetProblem; |
| import org.apache.royale.compiler.problems.CSSExcludedStylePropertyProblem; |
| import org.apache.royale.compiler.problems.CSSUndefinedNamespacePrefixProblem; |
| import org.apache.royale.compiler.problems.CSSUndefinedTypeProblem; |
| import org.apache.royale.compiler.problems.CSSUnknownDefaultNamespaceProblem; |
| import org.apache.royale.compiler.problems.CSSUnresolvedClassReferenceProblem; |
| import org.apache.royale.compiler.problems.CSSUnusedTypeSelectorProblem; |
| import org.apache.royale.compiler.problems.ICompilerProblem; |
| import org.apache.royale.compiler.projects.ICompilerProject; |
| import org.apache.royale.compiler.projects.IRoyaleProject; |
| import org.apache.royale.compiler.tree.metadata.IMetaTagNode; |
| import org.apache.royale.compiler.units.ICompilationUnit; |
| import org.apache.royale.utils.FilenameNormalization; |
| |
| import com.google.common.base.Function; |
| import com.google.common.base.Predicate; |
| import com.google.common.base.Splitter; |
| import com.google.common.collect.Collections2; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.ImmutableMap.Builder; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Multimap; |
| |
| /** |
| * Semantic analyzer for CSS. This is a static class. It's used by |
| * {@link CSSManager}. |
| */ |
| public class CSSSemanticAnalyzer |
| { |
| |
| /** Global selector. */ |
| private static final String GLOBAL_SELECTOR = "global"; |
| /** Universal selector. */ |
| private static final String UNIVERSAL_SELECTOR = "*"; |
| |
| /** |
| * Try to resolve all the dependencies introduced by |
| * {@code ClassReference()} and {@code Embed()} property values in a CSS |
| * rule. |
| * |
| * @param resolvedEmbedProperties A map from {@code Embed()} property values |
| * to their resolved {@link EmbedCompilationUnit}'s. |
| * @param cssRule CSS rule. |
| * @param project Current project. |
| * @param classReferences Definitions of the {@code ClassReference("...")} |
| * properties will be stored in this collection after the function returns. |
| * @param embedCompilationUnits {@link EmbedCompilationUnit}'s used in the |
| * rules will be stored in this collection after the function returns. |
| * @param problems Compiler problems. This method reports |
| * {@link CSSUnresolvedClassReferenceProblem} issues. |
| */ |
| public static void resolveDependencies( |
| final Map<CSSFunctionCallPropertyValue, EmbedCompilationUnit> resolvedEmbedProperties, |
| final ICSSRule cssRule, |
| final ICompilerProject project, |
| final Set<IClassDefinition> classReferences, |
| final Set<EmbedCompilationUnit> embedCompilationUnits, |
| final Collection<ICompilerProblem> problems) |
| { |
| assert cssRule != null : "CSS rule can't be null"; |
| assert project != null : "Project can't be null"; |
| assert problems != null : "Problems can't be null"; |
| assert classReferences != null : "Expected an output collection for ClassReferences."; |
| assert embedCompilationUnits != null : "Expected an output collection for Embed."; |
| |
| for (final ICSSProperty property : cssRule.getProperties()) |
| { |
| final ICSSPropertyValue propertyValue = property.getValue(); |
| if (propertyValue instanceof CSSFunctionCallPropertyValue) |
| { |
| final CSSFunctionCallPropertyValue functionCall = (CSSFunctionCallPropertyValue)propertyValue; |
| if (CSSFunctionCallPropertyValue.CLASS_REFERENCE.equals(functionCall.name)) |
| { |
| // Found a ClassReference() property. |
| if ("null".equals(functionCall.rawArguments)) |
| { |
| // Do nothing. ClassReference(null) resets the skin class. |
| } |
| else |
| { |
| final String qName; |
| if (CSSStringPropertyValue.isQuoted(functionCall.rawArguments)) |
| qName = stripQuotes(functionCall.rawArguments); |
| else |
| qName = functionCall.rawArguments; |
| |
| final IDefinition definition = project.resolveQNameToDefinition(qName); |
| // The definition is expected to be a class definition. |
| if (definition != null && definition instanceof IClassDefinition) |
| { |
| classReferences.add((IClassDefinition)definition); |
| } |
| else |
| { |
| final CSSUnresolvedClassReferenceProblem problem = new CSSUnresolvedClassReferenceProblem(functionCall); |
| problems.add(problem); |
| } |
| } |
| } |
| else if (CSSFunctionCallPropertyValue.EMBED.equals(functionCall.name)) |
| { |
| final String embedMetadata = String.format("[%s(%s)]", functionCall.name, functionCall.rawArguments); |
| // TODO Calling normalize here prevents an assert later |
| // in the getFileSpecification() of Workspace. The problem is that an embed |
| // in default.css inside a SWC has a source path which doesn't look normalized. |
| final String sourcePath = FilenameNormalization.normalize(functionCall.getSourcePath()); |
| final MetaTagsNode metadata = ASParser.parseMetadata(project.getWorkspace(), embedMetadata, |
| sourcePath, functionCall.getStart(), |
| functionCall.getLine(), functionCall.getColumn(), |
| problems); |
| final IMetaTagNode embedTag = metadata.getTagByName("Embed"); |
| if (embedTag == null) |
| { |
| problems.add(new CSSEmbedAssetProblem(functionCall)); |
| } |
| else |
| { |
| try |
| { |
| final EmbedCompilationUnit embedCompilationUnit = |
| EmbedCompilationUnitFactory.getCompilationUnit( |
| (ASProject)project, |
| embedTag.getSourcePath(), |
| functionCall, |
| embedTag.getAllAttributes(), |
| problems); |
| if (embedCompilationUnit == null) |
| { |
| problems.add(new CSSEmbedAssetProblem(functionCall)); |
| } |
| else |
| { |
| resolvedEmbedProperties.put(functionCall, embedCompilationUnit); |
| embedCompilationUnits.add(embedCompilationUnit); |
| } |
| } |
| catch (InterruptedException e) |
| { |
| // Incremental build interrupts the current build. We can |
| // throw away the results. |
| } |
| } |
| } |
| } |
| } |
| } |
| |
| /** |
| * Resolve type selectors to class definitions within a file scope context |
| * <p> |
| * If a namespace short name is mapped to a undefined namespace URI, just |
| * ignore. |
| * |
| * @param xmlNameResolver XML name resolver |
| * @param css CSS DOM. |
| * @param problems Collect problems. |
| * @param isCompatibilityVersion3 If true, do not create |
| * {@link CSSUnknownDefaultNamespaceProblem}'s. |
| * @return A map of CSS selectors to QNames of their resolved types. |
| */ |
| public static ImmutableMap<ICSSSelector, String> resolveSelectors( |
| final IXMLNameResolver xmlNameResolver, |
| final ICSSDocument css, |
| final Collection<ICompilerProblem> problems, |
| final IRoyaleProject project, |
| final boolean isCompatibilityVersion3) |
| { |
| assert xmlNameResolver != null : "Expected xmlNameResolver"; |
| assert css != null : "Expected CSS"; |
| |
| final ImmutableSet<ICSSSelector> allSelectors = getAllSelectors(css, project); |
| |
| if (isCompatibilityVersion3) |
| return resolveSelectorsAsFlex3Style(allSelectors); |
| |
| final ICSSNamespaceDefinition defaultNamespace = css.getDefaultNamespaceDefinition(); |
| final Builder<ICSSSelector, String> builder = new Builder<ICSSSelector, String>(); |
| for (final ICSSSelector selector : allSelectors) |
| { |
| // Expand selector to QName and conditions. |
| if (isWildcardSelector(selector)) |
| continue; |
| |
| final String prefix = selector.getNamespacePrefix(); |
| final ICSSNamespaceDefinition namespace; |
| |
| if (prefix == null) |
| { |
| // Check if the selector is a type selector without explicit namespace. |
| if (defaultNamespace == null) |
| { |
| problems.add(new CSSUnknownDefaultNamespaceProblem((CSSSelector)selector)); |
| continue; |
| } |
| else |
| { |
| namespace = defaultNamespace; |
| } |
| } |
| else |
| { |
| // Resolve namespace. |
| namespace = css.getNamespaceDefinition(prefix); |
| } |
| |
| if (namespace == null) |
| { |
| problems.add(new CSSUndefinedNamespacePrefixProblem((CSSSelector)selector)); |
| continue; |
| } |
| |
| assert (selector.getElementName() != null) : "Null element name should be skipped as a wildcard selector."; |
| final XMLName xmlName = new XMLName(namespace.getURI(), selector.getElementName()); |
| |
| // Resolve type name. |
| final String qname = xmlNameResolver.resolveXMLNameToQualifiedName(xmlName, MXMLDialect.MXML_2009); |
| if (qname == null) |
| { |
| if (defaultNamespace != null && defaultNamespace.getURI().equals("http://www.w3.org/1999/xhtml")) |
| builder.put(selector, selector.getElementName()); |
| else |
| problems.add(new CSSUndefinedTypeProblem((CSSSelector)selector)); |
| } |
| else |
| { |
| builder.put(selector, qname); |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Resolve selectors as Flex 3 CSS. In Flex 3 CSS, selectors don't have |
| * namespaces. As a result, they don't have to be resolved to a type |
| * definition. For example, selector "Button" will be emitted as a selector |
| * literal "Button". Whereas, in Flex 4 mode, "Button" would be resolved to |
| * a default namespace and then emitted as a Spark Button QName or MX Button |
| * QName. |
| * |
| * @param selectors All the selectors to resolve. |
| * @return A map from selectors to its resolved runtime style selector name. |
| */ |
| private static ImmutableMap<ICSSSelector, String> resolveSelectorsAsFlex3Style( |
| final Iterable<ICSSSelector> selectors) |
| { |
| final ImmutableMap.Builder<ICSSSelector, String> builder = new ImmutableMap.Builder<ICSSSelector, String>(); |
| for (final ICSSSelector selector : selectors) |
| { |
| builder.put(selector, selector.getCSSSyntax()); |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * Collect all the selectors in the CSS document including the subjects and |
| * the combination selectors. |
| * |
| * @param document CSS document |
| * @return All the selectors in the CSS. |
| */ |
| public static ImmutableSet<ICSSSelector> getAllSelectors(final ICSSDocument document, final IRoyaleProject project) |
| { |
| assert document != null : "Expected CSS document"; |
| |
| final ImmutableSet.Builder<ICSSSelector> builder = new ImmutableSet.Builder<ICSSSelector>(); |
| for (final ICSSRule rule : document.getRules()) |
| { |
| if (!project.isPlatformRule(rule)) |
| continue; |
| for (final ICSSSelector subject : rule.getSelectorGroup()) |
| { |
| ICSSSelector selector = subject; |
| while (selector != null) |
| { |
| builder.add(selector); |
| if (selector.getCombinator() != null) |
| selector = selector.getCombinator().getSelector(); |
| else |
| selector = null; |
| } |
| } |
| } |
| return builder.build(); |
| } |
| |
| /** |
| * A {@link Predicate} that filters {@link ICSSSelector}'s matched by a |
| * given set of class definitions. This is created for |
| * {@link Collections2#filter(Collection, Predicate)}. |
| */ |
| private static class MatchedCSSRulePredicate implements Predicate<ICSSRule> |
| { |
| /** |
| * The project |
| */ |
| private final IRoyaleProject project; |
| |
| /** |
| * QNames of the definitions to be matched by the CSS rules. |
| */ |
| private final ImmutableSet<String> qnames; |
| |
| /** |
| * A map of selectors resolved to class definitions. |
| */ |
| private final ImmutableMap<ICSSSelector, String> resolvedSelectors; |
| |
| /** |
| * Create a predicate for filtering matched CSS rules. |
| * |
| * @param qnames A set of class definitions to be matched by the |
| * CSS rules. |
| * @param resolvedSelectors A map of selectors resolved to class |
| * definitions. |
| */ |
| public MatchedCSSRulePredicate(final ImmutableSet<String> qnames, IRoyaleProject project, |
| final ImmutableMap<ICSSSelector, String> resolvedSelectors) |
| { |
| assert qnames != null : "Expected a set of definition for the CSS rules to match."; |
| assert resolvedSelectors != null : "Expected a map of selectors resolved to class definitions."; |
| this.qnames = qnames; |
| this.resolvedSelectors = resolvedSelectors; |
| this.project = project; |
| } |
| |
| /** |
| * Return true if any of the <b>subject</b> selectors in the |
| * {@code rule}'s selector group match any definitions in |
| * {@link #qnames}. Combinator selectors are ignored. |
| */ |
| @Override |
| public boolean apply(final ICSSRule rule) |
| { |
| if (!project.isPlatformRule(rule)) |
| return false; |
| |
| for (final ICSSSelector selector : rule.getSelectorGroup()) |
| { |
| if (isWildcardSelector(selector)) |
| { |
| String selName = getOptionalSelectorName(selector); |
| if (selName == null) return true; |
| return qnames.contains(selName); |
| } |
| final String qname = resolvedSelectors.get(selector); |
| if (qnames.contains(qname)) |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public boolean test(ICSSRule input) |
| { |
| return apply(input); |
| } |
| |
| } |
| |
| /** |
| * This predicate is created for {@code -compatibility-version=3} mode. In |
| * Flex 3, the selectors don't have namespace specifiers. Under the |
| * "compatible" mode, {@code Button} means {@code *|Button} in CSS3 syntax. |
| * <p> |
| * All selectors with Flex 4 advanced syntax will be dropped. |
| * <p> |
| * This class only compares the selector element names and the definition |
| * short names. |
| */ |
| private static class Flex3CSSRulePredicate implements Predicate<ICSSRule> |
| { |
| private final ImmutableSet<String> definitionSimpleNames; |
| |
| private Flex3CSSRulePredicate(final ImmutableSet<String> definitionSimpleNames) |
| { |
| this.definitionSimpleNames = definitionSimpleNames; |
| } |
| |
| @Override |
| public boolean apply(ICSSRule rule) |
| { |
| for (final ICSSSelector selector : rule.getSelectorGroup()) |
| { |
| // drop advanced selectors for flex 3 |
| if (selector.isAdvanced()) |
| return false; |
| |
| // drop unused css rules |
| final String elementName = selector.getElementName(); |
| if (GLOBAL_SELECTOR.equals(elementName)) |
| continue; |
| if (elementName == null) |
| continue; |
| if (!definitionSimpleNames.contains(elementName)) |
| return false; |
| } |
| return true; |
| } |
| |
| @Override |
| public boolean test(ICSSRule input) |
| { |
| return apply(input); |
| } |
| } |
| |
| /** |
| * Convert a dot-separated QName string to the simple name. For example: |
| * <ul> |
| * <li>{@code f("a.b.foo") = "foo";}</li> |
| * <li>{@code f("bar") = "bar";}</li> |
| * </ul> |
| */ |
| private static final Function<String, String> QNAME_TO_SIMPLE_NAME = new Function<String, String>() |
| { |
| @Override |
| public String apply(String qname) |
| { |
| return Iterables.getLast(Splitter.on(".").omitEmptyStrings().split(qname)); |
| } |
| }; |
| |
| /** |
| * Get a set of {@link ICSSRule}'s that match any of the class definitions |
| * passed in. |
| * |
| * @param session CSS compilation session data. |
| * @param royaleProject Flex project. |
| * @param cssDocument CSS document. |
| * @param qnames A set of QNames of the definitions to be matched the CSS |
| * rules. |
| * @param problems Problems collection. |
| * @return A set of CSS rules matched by one of the given class definitions. |
| */ |
| public static ImmutableSet<ICSSRule> getMatchedRules( |
| final CSSCompilationSession session, |
| final IRoyaleProject royaleProject, |
| final ICSSDocument cssDocument, |
| final ImmutableSet<String> qnames, |
| final Collection<ICompilerProblem> problems) |
| { |
| final boolean isFlex3CSS = royaleProject.getCSSManager().isFlex3CSS(); |
| final ImmutableMap<ICSSSelector, String> resolvedSelectors = |
| resolveSelectors(royaleProject, cssDocument, problems, royaleProject, isFlex3CSS); |
| final Predicate<ICSSRule> predicate; |
| if (isFlex3CSS) |
| { |
| final ImmutableSet<String> simpleNames = |
| ImmutableSet.copyOf(transform(qnames, QNAME_TO_SIMPLE_NAME)); |
| predicate = new Flex3CSSRulePredicate(simpleNames); |
| } |
| else |
| { |
| predicate = new MatchedCSSRulePredicate(qnames, royaleProject, resolvedSelectors); |
| } |
| |
| // Cache the result of selector resolution on the session. |
| // The CSS code generation will use this map later. |
| session.resolvedSelectors.putAll(resolvedSelectors); |
| |
| // Find rules with selectors that match types in a given definition set. |
| return ImmutableSet.copyOf(filter(cssDocument.getRules(), predicate)); |
| } |
| |
| /** |
| * Check if the selector is a wildcard selector. For example: |
| * <ul> |
| * <li>global</li> |
| * <li>*</li> |
| * <li>.highlight</li> |
| * <li>:up</li> |
| * </ul> |
| * |
| * @param selector CSS selector |
| * @return True if the selector is a "wildcard" selector. |
| */ |
| public static boolean isWildcardSelector(ICSSSelector selector) |
| { |
| final String elementName = selector.getElementName(); |
| return elementName == null || |
| UNIVERSAL_SELECTOR.equals(elementName) || |
| GLOBAL_SELECTOR.equals(elementName); |
| } |
| |
| /** |
| * Check if the selector is a optional class selector. |
| * An optional class selector is a class selector |
| * with the name opt_qname_otherstuff. |
| * |
| * The output will not contain the selector if the |
| * class identified by qname is not in the output. |
| * |
| * @param selector CSS selector |
| * @return True if the selector is a "optional" selector. |
| */ |
| public static String getOptionalSelectorName(ICSSSelector selector) |
| { |
| ImmutableList<ICSSSelectorCondition> conditions = selector.getConditions(); |
| if (conditions.size() == 0) |
| return null; |
| final String elementName = conditions.get(0).getValue(); |
| if (elementName == null) return null; |
| if (elementName.startsWith("opt_")) |
| { |
| int c = elementName.indexOf("_", 4); |
| if (c >= 0) |
| { |
| return elementName.substring(4, c).replace("-", "."); |
| |
| } |
| } |
| return null; |
| } |
| /** |
| * Build a map from QNames to class definitions. |
| * |
| * @param classDefinitions Class definitions. |
| * @return Lookup map. |
| */ |
| public static final ImmutableMap<String, IClassDefinition> buildQNameToDefinitionMap(final Collection<IClassDefinition> classDefinitions) |
| { |
| final Map<String, IClassDefinition> builder = new HashMap<String, IClassDefinition>(); |
| for (final IClassDefinition classDefinition : classDefinitions) |
| { |
| builder.put(classDefinition.getQualifiedName(), classDefinition); |
| } |
| return ImmutableMap.copyOf(builder); |
| } |
| |
| /** |
| * Find all the class definitions in the given collection. |
| * |
| * @param definitions A collection of definitions. |
| * @return A set of class definitions. |
| */ |
| public static ImmutableSet<IClassDefinition> getClassDefinitionSet(final Collection<IDefinition> definitions) |
| { |
| final ImmutableSet.Builder<IClassDefinition> builder = new ImmutableSet.Builder<IClassDefinition>(); |
| for (final IDefinition def : definitions) |
| { |
| if (def instanceof IClassDefinition) |
| builder.add((IClassDefinition)def); |
| } |
| final ImmutableSet<IClassDefinition> classDefinitions = builder.build(); |
| return classDefinitions; |
| } |
| |
| /** |
| * <p> |
| * Validate a CSS model. The validation only works for Flex 4+. |
| * </p> |
| * <h1>Find CSS rules with unused type selectors.</h1> |
| * <p> |
| * The result is added to the problem collection. |
| * <p> |
| * For example, if an MXML document only uses {@code <s:Button>} tags, and |
| * its {@code <fx:Style>} block contains: |
| * |
| * <pre> |
| * ... |
| * s|Button { fontSize : 12; } |
| * local|MyComponent { color : red; } |
| * ... |
| * </pre> |
| * |
| * Since {@code <local:MyComponent>} isn't used in the current MXML |
| * document, the {@code local|MyComponent} is a rule with an <i>unused type |
| * selector</i>. |
| * <p> |
| * The validation process only finds all the unused type selectors, but it |
| * doesn't take them out of the code generation. |
| * <p> |
| * <h1>Find usages of excludes styles.</h1> If a component declares one of |
| * its styles to be "excluded", usages of such styles will be reported as |
| * {@link CSSExcludedStylePropertyProblem}. |
| * |
| * <pre> |
| * [Exclude(kind="style", name="foo")] |
| * public class MyButton |
| * { |
| * } |
| * </pre> |
| * |
| * The following CSS will cause the problem: |
| * |
| * <pre> |
| * local|MyButton { foo : something; } |
| * </pre> |
| * |
| * @param linkingCompilationUnits All type selectors that doesn't map to any |
| * definition in this collection are "unused". |
| * @param session {@link CSSCompilationSession#cssDocuments} has all the CSS |
| * models an MXML document has. |
| * @throws InterruptedException Abort compilation. |
| */ |
| public static void validate( |
| final Set<ICompilationUnit> linkingCompilationUnits, |
| final CSSCompilationSession session, |
| final Collection<ICompilerProblem> problems) |
| throws InterruptedException |
| { |
| final CSSValidator validator = new CSSValidator(session, linkingCompilationUnits, problems); |
| for (final ICSSDocument cssDocument : session.cssDocuments) |
| { |
| visit(cssDocument, validator); |
| } |
| } |
| |
| /** |
| * CSS model visitor. |
| */ |
| private static interface ICSSVisitor |
| { |
| |
| /** |
| * Visit a CSS document. |
| */ |
| void beginDocument(final ICSSDocument document); |
| |
| /** |
| * Visit a CSS rule. |
| */ |
| void beginRule(final ICSSRule rule); |
| |
| /** |
| * Visit a CSS subject selector. |
| */ |
| void beginSubject(final ICSSSelector selector, final ICSSRule rule); |
| |
| /** |
| * Visit a CSS property. |
| */ |
| void beginProperty(final ICSSProperty property, final ICSSRule rule); |
| } |
| |
| /** |
| * Validate the following CSS semantic constraints: |
| * <ol> |
| * <li>unused type selectors</li> |
| * <li>usage of excluded styles</li> |
| * </ol> |
| */ |
| private static class CSSValidator implements ICSSVisitor |
| { |
| private final CSSCompilationSession session; |
| private final ImmutableMap<String, IClassDefinition> qnameToDefinition; |
| private final Collection<ICompilerProblem> problems; |
| private final Multimap<ICSSSelector, String> excludedStyles; |
| |
| /** |
| * Create a CSS validating visitor. |
| * |
| * @param session CSS compilation session. |
| * @param linkingCompilationUnits All the compilation units to be |
| * linked. This is used to find <i>unused type selectors</i>. |
| * @param problems Problem collection. |
| * @throws InterruptedException Compilation aborted. |
| */ |
| private CSSValidator(final CSSCompilationSession session, |
| final Set<ICompilationUnit> linkingCompilationUnits, |
| final Collection<ICompilerProblem> problems) throws InterruptedException |
| { |
| this.session = session; |
| final ImmutableList<IDefinition> linkingDefinitions = |
| Target.getAllExternallyVisibleDefinitions(linkingCompilationUnits); |
| final ImmutableSet<IClassDefinition> classDefinitions = |
| getClassDefinitionSet(linkingDefinitions); |
| this.qnameToDefinition = buildQNameToDefinitionMap(classDefinitions); |
| this.problems = problems; |
| this.excludedStyles = HashMultimap.create(); |
| } |
| |
| /** |
| * <ol> |
| * <li>Find all excluded styles for the current subject.</li> |
| * <li>Find unused type selectors.</li> |
| * </ol> |
| */ |
| @Override |
| public void beginSubject(final ICSSSelector selector, final ICSSRule rule) |
| { |
| if (!isWildcardSelector(selector)) |
| { |
| final String qname = session.resolvedSelectors.get(selector); |
| if (qnameToDefinition.containsKey(qname)) |
| { |
| // The subject's resolved QName is in the linking set. |
| // Collect all "excluded" styles for this subject selector. |
| // Only check "Exclude" styles on used styles. |
| final IClassDefinition classDefinition = qnameToDefinition.get(qname); |
| final IMetaTag[] excludeMetaTags = classDefinition.getMetaTagsByName(IASLanguageConstants.EXCLUDE_META_TAG); |
| for (final IMetaTag exclude : excludeMetaTags) |
| { |
| final String kind = exclude.getAttributeValue(IASLanguageConstants.EXCLUDE_META_TAG_KIND); |
| if (IASLanguageConstants.EXCLUDE_META_TAG_STYLE.equals(kind)) |
| { |
| final String excludedStyleName = exclude.getAttributeValue(IASLanguageConstants.EXCLUDE_META_TAG_NAME); |
| if (excludedStyleName != null && !excludedStyleName.isEmpty()) |
| { |
| this.excludedStyles.put(selector, excludedStyleName); |
| } |
| } |
| } |
| } |
| else |
| { |
| // Selector's resolved QName is not in the linking set. |
| problems.add(new CSSUnusedTypeSelectorProblem(selector)); |
| } |
| } |
| } |
| |
| @Override |
| public void beginRule(final ICSSRule rule) |
| { |
| } |
| |
| @Override |
| public void beginDocument(final ICSSDocument document) |
| { |
| } |
| |
| /** |
| * Check usages of excluded styles. |
| */ |
| @Override |
| public void beginProperty(final ICSSProperty property, final ICSSRule rule) |
| { |
| for (final ICSSSelector subject : rule.getSelectorGroup()) |
| { |
| final Collection<String> excludedStylesForSubject = excludedStyles.get(subject); |
| if (excludedStylesForSubject != null && excludedStylesForSubject.contains(property.getName())) |
| { |
| final String qname = session.resolvedSelectors.get(subject); |
| problems.add(new CSSExcludedStylePropertyProblem(property, qname)); |
| } |
| |
| } |
| } |
| |
| } |
| |
| /** |
| * Visit a CSS document model. |
| * |
| * @param document CSS model. |
| * @param visitor Handler for various visit methods. |
| */ |
| private static void visit(final ICSSDocument document, final ICSSVisitor visitor) |
| { |
| visitor.beginDocument(document); |
| for (final ICSSRule rule : document.getRules()) |
| { |
| visitor.beginRule(rule); |
| for (final ICSSSelector selector : rule.getSelectorGroup()) |
| visitor.beginSubject(selector, rule); |
| |
| for (final ICSSProperty property : rule.getProperties()) |
| visitor.beginProperty(property, rule); |
| } |
| } |
| |
| } |