Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 20 additions & 8 deletions java-frontend/src/main/java/org/sonar/java/model/JParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -575,20 +575,32 @@ private JavaTree.CompilationUnitTreeImpl convertCompilationUnit(CompilationUnit
for (int i = 0; i < e.imports().size(); i++) {
ImportDeclaration e2 = (ImportDeclaration) e.imports().get(i);
ExpressionTree name = convertImportName(e2.getName());
if (e2.isOnDemand()) {
name = new MemberSelectExpressionTreeImpl(
name,
lastTokenIn(e2, TerminalToken.TokenNameDOT),
new IdentifierTreeImpl(lastTokenIn(e2, TerminalToken.TokenNameMULTIPLY))
);

boolean isModuleImport = org.eclipse.jdt.core.dom.Modifier.isModule(e2.getModifiers());

// "on demand" means `import pkg.*;`
if (e2.isOnDemand() && !isModuleImport) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to mix an on-demand import and a module import? I am not sure the JLS allows this.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I believe this is not allowed: https://docs.oracle.com/javase/specs/jls/se25/html/jls-7.html#jls-7.5 On demend imports exist only in the context of type and static imports.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, because we have coverage, I will not question this one too much but it looks little weird to me

InternalSyntaxToken dotToken = lastTokenIn(e2, TerminalToken.TokenNameDOT);
InternalSyntaxToken identifierToken = lastTokenIn(e2, TerminalToken.TokenNameMULTIPLY);
name = new MemberSelectExpressionTreeImpl(name, dotToken, new IdentifierTreeImpl(identifierToken));
}

InternalSyntaxToken staticKeyword = e2.isStatic() ? firstTokenIn(e2, TerminalToken.TokenNamestatic) : null;
InternalSyntaxToken moduleKeyword = isModuleImport ? firstTokenIn(e2, TerminalToken.TokenNamemodule) : null;

JavaTree.ImportTreeImpl t = new JavaTree.ImportTreeImpl(
firstTokenIn(e2, TerminalToken.TokenNameimport),
e2.isStatic() ? firstTokenIn(e2, TerminalToken.TokenNamestatic) : null,
staticKeyword,
moduleKeyword,
name,
lastTokenIn(e2, TerminalToken.TokenNameSEMICOLON)
);
t.binding = e2.resolveBinding();

if (!isModuleImport) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we want to skip resolution of IModuleBinding when it is a module ? Asking it because we don't skip resolution of IPackageBinding when isOnDemand && !isModuleImport

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that it does not make sense for a module. In fact, when the condition is removed, we are getting type cast exception in jdt.

// There is no method to resolve bindings for a module import.
t.binding = e2.resolveBinding();
}

imports.add(t);

int tokenIndex = tokenManager.lastIndexIn(e2, TerminalToken.TokenNameSEMICOLON);
Expand Down
19 changes: 19 additions & 0 deletions java-frontend/src/main/java/org/sonar/java/model/JavaTree.java
Original file line number Diff line number Diff line change
Expand Up @@ -317,20 +317,27 @@ public static String packageNameAsString(@Nullable PackageDeclarationTree tree)

public static class ImportTreeImpl extends JavaTree implements ImportTree {
private final boolean isStatic;
private final boolean isModule;
private final Tree qualifiedIdentifier;
private final SyntaxToken semicolonToken;
private final SyntaxToken importToken;
@Nullable
private final SyntaxToken staticToken;
@Nullable
private final SyntaxToken moduleToken;

public IBinding binding;

public ImportTreeImpl(InternalSyntaxToken importToken, @Nullable InternalSyntaxToken staticToken,
@Nullable InternalSyntaxToken moduleToken,
Tree qualifiedIdentifier, InternalSyntaxToken semiColonToken) {
this.importToken = importToken;
this.staticToken = staticToken;
this.moduleToken = moduleToken;
this.qualifiedIdentifier = qualifiedIdentifier;
this.semicolonToken = semiColonToken;
isStatic = staticToken != null;
isModule = moduleToken != null;
}

@Nullable
Expand Down Expand Up @@ -358,6 +365,11 @@ public boolean isStatic() {
return isStatic;
}

@Override
public boolean isModule() {
return isModule;
}

@Override
public SyntaxToken importKeyword() {
return importToken;
Expand All @@ -369,6 +381,12 @@ public SyntaxToken staticKeyword() {
return staticToken;
}

@Nullable
@Override
public SyntaxToken moduleKeyword() {
return moduleToken;
}

@Override
public Tree qualifiedIdentifier() {
return qualifiedIdentifier;
Expand All @@ -389,6 +407,7 @@ public List<Tree> children() {
return ListUtils.concat(
Collections.singletonList(importToken),
isStatic ? Collections.singletonList(staticToken) : Collections.<Tree>emptyList(),
isModule ? Collections.singletonList(moduleToken) : Collections.<Tree>emptyList(),
Arrays.asList(qualifiedIdentifier, semicolonToken));
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,11 +41,22 @@ public interface ImportTree extends ImportClauseTree {
*/
boolean isStatic();

/**
* @since Java 25
*/
boolean isModule();

SyntaxToken importKeyword();

@Nullable
SyntaxToken staticKeyword();

@Nullable
/**
* @since Java 25
*/
SyntaxToken moduleKeyword();

Tree qualifiedIdentifier();

SyntaxToken semicolonToken();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
import org.sonar.java.model.expression.MethodInvocationTreeImpl;
import org.sonar.java.model.expression.MethodReferenceTreeImpl;
import org.sonar.java.model.expression.NewClassTreeImpl;
import org.sonar.java.model.location.InternalRange;
import org.sonar.java.model.statement.BlockTreeImpl;
import org.sonar.java.model.statement.BreakStatementTreeImpl;
import org.sonar.java.model.statement.ContinueStatementTreeImpl;
Expand All @@ -58,6 +59,7 @@
import org.sonar.java.model.statement.SwitchExpressionTreeImpl;
import org.sonar.java.model.statement.YieldStatementTreeImpl;
import org.sonar.plugins.java.api.JavaVersion;
import org.sonar.plugins.java.api.location.Position;
import org.sonar.plugins.java.api.semantic.Symbol;
import org.sonar.plugins.java.api.semantic.Symbol.MethodSymbol;
import org.sonar.plugins.java.api.semantic.SymbolMetadata;
Expand Down Expand Up @@ -2066,4 +2068,38 @@ void f2() {
assertNotEquals(pOfB.symbol(), pOfC.symbol());

}

@Test
void testImportModule() {
String source = """
package com.example;

import module java.base;

import static java.sql.Connection.TRANSACTION_NONE;

public class Source {
List<Integer> xs = new ArrayList<>();
Set<Integer> ys = new HashSet<>();
}
""";

JavaTree.CompilationUnitTreeImpl cu = test(source);

JavaTree.ImportTreeImpl importModule = (JavaTree.ImportTreeImpl) cu.imports().get(0);

assertThat(importModule.isModule()).isTrue();
assertThat(importModule.isStatic()).isFalse();
assertThat(importModule.symbol()).isNull();

MemberSelectExpressionTree javaBase = (MemberSelectExpressionTree) importModule.qualifiedIdentifier();
assertThat(javaBase.identifier().name()).isEqualTo("base");

assertThat(importModule.moduleKeyword().range()).isEqualTo(
new InternalRange(Position.at(3, 8), Position.at(3, 14))
);

JavaTree.ImportTreeImpl importConst = (JavaTree.ImportTreeImpl) cu.imports().get(1);
assertThat(importConst.isModule()).isFalse();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ void test_matchesTreeExactly() {
private static ImportTree importTree(int startLine, int startColumn, int endLine, int endColumn) {
InternalSyntaxToken fakeStartToken = syntaxToken(startLine, startColumn, " ");
InternalSyntaxToken fakeEndToken = syntaxToken(endLine, endColumn, " ");
return new JavaTree.ImportTreeImpl(fakeStartToken, null, null, fakeEndToken);
return new JavaTree.ImportTreeImpl(fakeStartToken, null, null, null, fakeEndToken);
}

private static VariableTree variableTree(int startLine, int startColumn, int endLine, int endColumn) {
Expand Down