Skip to content

Commit

Permalink
Adding import statements to files using package local classes (no imp…
Browse files Browse the repository at this point in the history
…ort statement) when they are moved.
  • Loading branch information
m0rkeulv committed Jul 11, 2024
1 parent 210c131 commit fa2e921
Show file tree
Hide file tree
Showing 13 changed files with 158 additions and 48 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.intellij.codeInsight.hint.HintManager;
import com.intellij.codeInsight.hint.QuestionAction;
import com.intellij.codeInsight.intention.preview.IntentionPreviewInfo;
import com.intellij.codeInsight.navigation.PsiTargetNavigator;
import com.intellij.codeInspection.HintAction;
import com.intellij.codeInspection.LocalQuickFix;
Expand All @@ -31,12 +32,14 @@
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.TextRange;
import com.intellij.plugins.haxe.HaxeBundle;
import com.intellij.plugins.haxe.HaxeFileType;
import com.intellij.plugins.haxe.lang.psi.HaxeClass;
import com.intellij.plugins.haxe.lang.psi.HaxeComponent;
import com.intellij.plugins.haxe.util.HaxeAddImportHelper;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.search.PsiElementProcessor;
import com.intellij.psi.util.PsiUtilCore;
import com.intellij.util.IncorrectOperationException;
import org.jetbrains.annotations.NotNull;

Expand Down Expand Up @@ -119,8 +122,25 @@ else if (!candidates.isEmpty()) {
}

private void doImport(final PsiElement component) {
WriteCommandAction.writeCommandAction(myType.getProject(), myType.getContainingFile())
.run(() -> HaxeAddImportHelper.addImport(((HaxeClass)component).getQualifiedName(), myType.getContainingFile()));
PsiFile file = myType.getContainingFile();

WriteCommandAction.writeCommandAction(myType.getProject(), file)
.run(() -> {
HaxeAddImportHelper.addImport(((HaxeClass)component).getQualifiedName(), file);
PsiUtilCore.ensureValid(file);
});
}

