Skip to content

Commit

Permalink
Fix for issue #404 and #461: search inner types for qualified ctor exprs
Browse files Browse the repository at this point in the history
  • Loading branch information
eric-milles committed Feb 11, 2018
1 parent 3a9cf25 commit dc1fdfd
Show file tree
Hide file tree
Showing 5 changed files with 392 additions and 81 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@
*/
package org.codehaus.groovy.eclipse.codeassist.tests

import groovy.transform.NotYetImplemented

import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist
import org.eclipse.jdt.ui.PreferenceConstants
import org.eclipse.jface.text.contentassist.ICompletionProposal
Expand Down Expand Up @@ -45,7 +47,7 @@ final class ConstructorCompletionTests extends CompletionTestSuite {
checkProposalApplicationNonType(contents, expected, getIndexOf(contents, 'new YY'), 'YYY')
}

@Test
@Test @NotYetImplemented // do this with Ctrl triggering of constructor proposal
void testConstructorCompletion2() {
String contents = 'class YYY { YYY() { } }\nnew YY()\nkkk' // trailing parens
String expected = 'class YYY { YYY() { } }\nnew YYY()\nkkk'
Expand Down Expand Up @@ -105,6 +107,218 @@ final class ConstructorCompletionTests extends CompletionTestSuite {
proposalExists(proposals, 'Annotation', 1)
}

@Test
void testConstructorCompletionInnerClass1() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'a'

String contents = '''\
new a.Outer.Inn
'''.stripIndent()
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - a.Outer.Inner', '()'), contents.replace('Inn', 'Inner()'))
}

@Test
void testConstructorCompletionInnerClass2() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'b'

