Skip to content

Commit

Permalink
Merge pull request #7090 from dbalek/dbalek/micronaut-duplicate-endpo…
Browse files Browse the repository at this point in the history
…ints-warnings

Micronaut: Detect and report duplicated endpoint URI paths.
  • Loading branch information
dbalek committed Feb 21, 2024
2 parents 30e2275 + adde5d9 commit 5de8e61
Show file tree
Hide file tree
Showing 7 changed files with 169 additions and 18 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
*/
package org.netbeans.modules.micronaut.completion;

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.VariableTree;
Expand Down Expand Up @@ -142,8 +143,16 @@ public void run(ResultIterator resultIterator) throws Exception {
switch (path.getLeaf().getKind()) {
case CLASS:
case INTERFACE:
resolveFinderMethods(cc, path, prefix, true, consumer);
items.addAll(resolveControllerMethods(cc, path, prefix, factory));
int startPos = (int) sp.getEndPosition(cc.getCompilationUnit(), ((ClassTree) path.getLeaf()).getModifiers());
if (startPos <= 0) {
startPos = (int) sp.getStartPosition(cc.getCompilationUnit(), path.getLeaf());
}
String headerText = cc.getText().substring(startPos, anchorOffset);
int idx = headerText.indexOf('{'); //NOI18N
if (idx >= 0) {
resolveFinderMethods(cc, path, prefix, true, consumer);
items.addAll(resolveControllerMethods(cc, path, prefix, factory));
}
break;
case METHOD:
Tree returnType = ((MethodTree) path.getLeaf()).getReturnType();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.VariableTree;
import java.util.ArrayList;
import java.util.Collections;

import java.util.EnumSet;
import java.util.HashSet;
Expand Down Expand Up @@ -111,7 +112,7 @@ public void preferenceChange(PreferenceChangeEvent evt) {
public static List<VariableElement> collectMissingDataEndpoints(CompilationInfo info, TypeElement te, String prefix, DataEndpointConsumer consumer) {
AnnotationMirror controllerAnn = getAnnotation(te.getAnnotationMirrors(), CONTROLLER_ANNOTATION_NAME);
if (controllerAnn == null) {
return List.of();
return Collections.emptyList();
}
List<VariableElement> repositories = getRepositoriesFor(info, te);
if (!repositories.isEmpty()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ public List<StructureElement> getStructure(Document doc) {
List<StructureElement> elements = new ArrayList<>();
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
for (MicronautSymbolFinder.SymbolLocation symbolLocation : MicronautSymbolFinder.scan(cc)) {
for (MicronautSymbolFinder.SymbolLocation symbolLocation : MicronautSymbolFinder.scan(cc, false)) {
elements.add(StructureProvider.newBuilder(symbolLocation.getName(), StructureElement.Kind.Interface)
.file(cc.getFileObject())
.expandedStartOffset(symbolLocation.getStart())
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.netbeans.modules.micronaut.symbol;

import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
import org.netbeans.api.editor.mimelookup.MimeRegistration;
import org.netbeans.api.lsp.Diagnostic;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.spi.lsp.ErrorProvider;
import org.openide.filesystems.FileObject;
import org.openide.util.Exceptions;
import org.openide.util.NbBundle;

/**
*
* @author Dusan Balek
*/
@MimeRegistration(mimeType = "text/x-java", service = ErrorProvider.class)
public final class MicronautSymbolErrorProvider implements ErrorProvider {

@Override
public List<? extends Diagnostic> computeErrors(Context context) {
if (context.errorKind() != ErrorProvider.Kind.ERRORS || context.isCancelled()) {
return Collections.emptyList();
}
FileObject fo = context.file();
Project project = fo != null ? FileOwnerQuery.getOwner(fo) : null;
if (project == null) {
return Collections.emptyList();
}
try {
return MicronautSymbolSearcher.getSymbolsWithPathDuplicates(project, fo).stream()
.filter(descriptor -> descriptor.getFileObject() == fo)
.map(descriptor -> desc2diag(descriptor))
.collect(Collectors.toList());
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
return Collections.emptyList();
}

@NbBundle.Messages({
"# {0} - symbol name",
"ERR_Duplicated_URI_path=Duplicated endpoint URI path: {0}"
})
private Diagnostic desc2diag(MicronautSymbolSearcher.SymbolDescriptor descriptor) {
OffsetRange offsetRange = descriptor.getOffsetRange(null);
return Diagnostic.Builder.create(() -> offsetRange.getStart(), () -> offsetRange.getEnd(), Bundle.ERR_Duplicated_URI_path(descriptor.getName()))
.setCode("WARN_Duplicated_MN_Data_Endpoint_Path " + offsetRange.getStart() + " - " + offsetRange.getEnd())
.setSeverity(Diagnostic.Severity.Warning)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import com.sun.source.tree.ClassTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.Tree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import com.sun.source.util.TreePathScanner;
Expand Down Expand Up @@ -93,7 +94,7 @@ protected void index(Indexable indexable, Parser.Result parserResult, Context co
if (initialize(cc)) {
try {
if (cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED).compareTo(JavaSource.Phase.ELEMENTS_RESOLVED) >= 0) {
store(context.getIndexFolder(), indexable.getURL(), indexable.getRelativePath(), scan(cc));
store(context.getIndexFolder(), indexable.getURL(), indexable.getRelativePath(), scan(cc, false));
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
Expand Down Expand Up @@ -123,7 +124,7 @@ private synchronized boolean initialize(CompilationController cc) {
return ret;
}

public static List<SymbolLocation> scan(CompilationController cc) {
public static List<SymbolLocation> scan(CompilationController cc, boolean selectEndpointAnnotation) {
final List<SymbolLocation> ret = new ArrayList<>();
SourcePositions sp = cc.getTrees().getSourcePositions();
TreePathScanner<Void, String> scanner = new TreePathScanner<Void, String>() {
Expand Down Expand Up @@ -159,7 +160,8 @@ public Void visitMethod(MethodTree node, String path) {
TreePath treePath = this.getCurrentPath();
MthIterator it = new MthIterator(cc.getTrees().getElement(treePath), cc.getElements(), cc.getTypes());
while (it.hasNext()) {
for (AnnotationMirror ann : it.next().getAnnotationMirrors()) {
ExecutableElement ee = it.next();
for (AnnotationMirror ann : ee.getAnnotationMirrors()) {
String method = getEndpointMethod((TypeElement) ann.getAnnotationType().asElement());
if (method != null) {
List<String> ids = new ArrayList<>();
Expand All @@ -180,6 +182,12 @@ public Void visitMethod(MethodTree node, String path) {
for (Object id : ids) {
String name = '@' + path + id + " -- " + method;
int[] span = cc.getTreeUtilities().findNameSpan(node);
if (selectEndpointAnnotation) {
Tree tree = cc.getTrees().getTree(ee, ann);
if (tree != null) {
span = new int[] {(int) sp.getStartPosition(treePath.getCompilationUnit(), tree), (int) sp.getEndPosition(treePath.getCompilationUnit(), tree)};
}
}
ret.add(new SymbolLocation(name, (int) sp.getStartPosition(treePath.getCompilationUnit(), node), (int) sp.getEndPosition(treePath.getCompilationUnit(), node), span[0], span[1]));
}
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,17 @@
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import javax.swing.Icon;
import javax.swing.text.StyledDocument;
import org.netbeans.api.java.project.JavaProjectConstants;
import org.netbeans.api.java.source.JavaSource;
import org.netbeans.api.lsp.Diagnostic;
import org.netbeans.api.project.FileOwnerQuery;
import org.netbeans.api.project.Project;
import org.netbeans.api.project.ProjectUtils;
Expand All @@ -43,10 +49,13 @@
import org.netbeans.modules.parsing.api.indexing.IndexingManager;
import org.netbeans.modules.parsing.impl.indexing.CacheFolder;
import org.netbeans.modules.parsing.spi.indexing.support.QuerySupport.Kind;
import org.openide.cookies.EditorCookie;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.filesystems.URLMapper;
import org.openide.util.Exceptions;
import org.openide.util.Lookup;
import org.openide.util.RequestProcessor;
import org.openide.util.lookup.ServiceProvider;

/**
Expand All @@ -66,7 +75,55 @@ public Set<? extends Descriptor> getSymbols(Project project, String textForQuery
if (project == null || !textForQuery.startsWith("@") || IndexingManager.getDefault().isIndexing()) {
return Collections.emptySet();
}
Set<Descriptor> symbols = new HashSet<>();
if (textForQuery.equals("@/")) {
RequestProcessor.getDefault().post(() -> {
try {
Set<FileObject> duplicates = getSymbolsWithPathDuplicates(project, null).stream().map(descriptor -> descriptor.getFileObject()).collect(Collectors.toSet());
if (!duplicates.isEmpty()) {
Diagnostic.ReporterControl control = Diagnostic.findReporterControl(Lookup.getDefault(), project.getProjectDirectory());
control.diagnosticChanged(duplicates, "text/x-java");
}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
});
}
return getSymbols(project, textForQuery);
}

static Set<SymbolDescriptor> getSymbolsWithPathDuplicates(Project project, FileObject fo) throws IOException {
EditorCookie ec = fo != null ? fo.getLookup().lookup(EditorCookie.class) : null;
StyledDocument doc = ec != null ? ec.openDocument() : null;
Set<SymbolDescriptor> duplicates = new HashSet<>();
Map<String, SymbolDescriptor> map = new HashMap<>();
for (SymbolDescriptor symbol : getSymbols(project, "@/")) {
if (doc == null || symbol.getFileObject() != fo) {
SymbolDescriptor previous = map.put(symbol.getSimpleName().replaceAll("\\{.*}", "{}"), symbol);
if (previous != null) {
duplicates.add(symbol);
duplicates.add(previous);
}
}
}
JavaSource js = doc != null ? JavaSource.forDocument(doc) : null;
if (js != null) {
js.runUserActionTask(cc -> {
cc.toPhase(JavaSource.Phase.ELEMENTS_RESOLVED);
for (MicronautSymbolFinder.SymbolLocation sl : MicronautSymbolFinder.scan(cc, true)) {
SymbolDescriptor symbol = new SymbolDescriptor(sl.getName(), fo, sl.getSelectionStart(), sl.getSelectionEnd());
SymbolDescriptor previous = map.put(symbol.getSimpleName().replaceAll("\\{.*}", "{}"), symbol);
if (previous != null) {
duplicates.add(symbol);
duplicates.add(previous);
}
}
}, true);
}
return duplicates;
}

private static Set<SymbolDescriptor> getSymbols(Project project, String textForQuery) {
Set<SymbolDescriptor> symbols = new HashSet<>();
for (SourceGroup sg : ProjectUtils.getSources(project).getSourceGroups(JavaProjectConstants.SOURCES_TYPE_JAVA)) {
try {
FileObject cacheRoot = getCacheRoot(sg.getRootFolder().toURL());
Expand All @@ -80,12 +137,14 @@ public Set<? extends Descriptor> getSymbols(Project project, String textForQuery
}
}
}
} catch (IOException ex) {}
} catch (IOException ex) {
Exceptions.printStackTrace(ex);
}
}
return symbols;
}

private static void loadSymbols(FileObject input, String textForQuery, Set<Descriptor> symbols) {
private static void loadSymbols(FileObject input, String textForQuery, Set<SymbolDescriptor> symbols) {
try (BufferedReader br = new BufferedReader(new InputStreamReader(input.getInputStream(), StandardCharsets.UTF_8))) {
FileObject fo = null;
String line;
Expand Down Expand Up @@ -121,7 +180,7 @@ private static FileObject getCacheRoot(URL root) throws IOException {
return dataFolder != null ? FileUtil.createFolder(dataFolder, MicronautSymbolFinder.NAME + "/" + MicronautSymbolFinder.VERSION) : null; //NOI18N
}

private static class SymbolDescriptor extends IndexSearcher.Descriptor implements org.netbeans.modules.csl.api.ElementHandle {
static class SymbolDescriptor extends IndexSearcher.Descriptor implements org.netbeans.modules.csl.api.ElementHandle {

private final String name;
private final FileObject fo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -926,8 +926,8 @@ public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> docume
Document rawDoc = server.getOpenedDocuments().getDocument(uri);
if (file != null && rawDoc instanceof StyledDocument) {
StyledDocument doc = (StyledDocument)rawDoc;
StructureProvider structureProvider = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookup(StructureProvider.class);
if (structureProvider != null) {
Collection<? extends StructureProvider> structureProviders = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc)).lookupAll(StructureProvider.class);
for (StructureProvider structureProvider : structureProviders) {
List<StructureElement> structureElements = structureProvider.getStructure(doc);
if (!structureElements.isEmpty()) {
for (StructureElement structureElement : structureElements) {
Expand All @@ -936,7 +936,7 @@ public CompletableFuture<List<Either<SymbolInformation, DocumentSymbol>>> docume
result.add(Either.forRight(ds));
}
}
};
}
}
}
resultFuture.complete(result);
Expand Down Expand Up @@ -1998,10 +1998,10 @@ private List<Diagnostic> computeDiags(String uri, int offset, ErrorProvider.Kind
Document doc = ec.openDocument();
long originalVersion = orgV != -1 ? orgV : documentVersion(doc);
Map<String, org.netbeans.api.lsp.Diagnostic> id2Errors = new HashMap<>();
ErrorProvider errorProvider = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc))
.lookup(ErrorProvider.class);
Collection<? extends ErrorProvider> errorProviders = MimeLookup.getLookup(DocumentUtilities.getMimeType(doc))
.lookupAll(ErrorProvider.class);
List<? extends org.netbeans.api.lsp.Diagnostic> errors;
if (errorProvider != null) {
if (!errorProviders.isEmpty()) {
ErrorProvider.Context context = new ErrorProvider.Context(file, offset, errorKind, hintsPrefsFile);
class CancelListener implements DocumentListener {
@Override
Expand All @@ -2024,7 +2024,7 @@ public void changedUpdate(DocumentEvent e) {}
try {
doc.addDocumentListener(l);
l.checkCancel();
errors = errorProvider.computeErrors(context);
errors = errorProviders.stream().flatMap(errorPorvider -> errorPorvider.computeErrors(context).stream()).collect(Collectors.toList());
} finally {
doc.removeDocumentListener(l);
}
Expand Down

0 comments on commit 5de8e61

Please sign in to comment.