Added JDT-derived import rewrite for snippets

This commit is contained in:
nsingh
2017-09-28 16:08:25 -07:00
parent ebe95edcd8
commit 5b434b16c4
5 changed files with 584 additions and 10 deletions

View File

@@ -75,7 +75,7 @@ public class BootJavaCompletionEngine implements ICompletionEngine {
System.out.println("AST node found: " + node.getClass().getName());
Collection<ICompletionProposal> completions = new ArrayList<ICompletionProposal>();
completions.addAll(collectCompletionsForAnnotations(node, offset, document));
completions.addAll(snippets.getCompletions(document, offset, node));
completions.addAll(snippets.getCompletions(document, offset, node, cu));
return completions;
}

View File

@@ -0,0 +1,558 @@
/*******************************************************************************
* Derived from:
* org.eclipse.jdt.core.dom.rewrite.ImportRewrite
*
* for use in STS4, where IProject and ICompilationUnit are not available when parsing a Java source.
*
* Original license:
*
* Copyright (c) 2000, 2016 IBM Corporation and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* IBM Corporation - initial API and implementation
* John Glassmyer <jogl@google.com> - import group sorting is broken - https://bugs.eclipse.org/430303
* Lars Vogel <Lars.Vogel@vogella.com> - Contributions for
* Bug 473178
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.jdt.imports;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.compiler.CharOperation;
import org.eclipse.jdt.core.dom.ASTParser;
import org.eclipse.jdt.core.dom.AbstractTypeDeclaration;
import org.eclipse.jdt.core.dom.Comment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.PackageDeclaration;
import org.eclipse.jdt.core.dom.PrimitiveType;
import org.eclipse.jdt.core.dom.SimpleName;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.util.text.IDocument;
/**
* The {@link ImportRewrite} helps updating imports following a import order and on-demand imports threshold as configured by a project.
* <p>
* The import rewrite is created on a compilation unit and collects references to types that are added or removed. When adding imports, e.g. using
* {@link #addImport(String)}, the import rewrite evaluates if the type can be imported and returns the a reference to the type that can be used in code.
* This reference is either unqualified if the import could be added, or fully qualified if the import failed due to a conflict with another element of the same name.
* </p>
* <p>
* On {@link #rewriteImports(IProgressMonitor)} the rewrite translates these descriptions into
* text edits that can then be applied to the original source. The rewrite infrastructure tries to generate minimal text changes and only
* works on the import statements. It is possible to combine the result of an import rewrite with the result of a {@link org.eclipse.jdt.core.dom.rewrite.ASTRewrite}
* as long as no import statements are modified by the AST rewrite.
* </p>
* <p>The options controlling the import order and on-demand thresholds are:
* <ul><li>{@link #setImportOrder(String[])} specifies the import groups and their preferred order</li>
* <li>{@link #setOnDemandImportThreshold(int)} specifies the number of imports in a group needed for a on-demand import statement (star import)</li>
* <li>{@link #setStaticOnDemandImportThreshold(int)} specifies the number of static imports in a group needed for a on-demand import statement (star import)</li>
*</ul>
* This class is not intended to be subclassed.
* </p>
* @since 3.2
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public final class ImportRewrite {
/**
* A {@link ImportRewrite.ImportRewriteContext} can optionally be used in e.g. {@link ImportRewrite#addImport(String, ImportRewrite.ImportRewriteContext)} to
* give more information about the types visible in the scope. These types can be for example inherited inner types where it is
* unnecessary to add import statements for.
*
* </p>
* <p>
* This class can be implemented by clients.
* </p>
*/
public static abstract class ImportRewriteContext {
/**
* Result constant signaling that the given element is know in the context.
*/
public final static int RES_NAME_FOUND= 1;
/**
* Result constant signaling that the given element is not know in the context.
*/
public final static int RES_NAME_UNKNOWN= 2;
/**
* Result constant signaling that the given element is conflicting with an other element in the context.
*/
public final static int RES_NAME_CONFLICT= 3;
/**
* Result constant signaling that the given element must be imported explicitly (and must not be folded into
* an on-demand import or filtered as an implicit import).
*
* @since 3.11
*/
public final static int RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT= 4;
/**
* Kind constant specifying that the element is a type import.
*/
public final static int KIND_TYPE= 1;
/**
* Kind constant specifying that the element is a static field import.
*/
public final static int KIND_STATIC_FIELD= 2;
/**
* Kind constant specifying that the element is a static method import.
*/
public final static int KIND_STATIC_METHOD= 3;
/**
* Searches for the given element in the context and reports if the element is known ({@link #RES_NAME_FOUND}),
* unknown ({@link #RES_NAME_UNKNOWN}), unknown in the context but known to require an explicit import
* ({@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}), or if its name conflicts ({@link #RES_NAME_CONFLICT})
* with an other element.
*
* @param qualifier The qualifier of the element, can be package or the qualified name of a type
* @param name The simple name of the element; either a type, method or field name or * for on-demand imports.
* @param kind The kind of the element. Can be either {@link #KIND_TYPE}, {@link #KIND_STATIC_FIELD} or
* {@link #KIND_STATIC_METHOD}. Implementors should be prepared for new, currently unspecified kinds and return
* {@link #RES_NAME_UNKNOWN} by default.
* @return Returns the result of the lookup. Can be either {@link #RES_NAME_FOUND}, {@link #RES_NAME_UNKNOWN},
* {@link #RES_NAME_CONFLICT}, or {@link #RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT}.
*/
public abstract int findInContext(String qualifier, String name, int kind);
}
private static final char STATIC_PREFIX= 's';
private static final char NORMAL_PREFIX= 'n';
private final ImportRewriteContext defaultContext;
private final CompilationUnit astRoot;
private final boolean restoreExistingImports;
private final List existingImports;
private List<String> addedImports;
/**
* Simple names of non-static imports which must not be reduced into on-demand imports
* or filtered out as implicit.
*/
private Set<String> typeExplicitSimpleNames;
private boolean filterImplicitImports;
private boolean useContextToFilterImplicitImports;
/**
* Creates an {@link ImportRewrite} from an AST ({@link CompilationUnit}). The AST has to be created from an
* {@link ICompilationUnit}, that means {@link ASTParser#setSource(ICompilationUnit)} has been used when creating the
* AST. If <code>restoreExistingImports</code> is <code>true</code>, all existing imports are kept, and new imports
* will be inserted at best matching locations. If <code>restoreExistingImports</code> is <code>false</code>, the
* existing imports will be removed and only the newly added imports will be created.
* <p>
* Note that this method is more efficient than using {@link #create(ICompilationUnit, boolean)} if an AST is already available.
* </p>
* @param astRoot the AST root node to create the imports for
* @param restoreExistingImports specifies if the existing imports should be kept or removed.
* @return the created import rewriter.
* @throws IllegalArgumentException thrown when the passed AST is null or was not created from a compilation unit.
*/
public static ImportRewrite create(CompilationUnit astRoot, boolean restoreExistingImports) {
if (astRoot == null) {
throw new IllegalArgumentException("AST must not be null"); //$NON-NLS-1$
}
List existingImport= null;
if (restoreExistingImports) {
existingImport= new ArrayList();
List imports= astRoot.imports();
for (int i= 0; i < imports.size(); i++) {
ImportDeclaration curr= (ImportDeclaration) imports.get(i);
StringBuffer buf= new StringBuffer();
buf.append(curr.isStatic() ? STATIC_PREFIX : NORMAL_PREFIX).append(curr.getName().getFullyQualifiedName());
if (curr.isOnDemand()) {
if (buf.length() > 1)
buf.append('.');
buf.append('*');
}
existingImport.add(buf.toString());
}
}
return new ImportRewrite(astRoot, existingImport);
}
private ImportRewrite(CompilationUnit astRoot, List existingImports) {
this.astRoot= astRoot; // might be null
if (existingImports != null) {
this.existingImports= existingImports;
this.restoreExistingImports= !existingImports.isEmpty();
} else {
this.existingImports= new ArrayList();
this.restoreExistingImports= false;
}
this.filterImplicitImports= true;
// consider that no contexts are used
this.useContextToFilterImplicitImports = false;
this.defaultContext= new ImportRewriteContext() {
@Override
public int findInContext(String qualifier, String name, int kind) {
return findInImports(qualifier, name, kind);
}
};
this.addedImports= new ArrayList<>();
this.typeExplicitSimpleNames = new HashSet<>();
}
/**
* Returns the default rewrite context that only knows about the imported types. Clients
* can write their own context and use the default context for the default behavior.
* @return the default import rewrite context.
*/
public ImportRewriteContext getDefaultImportRewriteContext() {
return this.defaultContext;
}
/**
* Specifies that implicit imports (for types in <code>java.lang</code>, types in the same package as the rewrite
* compilation unit, and types in the compilation unit's main type) should not be created, except if necessary to
* resolve an on-demand import conflict.
* <p>
* The filter is enabled by default.
* </p>
* <p>
* Note: {@link #setUseContextToFilterImplicitImports(boolean)} can be used to filter implicit imports
* when a context is used.
* </p>
*
* @param filterImplicitImports
* if <code>true</code>, implicit imports will be filtered
*
* @see #setUseContextToFilterImplicitImports(boolean)
*/
public void setFilterImplicitImports(boolean filterImplicitImports) {
this.filterImplicitImports= filterImplicitImports;
}
/**
* Sets whether a context should be used to properly filter implicit imports.
* <p>
* By default, the option is disabled to preserve pre-3.6 behavior.
* </p>
* <p>
* When this option is set, the context passed to the <code>addImport*(...)</code> methods is used to determine
* whether an import can be filtered because the type is implicitly visible. Note that too many imports
* may be kept if this option is set and <code>addImport*(...)</code> methods are called without a context.
* </p>
*
* @param useContextToFilterImplicitImports the given setting
*
* @see #setFilterImplicitImports(boolean)
* @since 3.6
*/
public void setUseContextToFilterImplicitImports(boolean useContextToFilterImplicitImports) {
this.useContextToFilterImplicitImports = useContextToFilterImplicitImports;
}
private static int compareImport(char prefix, String qualifier, String name, String curr) {
if (curr.charAt(0) != prefix || !curr.endsWith(name)) {
return ImportRewriteContext.RES_NAME_UNKNOWN;
}
curr= curr.substring(1); // remove the prefix
if (curr.length() == name.length()) {
if (qualifier.length() == 0) {
return ImportRewriteContext.RES_NAME_FOUND;
}
return ImportRewriteContext.RES_NAME_CONFLICT;
}
// at this place: curr.length > name.length
int dotPos= curr.length() - name.length() - 1;
if (curr.charAt(dotPos) != '.') {
return ImportRewriteContext.RES_NAME_UNKNOWN;
}
if (qualifier.length() != dotPos || !curr.startsWith(qualifier)) {
return ImportRewriteContext.RES_NAME_CONFLICT;
}
return ImportRewriteContext.RES_NAME_FOUND;
}
/**
* Not API, package visibility as accessed from an anonymous type
*/
/* package */ final int findInImports(String qualifier, String name, int kind) {
boolean allowAmbiguity= (kind == ImportRewriteContext.KIND_STATIC_METHOD) || (name.length() == 1 && name.charAt(0) == '*');
List imports= this.existingImports;
char prefix= (kind == ImportRewriteContext.KIND_TYPE) ? NORMAL_PREFIX : STATIC_PREFIX;
for (int i= imports.size() - 1; i >= 0 ; i--) {
String curr= (String) imports.get(i);
int res= compareImport(prefix, qualifier, name, curr);
if (res != ImportRewriteContext.RES_NAME_UNKNOWN) {
if (!allowAmbiguity || res == ImportRewriteContext.RES_NAME_FOUND) {
if (prefix != STATIC_PREFIX) {
return res;
}
}
}
}
String packageName = getPackageName();
if (kind == ImportRewriteContext.KIND_TYPE) {
if (this.filterImplicitImports && this.useContextToFilterImplicitImports) {
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
// String mainTypeSimpleName= JavaCore.removeJavaLikeExtension(this.compilationUnit.getElementName());
// String mainTypeName= Util.concatenateName(packageName, mainTypeSimpleName, '.');
// if (qualifier.equals(packageName)
// || mainTypeName.equals(Util.concatenateName(qualifier, name, '.'))) {
// return ImportRewriteContext.RES_NAME_FOUND;
// }
if (this.astRoot != null) {
List<AbstractTypeDeclaration> types = this.astRoot.types();
int nTypes = types.size();
for (int i = 0; i < nTypes; i++) {
AbstractTypeDeclaration type = types.get(i);
SimpleName simpleName = type.getName();
if (simpleName.getIdentifier().equals(name)) {
return qualifier.equals(packageName)
? ImportRewriteContext.RES_NAME_FOUND
: ImportRewriteContext.RES_NAME_CONFLICT;
}
}
} else {
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
// try {
// IType[] types = this.compilationUnit.getTypes();
// int nTypes = types.length;
// for (int i = 0; i < nTypes; i++) {
// IType type = types[i];
// String typeName = type.getElementName();
// if (typeName.equals(name)) {
// return qualifier.equals(packageName)
// ? ImportRewriteContext.RES_NAME_FOUND
// : ImportRewriteContext.RES_NAME_CONFLICT;
// }
// }
// } catch (JavaModelException e) {
// // don't want to throw an exception here
// }
}
}
}
return ImportRewriteContext.RES_NAME_UNKNOWN;
}
private String getPackageName() {
// [STS4] No ICompilationUnit available as there is no class file or associated IJavaElement available for the source
// this.compilationUnit.getParent().getElementName();
return this.astRoot.getPackage().getName().getFullyQualifiedName();
}
/**
* Adds a new import to the rewriter's record and returns a type reference that can be used
* in the code. The type binding can only be an array or non-generic type.
* <p>
* No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
* </p>
* <p>
* The content of the compilation unit itself is actually not modified
* in any way by this method; rather, the rewriter just records that a new import has been added.
* </p>
* @param qualifiedTypeName the qualified type name of the type to be added
* @param context an optional context that knows about types visible in the current scope or <code>null</code>
* to use the default context only using the available imports.
* @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
* or else a qualified name if an import conflict prevented an import.
*/
public String addImport(String qualifiedTypeName, ImportRewriteContext context) {
int angleBracketOffset= qualifiedTypeName.indexOf('<');
if (angleBracketOffset != -1) {
return internalAddImport(qualifiedTypeName.substring(0, angleBracketOffset), context) + qualifiedTypeName.substring(angleBracketOffset);
}
int bracketOffset= qualifiedTypeName.indexOf('[');
if (bracketOffset != -1) {
return internalAddImport(qualifiedTypeName.substring(0, bracketOffset), context) + qualifiedTypeName.substring(bracketOffset);
}
return internalAddImport(qualifiedTypeName, context);
}
/**
* Adds a new import to the rewriter's record and returns a type reference that can be used
* in the code. The type binding can only be an array or non-generic type.
* <p>
* No imports are added for types that are already known. If a import for a type is recorded to be removed, this record is discarded instead.
* </p>
* <p>
* The content of the compilation unit itself is actually not modified
* in any way by this method; rather, the rewriter just records that a new import has been added.
* </p>
* @param qualifiedTypeName the qualified type name of the type to be added
* @return a type reference for the given qualified type name. The type name is a simple name if an import could be used,
* or else a qualified name if an import conflict prevented an import.
*/
public String addImport(String qualifiedTypeName) {
return addImport(qualifiedTypeName, this.defaultContext);
}
private String internalAddImport(String fullTypeName, ImportRewriteContext context) {
int idx= fullTypeName.lastIndexOf('.');
String typeContainerName, typeName;
if (idx != -1) {
typeContainerName= fullTypeName.substring(0, idx);
typeName= fullTypeName.substring(idx + 1);
} else {
typeContainerName= ""; //$NON-NLS-1$
typeName= fullTypeName;
}
if (typeContainerName.length() == 0 && PrimitiveType.toCode(typeName) != null) {
return fullTypeName;
}
if (context == null)
context= this.defaultContext;
int res= context.findInContext(typeContainerName, typeName, ImportRewriteContext.KIND_TYPE);
if (res == ImportRewriteContext.RES_NAME_CONFLICT) {
return fullTypeName;
}
if (res == ImportRewriteContext.RES_NAME_UNKNOWN) {
addEntry(NORMAL_PREFIX + fullTypeName);
}
if (res == ImportRewriteContext.RES_NAME_UNKNOWN_NEEDS_EXPLICIT_IMPORT) {
addEntry(NORMAL_PREFIX + fullTypeName);
this.typeExplicitSimpleNames.add(typeName);
}
return typeName;
}
private void addEntry(String entry) {
this.existingImports.add(entry);
this.addedImports.add(entry);
}
/**
* Returns all non-static imports that are recorded to be added.
*
* @return the imports recorded to be added.
*/
public String[] getAddedImports() {
return filterFromList(this.addedImports, NORMAL_PREFIX);
}
/**
* Returns <code>true</code> if imports have been recorded to be added or removed.
* @return boolean returns if any changes to imports have been recorded.
*/
public boolean hasRecordedChanges() {
return !this.restoreExistingImports
|| !this.addedImports.isEmpty();
}
private static String[] filterFromList(List<String> imports, char prefix) {
if (imports == null) {
return CharOperation.NO_STRINGS;
}
List<String> res= new ArrayList<>();
for (String curr : imports) {
if (prefix == curr.charAt(0)) {
res.add(curr.substring(1));
}
}
return res.toArray(new String[res.size()]);
}
/**
* Reads the positions of each existing import declaration along with any associated comments,
* and returns these in a list whose iteration order reflects the existing order of the imports
* in the compilation unit.
*/
private int getAddedImportsInsertLocation() {
List<ImportDeclaration> importDeclarations = astRoot.imports();
if (importDeclarations == null) {
importDeclarations = Collections.emptyList();
}
List<Comment> comments = astRoot.getCommentList();
int currentCommentIndex = 0;
// Skip over package and file header comments (see https://bugs.eclipse.org/121428).
ImportDeclaration firstImport = importDeclarations.get(0);
PackageDeclaration packageDeclaration = astRoot.getPackage();
int firstImportStartPosition = packageDeclaration == null
? firstImport.getStartPosition()
: astRoot.getExtendedStartPosition(packageDeclaration)
+ astRoot.getExtendedLength(packageDeclaration);
while (currentCommentIndex < comments.size()
&& comments.get(currentCommentIndex).getStartPosition() < firstImportStartPosition) {
currentCommentIndex++;
}
int previousExtendedEndPosition = -1;
for (ImportDeclaration currentImport : importDeclarations) {
int extendedEndPosition = astRoot.getExtendedStartPosition(currentImport)
+ astRoot.getExtendedLength(currentImport);
int commentAfterImportIndex = currentCommentIndex;
while (commentAfterImportIndex < comments.size()
&& comments.get(commentAfterImportIndex).getStartPosition() < extendedEndPosition) {
commentAfterImportIndex++;
}
currentCommentIndex = commentAfterImportIndex;
previousExtendedEndPosition = extendedEndPosition;
}
return previousExtendedEndPosition;
}
public DocumentEdits createEdit(IDocument doc) {
DocumentEdits edits = null;
StringBuffer buffer = new StringBuffer();
String[] createdImprts = getAddedImports();
if (createdImprts != null && createdImprts.length >0) {
edits =new DocumentEdits(doc);
buffer.append('\n');
for (String imp : createdImprts) {
buffer.append("import ");
buffer.append(imp);
buffer.append(';');
buffer.append('\n');
}
edits.insert(getAddedImportsInsertLocation(), buffer.toString());
}
return edits;
}
}

