/*
* Copyright 2009-2017 the original author or authors.
*
* 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 org.eclipse.jdt.core.groovy.tests.locations;
import static org.junit.Assert.assertEquals;
import java.util.List;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.IPath;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.SourceRange;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.AnnotationTypeMemberDeclaration;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.eclipse.jdt.core.groovy.tests.builder.BuilderTestSuite;
import org.eclipse.jdt.core.tests.builder.Problem;
import org.eclipse.jdt.core.tests.util.Util;
import org.eclipse.jdt.groovy.core.util.JavaConstants;
import org.eclipse.jdt.internal.core.CompilationUnit;
import org.junit.Test;
/**
* Tests that source locations for groovy compilation units are computed properly.
*
* Source locations are deteremined by special marker comments in the code
* markers /*m1s* / /*f1s* / /*t1s* / indicate start of method, field and type
* markers /*m1e* / /*f1e* / /*t1e* / indicate end of method, field and type
* markers /*m1sn* / /*f1sn* / /*t1sn* / indicate start of method, field and type names
* markers /*m1en* / /*f1en* / /*t1en* / indicate end of method, field and type names
* markers /*m1sb* / indicate the start of a method body
* NOTE: the start of a type body is not being calculated correctly
*/
public final class SourceLocationsTests extends BuilderTestSuite {
private static void assertUnitWithSingleType(String source, ICompilationUnit unit) throws Exception {
assertUnit(unit, source);
ASTParser newParser = ASTParser.newParser(JavaConstants.AST_LEVEL);
newParser.setSource(unit);
org.eclipse.jdt.core.dom.CompilationUnit ast = (org.eclipse.jdt.core.dom.CompilationUnit) newParser.createAST(null);
int maxLength = ((CompilationUnit) unit).getContents().length-1;
IType decl = unit.getTypes()[0];
AbstractTypeDeclaration typeDecl = (AbstractTypeDeclaration) ast.types().get(0);
assertDeclaration(decl, typeDecl, 0, source, maxLength);
IJavaElement[] children = decl.getChildren();
List<BodyDeclaration> bodyDecls = typeDecl.bodyDeclarations();
for (int i = 0, j= 0; i < children.length; i++, j++) {
// look for method variants that use default params
if (i > 0 && (children[i] instanceof IMethod) && children[i].getElementName().equals(children[i-1].getElementName())) {
j--;
}
// check for multiple declaration fragments inside a field declaration
// start locations and end locations for fragments are not calculated entirely correctly
// the first fragment has a start and end of the entire declaration
// the subsequent fragments have a start at the name start and an end after the fragment's optional expression (or the name end if there is none.
// so, here check to see if the name start and source start are the same. If so, then this is a second fragment
if (decl.isEnum()) {
// not properly testing synthetic enum members yet
IMember member = (IMember) children[i];
assertEquals(new SourceRange(0, 0), member.getSourceRange());
} else {
assertDeclaration((IMember) children[i], bodyDecls.get(i), j, source, maxLength);
}
}
}
private static void assertDeclaration(IMember decl, BodyDeclaration bd, int methodNumber, String source, int maxLength) throws Exception {
char astKind;
if (decl instanceof IMethod) {
astKind = 'm';
} else if (decl instanceof IField) {
astKind = 'f';
} else {
astKind = 't';
}
String startTag = "/*" + astKind + methodNumber + "s*/";
int start = source.indexOf(startTag) + startTag.length();
String endTag = "/*" + astKind + methodNumber + "e*/";
int end = Math.min(source.indexOf(endTag) + endTag.length(), maxLength);
boolean ignore = false;
if (decl instanceof IField && (start == 6 || end == 6)) {
// a field with multiple variable declaration fragments
// this is not yet being calculated properly
ignore = true;
}
if (!ignore) {
ISourceRange declRange = decl.getSourceRange();
assertEquals(decl + "\nhas incorrect source start value", start, declRange.getOffset());
assertEquals(decl + "\nhas incorrect source end value", end, declRange.getOffset() + declRange.getLength());
// now check the AST
assertEquals(bd + "\nhas incorrect source start value", start, bd.getStartPosition());
int sourceEnd = bd.getStartPosition() + bd.getLength();
// It seems like field declarations should not include the trailing ';' in their source end if one exists
if (bd instanceof FieldDeclaration) sourceEnd --;
assertEquals(bd + "\nhas incorrect source end value", end, sourceEnd);
}
String nameStartTag = "/*" + astKind + methodNumber + "sn*/";
int nameStart = source.indexOf(nameStartTag) + nameStartTag.length();
String nameEndTag = "/*" + astKind + methodNumber + "en*/";
int nameEnd = source.indexOf(nameEndTag);
// because the name of the constructor is not stored in the Antlr AST,
// we calculate offsets of the constructor name by looking at the end
// of the modifiers and the start of the opening paren
if (decl instanceof IMethod && ((IMethod) decl).isConstructor()) {
nameEnd+= nameEndTag.length();
}
ISourceRange nameDeclRange = decl.getNameRange();
assertEquals(decl + "\nhas incorrect source start value", nameStart, nameDeclRange.getOffset());
assertEquals(decl + "\nhas incorrect source end value", nameEnd, nameDeclRange.getOffset() + nameDeclRange.getLength());
// now check the AST
if (bd instanceof FieldDeclaration) {
FieldDeclaration fd = (FieldDeclaration) bd;
SimpleName name = ((VariableDeclarationFragment) fd.fragments().get(0)).getName();
assertEquals(bd + "\nhas incorrect source start value", nameStart, name.getStartPosition());
assertEquals(bd + "\nhas incorrect source end value", nameEnd, name.getStartPosition() + name.getLength());
}
if (bd instanceof MethodDeclaration) {
MethodDeclaration md = (MethodDeclaration) bd;
SimpleName name = md.getName();
assertEquals(bd + "\nhas incorrect source start value", nameStart, name.getStartPosition());
assertEquals(bd + "\nhas incorrect source end value", nameEnd, name.getStartPosition() + name.getLength());
}
if (decl.getElementType() == IJavaElement.METHOD) {
// body start is only calculated for methods
String bodyStartTag = "/*" + astKind + methodNumber + "sb*/";
int bodyStart = source.indexOf(bodyStartTag) + bodyStartTag.length();
if (bd instanceof MethodDeclaration) {
MethodDeclaration md = (MethodDeclaration) bd;
// will be null for interfaces or abstract methods
if (md.getBody() != null) {
int actualBodyStart = md.getBody().getStartPosition();
assertEquals(bd + "\nhas incorrect body start value", bodyStart, actualBodyStart);
}
} else if (bd instanceof AnnotationTypeMemberDeclaration) {
// no body start to check
}
}
}
private static void assertScript(String source, ICompilationUnit unit, String startText, String endText) throws Exception {
IType script = unit.getTypes()[0];
IMethod runMethod = script.getMethod("run", new String[0]);
int start = source.indexOf(startText);
int end = source.lastIndexOf(endText)+endText.length();
assertEquals("Wrong start for script class. Text:\n" + source, start, script.getSourceRange().getOffset());
assertEquals("Wrong end for script class. Text:\n" + source, end, script.getSourceRange().getOffset()+script.getSourceRange().getLength());
assertEquals("Wrong start for run method. Text:\n" + source, start, runMethod.getSourceRange().getOffset());
assertEquals("Wrong end for run method. Text:\n" + source, end, runMethod.getSourceRange().getOffset()+script.getSourceRange().getLength());
}
private static void assertUnit(ICompilationUnit unit, String source) throws Exception {
assertEquals(unit + "\nhas incorrect source start value", 0, unit.getSourceRange().getOffset());
assertEquals(unit + "\nhas incorrect source end value", source.length(), unit.getSourceRange().getLength());
}
private IPath createGenericProject() throws Exception {
IPath projectPath = env.addProject("Project", "1.5");
env.addExternalJars(projectPath, Util.getJavaClassLibs());
env.addGroovyJars(projectPath);
fullBuild(projectPath);
// remove old package fragment root so that names don't collide
env.removePackageFragmentRoot(projectPath, "");
IPath root = env.addPackageFragmentRoot(projectPath, "src");
env.setOutputFolder(projectPath, "bin");
return root;
}
private ICompilationUnit createCompilationUnitFor(String pack, String name, String source) throws Exception {
IPath root = createGenericProject();
IPath path = env.addGroovyClass(root, pack, name, source);
fullBuild();
expectingNoProblems();
IFile groovyFile = ResourcesPlugin.getWorkspace().getRoot().getFile(path);
return JavaCore.createCompilationUnitFrom(groovyFile);
}
//--------------------------------------------------------------------------
@Test
public void testSourceLocations() throws Exception {
String source = "package p1;\n"+
"/*t0s*/public class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public static void /*m0sn*/main/*m0en*/(String[] args) /*m0sb*/{\n"+
" System.out.println(\"Hello world\");\n"+
" }/*m0e*/\n"+
" /*f1s*/int /*f1sn*/x/*f1en*/ = 9/*f1e*/;\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsNoSemiColons() throws Exception {
String source = "package p1;\n"+
"/*t0s*/public class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public static void /*m0sn*/main/*m0en*/(String[] args) /*m0sb*/{\n"+
" System.out.println(\"Hello world\");\n"+
" }/*m0e*/\n"+
" /*f1s*/int /*f1sn*/x/*f1en*/ = 9/*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsNoModifiers() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/def /*m0sn*/main/*m0en*/(String[] args) /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*f1s*/def /*f1sn*/x/*f1en*//*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsMultipleVariableFragments() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" int /*f0sn*/x/*f0en*/, /*f1sn*/y/*f1en*/, /*f2sn*/z/*f2en*/ = 7\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsNoParameterTypes() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/def /*m0sn*/main/*m0en*/(args, fargs, blargs) /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsNoParameters() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/def /*m0sn*/main/*m0en*/() /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*m1s*/def /*m1sn*/main2/*m1en*/() /*m1sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m1e*/\n"+
" /*m2s*/def /*m2sn*/main3/*m2en*/() /*m2sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m2e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsDefaultParameters() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/def /*m0sn*/main/*m0en*/(args = \"hi!\") /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*m1s*/def /*m1sn*/main2/*m1en*/(args = \"hi!\", blargs = \"bye\") /*m1sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsConstructor() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public /*m0sn*/Hello/*m0en*/() /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*f1s*/def /*f1sn*/x/*f1en*//*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsConstructorWithParam() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public /*m0sn*/Hello/*m0en*/(String x) /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*f1s*/def /*f1sn*/x/*f1en*//*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsConstructorWithParamNoType() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public /*m0sn*/Hello/*m0en*/(x) /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*f1s*/def /*f1sn*/x/*f1en*//*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsConstructorWithDefaultParam() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public /*m0sn*/Hello/*m0en*/(args = \"9\") /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*f1s*/def /*f1sn*/x/*f1en*//*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsForScript1() throws Exception {
String source = "package p1;\n"+
"def x";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertScript(source, unit, "def x", "def x");
assertUnit(unit, source);
}
@Test
public void testSourceLocationsForScript2() throws Exception {
String source = "package p1;\n"+
"def x() { }";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertScript(source, unit, "def x", "{ }");
assertUnit(unit, source);
}
@Test
public void testSourceLocationsForScript3() throws Exception {
String source = "package p1;\n"+
"x() \n def x() { }";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertScript(source, unit, "x()", "{ }");
assertUnit(unit, source);
}
@Test
public void testSourceLocationsForScript4() throws Exception {
String source = "package p1;\n"+
"def x() { }\nx()";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertScript(source, unit, "def x", "x()");
assertUnit(unit, source);
}
@Test
public void testSourceLocationsForScript5() throws Exception {
String source = "package p1;\n"+
"def x() { }\nx()\ndef y() { }";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertScript(source, unit, "def x", "def y() { }");
assertUnit(unit, source);
}
@Test
public void testSourceLocationsForScript6() throws Exception {
String source = "package p1;\n"+
"x()\n def x() { }\n\ndef y() { }\ny()";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertScript(source, unit, "x()", "\ny()");
assertUnit(unit, source);
}
@Test
public void testSourceLocationsConstructorWithDefaultParams() throws Exception {
String source = "package p1;\n"+
"/*t0s*/class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public /*m0sn*/Hello/*m0en*/(args = \"9\", String blargs = \"8\") /*m0sb*/{\n"+
" System.out.println(\"Hello world\")\n"+
" }/*m0e*/\n"+
" /*f1s*/def /*f1sn*/x/*f1en*//*f1e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsInterface() throws Exception {
String source = "package p1;\n"+
"/*t0s*/interface /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public /*m0sn*/hello/*m0en*/(args, String blargs)/*m0e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsAbstractClass() throws Exception {
String source = "package p1;\n"+
"/*t0s*/abstract class /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/public abstract /*m0sn*/hello/*m0en*/(args, String blargs)/*m0e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsAnnotationDeclaration() throws Exception {
String source = "package p1;\n"+
"/*t0s*/@interface /*t0sn*/Hello/*t0en*/ {\n"+
" /*m0s*/String /*m0sn*/val/*m0en*/()/*m0sb*//*m0e*/\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test
public void testSourceLocationsEnumDeclaration() throws Exception {
String source = "package p1;\n"+
"/*t0s*/enum /*t0sn*/Hello/*t0en*/ {\n"+
"}/*t0e*/\n";
ICompilationUnit unit = createCompilationUnitFor("p1", "Hello", source);
assertUnitWithSingleType(source, unit);
}
@Test // STS-3878
public void testErrorPositionForUnsupportedOperation() throws Exception {
String source =
"def a = 'a'\n" +
"def b = 'b'\n" +
"println a === b\n";
IPath root = createGenericProject();
IPath path = env.addGroovyClass(root, "p", "Hello", source);
fullBuild();
expectingSpecificProblemFor(root, new Problem("p/Hello", "Groovy:Operator (\"===\" at 3:11: \"===\" ) not supported @ line 3, column 11.", path, 34, 37, 60, IMarker.SEVERITY_ERROR));
}
}