/*
* Copyright (c) 2013, the Dart project authors.
*
* Licensed under the Eclipse Public License v1.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.eclipse.org/legal/epl-v10.html
*
* 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.dart.engine.services.util;
import com.google.common.base.Joiner;
import com.google.dart.engine.ast.AstFactory;
import com.google.dart.engine.ast.AstNode;
import com.google.dart.engine.ast.CompilationUnit;
import com.google.dart.engine.ast.SimpleIdentifier;
import com.google.dart.engine.ast.visitor.NodeLocator;
import com.google.dart.engine.context.AnalysisContext;
import com.google.dart.engine.context.AnalysisException;
import com.google.dart.engine.element.CompilationUnitElement;
import com.google.dart.engine.element.LibraryElement;
import com.google.dart.engine.resolver.ResolverTestCase;
import com.google.dart.engine.source.Source;
import static org.fest.assertions.Assertions.assertThat;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class NameOccurrencesFinderTest extends ResolverTestCase {
public void test_fieldFormal_asMember() throws Exception {
test(//
compose(//
"class A<T> {",
" T !1element;//",
" A({this.element});",
"}",
"main() {",
" new A<int>(!2element: 0);",
"}"),
"1+element;//",
"1+element}",
"1+element: 0",
"2+element;//",
"2+element}",
"2+element: 0");
}
public void test_findIn_nullElement() throws Exception {
SimpleIdentifier ident = AstFactory.identifier("test");
Collection<AstNode> matches = NameOccurrencesFinder.findIn(ident, null);
assertThat(matches).isEmpty();
}
public void test_findIn_nullIdentifier() throws Exception {
Collection<AstNode> matches = NameOccurrencesFinder.findIn(null, null);
assertThat(matches).isEmpty();
}
public void test1() throws Exception {
test(
compose(
"import 'dart:html';",
"import 'dart:math' as Math;",
"main() {",
" new Sunfl!2ower();",
"}",
"class InputElement2 extends InputElement {",
" Stream<Event> get onChange2 => null;",
"}",
"class Sunflower!b {",
" Sunflower() {",
" PHI = (Ma!1th.sqrt(5) + 1) / 2;",
" CanvasElement canvas = document.query('#canvas');",
" xc = yc = !3MAX_D / 2;",
" ctx = canvas.getContext('2d');",
" InputElement2 slider!5 = document.query('#slider');",
" slider.onCh!9ange2.listen((Event e) {",
" seeds = int.parse(sli!6der.value);",
" drawFrame();",
" }, true);",
" seeds = int.parse(slider.value);",
" drawFrame();",
" }",
" // Draw the complete figure for the current number of seeds.",
" void drawFrame() {",
" ctx.clearRect(0, 0, MAX_D, MAX_D);",
" for (var i=0; i<seeds; i++) {",
" var theta = i * TAU / PHI;",
" var r = Math.sqrt(i) * SCALE_FACTOR;",
" var x = xc + r * Math.cos(theta);",
" var y = yc - r * Math.sin(theta);",
" draw!7Seed(x,y);",
" }",
" }",
" // Draw a small circle representing a seed centered at (x,y).",
" void !8drawSeed(num x!0, num y) {",
" ctx.beginPath();",
" ctx.lineWidth = 2;",
" ctx.fillStyle = ORANGE;",
" ctx.strokeStyle = ORANGE;",
" ctx.arc(x, y, SEED_RADIUS, 0, TAU, false);",
" ctx.fill();",
" ctx.closePath();",
" ctx.stroke();",
" }",
" CanvasRenderingContext2D ctx;",
" num xc, yc;",
" num seeds = 0;",
" static final SEED_RADIUS = 2;",
" static final SCALE_FACTOR = 4;",
" static final TAU = Math.PI * 2;",
" var PHI!a;",
" static final MAX_D!4 = 300;",
" static final String ORANGE = 'orange';",
"}"),
// 1 class ref
"1+Math.sqrt(5)",
"1+Math.PI",
// 2 constructor ref from new expr
"2+Sunflower() {",
"2+Sunflower {",
"2-new Sunflower();",
// 3 static var ref
"3+MAX_D / 2",
"3+MAX_D,",
"3+MAX_D)",
"3+MAX_D = 300",
"3-TAU",
// 4 static var decl
"4+MAX_D / 2",
"4+MAX_D,",
"4+MAX_D)",
"4+MAX_D = 300",
"4-TAU",
// 5 local var decl
"5+slider =",
"5+slider.on",
"5+slider.value",
// 6 local var ref
"6+slider =",
"6+slider.on",
"6+slider.value",
// 7 unqualified method invocation
"7+drawSeed(x,y);",
"7+drawSeed(num x, num y)",
// 8 method decl
"8+drawSeed(num x, num y)",
"8+drawSeed(x,y);",
// 9 property access getter
"9+onChange2",
// 0 parameter
"0+x, num y) {",
"0+x, y, SEED_RADIUS, 0",
// a field
"a+PHI = (Math",
"a+PHI;",
// b class definition reference
"b+Sunflower {",
"b+Sunflower() {",
"b-new Sunflower();"
// end of tests
);
}
public void test2() throws Exception {
test(
compose(
"var y = 0;",
"class S {",
" S();",
" S.s(x);",
"}",
"int tf!6(var xx) => xx * xx + 42 - !7tf(xx-1);",
"class R extends S {",
" R.a() : this.!1b();",
" R.b() : super.s!2(1);",
" var q = new Map<St!5ring, Object!3>();",
" var w = new Map<String, !4dynamic>();",
" var zz = t!8f(y);",
" var z2 = tf((xx!9) => 11);",
"}"),
// 1 redirect constructor
"1+b();",
"1+b() ",
// 2 super constructor
"2+s(x);",
"2+s(1);",
// 3 terminal generic type ref
"3+Object>",
// 4 explicit dynamic ref
"4+dynamic>",
// 5 multiple generic type ref
"5+String, O",
"5+String, d",
// 6 top-level function def
"6+tf(var",
"6+tf(xx",
"6+tf(y",
// 7 top-level function ref in top-level func
"7+tf(var",
"7+tf(xx",
"7+tf(y",
// 8 top-level function def in class method
"8+tf(var",
"8+tf(xx",
"8+tf(y",
// not finding parameter
"9-xx) =>"
// end of tests
);
}
public void test3() throws Exception {
// test refs of synthetic field
test(
compose(//
"class A {",
" get !1x => null;",
" set !2x(i) => null;",
" var q = !3x;",
" t() {",
" !4x = q;",
" }",
"}"),
"1+x =>",
"1+x(i)",
"1+x;",
"1+x = q",
"2+x(i)",
"2+x;",
"2+x = q",
"3+x(i)",
"3+x;",
"3+x = q",
"4+x(i)",
"4+x;",
"4+x = q"
// end
);
}
public void test4() throws Exception {
// test refs of synthetic getter & setter
test(//
compose(//
"class B {",
" var !1x;",
" var a = !2x;//",
" t() {",
" !3x = a;",
" }",
"}"),
"1+x;",
"1+x;//",
"1+x =",
"2+x;",
"2+x;//",
"2+x =",
"3+x;",
"3+x;//",
"3+x ="
//end
);
}
public void test5() throws Exception {
// test refs of synthetic member
test(//
compose(//
"class A<T> {",
" !1mmm(T !7t) {!8t.toString();}",
" nnn() {}",
" T !4v1;",
"}",
"main() {",
" A<int> a1;",
" A<String> a2;",
" a1.!2mmm(0);",
" a1.mmm(1);",
" a1.nnn();",
" a2.mmm('0');",
" a2.!3mmm('1');",
" a2.nnn();",
" a1.!5v1;//",
" a2.!6v1;///",
"}"),
"1+mmm(T",
"1+mmm(0)",
"1+mmm('1')",
"2+mmm(T",
"2+mmm(0)",
"2+mmm('1')",
"3+mmm(T",
"3+mmm(0)",
"3+mmm('1')",
"4+v1;",
"4+v1;//",
"4+v1;///",
"5+v1;",
"5+v1;//",
"5+v1;///",
"6+v1;",
"6+v1;//",
"6+v1;///",
"7+t)",
"7+t.t",
"8+t)",
"8+t.t"
//end
);
}
public void test6() throws Exception {
// test refs of synthetic member
test(//
compose(//
"class X {",
" var !1element;//",
" X({this.!2element});",
"}"),
"1+element;//",
"1+element}",
"1+element;//",
"2+element}"
//end
);
}
protected CompilationUnit resolve(List<Source> sources) throws AnalysisException {
AnalysisContext context = getAnalysisContext();
CompilationUnit libraryUnit = null;
for (Source source : sources) {
LibraryElement library = resolve(source);
libraryUnit = context.resolveCompilationUnit(source, library);
for (CompilationUnitElement partElement : library.getParts()) {
context.resolveCompilationUnit(partElement.getSource(), library);
}
}
return libraryUnit;
}
/**
* Run a set of location tests on the given <code>originalSource</code>. The source string has
* test locations embedded in it, which are identified by '!X' where X is a single character. Each
* X is matched to positive or negative results in the array of <code>validationStrings</code>.
* Validation strings contain the name of a prediction with a two character prefix. The first
* character of the prefix corresponds to an X in the <code>originalSource</code>. The second
* character is either a '+' or a '-' indicating whether the string is a positive or negative
* result.
*
* @param originalSource The source for a completion test that contains completion points
* @param validationStrings The positive and negative predictions
*/
protected void test(String originalSource, List<Source> sources, String... results)
throws URISyntaxException, AnalysisException {
Collection<LocationSpec> completionTests = LocationSpec.from(originalSource, results);
assertTrue(
"Expected exclamation point ('!') within the source denoting the test location",
!completionTests.isEmpty());
sources.add(addSource(completionTests.iterator().next().source));
CompilationUnit compilationUnit = resolve(sources);
for (LocationSpec test : completionTests) {
NodeLocator locator = new NodeLocator(test.testLocation);
AstNode selectedNode = locator.searchWithin(compilationUnit);
Collection<AstNode> matches = null;
if (selectedNode instanceof SimpleIdentifier) {
SimpleIdentifier ident = (SimpleIdentifier) selectedNode;
matches = NameOccurrencesFinder.findIn(ident, compilationUnit);
verifyLocations(test, matches);
} else {
fail();
}
}
}
protected void test(String originalSource, String... results) throws URISyntaxException,
AnalysisException {
test(originalSource, new ArrayList<Source>(), results);
}
private String compose(String... lines) {
return Joiner.on("\n").join(lines);
}
private void verifyLocations(LocationSpec test, Collection<AstNode> matches) {
for (String result : test.positiveResults) {
// verify that result is present in matches
int start = test.source.indexOf(result);
boolean found = false;
for (AstNode match : matches) {
if (start == match.getOffset()) {
found = true;
break;
}
}
if (!found) {
fail("No match found for: " + result);
}
}
for (String result : test.negativeResults) {
// verify that result is not present in matches
int start = test.source.indexOf(result);
if (start < 0) {
fail("Bad test result: " + result);
}
boolean found = false;
for (AstNode match : matches) {
if (start == match.getOffset()) {
found = true;
break;
}
}
if (found) {
fail("Invalid match found for: " + result);
}
}
}
}