/*
* Copyright 2013 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.css.SourceCode;
import com.google.common.css.SourceCodeLocation;
import com.google.common.css.compiler.ast.CssDeclarationBlockNode;
import com.google.common.css.compiler.ast.CssLiteralNode;
import com.google.common.css.compiler.ast.CssNode;
import com.google.common.css.compiler.ast.CssSelectorNode;
import com.google.common.css.compiler.ast.CssTreeVisitor;
import com.google.common.css.compiler.ast.testing.NewFunctionalTestBase;
/**
* Unit tests for {@link LocationBoundingVisitor}.
*/
public class LocationBoundingVisitorTest extends NewFunctionalTestBase {
private LocationBoundingVisitor locationBoundingVisitor;
@Override
protected void runPass() {
locationBoundingVisitor = new LocationBoundingVisitor();
tree.getVisitController()
.startVisit(UniformVisitor.Adapters.asVisitor(locationBoundingVisitor));
}
public void testTrivialBound() throws Exception {
CssLiteralNode red = new CssLiteralNode("red");
SourceCodeLocation expected =
new SourceCodeLocation(
new SourceCode(null, ""),
5 /* beginCharacterIndex */,
3 /* beginLineNumber */,
1 /* beginIndexInLine */,
15 /* endCharacterIndex */,
3 /* endLineNumber */,
11 /* endIndexInLine */);
red.setSourceCodeLocation(expected);
assertEquals(expected, LocationBoundingVisitor.bound(red));
}
public void testUnknown() throws Exception {
parseAndRun("div { color: red; }");
CssTreeVisitor eraseLocations =
UniformVisitor.Adapters.asVisitor(
new UniformVisitor() {
@Override
public void enter(CssNode n) {
n.setSourceCodeLocation(SourceCodeLocation.getUnknownLocation());
}
@Override
public void leave(CssNode node) {}
});
tree.getMutatingVisitController().startVisit(eraseLocations);
SourceCodeLocation actual = LocationBoundingVisitor.bound(tree.getRoot());
assertEquals(
new com.google.common.css.compiler.ast.GssError("boo", actual).format(),
SourceCodeLocation.getUnknownLocation(), actual);
}
public void testMixedSubtree() throws Exception {
// Let's examine a non-trivial tree
parseAndRun("div { color: red; }");
// First: establish some facts we can use later on
CssSelectorNode div = findFirstNodeOf(CssSelectorNode.class);
assertFalse(
"There should be a node with known location",
div.getSourceCodeLocation().isUnknown());
CssLiteralNode red = findFirstNodeOf(CssLiteralNode.class);
assertEquals(
"There should be a distinguished second node",
"red", red.getValue());
assertFalse(
"The second node should also have known location",
red.getSourceCodeLocation().isUnknown());
CssDeclarationBlockNode block =
findFirstNodeOf(CssDeclarationBlockNode.class);
assertTrue(
"There should be a node with an unknown location",
block.getSourceCodeLocation() == null
|| block.getSourceCodeLocation().isUnknown());
// Next: demonstrate some properties of the visitor
SourceCodeLocation actual = LocationBoundingVisitor.bound(tree.getRoot());
assertFalse(actual.isUnknown());
assertTrue(
"The tree-wide lower bound should l.b. a known node.",
actual.getBeginCharacterIndex()
<= div.getSourceCodeLocation().getBeginCharacterIndex());
assertTrue(
"The tree-wide lower bound should l.b. all the known nodes.",
actual.getBeginCharacterIndex()
<= red.getSourceCodeLocation().getBeginCharacterIndex());
assertTrue(
"The tree-wide upper bound should u.b. a known node.",
actual.getEndCharacterIndex()
>= div.getSourceCodeLocation().getEndCharacterIndex());
assertTrue(
"The tree-wide upper bound should u.b. all the known nodes.",
actual.getEndCharacterIndex()
>= red.getSourceCodeLocation().getEndCharacterIndex());
for (CssNode n : new CssNode[] {div, red, block}) {
SourceCodeLocation nLocation = n.getSourceCodeLocation();
for (CssNode a : n.ancestors()) {
try {
if (!a.getSourceCodeLocation().isUnknown()) {
// LocationBoundingVisitor guarantees that ancestors contain
// their descendents only as long as the ancestor doesn't
// have explicit bounds, in which case all bets are off.
// E.g., consider this tree
//
// graph beginCharacterIndex endCharacterIndex
// --- --- ---
// div 5 8
// |
// span 3 42
// These indices make no sense but GIGO.
continue;
}
SourceCodeLocation aBound = LocationBoundingVisitor.bound(a);
assertTrue(
"ancestral lower bounds should not exceed descendent l.b.s",
aBound.getBeginCharacterIndex()
<= nLocation.getBeginCharacterIndex());
assertTrue(
"ancestral upper bounds should equal or exceed descendent u.b.s",
aBound.getBeginCharacterIndex()
>= nLocation.getBeginCharacterIndex());
} catch (NullPointerException e) {
// Our tree-traversal code is a bit buggy, so give up
// on this ancestor and try another one. To the extent
// we can visit ancestors, these properties we assert
// should hold.
}
}
}
// For good measure: some specific, empirical, and reasonable-looking
// assertions.
assertEquals(0, actual.getBeginCharacterIndex());
assertEquals(18, actual.getEndCharacterIndex());
}
}