@Override
public @NotNull IntentionPreviewInfo generatePreview(@NotNull Project project, @NotNull Editor editor, @NotNull PsiFile original) {
PsiFile file = (PsiFile)original.copy();

HaxeComponent next = candidates.iterator().next();
if (next instanceof HaxeClass haxeClass) {
HaxeAddImportHelper.addImport((haxeClass).getQualifiedName(), file);
return new IntentionPreviewInfo.CustomDiff(HaxeFileType.INSTANCE, null, original.getText(), file.getText(), true);
}
return HintAction.super.generatePreview(project, editor, file);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
import static com.intellij.plugins.haxe.ide.completion.HaxeCommonCompletionPattern.*;
import static com.intellij.plugins.haxe.ide.completion.HaxeKeywordCompletionPatterns.*;
import static com.intellij.plugins.haxe.ide.completion.HaxeKeywordCompletionUtil.*;
import static com.intellij.plugins.haxe.ide.completion.HaxeKeywordCompletionUtil.addKeywords;
import static com.intellij.plugins.haxe.ide.completion.KeywordCompletionData.keywordOnly;
import static com.intellij.plugins.haxe.ide.completion.KeywordCompletionData.keywordWithSpace;
import static com.intellij.plugins.haxe.lang.lexer.HaxeTokenTypeSets.*;
Expand Down Expand Up @@ -95,6 +96,10 @@ private static void suggestKeywords(PsiElement position, @NotNull CompletionResu

List<LookupElement> lookupElements = new ArrayList<>();

if(completionElementAsComment != null && completionElementAsComment.getParent() == completionElementAsComment.getContainingFile()) {
addKeywords(lookupElements, PACKAGE_KEYWORD);
}

boolean isPPExpression = psiElement().withElementType(PPEXPRESSION).accepts(position);
// avoid showing keyword suggestions when not relevant
if (allowLookupPattern.accepts(completionElementAsComment)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ public boolean isEnabledByDefault() {
@NotNull
@Override
public String getShortName() {
return "HaxeUnusedVariable";
return "HaxeUnusedVar";
}

@Nullable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1066,12 +1066,60 @@ private void bindToClass(PsiClass element) {

private void handleClassMovement(PsiClass element) {
String thisFqn = getQualifiedName();
//This reference is not a fully qualified name. Nothing to do.
String newFqn = getFqn((HaxeClassDeclaration)element);
//This reference is not a fully qualified name.
if (!thisFqn.contains(".")) {
return;
// if ref is not import statement, check if an import statement is needed after move
// (ex. when changing from same package (no import required), to different packages (might be import required))
if (PsiTreeUtil.getParentOfType(this, HaxeImportStatement.class) == null &&
PsiTreeUtil.getParentOfType(this, HaxeUsingStatement.class) == null) {
if (missingImport(newFqn)) {
addImportFor(newFqn);
}
}else {
updateFullyQualifiedReference(newFqn);
}
}else {
updateFullyQualifiedReference(newFqn);
}
}

private static String getFqn(HaxeClassDeclaration element) {
HaxeClassModel model = element.getModel();
FullyQualifiedInfo info = model.getQualifiedInfo();
if (info != null) return info.toShortendImportReferenceString();
return model.haxeClass.getQualifiedName();
}


private boolean missingImport(String fqn) {
PsiFile containingFile = this.getContainingFile();
Collection<HaxeImportStatement> imports = PsiTreeUtil.findChildrenOfType(containingFile, HaxeImportStatement.class);

Set<String> membersFqn = convertExposedMembersToFqn(imports);

return !membersFqn.contains(fqn);

}

private Set<String> convertExposedMembersToFqn(Collection<HaxeImportStatement> imports) {
Set<String> memberFqnList = new HashSet<>();
for (HaxeImportStatement importStatement : imports) {
List<HaxeModel> exposedMembers = importStatement.getModel().getExposedMembers();
for (HaxeModel member : exposedMembers) {
if (member instanceof HaxeExposableModel model) {
if(model.getQualifiedInfo() != null) {
memberFqnList.add(model.getQualifiedInfo().toShortendImportReferenceString());
}
}
}
}
String newFqn = ((HaxeClassDeclaration)element).getModel().haxeClass.getQualifiedName();
updateFullyQualifiedReference(newFqn);
return memberFqnList;
}


private void addImportFor(String fqn) {
HaxeAddImportHelper.addImport(fqn, this.getContainingFile());
}

private void bindToPackage(PsiPackage element) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,13 @@ public String getFilePath() {
public FullyQualifiedInfo toPackageQualifiedName() {
return new FullyQualifiedInfo(this.packagePath, null, null, null);
}
// avoiding full path to class (MyPackage.MyClass.Myclass, where Myclass is both module name and classname)
public String toShortendImportReferenceString() {
if (fileName.equals(className)) {
return new FullyQualifiedInfo(packagePath, fileName, null, memberName).toString();
}
return toString();
}

public boolean equalsToNamedPart(String name) {
return equalsToMemberName(name) || equalsToClassName(name) || equalsToFileName(name);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,9 +52,6 @@ public class HaxeClassModel implements HaxeCommonMembersModel {

private SpecificHaxeClassReference reference;

//for caching purposes
private FullyQualifiedInfo myQualifiedInfo;

public HaxeClassModel(@NotNull HaxeClass haxeClass) {
this.haxeClass = haxeClass;
}
Expand Down Expand Up @@ -657,16 +654,14 @@ public HaxeExposableModel getExhibitor() {
@Nullable
@Override
public FullyQualifiedInfo getQualifiedInfo() {
if (myQualifiedInfo == null) {
HaxeExposableModel exhibitor = getExhibitor();
if (exhibitor != null) {
FullyQualifiedInfo containerInfo = exhibitor.getQualifiedInfo();
if (containerInfo != null) {
myQualifiedInfo = new FullyQualifiedInfo(containerInfo.packagePath, containerInfo.fileName, getName(), null);
return new FullyQualifiedInfo(containerInfo.packagePath, containerInfo.fileName, getName(), null);
}
}
}
return myQualifiedInfo;
return null;
}

public void addMethodsFromPrototype(List<HaxeMethodModel> methods) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,10 @@
import com.intellij.psi.PsiDirectory;
import com.intellij.psi.PsiFileSystemItem;
import com.intellij.psi.PsiManager;
import lombok.CustomLog;
import org.jetbrains.annotations.Nullable;

@CustomLog
public class HaxeSourceRootModel {
public static final HaxeSourceRootModel DUMMY = new HaxeSourceRootModel(null, null);
public final HaxeProjectModel project;
Expand Down Expand Up @@ -62,7 +64,12 @@ public PsiDirectory access(String packagePath) {
PsiDirectory current = directory;
for (String part : HaxeStringUtil.split(packagePath, '.')) {
if (current == null) break;
current = current.findSubdirectory(part);
try {
current = current.findSubdirectory(part);
}catch (Exception e) {
log.warn(e);
current = null;
}
}
return current;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,47 +18,31 @@
*/
package com.intellij.plugins.haxe.util;

import com.intellij.openapi.project.Project;
import com.intellij.plugins.haxe.lang.psi.HaxeImportStatement;
import com.intellij.plugins.haxe.lang.psi.HaxePackageStatement;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiFile;
import com.intellij.psi.PsiParserFacade;
import com.intellij.psi.codeStyle.CodeStyleManager;
import com.intellij.psi.util.PsiTreeUtil;
import lombok.CustomLog;

import static com.intellij.plugins.haxe.util.UsefulPsiTreeUtil.isWhitespaceOrCommentButNotDocs;
import java.util.List;

/**
* @author: Fedor.Korotkov
*/
@CustomLog
public class HaxeAddImportHelper {
public static HaxeImportStatement addImport(String path, PsiFile file) {
int positionIndex = 0;
final PsiElement[] children = file.getChildren();

PsiElement child = children[positionIndex];
// search for package or first import
while (!(child instanceof HaxePackageStatement)
&& !(child instanceof HaxeImportStatement)) {
if (++positionIndex < children.length) {
child = children[positionIndex];
} else {
child = null;
break;
}
}
// find last whitespace line before docs or code and after last import
while (child instanceof HaxePackageStatement
|| child instanceof HaxeImportStatement
|| isWhitespaceOrCommentButNotDocs(child)) {
if (++positionIndex < children.length) {
child = children[positionIndex];
}else {
child = null;
break;
}
PsiElement child = PsiTreeUtil.findChildOfType(file, HaxePackageStatement.class);
List<HaxeImportStatement> importStatements = PsiTreeUtil.findChildrenOfType(file, HaxeImportStatement.class).stream().toList();
if(!importStatements.isEmpty()) {
child = importStatements.get(importStatements.size()-1);
}


if(child != null) {
return insertImportBefore(path, file, child);
}else {
Expand All @@ -75,21 +59,20 @@ private static HaxeImportStatement insertImportBefore(String path, PsiFile file,
}

final PsiElement newLineElement = PsiParserFacade.getInstance(file.getProject()).createWhiteSpaceFromText("\n");
file.addBefore(newLineElement, child);
file.addBefore(importStatement, child);
return importStatement;
HaxeImportStatement element = (HaxeImportStatement)file.addAfter(importStatement, child);
file.addAfter(newLineElement, child);
return element;
}
private static HaxeImportStatement insertImportTop(String path, PsiFile file) {
final HaxeImportStatement importStatement =
HaxeElementGenerator.createImportStatementFromPath(file.getProject(), path);
HaxeImportStatement importStatement = HaxeElementGenerator.createImportStatementFromPath(file.getProject(), path);
if (importStatement == null) {
return null;
}

final PsiElement newLineElement = PsiParserFacade.getInstance(file.getProject()).createWhiteSpaceFromText("\n");
PsiElement child = file.getFirstChild();
file.addBefore(newLineElement, child);
file.addBefore(importStatement, child);
return importStatement;
HaxeImportStatement element = (HaxeImportStatement)file.addBefore(importStatement.copy(), child);
file.addAfter(newLineElement.copy(), element);
return element;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,19 @@
package com.intellij.plugins.haxe.actions;

import com.intellij.openapi.fileEditor.FileDocumentManager;
import com.intellij.openapi.project.Project;
import com.intellij.plugins.haxe.HaxeCodeInsightFixtureTestCase;
import com.intellij.plugins.haxe.HaxeFileType;
import com.intellij.plugins.haxe.HaxeLanguage;
import com.intellij.plugins.haxe.ide.actions.HaxeTypeAddImportIntentionAction;
import com.intellij.plugins.haxe.ide.index.HaxeComponentIndex;
import com.intellij.plugins.haxe.lang.psi.HaxeType;
import com.intellij.plugins.haxe.util.HaxeResolveUtil;
import com.intellij.psi.PsiDocumentManager;
import com.intellij.psi.PsiFile;
import com.intellij.psi.codeStyle.CodeStyleSettings;
import com.intellij.psi.codeStyle.CodeStyleSettingsManager;
import com.intellij.psi.codeStyle.CommonCodeStyleSettings;
import com.intellij.psi.search.GlobalSearchScope;
import com.intellij.psi.util.PsiTreeUtil;
import org.junit.Test;
Expand All @@ -38,6 +44,9 @@ protected String getBasePath() {
return "/addImportIntention/";
}

protected CommonCodeStyleSettings myTestStyleSettings;


public void doTest() {
final PsiFile file = PsiDocumentManager.getInstance(myFixture.getProject()).getPsiFile(myFixture.getEditor().getDocument());
assertNotNull(file);
Expand All @@ -51,6 +60,38 @@ public void doTest() {
myFixture.checkResultByFile(getTestName(false) + ".txt");
}

@Override
protected void setUp() throws Exception {
super.setUp();
setTestStyleSettings();
}


@Override
public void setTestStyleSettings() {
Project project = getProject();
CodeStyleSettings currSettings = CodeStyleSettingsManager.getSettings(project);
assertNotNull(currSettings);
CodeStyleSettings tempSettings = currSettings.clone();
CodeStyleSettings.IndentOptions indentOptions = tempSettings.getIndentOptions(HaxeFileType.INSTANCE);
assertNotNull(indentOptions);
defineStyleSettings(tempSettings);
CodeStyleSettingsManager.getInstance(project).setTemporarySettings(tempSettings);
}

protected void defineStyleSettings(CodeStyleSettings tempSettings) {
myTestStyleSettings = tempSettings.getCommonSettings(HaxeLanguage.INSTANCE);
myTestStyleSettings.KEEP_BLANK_LINES_IN_CODE = 2;
myTestStyleSettings.METHOD_BRACE_STYLE = CommonCodeStyleSettings.END_OF_LINE;
myTestStyleSettings.BRACE_STYLE = CommonCodeStyleSettings.END_OF_LINE;
myTestStyleSettings.ALIGN_MULTILINE_PARAMETERS = false;
myTestStyleSettings.ALIGN_MULTILINE_PARAMETERS_IN_CALLS = false;
myTestStyleSettings.KEEP_FIRST_COLUMN_COMMENT = false;
myTestStyleSettings.BLANK_LINES_AFTER_PACKAGE = 2;
myTestStyleSettings.BLANK_LINES_AFTER_IMPORTS = 2;
}


@Test
public void testSimple() throws Throwable {
myFixture.configureByFiles(getTestName(false) + ".hx", "foo/Bar.hx");
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/testData/addImportIntention/Helper.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package;

import foo.Bar.Baz;

/**
Expand Down
1 change: 1 addition & 0 deletions src/test/resources/testData/addImportIntention/Module.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package;

import foo.Types.Type1;

class Module {
var t:Type1;
}
1 change: 1 addition & 0 deletions src/test/resources/testData/addImportIntention/Simple.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package;

import foo.Bar;

class Simple {
var bar:Bar;
}
1 change: 1 addition & 0 deletions src/test/resources/testData/move/moveFile1/after/Test.hx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package ;

import bar.ArrayUtils;

class Test {
static function main() {
function is_c(val) { return val == "c"; }
Expand Down

0 comments on commit fa2e921

Please sign in to comment.