String contents = '''\
new Outer.Inn
'''.stripIndent()
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - b.Outer.Inner', '()'), '''\
|import b.Outer
|
|new Outer.Inner()
|'''.stripMargin())
}

@Test
void testConstructorCompletionInnerClass3() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'c'

String contents = '''\
new Outer.Inn
'''.stripIndent()
setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false')
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - c.Outer.Inner', '()'), '''\
|new c.Outer.Inner()
|'''.stripMargin())
}

@Test @NotYetImplemented // qualifier missing Outer
void testConstructorCompletionInnerClass4() {
addGroovySource '''\
class Outer {
static class XyzInner {
XyzInner() {}
}
}
'''.stripIndent(), 'Outer', 'd'

String contents = '''\
new XyzInn
'''.stripIndent()
applyProposalAndCheck(checkUniqueProposal(contents, 'XyzInn', 'XyzInner() - d.Outer.XyzInner', '()'), '''\
|import d.Outer.XyzInner
|
|new Outer.XyzInner()
|'''.stripMargin())
}

@Test @NotYetImplemented // qualifier missing Outer
void testConstructorCompletionInnerClass5() {
addGroovySource '''\
class Outer {
static class XyzInner {
XyzInner() {}
}
}
'''.stripIndent(), 'Outer', 'e'

String contents = '''\
new XyzInn
'''.stripIndent()
setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false')
applyProposalAndCheck(checkUniqueProposal(contents, 'XyzInn', 'XyzInner() - e.Outer.Inner', '()'), '''\
|new e.Outer.XyzInner()
|'''.stripMargin())
}

@Test
void testConstructorCompletionInnerClass6() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'f'

String contents = '''\
import f.Outer
new Outer.Inn
'''.stripIndent()
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - f.Outer.Inner', '()'), '''\
|import f.Outer
|new Outer.Inner()
|'''.stripMargin())
}

@Test
void testConstructorCompletionInnerClass7() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'g'

String contents = '''\
import g.Outer
new Outer.Inn
'''.stripIndent()
setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false')
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - g.Outer.Inner', '()'), '''\
|import g.Outer
|new Outer.Inner()
|'''.stripMargin())
}

@Test
void testConstructorCompletionInnerClass8() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'h'

String contents = '''\
import h.*
new Outer.Inn
'''.stripIndent()
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - h.Outer.Inner', '()'), '''\
|import h.*
|new Outer.Inner()
|'''.stripMargin())
}

@Test
void testConstructorCompletionInnerClass9() {
addGroovySource '''\
class Outer {
static class Inner {
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'i'

String contents = '''\
import i.*
new Outer.Inn
'''.stripIndent()
setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false')
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner() - i.Outer.Inner', '()'), '''\
|import i.*
|new Outer.Inner()
|'''.stripMargin())
}

@Test
void testConstructorCompletionInnerClass10() {
addGroovySource '''\
class Outer {
static class Inner {
Inner(Number number, String string) {}
}
}
'''.stripIndent(), 'Outer', 'j'

String contents = '''\
new j.Outer.Inn
'''.stripIndent()
setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false')
setJavaPreference(PreferenceConstants.CODEASSIST_GUESS_METHOD_ARGUMENTS, 'false')
applyProposalAndCheck(checkUniqueProposal(contents, 'Inn', 'Inner(Number number, String string) - j.Outer.Inner', '(number, string)'), contents.replace('Inn', 'Inner(number, string)'))
}

@Test
void testConstructorCompletionInnerClass11() {
addGroovySource '''\
class Outer {
static class Inner {
Inner(Number number, String string) {}
}
}
'''.stripIndent(), 'Outer', 'k'

String contents = '''\
new k.Outer.Inner()
'''.stripIndent()
setJavaPreference(PreferenceConstants.CODEASSIST_ADDIMPORT, 'false')
setJavaPreference(PreferenceConstants.CODEASSIST_GUESS_METHOD_ARGUMENTS, 'false') // TODO: Should not need to remove the qualifier
applyProposalAndCheck(checkUniqueProposal(contents, '(', 'Inner(Number number, String string) - k.Outer.Inner' - ~/k.Outer./, ''), contents) // context display
}

@Test
void testContructorCompletionImportHandling0() {
String contents = '''\
Expand Down Expand Up @@ -346,6 +560,46 @@ final class ConstructorCompletionTests extends CompletionTestSuite {
proposalExists(proposals, 'xyz : __', 0)
}

@Test
void testNamedArgs15() {
addGroovySource '''\
class Outer {
static class Inner {
Number number
String string
Inner() {}
}
}
'''.stripIndent(), 'Outer', 'pack'

String contents = '''\
new pack.Outer.Inner()
'''.stripIndent()
ICompletionProposal[] proposals = createProposalsAtOffset(contents, getLastIndexOf(contents, '('))
proposalExists(proposals, 'number : __', 1)
proposalExists(proposals, 'string : __', 1)
}

@Test @NotYetImplemented // https://github.com/groovy/groovy-eclipse/issues/404
void testNamedArgs16() {
addGroovySource '''\
class Outer {
static class Inner {
Number number
String string
}
}
'''.stripIndent(), 'Outer', 'qual'

String contents = '''\
import qual.Outer
new Outer.Inner()
'''.stripIndent()
ICompletionProposal[] proposals = createProposalsAtOffset(contents, getLastIndexOf(contents, '('))
proposalExists(proposals, 'number : __', 1)
proposalExists(proposals, 'string : __', 1)
}

@Test
void testNamedArgs14() {
String contents = '''\
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,26 +15,36 @@
*/
package org.codehaus.groovy.eclipse.codeassist.processors;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.function.Consumer;

import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.ast.ClassHelper;
import org.codehaus.groovy.ast.ClassNode;
import org.codehaus.groovy.ast.ModuleNode;
import org.codehaus.groovy.ast.expr.ConstructorCallExpression;
import org.codehaus.groovy.ast.expr.Expression;
import org.codehaus.groovy.ast.expr.MapEntryExpression;
import org.codehaus.groovy.ast.expr.MapExpression;
import org.codehaus.groovy.ast.expr.TupleExpression;
import org.codehaus.groovy.eclipse.codeassist.GroovyContentAssist;
import org.codehaus.groovy.eclipse.codeassist.ProposalUtils;
import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistContext;
import org.codehaus.groovy.eclipse.codeassist.requestor.ContentAssistLocation;
import org.codehaus.groovy.eclipse.codeassist.requestor.MethodInfoContentAssistContext;
import org.codehaus.jdt.groovy.internal.compiler.ast.JDTResolver;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.groovy.search.ITypeResolver;
import org.eclipse.jdt.internal.core.SearchableEnvironment;
import org.eclipse.jdt.internal.ui.text.java.AbstractJavaCompletionProposal;
import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext;
import org.eclipse.jface.text.contentassist.ICompletionProposal;

Expand All @@ -60,22 +70,70 @@ public List<ICompletionProposal> generateProposals(IProgressMonitor monitor) {
switch (context.location) {
case CONSTRUCTOR:
context.extend(getJavaContext().getCoreContext(), null);
completionChars = context.fullCompletionExpression.replaceAll("^new|\\s+", "").toCharArray();
completionStart = context.completionLocation - CharOperation.lastSegment(completionChars, '.').length;
completionStart = context.completionNode.getStart(); // skip "new "
completionChars = context.getQualifiedCompletionExpression().toCharArray();
break;
case METHOD_CONTEXT:
completionChars = ((MethodInfoContentAssistContext) context).methodName.replace('$', '.').toCharArray();
completionStart = ((MethodInfoContentAssistContext) context).methodNameEnd - CharOperation.lastSegment(completionChars, '.').length;
completionStart = ((Expression) context.completionNode).getNameStart();
completionChars = ((Expression) context.completionNode).getType().getName().replace('$', '.').toCharArray();
break;
default:
throw new IllegalStateException("Invalid constructor completion location: " + context.location.name());
}

SearchableEnvironment environment = getNameEnvironment();
int replacementLength = context.completionEnd - completionStart;
GroovyProposalTypeSearchRequestor requestor = new GroovyProposalTypeSearchRequestor(
context, getJavaContext(), completionStart, replacementLength, environment.nameLookup, monitor);
environment.findConstructorDeclarations(completionChars, requestor.options.camelCaseMatch, requestor, monitor);
context, getJavaContext(), completionStart, -1, environment.nameLookup, monitor);

int lastDotIndex = CharOperation.lastIndexOf('.', completionChars);
// check for unqualified or fully-qualified (by packages) expression
if (lastDotIndex < 0 || environment.nameLookup.isPackage(CharOperation.toStrings(CharOperation.splitOn('.', completionChars, 0, lastDotIndex)))) {

environment.findConstructorDeclarations(completionChars, requestor.options.camelCaseMatch, requestor, monitor);
} else {
// qualified expression; requires manual inner types checking

String qualifier = String.valueOf(completionChars, 0, lastDotIndex);
String pattern = String.valueOf(completionChars, lastDotIndex + 1, completionChars.length - lastDotIndex - 1);

Consumer<IType> checker = (IType outerType) -> {
if (outerType != null && outerType.exists() && qualifier.endsWith(outerType.getElementName()))
try {
for (IType innerType : outerType.getTypes()) {
if (matches(pattern, innerType.getElementName(), requestor.options.camelCaseMatch)) {
for (IMethod m : innerType.getMethods()) { // TODO: not returning default no-arg ctor...
if (!m.isConstructor() || Flags.isStatic(m.getFlags()) || Flags.isSynthetic(m.getFlags())) {
continue;
}
int extraFlags = 0; // see ConstructorPattern.encodeExtraFlags(int)
char[][] parameterNames = CharOperation.toCharArrays(Arrays.asList(m.getParameterNames()));
char[][] parameterTypes = null; //CharOperation.toCharArrays(Arrays.asList(m.getParameterTypes()));

requestor.acceptConstructor(m.getFlags(), innerType.getTypeQualifiedName().toCharArray(),
m.getNumberOfParameters(), m.getSignature().toCharArray(), parameterTypes, parameterNames, innerType.getFlags(),
innerType.getPackageFragment().getElementName().toCharArray(), extraFlags, innerType.getPath().toString(), ProposalUtils.getTypeAccessibility(innerType));
}
}
}
} catch (JavaModelException e) {
GroovyContentAssist.logError(e);
}
};

ClassNode outerTypeNode = resolver.resolve(qualifier);
if (!ClassHelper.DYNAMIC_TYPE.equals(outerTypeNode)) {
checker.accept(environment.nameLookup.findType(outerTypeNode.getName(), false, 0));
} else if (qualifier.indexOf('.') < 0) {
// unknown qualifier; search for types with exact matching
environment.findTypes(qualifier.toCharArray(), true, false, 0, requestor, monitor);
List<ICompletionProposal> proposals = requestor.processAcceptedTypes(resolver);
for (ICompletionProposal proposal : proposals) {
if (proposal instanceof AbstractJavaCompletionProposal) {
checker.accept((IType) ((AbstractJavaCompletionProposal) proposal).getJavaElement());
}
}
}
}

return requestor.processAcceptedConstructors(findUsedParameters(context), resolver);
}
Expand Down
Loading

0 comments on commit dc1fdfd

Please sign in to comment.