View File

@@ -14,6 +14,7 @@ import java.util.List;
import java.util.Optional;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
@@ -44,12 +45,13 @@ public class JavaSnippet {
}
public Optional<ICompletionProposal> generateCompletion(Supplier<SnippetBuilder> snippetBuilderFactory,
DocumentRegion query, ASTNode node) {
DocumentRegion query, ASTNode node, CompilationUnit cu) {
if (context.appliesTo(node)) {
return Optional.of(
new JavaSnippetCompletion(snippetBuilderFactory,
query,
cu,
this
)
);
@@ -66,8 +68,8 @@ public class JavaSnippet {
return this.template;
}
public List<String> getImports() {
return this.imports;
public Optional<List<String>> getImports() {
return Optional.of(this.imports);
}
public CompletionItemKind getKind() {

View File

@@ -12,7 +12,9 @@ package org.springframework.ide.vscode.boot.java.snippets;
import java.util.Optional;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.lsp4j.CompletionItemKind;
import org.springframework.ide.vscode.boot.java.jdt.imports.ImportRewrite;
import org.springframework.ide.vscode.commons.languageserver.completion.DocumentEdits;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
@@ -27,10 +29,12 @@ public class JavaSnippetCompletion implements ICompletionProposal{
private DocumentRegion query;
private JavaSnippet javaSnippet;
private Supplier<SnippetBuilder> snippetBuilderFactory;
private CompilationUnit cu;
public JavaSnippetCompletion(Supplier<SnippetBuilder> snippetBuilderFactory, DocumentRegion query, JavaSnippet javaSnippet) {
public JavaSnippetCompletion(Supplier<SnippetBuilder> snippetBuilderFactory, DocumentRegion query, CompilationUnit cu, JavaSnippet javaSnippet) {
this.snippetBuilderFactory = snippetBuilderFactory;
this.query = query;
this.cu = cu;
this.javaSnippet = javaSnippet;
}
@@ -61,8 +65,17 @@ public class JavaSnippetCompletion implements ICompletionProposal{
@Override
public Optional<DocumentEdits> getAdditionalEdit() {
DocumentEdits edit = new DocumentEdits(query.getDocument());
edit.insert(0, "import my.stuff;\n");
return Optional.of(edit );
ImportRewrite rewrite = ImportRewrite.create(cu, true);
javaSnippet.getImports().ifPresent((imprts ->
{
for (String imprt : imprts) {
rewrite.addImport(imprt);
}
}));
DocumentEdits edit = rewrite.createEdit(query.getDocument());
return Optional.of(edit);
}
}

View File

@@ -15,6 +15,7 @@ import java.util.Collection;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.springframework.ide.vscode.commons.languageserver.completion.ICompletionProposal;
import org.springframework.ide.vscode.commons.languageserver.util.DocumentRegion;
import org.springframework.ide.vscode.commons.languageserver.util.PrefixFinder;
@@ -46,14 +47,14 @@ public class JavaSnippetManager {
}
public Collection<ICompletionProposal> getCompletions(IDocument doc, int offset, ASTNode node) {
public Collection<ICompletionProposal> getCompletions(IDocument doc, int offset, ASTNode node, CompilationUnit cu) {
Collection<ICompletionProposal> completions = new ArrayList<>();
DocumentRegion query = PREFIX_FINDER.getPrefixRegion(doc, offset);
for (JavaSnippet javaSnippet : snippets) {
if (FuzzyMatcher.matchScore(query.toString(), javaSnippet.getName()) != 0) {
javaSnippet.generateCompletion(snippetBuilderFactory, query, node)
javaSnippet.generateCompletion(snippetBuilderFactory, query, node, cu)
.ifPresent((completion) -> completions.add(completion));
}
}