Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Func reference prevent unused func inspection #215

Merged
merged 8 commits into from
Apr 17, 2024
1 change: 1 addition & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ task parser(type: org.jetbrains.grammarkit.tasks.GenerateParser) {

// See https://github.com/JetBrains/gradle-intellij-plugin/
intellij {
downloadSources = true // https://plugins.jetbrains.com/docs/intellij/tools-gradle-intellij-plugin.html#ide-configuration
version = '2021.1.3'
plugins = ['com.intellij.java']
}
Expand Down
10 changes: 8 additions & 2 deletions src/main/java/intellij_awk/Awk.bnf
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ non_unary_expr ::= (
| NOT expr
| simple_get (LT expr)?
| NUMBER
| STRING
| str
| ERE
| INCR lvalue
| DECR lvalue
Expand Down Expand Up @@ -391,6 +391,12 @@ function_call_name ::= FUNC_NAME
implements="intellij_awk.psi.AwkNamedElement"
}

str ::= STRING // we need this as PSI to enable GAWK's function references `f="fname"; @f()`
{
mixin="intellij_awk.psi.AwkStringMixin"
implements="intellij_awk.psi.AwkNamedElement"
}

private non_unary_expr1 ::= [
( POW expr
| MUL expr
Expand Down Expand Up @@ -449,7 +455,7 @@ private non_unary_print_expr ::= (
| LPAREN expr_lst RPAREN IN gawk_var_name
| simple_get
| NUMBER
| STRING
| str
| ERE
| INCR lvalue
| DECR lvalue
Expand Down
8 changes: 4 additions & 4 deletions src/main/java/intellij_awk/AwkCompletionContributorBase.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@
abstract class AwkCompletionContributorBase extends CompletionContributor {
static final PsiElementPattern.Capture<PsiElement> INSIDE_STRING = psiElement(AwkTypes.STRING);
private static final ElementPattern<PsiElement> notInsideERE =
not(or(psiElement(AwkTypes.ERE), psiElement(AwkTypes.TYPED_ERE)));
not(or(psiElement(AwkTypes.ERE), psiElement(AwkTypes.TYPED_ERE)));

private static final ObjectPattern.Capture<PsiElement> notInsideStringEre =
not(INSIDE_STRING).and(notInsideERE);
not(INSIDE_STRING).and(notInsideERE);

static ElementPattern<? extends PsiElement> notInsideStringERE(
ElementPattern<? extends PsiElement> pattern) {
ElementPattern<? extends PsiElement> pattern) {
return and(notInsideStringEre, pattern);
}

static ElementPattern<? extends PsiElement> notInsideERE(
ElementPattern<? extends PsiElement> pattern) {
ElementPattern<? extends PsiElement> pattern) {
return and(notInsideERE, pattern);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -124,7 +124,7 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file)
}
}
if (name == null) {
PsiElement string = nonUnaryExpr.getString();
PsiElement string = nonUnaryExpr.getStr();
if (string != null && string.getText().equals(nonUnaryExpr.getText())) {
String text = string.getText();
name = text.substring(1, text.length() - 1);
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/intellij_awk/AwkParserDefinition.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public class AwkParserDefinition implements ParserDefinition {
new IStubFileElementType<>(AwkLanguage.INSTANCE) {
@Override
public int getStubVersion() {
return 15;
return 16;
}

@Override
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/intellij_awk/psi/AwkElementFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public static AwkFunctionName createFunctionName(Project project, String name) {
return createAwkPsiElement(project, "function " + name + "(){}", AwkFunctionName.class);
}

public static AwkStr createString(Project project, String str) {
return createAwkPsiElement(
project, "{ a = \"" + str.replace("\"", "\\\"") + "\" }", AwkStr.class);
}

public static AwkItem createFunctionItem(Project project, String name) {
return createAwkPsiElement(project, "function " + name + "(){}", AwkItem.class);
}
Expand Down
69 changes: 69 additions & 0 deletions src/main/java/intellij_awk/psi/AwkStringMixin.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package intellij_awk.psi;

import static com.intellij.openapi.util.text.StringUtil.unquoteString;

import com.intellij.lang.ASTNode;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiReference;
import intellij_awk.AwkReferenceFunction;
import intellij_awk.AwkUtil;
import java.util.regex.Pattern;
import javax.swing.*;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

public abstract class AwkStringMixin extends AwkNamedElementImpl {
public AwkStringMixin(@NotNull ASTNode node) {
super(node);
}

private static final Pattern stringThatCanBeFunctionName =
Pattern.compile("\"[A-Za-z_][A-Za-z0-9_]*\"");

@Override
public String getName() {
return getPossibleFunctionName();
}

@Nullable
private String getPossibleFunctionName() {
String str = getText();
return canBeFunctionName(str) ? unquoteString(str) : null;
}

static boolean canBeFunctionName(String s) {
return stringThatCanBeFunctionName.matcher(s).matches();
}

public PsiElement setName(String newName) {
return replaceNameNode(AwkElementFactory.createString(getProject(), newName));
}

@Override
public PsiReference getReference() {
return getPossibleFunctionName() != null && canBeFunctionReference()
? new AwkReferenceFunction(this, TextRange.from(1, getTextLength() - 2))
: null;
}

private boolean canBeFunctionReference() {
return isRhsOfAssignment() || isUserFunctionArgument();
}

/** `f("fname")` but not `substr("fname")` (built-in) */
private boolean isUserFunctionArgument() {
AwkGawkFuncCallList callList = AwkUtil.findParent(this, AwkGawkFuncCallList.class);
return callList != null && callList.getParent() instanceof AwkFunctionCallUser;
}

/** `a = "fname"` */
private boolean isRhsOfAssignment() {
return AwkUtil.isType(AwkUtil.getPrevNotWhitespace(getParent()), AwkTypes.ASSIGN);
}

@Override
public @Nullable Icon getIcon(int flags) {
return null;
}
}
37 changes: 35 additions & 2 deletions src/test/java/intellij_awk/AwkInspectionTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -102,20 +102,21 @@ public void testIssue203NoUnusedFunctionErrForUsageInOtherFile() {
myFixture.addFileToProject("c.awk", "BEGIN { f() } ");
assertNoInspectionAtCaret(unusedFunction);
}

public void testIssue203NoUnusedFunctionErrForRepeatingDefinitionInSeparateFiles() {
myFixture.configureByText("a.awk", "function <caret>f(){}");
myFixture.addFileToProject("b.awk", "function f() {}");
myFixture.addFileToProject("c.awk", "BEGIN { f() } ");
assertNoInspectionAtCaret(unusedFunction);
}

public void testIssue203UnusedFunctionErrForRepeatingDefinitionWhenFunctionIsUsedInSameFileAsDefined() {
public void
testIssue203UnusedFunctionErrForRepeatingDefinitionWhenFunctionIsUsedInSameFileAsDefined() {
myFixture.configureByText("a.awk", "function <caret>f(){}");
myFixture.addFileToProject("c.awk", "BEGIN { f() } function f() {}");
assertInspectionIsShown(unusedFunction);
}


public void testUsedVarInFileOutsideProject() {
checkByFileNoProblemAtCaret(unusedGlobalVariable, true);
}
Expand Down Expand Up @@ -299,6 +300,38 @@ public void testUnnecessarySemicolonNecessary4() {
checkByFileNoProblemAtCaret(unnecessarySemicolon);
}

public void testFuncRefToPreventUnusedFunc1() {
checkByFileNoProblemAtCaret(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc1_1() {
checkByFile(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc2() {
checkByFileNoProblemAtCaret(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc2_1() {
checkByFile(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc3() {
checkByFileNoProblemAtCaret(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc3_1() {
checkByFile(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc4() {
checkByFileNoProblemAtCaret(unusedFunction);
}

public void testFuncRefToPreventUnusedFunc4_1() {
checkByFile(unusedFunction);
}

@Override
protected String getTestDataPath() {
return "src/test/testData/inspection";
Expand Down
1 change: 1 addition & 0 deletions src/test/java/intellij_awk/AwkRenameTests.java
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class AwkRenameTests extends BasePlatformTestCase {
public void testVars6() { checkByFile(); }
public void testVars7() { checkByFile(); }
public void testFunc1() { checkByFile(); }
public void testFuncRef() { checkByFile(); }
public void testIndirect1() { checkByFile(); }

@Override
Expand Down
23 changes: 23 additions & 0 deletions src/test/java/intellij_awk/psi/AwkStringMixinTests.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package intellij_awk.psi;

import junit.framework.TestCase;
import org.junit.Assert;

public class AwkStringMixinTests extends TestCase {
public void testTrue() {
Assert.assertTrue(AwkStringMixin.canBeFunctionName("\"aaa\""));
Assert.assertTrue(AwkStringMixin.canBeFunctionName("\"_aaa01\""));
Assert.assertTrue(AwkStringMixin.canBeFunctionName("\"Xxx123\""));
Assert.assertTrue(AwkStringMixin.canBeFunctionName("\"_\""));
Assert.assertTrue(AwkStringMixin.canBeFunctionName("\"X\""));
}
public void testFalse() {
Assert.assertFalse(AwkStringMixin.canBeFunctionName(" \"aaa\" "));
Assert.assertFalse(AwkStringMixin.canBeFunctionName("\"111\""));
Assert.assertFalse(AwkStringMixin.canBeFunctionName("\"1aaa\""));
Assert.assertFalse(AwkStringMixin.canBeFunctionName("\"_aa a01\""));
Assert.assertFalse(AwkStringMixin.canBeFunctionName("\"Xxx'123\""));
Assert.assertFalse(AwkStringMixin.canBeFunctionName("\"-\""));
Assert.assertFalse(AwkStringMixin.canBeFunctionName("\"aaaa\\\"bbb\""));
}
}
5 changes: 5 additions & 0 deletions src/test/testData/inspection/funcRefToPreventUnusedFunc1.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
PROCINFO["sorted_in"] = "compare"
}

function <caret>compare() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
PROCINFO["sorted_in"] = "compare1"
}

function <caret>compare() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BEGIN {
PROCINFO["sorted_in"] = "compare1"
}

6 changes: 6 additions & 0 deletions src/test/testData/inspection/funcRefToPreventUnusedFunc2.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BEGIN {
F = "fname"
@F()
}

function <caret>fname() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
BEGIN {
F = "aaa fname bbb"
@F()
}

function <caret>fname() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
F = "aaa fname bbb"
@F()
}

5 changes: 5 additions & 0 deletions src/test/testData/inspection/funcRefToPreventUnusedFunc3.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
print a = "fname"
}

function <caret>fname() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
print "fname"
}

function <caret>fname() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
BEGIN {
print "fname"
}

7 changes: 7 additions & 0 deletions src/test/testData/inspection/funcRefToPreventUnusedFunc4.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
BEGIN {
b("fname")
}

function b(f) { @f() }

function <caret>fname() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
length("fname")
}

function <caret>fname() {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
BEGIN {
length("fname")
}

<caret>
3 changes: 2 additions & 1 deletion src/test/testData/parser/BuiltInFuncSpace.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,8 @@ AWK File
PsiElement(AwkTokenType.()('(')
AwkGawkFuncCallListImpl(GAWK_FUNC_CALL_LIST)
AwkNonUnaryExprImpl(NON_UNARY_EXPR)
PsiElement(AwkTokenType.STRING)('"abc"')
AwkStrImpl(STR)
PsiElement(AwkTokenType.STRING)('"abc"')
PsiElement(AwkTokenType.,)(',')
AwkNonUnaryExprImpl(NON_UNARY_EXPR)
PsiElement(AwkTokenType.NUMBER)('2')
Expand Down
3 changes: 2 additions & 1 deletion src/test/testData/parser/UserFuncSpace.txt
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ AWK File
AwkNonUnaryExprImpl(NON_UNARY_EXPR)
PsiElement(AwkTokenType.()('(')
AwkNonUnaryExprImpl(NON_UNARY_EXPR)
PsiElement(AwkTokenType.STRING)('"A"')
AwkStrImpl(STR)
PsiElement(AwkTokenType.STRING)('"A"')
PsiElement(AwkTokenType.))(')')
PsiWhiteSpace(' ')
PsiElement(AwkTokenType.})('}')
8 changes: 8 additions & 0 deletions src/test/testData/rename/funcRef.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BEGIN {
a = "fname"
b("fname")
substr("fname")
print "fname"
}
function b() {}
function fname<caret>() {}
8 changes: 8 additions & 0 deletions src/test/testData/rename/funcRefAfter.awk
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
BEGIN {
a = "newName"
b("newName")
substr("fname")
print "fname"
}
function b() {}
function newName<caret>() {}
Loading