Skip to content

Commit

Permalink
FixUsesPerformer,ImportDataCreator and UsedNamespaceCollector logic u…
Browse files Browse the repository at this point in the history
…pdate to fix imports in whole file, fix ModelUtils multiple default namespace detection
  • Loading branch information
rossluk committed Mar 20, 2023
1 parent 6928b96 commit d49732a
Show file tree
Hide file tree
Showing 24 changed files with 794 additions and 290 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ private static void setRemoveUnusedUses(final boolean removeUnusedUses) {
private static ImportData computeUses(final PHPParseResult parserResult, final int caretPosition) {
Map<String, List<UsedNamespaceName>> filteredExistingNames = new UsedNamesCollector(parserResult, caretPosition).collectNames();
Index index = parserResult.getModel().getIndexScope().getIndex();
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(parserResult, caretPosition);
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(parserResult.getModel().getFileScope(), caretPosition);
assert namespaceScope != null;
ImportData importData = new ImportDataCreator(filteredExistingNames, index, namespaceScope.getNamespaceName(), createOptions(parserResult)).create();
importData.caretPosition = caretPosition;
Expand Down

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.TreeSet;
import java.util.HashMap;
import java.util.TreeMap;
import org.netbeans.modules.php.api.PhpVersion;
import org.netbeans.modules.php.editor.actions.FixUsesAction.Options;
import org.netbeans.modules.php.editor.actions.ImportData.DataItem;
Expand Down Expand Up @@ -74,8 +75,22 @@ public ImportDataCreator(final Map<String, List<UsedNamespaceName>> usedNames, f
}

public ImportData create() {
for (String fqElementName : new TreeSet<>(usedNames.keySet())) {
processFQElementName(fqElementName);
for (Map.Entry<String, List<UsedNamespaceName>> entry : (new TreeMap<>(usedNames)).entrySet()) {
if (entry.getValue().size() > 1) {
Map<Integer, List<UsedNamespaceName>> scopeNames = new HashMap();
for (UsedNamespaceName usedName : entry.getValue()) {
Integer scopeOffset = usedName.getInScope().getBlockRange().getStart();
if (!scopeNames.containsKey(scopeOffset)) {
scopeNames.put(scopeOffset, new ArrayList());
}
scopeNames.get(scopeOffset).add(usedName);
}
for (Map.Entry<Integer, List<UsedNamespaceName>> keyNames : scopeNames.entrySet()) {
processFQElementName(entry.getKey(), keyNames.getValue());
}
} else {
processFQElementName(entry.getKey(), entry.getValue());
}
}
ImportData data = new ImportData();
for (PossibleItem possibleItem : possibleItems) {
Expand All @@ -85,7 +100,7 @@ public ImportData create() {
return data;
}

private void processFQElementName(final String fqElementName) {
private void processFQElementName(final String fqElementName, List<UsedNamespaceName> usedNames) {
Collection<FullyQualifiedElement> possibleFQElements = fetchPossibleFQElements(fqElementName);
Collection<FullyQualifiedElement> filteredPlatformConstsAndFunctions = filterPlatformConstsAndFunctions(possibleFQElements);
Collection<FullyQualifiedElement> filteredDuplicates = filterDuplicates(filteredPlatformConstsAndFunctions);
Expand All @@ -101,10 +116,11 @@ private void processFQElementName(final String fqElementName) {
} else {
Collection<FullyQualifiedElement> filteredFQElements = filterFQElementsFromCurrentNamespace(filteredExactUnqualifiedNames);
if (filteredFQElements.isEmpty()) {
possibleItems.add(new ReplaceItem(fqElementName, filteredExactUnqualifiedNames));
possibleItems.add(new ReplaceItem(fqElementName, usedNames, filteredExactUnqualifiedNames));
} else {
possibleItems.add(new ValidItem(
fqElementName,
usedNames,
filteredFQElements,
filteredFQElements.size() != filteredExactUnqualifiedNames.size()));
}
Expand Down Expand Up @@ -226,10 +242,12 @@ public void insertData(ImportData data) {

private final class ReplaceItem implements PossibleItem {
private final String fqName;
private final List<UsedNamespaceName> usedNames;
private final Collection<FullyQualifiedElement> filteredExactUnqualifiedNames;

public ReplaceItem(String fqName, Collection<FullyQualifiedElement> filteredExactUnqualifiedNames) {
public ReplaceItem(String fqName, List<UsedNamespaceName> usedNames, Collection<FullyQualifiedElement> filteredExactUnqualifiedNames) {
this.fqName = fqName;
this.usedNames = usedNames;
this.filteredExactUnqualifiedNames = filteredExactUnqualifiedNames;
}

Expand All @@ -241,7 +259,7 @@ public void insertData(ImportData data) {
? fqElement.getFullyQualifiedName().toString()
: fqElement.getName();
ItemVariant replaceItemVariant = new ItemVariant(itemVariantReplaceName, ItemVariant.UsagePolicy.CAN_BE_USED);
data.addJustToReplace(new DataItem(fqName, Collections.singletonList(replaceItemVariant), replaceItemVariant, usedNames.get(fqName)));
data.addJustToReplace(new DataItem(fqName, Collections.singletonList(replaceItemVariant), replaceItemVariant, usedNames));
}

private FullyQualifiedElement findBestElement() {
Expand All @@ -258,10 +276,12 @@ private FullyQualifiedElement findBestElement() {
private final class ValidItem implements PossibleItem {
private final Collection<FullyQualifiedElement> filteredFQElements;
private final String typeName;
private final List<UsedNamespaceName> usedNames;
private final boolean existsFQElementFromCurrentNamespace;

private ValidItem(String typeName, Collection<FullyQualifiedElement> filteredFQElements, boolean existsFQELEMENTFromCurrentNamespace) {
private ValidItem(String typeName, List<UsedNamespaceName> usedNames, Collection<FullyQualifiedElement> filteredFQElements, boolean existsFQELEMENTFromCurrentNamespace) {
this.typeName = typeName;
this.usedNames = usedNames;
this.filteredFQElements = filteredFQElements;
this.existsFQElementFromCurrentNamespace = existsFQELEMENTFromCurrentNamespace;
}
Expand Down Expand Up @@ -306,7 +326,7 @@ public void insertData(ImportData data) {
}
}
Collections.sort(variants);
data.add(new DataItem(typeName, variants, defaultValue, usedNames.get(typeName)));
data.add(new DataItem(typeName, variants, defaultValue, usedNames));
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
Expand All @@ -35,7 +36,6 @@
import org.netbeans.modules.php.editor.model.impl.Type;
import org.netbeans.modules.php.editor.parser.PHPParseResult;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.Block;
import org.netbeans.modules.php.editor.parser.astnodes.NamespaceDeclaration;
import org.netbeans.modules.php.editor.parser.astnodes.NamespaceName;
import org.netbeans.modules.php.editor.parser.astnodes.PHPDocTypeNode;
Expand Down Expand Up @@ -65,56 +65,19 @@ public UsedNamesCollector(final PHPParseResult parserResult, final int caretPosi
}

public Map<String, List<UsedNamespaceName>> collectNames() {
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(parserResult, caretPosition);
NamespaceScope namespaceScope = ModelUtils.getNamespaceScope(parserResult.getModel().getFileScope(), caretPosition);
assert namespaceScope != null;
OffsetRange offsetRange = namespaceScope.getBlockRange();
// in the following case, avoid being inserted incorrect uses
// because default namespace range is whole file...
// // caret here^
// declare (strict_types=1);
//
// namespace {
// class GlobalNamespace {}
// }
//
// namespace Test {
// class TestClass {
// public function __construct()
// {
// $test = new Foo();
// }
// public function test(?Foo $foo): ?Foo
// {
// return null;
// }
// }
// }
if (namespaceScope.isDefaultNamespace()) {
NamespaceDeclarationVisitor namespaceDeclarationVisitor = new NamespaceDeclarationVisitor();
parserResult.getProgram().accept(namespaceDeclarationVisitor);
List<NamespaceDeclaration> globalNamespaces = namespaceDeclarationVisitor.getGlobalNamespaceDeclarations();
if (!globalNamespaces.isEmpty()) {
Block body = globalNamespaces.get(0).getBody();
offsetRange = new OffsetRange(body.getStartOffset(), body.getEndOffset());
}
for (NamespaceDeclaration globalNamespace : globalNamespaces) {
if (globalNamespace.getBody().getStartOffset() <= caretPosition
&& caretPosition <= globalNamespace.getBody().getEndOffset()) {
offsetRange = new OffsetRange(globalNamespace.getBody().getStartOffset(), globalNamespace.getBody().getEndOffset());
}
}
}
Collection<? extends UseScope> declaredUses = namespaceScope.getAllDeclaredSingleUses();
NamespaceNameVisitor namespaceNameVisitor = new NamespaceNameVisitor(offsetRange);
NamespaceNameVisitor namespaceNameVisitor = new NamespaceNameVisitor(offsetRange, namespaceScope);
parserResult.getProgram().accept(namespaceNameVisitor);
possibleNames = namespaceNameVisitor.getExistingNames();
return filterNamesWithoutUses(declaredUses);
return filterNamesWithoutUses(namespaceNameVisitor.getScopeMap());
}

private Map<String, List<UsedNamespaceName>> filterNamesWithoutUses(final Collection<? extends UseScope> declaredUses) {
final Map<String, List<UsedNamespaceName>> result = new HashMap<>();
private Map<String, List<UsedNamespaceName>> filterNamesWithoutUses(final Map<String, NamespaceScope> scopeMap) {
final Map<String, List<UsedNamespaceName>> result = new LinkedHashMap<>();
for (Map.Entry<String, List<UsedNamespaceName>> entry : possibleNames.entrySet()) {
if (!existsUseForTypeName(declaredUses, QualifiedName.create(entry.getKey()))) {
if (!existsUseForTypeName(scopeMap.get(entry.getKey()).getAllDeclaredSingleUses(), QualifiedName.create(entry.getKey()))) {
result.put(entry.getKey(), entry.getValue());
}
}
Expand Down Expand Up @@ -143,10 +106,14 @@ private boolean existsUseForTypeName(final Collection<? extends UseScope> declar

private static class NamespaceNameVisitor extends DefaultVisitor {
private final OffsetRange offsetRange;
private final Map<String, List<UsedNamespaceName>> existingNames = new HashMap<>();
private final NamespaceScope namespaceScope;
private NamespaceScope currentScope;
private final Map<String, NamespaceScope> scopeMap = new HashMap<>();
private final Map<String, List<UsedNamespaceName>> existingNames = new LinkedHashMap<>();

public NamespaceNameVisitor(OffsetRange offsetRange) {
public NamespaceNameVisitor(OffsetRange offsetRange, NamespaceScope namespaceScope) {
this.offsetRange = offsetRange;
this.namespaceScope = namespaceScope;
}

@Override
Expand All @@ -166,26 +133,31 @@ private boolean isInNamespace(ASTNode node) {

@Override
public void visit(Program node) {
// just for safety to reset initial scope on a next run
currentScope = namespaceScope;
scan(node.getStatements());
scan(node.getComments());
}

@Override
public void visit(NamespaceDeclaration node) {
if (namespaceScope.isDefaultNamespace()) {
currentScope = ModelUtils.getNamespaceScope(namespaceScope.getFileScope(), node.getBody().getStartOffset());
}
scan(node.getBody());
}

@Override
public void visit(NamespaceName node) {
UsedNamespaceName usedName = new UsedNamespaceName(node);
UsedNamespaceName usedName = new UsedNamespaceName(node, currentScope);
if (isValidTypeName(usedName.getName())) {
processUsedName(usedName);
}
}

@Override
public void visit(PHPDocTypeNode node) {
UsedNamespaceName usedName = new UsedNamespaceName(node);
UsedNamespaceName usedName = new UsedNamespaceName(node, currentScope);
if (isValidTypeName(usedName.getName()) && isValidAliasTypeName(usedName.getName())) {
processUsedName(usedName);
}
Expand All @@ -204,6 +176,7 @@ private void processUsedName(final UsedNamespaceName usedName) {
if (usedNames == null) {
usedNames = new LinkedList<>();
existingNames.put(usedName.getName(), usedNames);
scopeMap.put(usedName.getName(), usedName.getInScope());
}
usedNames.add(usedName);
}
Expand All @@ -212,6 +185,9 @@ public Map<String, List<UsedNamespaceName>> getExistingNames() {
return Collections.unmodifiableMap(existingNames);
}

public Map<String, NamespaceScope> getScopeMap() {
return Collections.unmodifiableMap(scopeMap);
}
}

private static class NamespaceDeclarationVisitor extends DefaultVisitor {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
package org.netbeans.modules.php.editor.actions;

import org.netbeans.modules.php.editor.api.QualifiedName;
import org.netbeans.modules.php.editor.model.NamespaceScope;
import org.netbeans.modules.php.editor.parser.astnodes.ASTNode;
import org.netbeans.modules.php.editor.parser.astnodes.NamespaceName;
import org.netbeans.modules.php.editor.parser.astnodes.PHPDocTypeNode;
Expand All @@ -29,21 +30,28 @@
*/
public class UsedNamespaceName {
private final ASTNode node;
private final NamespaceScope inScope;
private final QualifiedName qualifiedName;

public UsedNamespaceName(NamespaceName node) {
public UsedNamespaceName(NamespaceName node, NamespaceScope inScope) {
this.node = node;
this.inScope = inScope;
this.qualifiedName = QualifiedName.create(node);
}

public UsedNamespaceName(PHPDocTypeNode node) {
public UsedNamespaceName(PHPDocTypeNode node, NamespaceScope inScope) {
this.node = node;
this.inScope = inScope;
this.qualifiedName = QualifiedName.create(node.getValue());
}

public int getOffset() {
return node.getStartOffset();
}

public NamespaceScope getInScope() {
return inScope;
}

public int getReplaceLength() {
return qualifiedName.toString().length();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,22 @@ public static Set<AliasedName> getAliasedNames(final Model model, final int offs

public static NamespaceScope getNamespaceScope(NamespaceDeclaration currenNamespace, FileScope fileScope) {
NamespaceDeclarationInfo ndi = currenNamespace != null ? NamespaceDeclarationInfo.create(currenNamespace) : null;
NamespaceScope currentScope = ndi != null
? ModelUtils.getFirst(ModelUtils.filter(fileScope.getDeclaredNamespaces(), ndi.getName()))
: fileScope.getDefaultDeclaredNamespace();
NamespaceScope currentScope;
if (ndi != null) {
// we could have many unnamed namespaces, so should select right one
final int currentNamespaceNameStartOffset = ndi.getRange().getStart();
currentScope = ndi.isDefaultNamespace()
? ModelUtils.getFirst(filter(fileScope.getDeclaredNamespaces(), new ElementFilter<NamespaceScope>() {
@Override
public boolean isAccepted(NamespaceScope element) {
return element.getNameRange().getStart() == currentNamespaceNameStartOffset;
}
}))
: ModelUtils.getFirst(ModelUtils.filter(fileScope.getDeclaredNamespaces(), ndi.getName()));
} else {
currentScope = fileScope.getDefaultDeclaredNamespace();
}

return currentScope;
}

Expand Down Expand Up @@ -435,70 +448,6 @@ public static NamespaceScope getNamespaceScope(FileScope fileScope, int offset)
return retval;
}

public static NamespaceScope getNamespaceScope(PHPParseResult parserResult, int offset) {
// NETBEANS-4978
// if the caret position is not in each namespace scope, get the first namespace scope
// e.g.
// [original]
// // caret is here^
// declare (strict_types=1);
// namespace Foo;
// ...
// namespace Bar;
//
// [inserted]
// // caret is here
// declare (strict_types=1);
// namespace Foo;
// use ...;
// ...
// namespace Bar;
FileScope fileScope = parserResult.getModel().getFileScope();
List<NamespaceDeclaration> namespaceDeclarations = getNamespaceDeclarations(parserResult);
boolean isBracketed = false;
if (!namespaceDeclarations.isEmpty()) {
isBracketed = namespaceDeclarations.get(0).isBracketed();
}
NamespaceScope retval = ModelUtils.getNamespaceScope(fileScope, offset);
assert retval != null;
Collection<? extends NamespaceScope> declaredNamespaces = fileScope.getDeclaredNamespaces();
if (retval != null && retval.isDefaultNamespace()) {
// get the first namespace scope
if (isBracketed) {
for (NamespaceDeclaration namespaceDeclaration : namespaceDeclarations) {
if (namespaceDeclaration.getName() == null
&& namespaceDeclaration.getStartOffset() <= offset && offset <= namespaceDeclaration.getEndOffset()) {
return retval;
}
}
if (namespaceDeclarations.get(0).getName() == null) {
return retval;
}
retval = getFirstNamespaceScope(declaredNamespaces, retval);
}
if (!isBracketed && !namespaceDeclarations.isEmpty()) {
// if namespace is not bracketed,
// both the global namespace and another namespace don't exist in the same file
retval = getFirstNamespaceScope(declaredNamespaces, retval);
}
}
return retval;
}

private static NamespaceScope getFirstNamespaceScope(Collection<? extends NamespaceScope> declaredNamespaces, NamespaceScope namespace) {
NamespaceScope retval = namespace;
for (NamespaceScope namespaceScope : declaredNamespaces) {
if (!namespaceScope.isDefaultNamespace()) {
if (retval.isDefaultNamespace()) {
retval = namespaceScope;
} else if (retval.getBlockRange().getStart() > namespaceScope.getBlockRange().getStart()) {
retval = namespaceScope;
}
}
}
return retval;
}

@CheckForNull
public static TypeScope getTypeScope(ModelElement element) {
TypeScope retval = (element instanceof TypeScope) ? (TypeScope) element : null;
Expand Down
Loading

0 comments on commit d49732a

Please sign in to comment.