Author: b...@google.com Date: Tue Mar 17 11:59:31 2009 New Revision: 5028 Added: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CollapsedNode.java (contents, props changed) changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssNodeCloner.java (contents, props changed) changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/HasSelectors.java (contents, props changed) changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssNodeClonerTest.java (contents, props changed) Modified: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssProperty.java changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssRule.java changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssSprite.java changes/bobv/clientbundle/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java changes/bobv/clientbundle/user/test/com/google/gwt/resources/ResourcesSuite.java changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssReorderTest.java changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssRtlTest.java changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssTestCase.java changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_expected.css changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_test.css
Log: Merge r1572 and 1573 from incubator that fixed an AST aliasing bug. Original log entries: Fix an aliasing bug in SplitRulesVisitor by adding a general-purpose CssNode cloner. Ensure immutability of all Value types. Improve testing framework to detect aliasing in the future. svn diff -c1572 | sed 's|gwt\.libideas\.|gwt.|g' | sed 's|gwt/libideas/| gwt/|g' svn revert CSSResourceTest.java Added: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CollapsedNode.java ============================================================================== --- (empty file) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CollapsedNode.java Tue Mar 17 11:59:31 2009 @@ -0,0 +1,44 @@ +/* + * 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.gwt.resources.css.ast; + +import java.util.List; + +/** + * This delegate class bypasses traversal of a node, instead traversing the + * node's children. Any modifications made to the node list of the CollapsedNode + * will be reflected in the original node. + */ +public class CollapsedNode extends CssNode implements HasNodes { + + private final List<CssNode> nodes; + + public CollapsedNode(HasNodes parent) { + this(parent.getNodes()); + } + + public CollapsedNode(List<CssNode> nodes) { + this.nodes = nodes; + } + + public List<CssNode> getNodes() { + return nodes; + } + + public void traverse(CssVisitor visitor, Context context) { + visitor.acceptWithInsertRemove(getNodes()); + } +} \ No newline at end of file Added: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssNodeCloner.java ============================================================================== --- (empty file) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssNodeCloner.java Tue Mar 17 11:59:31 2009 @@ -0,0 +1,310 @@ +/* + * 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.gwt.resources.css.ast; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Stack; + +/** + * Clones CssNodes. + */ +public class CssNodeCloner extends CssVisitor { + + /** + * Clone a list of nodes. + */ + public static <T extends CssNode> List<T> clone(Class<T> clazz, List<T> nodes) { + + // Push a fake context that will contain the cloned node + List<CssNode> topContext = new ArrayList<CssNode>(); + final List<CssProperty> topProperties = new ArrayList<CssProperty>(); + final List<CssSelector> topSelectors = new ArrayList<CssSelector>(); + + CssNodeCloner cloner = new CssNodeCloner(); + cloner.curentNodes.push(topContext); + cloner.currentHasProperties = new HasProperties() { + public List<CssProperty> getProperties() { + return topProperties; + } + }; + cloner.currentHasSelectors = new HasSelectors() { + public List<CssSelector> getSelectors() { + return topSelectors; + } + }; + + // Process the nodes + cloner.accept(nodes); + + /* + * Return the requested data. Different AST nodes will have been collected + * in different parts of the initial context. + */ + List<?> toIterate; + if (CssProperty.class.isAssignableFrom(clazz)) { + toIterate = topProperties; + } else if (CssSelector.class.isAssignableFrom(clazz)) { + toIterate = topSelectors; + } else { + toIterate = topContext; + } + + assert toIterate.size() == nodes.size() : "Wrong number of nodes in top " + + "context. Expecting: " + nodes.size() + " Found: " + toIterate.size(); + + // Create the final return value + List<T> toReturn = new ArrayList<T>(toIterate.size()); + for (Object node : toIterate) { + assert clazz.isInstance(node) : "Return type mismatch. Expecting: " + + clazz.getName() + " Found: " + node.getClass().getName(); + + // Cast to the correct type to avoid an unchecked generic cast + toReturn.add(clazz.cast(node)); + } + + return toReturn; + } + + /** + * Clone a single node. + */ + public static <T extends CssNode> T clone(Class<T> clazz, T node) { + return clone(clazz, Collections.singletonList(node)).get(0); + } + + private HasProperties currentHasProperties; + + private HasSelectors currentHasSelectors; + + private final Stack<List<CssNode>> curentNodes = new Stack<List<CssNode>>(); + + private CssNodeCloner() { + } + + @Override + public void endVisit(CssMediaRule x, Context ctx) { + popNodes(x); + } + + @Override + public void endVisit(CssNoFlip x, Context ctx) { + popNodes(x); + } + + @Override + public void endVisit(CssStylesheet x, Context ctx) { + popNodes(x); + } + + @Override + public boolean visit(CssDef x, Context ctx) { + CssDef newDef = new CssDef(x.getKey()); + newDef.getValues().addAll(x.getValues()); + + addToNodes(newDef); + return true; + } + + @Override + public boolean visit(CssEval x, Context ctx) { + assert x.getValues().size() == 1; + assert x.getValues().get(0).isExpressionValue() != null; + + String value = x.getValues().get(0).isExpressionValue().getExpression(); + CssEval newEval = new CssEval(x.getKey(), value); + addToNodes(newEval); + return true; + } + + /** + * A CssIf has two lists of nodes, so we want to handle traversal in this + * visitor. + */ + @Override + public boolean visit(CssIf x, Context ctx) { + CssIf newIf = new CssIf(); + + if (x.getExpression() != null) { + newIf.setExpression(x.getExpression()); + } else { + newIf.setProperty(x.getPropertyName()); + + String[] newValues = new String[x.getPropertyValues().length]; + System.arraycopy(x.getPropertyValues(), 0, newValues, 0, newValues.length); + newIf.setPropertyValues(newValues); + + newIf.setNegated(x.isNegated()); + } + + // Handle the "then" part + pushNodes(newIf); + accept(x.getNodes()); + popNodes(x, newIf); + + /* + * Push the "else" part as though it were its own node, but don't add it as + * its own top-level node. + */ + CollapsedNode oldElseNodes = new CollapsedNode(x.getElseNodes()); + CollapsedNode newElseNodes = new CollapsedNode(newIf.getElseNodes()); + pushNodes(newElseNodes, false); + accept(oldElseNodes); + popNodes(oldElseNodes, newElseNodes); + + return false; + } + + @Override + public boolean visit(CssMediaRule x, Context ctx) { + CssMediaRule newRule = new CssMediaRule(); + newRule.getMedias().addAll(newRule.getMedias()); + + pushNodes(newRule); + return true; + } + + @Override + public boolean visit(CssNoFlip x, Context ctx) { + pushNodes(new CssNoFlip()); + return true; + } + + @Override + public boolean visit(CssPageRule x, Context ctx) { + CssPageRule newRule = new CssPageRule(); + newRule.setPseudoPage(x.getPseudoPage()); + addToNodes(newRule); + return true; + } + + @Override + public boolean visit(CssProperty x, Context ctx) { + CssProperty newProperty = new CssProperty(x.getName(), x.getValues(), + x.isImportant()); + currentHasProperties.getProperties().add(newProperty); + return true; + } + + @Override + public boolean visit(CssRule x, Context ctx) { + CssRule newRule = new CssRule(); + addToNodes(newRule); + return true; + } + + @Override + public boolean visit(CssSelector x, Context ctx) { + CssSelector newSelector = new CssSelector(x.getSelector()); + currentHasSelectors.getSelectors().add(newSelector); + return true; + } + + @Override + public boolean visit(CssSprite x, Context ctx) { + CssSprite newSprite = new CssSprite(); + newSprite.setResourceFunction(x.getResourceFunction()); + addToNodes(newSprite); + return true; + } + + @Override + public boolean visit(CssStylesheet x, Context ctx) { + CssStylesheet newSheet = new CssStylesheet(); + pushNodes(newSheet); + return true; + } + + @Override + public boolean visit(CssUrl x, Context ctx) { + assert x.getValues().size() == 1; + assert x.getValues().get(0).isIdentValue() != null; + String ident = x.getValues().get(0).isIdentValue().getIdent(); + CssUrl newUrl = new CssUrl(x.getKey(), ident); + addToNodes(newUrl); + return true; + } + + /** + * Add a cloned node instance to the output. + */ + private void addToNodes(CssNode node) { + curentNodes.peek().add(node); + + currentHasProperties = node instanceof HasProperties ? (HasProperties) node + : null; + currentHasSelectors = node instanceof HasSelectors ? (HasSelectors) node + : null; + } + + /** + * Remove a frame. + * + * @param original the node that was being cloned so that validity checks may + * be performed + */ + private List<CssNode> popNodes(HasNodes original) { + List<CssNode> toReturn = curentNodes.pop(); + + if (toReturn.size() != original.getNodes().size()) { + throw new RuntimeException("Insufficient number of nodes for a " + + original.getClass().getName() + " Expected: " + + original.getNodes().size() + " Found: " + toReturn.size()); + } + + return toReturn; + } + + /** + * Remove a frame. + * + * @param original the node that was being cloned so that validity checks may + * be performed + * @param expected the HasNodes whose nodes were being populated by the frame + * being removed. + */ + private List<CssNode> popNodes(HasNodes original, HasNodes expected) { + List<CssNode> toReturn = popNodes(original); + + if (toReturn != expected.getNodes()) { + throw new RuntimeException("Incorrect parent node list popped"); + } + + return toReturn; + } + + /** + * Push a new frame, adding the new parent as a child of the current parent. + */ + private <T extends CssNode & HasNodes> void pushNodes(T parent) { + pushNodes(parent, true); + } + + /** + * Push a new frame. + * + * @param addToNodes if <code>true</code> add the new parent node as a child + * of the current parent. + */ + private <T extends CssNode & HasNodes> void pushNodes(T parent, + boolean addToNodes) { + if (addToNodes) { + addToNodes(parent); + } + this.curentNodes.push(parent.getNodes()); + } +} Modified: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssProperty.java ============================================================================== --- changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssProperty.java (original) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssProperty.java Tue Mar 17 11:59:31 2009 @@ -19,6 +19,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.Iterator; import java.util.List; @@ -130,7 +131,7 @@ private final List<Value> values; public ListValue(List<Value> values) { - this.values = new ArrayList<Value>(values); + this.values = Collections.unmodifiableList(new ArrayList<Value>(values)); } public ListValue(Value... values) { Modified: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssRule.java ============================================================================== --- changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssRule.java (original) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssRule.java Tue Mar 17 11:59:31 2009 @@ -21,9 +21,9 @@ /** * */ -public class CssRule extends CssNode implements HasProperties { - private final List<CssProperty> properties = new ArrayList<CssProperty>(); - private final List<CssSelector> selectors = new ArrayList<CssSelector>(); +public class CssRule extends CssNode implements HasProperties, HasSelectors { + protected final List<CssProperty> properties = new ArrayList<CssProperty>(); + protected final List<CssSelector> selectors = new ArrayList<CssSelector>(); public List<CssProperty> getProperties() { return properties; Modified: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssSprite.java ============================================================================== --- changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssSprite.java (original) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/CssSprite.java Tue Mar 17 11:59:31 2009 @@ -167,8 +167,15 @@ return resourceFunction; } + public void setResourceFunction(String resourceFunction) { + this.resourceFunction = resourceFunction; + } + public void traverse(CssVisitor visitor, Context context) { - visitor.visit(this, context); + if (visitor.visit(this, context)) { + visitor.acceptWithInsertRemove(selectors); + visitor.acceptWithInsertRemove(getProperties()); + } visitor.endVisit(this, context); } Added: changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/HasSelectors.java ============================================================================== --- (empty file) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/css/ast/HasSelectors.java Tue Mar 17 11:59:31 2009 @@ -0,0 +1,25 @@ +/* + * 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.gwt.resources.css.ast; + +import java.util.List; + +/** + * Something that has CSS selectors. + */ +public interface HasSelectors { + List<CssSelector> getSelectors(); +} Modified: changes/bobv/clientbundle/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java ============================================================================== --- changes/bobv/clientbundle/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java (original) +++ changes/bobv/clientbundle/user/src/com/google/gwt/resources/rg/CssResourceGenerator.java Tue Mar 17 11:59:31 2009 @@ -41,6 +41,7 @@ import com.google.gwt.resources.client.ImageResource.RepeatStyle; import com.google.gwt.resources.css.CssGenerationVisitor; import com.google.gwt.resources.css.GenerateCssAst; +import com.google.gwt.resources.css.ast.CollapsedNode; import com.google.gwt.resources.css.ast.Context; import com.google.gwt.resources.css.ast.CssCompilerException; import com.google.gwt.resources.css.ast.CssDef; @@ -50,6 +51,7 @@ import com.google.gwt.resources.css.ast.CssModVisitor; import com.google.gwt.resources.css.ast.CssNoFlip; import com.google.gwt.resources.css.ast.CssNode; +import com.google.gwt.resources.css.ast.CssNodeCloner; import com.google.gwt.resources.css.ast.CssProperty; import com.google.gwt.resources.css.ast.CssRule; import com.google.gwt.resources.css.ast.CssSelector; @@ -198,32 +200,6 @@ } /** - * This delegate class bypasses traversal of a node, instead traversing the - * node's children. Any modifications made to the node list of the - * CollapsedNode will be reflected in the original node. - */ - static class CollapsedNode extends CssNode implements HasNodes { - - private final List<CssNode> nodes; - - public CollapsedNode(HasNodes parent) { - this(parent.getNodes()); - } - - public CollapsedNode(List<CssNode> nodes) { - this.nodes = nodes; - } - - public List<CssNode> getNodes() { - return nodes; - } - - public void traverse(CssVisitor visitor, Context context) { - visitor.acceptWithInsertRemove(getNodes()); - } - } - - /** * Statically evaluates {...@literal @if} rules. */ static class IfEvaluator extends CssModVisitor { @@ -436,7 +412,6 @@ @Override public void endVisit(CssProperty x, Context ctx) { String name = x.getName(); - List<Value> values = x.getValues().getValues(); if (name.equalsIgnoreCase("left")) { x.setName("right"); @@ -453,7 +428,9 @@ } else if (name.contains("-left-")) { x.setName(name.replace("-left-", "-right-")); } else { + List<Value> values = new ArrayList<Value>(x.getValues().getValues()); invokePropertyHandler(x.getName(), values); + x.setValue(new CssProperty.ListValue(values)); } } @@ -692,7 +669,8 @@ for (CssSelector sel : x.getSelectors()) { CssRule newRule = new CssRule(); newRule.getSelectors().add(sel); - newRule.getProperties().addAll(x.getProperties()); + newRule.getProperties().addAll( + CssNodeCloner.clone(CssProperty.class, x.getProperties())); ctx.insertBefore(newRule); } ctx.removeMe(); Modified: changes/bobv/clientbundle/user/test/com/google/gwt/resources/ResourcesSuite.java ============================================================================== --- changes/bobv/clientbundle/user/test/com/google/gwt/resources/ResourcesSuite.java (original) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/ResourcesSuite.java Tue Mar 17 11:59:31 2009 @@ -20,6 +20,7 @@ import com.google.gwt.resources.client.ImageResourceTest; import com.google.gwt.resources.client.NestedBundleTest; import com.google.gwt.resources.client.TextResourceTest; +import com.google.gwt.resources.rg.CssNodeClonerTest; import com.google.gwt.resources.rg.CssReorderTest; import com.google.gwt.resources.rg.CssRtlTest; @@ -35,6 +36,7 @@ suite.addTestSuite(CSSResourceTest.class); suite.addTestSuite(CssReorderTest.class); suite.addTestSuite(CssRtlTest.class); + suite.addTestSuite(CssNodeClonerTest.class); suite.addTestSuite(ImageResourceTest.class); suite.addTestSuite(NestedBundleTest.class); suite.addTestSuite(TextResourceTest.class); Added: changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssNodeClonerTest.java ============================================================================== --- (empty file) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssNodeClonerTest.java Tue Mar 17 11:59:31 2009 @@ -0,0 +1,80 @@ +/* + * 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.gwt.resources.rg; + +import com.google.gwt.core.ext.TreeLogger; +import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.resources.css.GenerateCssAst; +import com.google.gwt.resources.css.ast.CssNode; +import com.google.gwt.resources.css.ast.CssNodeCloner; +import com.google.gwt.resources.css.ast.CssProperty; +import com.google.gwt.resources.css.ast.CssSelector; +import com.google.gwt.resources.css.ast.CssStylesheet; + +import java.net.URL; +import java.util.List; + +/** + * Tests the CssNodeCloner utility class. + */ +public class CssNodeClonerTest extends CssTestCase { + + public void testClone() throws UnableToCompleteException { + CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL, + new URL[] {getClass().getClassLoader().getResource( + "com/google/gwt/resources/client/test.css")}); + + CssStylesheet cloned = CssNodeCloner.clone(CssStylesheet.class, sheet); + + assertNotSame(sheet, cloned); + assertNoAliasing(cloned); + } + + public void testCloneList() throws UnableToCompleteException { + CssStylesheet sheet = GenerateCssAst.exec(TreeLogger.NULL, + new URL[] {getClass().getClassLoader().getResource( + "com/google/gwt/resources/client/test.css")}); + + List<CssNode> cloned = CssNodeCloner.clone(CssNode.class, sheet.getNodes()); + + assertEquals(sheet.getNodes().size(), cloned.size()); + + for (CssNode node : cloned) { + assertNoAliasing(node); + } + } + + public void testCloneProperty() { + CssProperty.IdentValue value = new CssProperty.IdentValue("value"); + CssProperty p = new CssProperty("name", value, true); + + CssProperty clone = CssNodeCloner.clone(CssProperty.class, p); + + assertNotSame(p, clone); + assertEquals(p.getName(), clone.getName()); + assertEquals(value.getIdent(), + clone.getValues().getValues().get(0).isIdentValue().getIdent()); + } + + public void testCloneSelector() { + CssSelector sel = new CssSelector("a , b"); + + CssSelector clone = CssNodeCloner.clone(CssSelector.class, sel); + + assertNotSame(sel, clone); + assertEquals(sel.getSelector(), clone.getSelector()); + } +} Modified: changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssReorderTest.java ============================================================================== --- changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssReorderTest.java (original) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssReorderTest.java Tue Mar 17 11:59:31 2009 @@ -36,7 +36,8 @@ private CssVisitor[] makeVisitors() { return new CssVisitor[] { - new SplitRulesVisitor(), new MergeIdenticalSelectorsVisitor(), - new MergeRulesByContentVisitor()}; + new SplitRulesVisitor(), new AliasDetector(), + new MergeIdenticalSelectorsVisitor(), new AliasDetector(), + new MergeRulesByContentVisitor(), new AliasDetector()}; } } Modified: changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssRtlTest.java ============================================================================== --- changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssRtlTest.java (original) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssRtlTest.java Tue Mar 17 11:59:31 2009 @@ -17,6 +17,7 @@ import com.google.gwt.core.ext.TreeLogger; import com.google.gwt.core.ext.UnableToCompleteException; +import com.google.gwt.resources.css.ast.CssVisitor; import com.google.gwt.resources.rg.CssResourceGenerator.RtlVisitor; /** @@ -24,26 +25,30 @@ */ public class CssRtlTest extends CssTestCase { public void testBackgroundProperties() throws UnableToCompleteException { - test(TreeLogger.NULL, "backgroundProperties", true, new RtlVisitor()); + test(TreeLogger.NULL, "backgroundProperties", true, makeVisitors()); } public void testCursorProperties() throws UnableToCompleteException { - test(TreeLogger.NULL, "cursorProperties", true, new RtlVisitor()); + test(TreeLogger.NULL, "cursorProperties", true, makeVisitors()); } public void testDirectionUpdatedInBodyOnly() throws UnableToCompleteException { - test(TreeLogger.NULL, "directionProperty", true, new RtlVisitor()); + test(TreeLogger.NULL, "directionProperty", true, makeVisitors()); } public void testFourValuedProperties() throws UnableToCompleteException { - test(TreeLogger.NULL, "fourValuedProperties", true, new RtlVisitor()); + test(TreeLogger.NULL, "fourValuedProperties", true, makeVisitors()); } public void testLeftRightProperties() throws UnableToCompleteException { - test(TreeLogger.NULL, "leftRightProperties", true, new RtlVisitor()); + test(TreeLogger.NULL, "leftRightProperties", true, makeVisitors()); } public void testNoFlip() throws UnableToCompleteException { - test(TreeLogger.NULL, "noflip", true, new RtlVisitor()); + test(TreeLogger.NULL, "noflip", true, makeVisitors()); + } + + private CssVisitor[] makeVisitors() { + return new CssVisitor[] {new RtlVisitor(), new AliasDetector()}; } } Modified: changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssTestCase.java ============================================================================== --- changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssTestCase.java (original) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/CssTestCase.java Tue Mar 17 11:59:31 2009 @@ -20,13 +20,18 @@ import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.resources.css.GenerateCssAst; +import com.google.gwt.resources.css.ast.CssNode; import com.google.gwt.resources.css.ast.CssStylesheet; import com.google.gwt.resources.css.ast.CssVisitor; +import com.google.gwt.resources.css.ast.HasNodes; import com.google.gwt.resources.ext.ResourceContext; import junit.framework.TestCase; import java.net.URL; +import java.util.IdentityHashMap; +import java.util.List; +import java.util.Map; /** * Contains functions for golden-output tests that are concerned with structural @@ -35,6 +40,37 @@ class CssTestCase extends TestCase { /** + * Triggers an assertion if a CssNode is traversed more than once. + * + * @see CssTestCase#assertNoAliasing(CssNode) + */ + protected static class AliasDetector extends CssVisitor { + private final Map<CssNode, Void> seen = new IdentityHashMap<CssNode, Void>(); + + @Override + protected void doAccept(List<? extends CssNode> list) { + for (CssNode node : list) { + doAccept(node); + } + } + + @Override + protected void doAcceptWithInsertRemove(List<? extends CssNode> list) { + for (CssNode node : list) { + doAccept(node); + } + } + + @Override + protected <T extends CssNode> T doAccept(T node) { + assertFalse("Found repeated node " + node.toString(), + seen.containsKey(node)); + seen.put(node, null); + return super.doAccept(node); + } + } + + /** * Total fake, no implementations. */ private static class FakeContext implements ResourceContext { @@ -67,35 +103,29 @@ } /** - * Runs a test. - * - * @param testName is used to compute the test and expected resource paths. - * @param reversible if <code>true</code>, the test will attempt to transform - * the expected css into the test css + * Asserts that two CssNodes are identical. */ - void test(TreeLogger logger, String testName, boolean reversible, - CssVisitor... visitors) throws UnableToCompleteException { - String packagePath = getClass().getPackage().getName().replace('.', '/') - + "/"; - URL testUrl = getClass().getClassLoader().getResource( - packagePath + testName + "_test.css"); - assertNotNull("Cauld not find testUrl", testUrl); - URL expectedUrl = getClass().getClassLoader().getResource( - packagePath + testName + "_expected.css"); - assertNotNull("Cauld not find testUrl", expectedUrl); - - test(logger, testUrl, expectedUrl, visitors); + protected static <T extends CssNode & HasNodes> void assertEquals( + TreeLogger logger, T expected, T test) throws UnableToCompleteException { + String expectedCss = CssResourceGenerator.makeExpression(logger, + new FakeContext(), null, expected, false); + String testCss = CssResourceGenerator.makeExpression(logger, + new FakeContext(), null, test, false); + assertEquals(expectedCss, testCss); + } - if (reversible) { - test(logger, expectedUrl, testUrl, visitors); - } + /** + * Ensure that no CssNode is traversed more than once due to AST errors. + */ + protected static void assertNoAliasing(CssNode node) { + (new AliasDetector()).accept(node); } /** * Compares the generated Java expressions for an input file, transformed in * order by the specified visitors, and a golden-output file. */ - private void test(TreeLogger logger, URL test, URL expected, + private static void test(TreeLogger logger, URL test, URL expected, CssVisitor... visitors) throws UnableToCompleteException { CssStylesheet expectedSheet = null; @@ -112,10 +142,31 @@ v.accept(testSheet); } - String testCss = CssResourceGenerator.makeExpression(logger, - new FakeContext(), null, testSheet, false); - String expectedCss = CssResourceGenerator.makeExpression(logger, - new FakeContext(), null, expectedSheet, false); - assertEquals(expectedCss, testCss); + assertEquals(logger, expectedSheet, testSheet); + } + + /** + * Runs a test. + * + * @param testName is used to compute the test and expected resource paths. + * @param reversible if <code>true</code>, the test will attempt to transform + * the expected css into the test css + */ + protected void test(TreeLogger logger, String testName, boolean reversible, + CssVisitor... visitors) throws UnableToCompleteException { + String packagePath = getClass().getPackage().getName().replace('.', '/') + + "/"; + URL testUrl = getClass().getClassLoader().getResource( + packagePath + testName + "_test.css"); + assertNotNull("Cauld not find testUrl", testUrl); + URL expectedUrl = getClass().getClassLoader().getResource( + packagePath + testName + "_expected.css"); + assertNotNull("Cauld not find testUrl", expectedUrl); + + test(logger, testUrl, expectedUrl, visitors); + + if (reversible) { + test(logger, expectedUrl, testUrl, visitors); + } } } Modified: changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_expected.css ============================================================================== --- changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_expected.css (original) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_expected.css Tue Mar 17 11:59:31 2009 @@ -19,6 +19,10 @@ prop: value; foo: bar; } +c { + prop: value; +} + aNonBlocker { random: other; } Modified: changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_test.css ============================================================================== --- changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_test.css (original) +++ changes/bobv/clientbundle/user/test/com/google/gwt/resources/rg/selectorMerging_test.css Tue Mar 17 11:59:31 2009 @@ -15,7 +15,7 @@ */ /* Tests merging adjacent rules with identical selectors */ -a { +a , c { prop: value; } --~--~---------~--~----~------------~-------~--~----~ http://groups.google.com/group/Google-Web-Toolkit-Contributors -~----------~----~----~----~------~----~------~--~---