From 1dfd8f29f9e4570a337b9db1471776c9ca38d780 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Thu, 26 May 2022 16:29:13 +0200 Subject: [PATCH 01/12] Show the correct icon for type reference --- .../candid/psi/mixin/CandidIdentifierReferenceMixin.kt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidIdentifierReferenceMixin.kt b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidIdentifierReferenceMixin.kt index 51b3dd3..5e53419 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidIdentifierReferenceMixin.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidIdentifierReferenceMixin.kt @@ -1,14 +1,19 @@ package com.github.alaanor.candid.psi.mixin + +import com.github.alaanor.candid.icon.CandidIcons import com.github.alaanor.candid.psi.CandidIdentifierReference import com.github.alaanor.candid.psi.primitive.CandidElementBase import com.github.alaanor.candid.reference.CandidTypeReference import com.intellij.lang.ASTNode import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiReference +import javax.swing.Icon abstract class CandidIdentifierReferenceMixin(node: ASTNode) : CandidElementBase(node), CandidIdentifierReference { override fun getReference(): PsiReference? { return CandidTypeReference(this, TextRange.create(0, textLength)) } + + override fun getIcon(flags: Int): Icon = CandidIcons.Type } \ No newline at end of file From 7c83a40b619c733e98df88e7aa3de9adf1ba91a6 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Fri, 27 May 2022 20:36:55 +0200 Subject: [PATCH 02/12] Add a line marker in rust file if a corresponding candid method exist --- gradle.properties | 2 +- grammar/Candid.bnf | 7 +++- .../alaanor/candid/CandidFindUsageProvider.kt | 14 +++++-- .../completion/MethodAnnotationCompletion.kt | 4 +- .../alaanor/candid/formatter/CandidBlock.kt | 4 +- .../formatter/CandidFormattingModelBuilder.kt | 2 +- .../CandidMethodRsLineMarkerProvider.kt | 39 +++++++++++++++++++ .../alaanor/candid/psi/CandidExtension.kt | 4 +- .../candid/psi/mixin/CandidMethodMixin.kt | 34 +++++++++++++--- .../candid/psi/stub/impl/CandidMethodStub.kt | 8 ++++ .../psi/stub/index/CandidStubMethodIndex.kt | 15 +++++++ .../CandidIdentifierDeclarationStubType.kt | 4 +- .../psi/stub/type/CandidMethodStubType.kt | 23 +++++++++++ .../psi/stub/type/CandidStubTypeBase.kt | 3 ++ .../candid/psi/stub/type/CandidStubTypes.kt | 1 + .../candid/util/CandidRustIcCdkUtil.kt | 24 ++++++++++++ src/main/resources/META-INF/candid-rust.xml | 6 +++ src/main/resources/META-INF/plugin.xml | 2 + 18 files changed, 175 insertions(+), 21 deletions(-) create mode 100644 src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt create mode 100644 src/main/kotlin/com/github/alaanor/candid/psi/stub/impl/CandidMethodStub.kt create mode 100644 src/main/kotlin/com/github/alaanor/candid/psi/stub/index/CandidStubMethodIndex.kt create mode 100644 src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt create mode 100644 src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt create mode 100644 src/main/resources/META-INF/candid-rust.xml diff --git a/gradle.properties b/gradle.properties index 4e7e962..a10e8e3 100644 --- a/gradle.properties +++ b/gradle.properties @@ -17,7 +17,7 @@ platformVersion = 2021.1.3 # Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html # Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22 -platformPlugins = +platformPlugins = org.rust.lang:0.4.156.4145-211, org.toml.lang:0.2.155.4114-211 # Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3 javaVersion = 11 diff --git a/grammar/Candid.bnf b/grammar/Candid.bnf index 9422ca9..e4b7b80 100644 --- a/grammar/Candid.bnf +++ b/grammar/Candid.bnf @@ -54,7 +54,7 @@ actor ::= service actor_name? ':' (tup_type '->')? (actor_type | identifier_refe actor_name ::= id -private actor_type ::= '{' <>? '}' { pin=1 } +private actor_type ::= '{' <>? '}' { pin=1 } import_statement ::= import string_literal { mixin="com.github.alaanor.candid.psi.mixin.CandidImportStatementMixin" @@ -62,8 +62,11 @@ import_statement ::= import string_literal { pin=1 } -meth_type ::= method_name ':' (func_type | identifier_reference) { +method_type ::= method_name ':' (func_type | identifier_reference) { mixin="com.github.alaanor.candid.psi.mixin.CandidMethodMixin" + stubClass="com.github.alaanor.candid.psi.stub.impl.CandidMethodStub" + elementTypeFactory="com.github.alaanor.candid.psi.stub.type.CandidStubTypes.get" + name = "method" } method_name ::= name diff --git a/src/main/kotlin/com/github/alaanor/candid/CandidFindUsageProvider.kt b/src/main/kotlin/com/github/alaanor/candid/CandidFindUsageProvider.kt index 2d7ac0a..fdb6b0b 100644 --- a/src/main/kotlin/com/github/alaanor/candid/CandidFindUsageProvider.kt +++ b/src/main/kotlin/com/github/alaanor/candid/CandidFindUsageProvider.kt @@ -1,19 +1,27 @@ package com.github.alaanor.candid import com.github.alaanor.candid.psi.CandidIdentifierDeclaration -import com.github.alaanor.candid.psi.primitive.CandidNamedElement +import com.github.alaanor.candid.psi.CandidIdentifierReference +import com.github.alaanor.candid.psi.CandidMethodType import com.intellij.lang.findUsages.FindUsagesProvider import com.intellij.psi.PsiElement class CandidFindUsageProvider : FindUsagesProvider { - override fun canFindUsagesFor(psiElement: PsiElement): Boolean = psiElement is CandidNamedElement + override fun canFindUsagesFor(psiElement: PsiElement): Boolean { + return psiElement is CandidIdentifierDeclaration + || psiElement is CandidIdentifierReference + || psiElement is CandidMethodType + } + override fun getHelpId(psiElement: PsiElement): String? = null - override fun getType(element: PsiElement): String = "Candid type" + override fun getType(element: PsiElement): String = getNodeText(element, false) override fun getDescriptiveName(element: PsiElement): String = getNodeText(element, true) override fun getNodeText(element: PsiElement, useFullName: Boolean): String { return when (element) { is CandidIdentifierDeclaration -> "Type declaration" + is CandidIdentifierReference -> "Type reference" + is CandidMethodType -> "Method" else -> "Unknown node" } } diff --git a/src/main/kotlin/com/github/alaanor/candid/completion/MethodAnnotationCompletion.kt b/src/main/kotlin/com/github/alaanor/candid/completion/MethodAnnotationCompletion.kt index 9fe62ca..98fdb83 100644 --- a/src/main/kotlin/com/github/alaanor/candid/completion/MethodAnnotationCompletion.kt +++ b/src/main/kotlin/com/github/alaanor/candid/completion/MethodAnnotationCompletion.kt @@ -1,7 +1,7 @@ package com.github.alaanor.candid.completion import com.github.alaanor.candid.psi.CandidActor -import com.github.alaanor.candid.psi.CandidMethType +import com.github.alaanor.candid.psi.CandidMethodType import com.intellij.codeInsight.completion.InsertHandler import com.intellij.codeInsight.lookup.LookupElement import com.intellij.patterns.ElementPattern @@ -18,7 +18,7 @@ class MethodAnnotationCompletion : CandidBasicCompletion() { psiElement(CandidActor::class.java) .with(object : PatternCondition("bruh") { override fun accepts(t: CandidActor, context: ProcessingContext?): Boolean { - return t.lastChild is CandidMethType + return t.lastChild is CandidMethodType } }) ) diff --git a/src/main/kotlin/com/github/alaanor/candid/formatter/CandidBlock.kt b/src/main/kotlin/com/github/alaanor/candid/formatter/CandidBlock.kt index 9c5375e..daa211f 100644 --- a/src/main/kotlin/com/github/alaanor/candid/formatter/CandidBlock.kt +++ b/src/main/kotlin/com/github/alaanor/candid/formatter/CandidBlock.kt @@ -37,7 +37,7 @@ class CandidBlock(node: ASTNode, wrap: Wrap?, alignment: Alignment?, private val return when (node.elementType) { CandidTypes.FIELD_TYPE_RECORD, CandidTypes.FIELD_TYPE_VARIANT, - CandidTypes.METH_TYPE, + CandidTypes.METHOD_TYPE, CandidTypes.BLOCK_COMMENT, CandidTypes.LINE_COMMENT -> { Indent.getNormalIndent() @@ -53,7 +53,7 @@ class CandidBlock(node: ASTNode, wrap: Wrap?, alignment: Alignment?, private val return when (node.elementType) { CandidTypes.RECORD_STATEMENT, CandidTypes.VARIANT_STATEMENT, - CandidTypes.METH_TYPE -> { + CandidTypes.METHOD_TYPE -> { ChildAttributes(Indent.getNormalIndent(), null) } diff --git a/src/main/kotlin/com/github/alaanor/candid/formatter/CandidFormattingModelBuilder.kt b/src/main/kotlin/com/github/alaanor/candid/formatter/CandidFormattingModelBuilder.kt index 99b5efa..08de1f2 100644 --- a/src/main/kotlin/com/github/alaanor/candid/formatter/CandidFormattingModelBuilder.kt +++ b/src/main/kotlin/com/github/alaanor/candid/formatter/CandidFormattingModelBuilder.kt @@ -11,7 +11,7 @@ class CandidFormattingModelBuilder : FormattingModelBuilder { val fields_tokens = TokenSet.create( CandidTypes.FIELD_TYPE_RECORD, CandidTypes.FIELD_TYPE_VARIANT, - CandidTypes.METH_TYPE + CandidTypes.METHOD_TYPE ) val comment_tokens = TokenSet.create( diff --git a/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt b/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt new file mode 100644 index 0000000..2069856 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt @@ -0,0 +1,39 @@ +package com.github.alaanor.candid.marker + +import com.github.alaanor.candid.icon.CandidIcons +import com.github.alaanor.candid.psi.CandidMethodType +import com.github.alaanor.candid.psi.stub.index.CandidStubMethodIndex +import com.github.alaanor.candid.util.CandidRustIcCdkUtil +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerInfo +import com.intellij.codeInsight.daemon.RelatedItemLineMarkerProvider +import com.intellij.codeInsight.navigation.NavigationGutterIconBuilder +import com.intellij.psi.PsiElement +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StubIndex +import org.rust.lang.core.psi.RsFunction + +class CandidMethodRsLineMarkerProvider : RelatedItemLineMarkerProvider() { + + override fun collectNavigationMarkers( + element: PsiElement, + result: MutableCollection> + ) { + val rsFunction = element as? RsFunction ?: return + val name = CandidRustIcCdkUtil.getName(rsFunction) ?: return + val candidMethod = StubIndex.getElements( + CandidStubMethodIndex.Key, + name, + element.project, + GlobalSearchScope.projectScope(element.project), + CandidMethodType::class.java + ).firstOrNull() ?: return + + // we found a matching rust method and candid method + + val builder = NavigationGutterIconBuilder.create(CandidIcons.FileType) + .setTarget(candidMethod) + .setTooltipText("Navigate to the corresponding candid method") + + result.add(builder.createLineMarkerInfo(element.identifier)) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt b/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt index 7f5596c..d4dc91a 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt @@ -8,8 +8,10 @@ import com.intellij.psi.PsiFile import com.intellij.psi.PsiWhiteSpace import com.intellij.psi.util.elementType +fun CandidStringLiteral.textWithoutQuote(): String = this.text.trim('"') + fun CandidImportStatement.importPathString(): String? { - return this.stringLiteral?.text?.trim('"') + return this.stringLiteral?.textWithoutQuote() } fun CandidImportStatement.importedPsiFile(): PsiFile? { diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt index 7e7dd2d..1f06395 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt @@ -1,17 +1,39 @@ package com.github.alaanor.candid.psi.mixin import com.github.alaanor.candid.icon.CandidIcons -import com.github.alaanor.candid.psi.CandidFuncAnnotation -import com.github.alaanor.candid.psi.CandidMethType -import com.github.alaanor.candid.psi.CandidMethodName -import com.github.alaanor.candid.psi.primitive.CandidElementBase +import com.github.alaanor.candid.psi.* +import com.github.alaanor.candid.psi.primitive.CandidStubBasedElementBase +import com.github.alaanor.candid.psi.stub.CandidStubBasedPsiElement +import com.github.alaanor.candid.psi.stub.impl.CandidMethodStub import com.intellij.lang.ASTNode import com.intellij.navigation.ItemPresentation +import com.intellij.openapi.util.TextRange +import com.intellij.psi.stubs.IStubElementType import javax.swing.Icon -abstract class CandidMethodMixin(node: ASTNode) : CandidElementBase(node), CandidMethType, ItemPresentation { - override fun getName(): String? = children.find { it is CandidMethodName }?.text +abstract class CandidMethodMixin : + CandidStubBasedElementBase, + CandidStubBasedPsiElement, + CandidMethodType, + ItemPresentation { + + constructor(node: ASTNode) : super(node) + constructor(stub: CandidMethodStub, type: IStubElementType<*, *>) : super(stub, type) + + override fun getName(): String? { + if (methodName.stringLiteral !== null) + return methodName.stringLiteral?.textWithoutQuote() + return methodName.text + } + override fun getPresentableText(): String = name ?: "Unnamed method" + override fun getTextOffset(): Int = methodName.textOffset + + override fun getTextRange(): TextRange { + if (methodName.stringLiteral !== null) + return methodName.stringLiteral?.getTextRangeWithoutQuote() ?: node.textRange + return methodName.textRange + } override fun getIcon(unused: Boolean): Icon { return when (getMethodType()) { diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/impl/CandidMethodStub.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/impl/CandidMethodStub.kt new file mode 100644 index 0000000..10b3b50 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/impl/CandidMethodStub.kt @@ -0,0 +1,8 @@ +package com.github.alaanor.candid.psi.stub.impl + +import com.github.alaanor.candid.psi.CandidMethodType +import com.github.alaanor.candid.psi.stub.type.CandidMethodStubType +import com.intellij.psi.stubs.StubElement + +class CandidMethodStub(override val name: String, parent: StubElement<*>?) : + CandidStubBase(parent, CandidMethodStubType) \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/index/CandidStubMethodIndex.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/index/CandidStubMethodIndex.kt new file mode 100644 index 0000000..3f02e64 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/index/CandidStubMethodIndex.kt @@ -0,0 +1,15 @@ +package com.github.alaanor.candid.psi.stub.index + +import com.github.alaanor.candid.psi.CandidMethodType +import com.intellij.psi.stubs.StringStubIndexExtension +import com.intellij.psi.stubs.StubIndexKey + +class CandidStubMethodIndex: StringStubIndexExtension() { + companion object { + val Key = StubIndexKey.createIndexKey("candid.method.name") + } + + override fun getKey(): StubIndexKey { + return Key + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidIdentifierDeclarationStubType.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidIdentifierDeclarationStubType.kt index 42be469..2057bd0 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidIdentifierDeclarationStubType.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidIdentifierDeclarationStubType.kt @@ -8,9 +8,7 @@ import com.intellij.psi.stubs.StubElement object CandidIdentifierDeclarationStubType : CandidStubTypeBase("CANDID_TYPE_DEFINITION") { - override fun getExternalId(): String { - return "candid.type.stub" - } + override fun getExternalId(): String = "candid.type.stub" override fun createPsi(stub: CandidIdentifierDeclarationStub): CandidIdentifierDeclaration { return CandidIdentifierDeclarationImpl(stub, stub.stubType) diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt new file mode 100644 index 0000000..11d40ec --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt @@ -0,0 +1,23 @@ +package com.github.alaanor.candid.psi.stub.type + +import com.github.alaanor.candid.psi.CandidMethodType +import com.github.alaanor.candid.psi.impl.CandidMethodTypeImpl +import com.github.alaanor.candid.psi.stub.impl.CandidMethodStub +import com.intellij.psi.PsiElement +import com.intellij.psi.stubs.StubElement + +object CandidMethodStubType : CandidStubTypeBase("CANDID_METHOD") { + override fun getExternalId(): String = "candid.method.stub" + + override fun createStub(name: String, parentStub: StubElement<*>?): CandidMethodStub { + return CandidMethodStub(name, parentStub) + } + + override fun createPsi(stub: CandidMethodStub): CandidMethodType { + return CandidMethodTypeImpl(stub, stub.stubType) + } + + override fun createStub(psi: CandidMethodType, parentStub: StubElement?): CandidMethodStub { + return CandidMethodStub(psi.methodName.text, parentStub) + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypeBase.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypeBase.kt index 9cd1214..737a9e0 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypeBase.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypeBase.kt @@ -2,7 +2,9 @@ package com.github.alaanor.candid.psi.stub.type import com.github.alaanor.candid.CandidLanguage import com.github.alaanor.candid.psi.stub.impl.CandidIdentifierDeclarationStub +import com.github.alaanor.candid.psi.stub.impl.CandidMethodStub import com.github.alaanor.candid.psi.stub.impl.CandidStubBase +import com.github.alaanor.candid.psi.stub.index.CandidStubMethodIndex import com.github.alaanor.candid.psi.stub.index.CandidStubTypeIndex import com.intellij.psi.PsiElement import com.intellij.psi.stubs.* @@ -21,6 +23,7 @@ abstract class CandidStubTypeBase, TPsi : PsiElement override fun indexStub(stub: TStub, sink: IndexSink) { when (stub) { is CandidIdentifierDeclarationStub -> sink.occurrence(CandidStubTypeIndex.Key, stub.name) + is CandidMethodStub -> sink.occurrence(CandidStubMethodIndex.Key, stub.name) } } diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypes.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypes.kt index 47bdbc8..f9e1ca5 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypes.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidStubTypes.kt @@ -7,6 +7,7 @@ object CandidStubTypes { fun get(name: String): IElementType { return when (name) { "IDENTIFIER_DECLARATION" -> CandidIdentifierDeclarationStubType + "METHOD_TYPE" -> CandidMethodStubType else -> throw UnsupportedOperationException("Unsupported stub type") } } diff --git a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt new file mode 100644 index 0000000..214b8b9 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt @@ -0,0 +1,24 @@ +package com.github.alaanor.candid.util + +import org.rust.lang.core.psi.RsFunction +import org.rust.lang.core.psi.ext.containingCrate +import org.rust.lang.core.psi.ext.name + +class CandidRustIcCdkUtil { + companion object { + private val metaName = arrayOf("update", "query") + + fun getName(rsFunction: RsFunction): String? { + val icCdkMetaItem = rsFunction.rawMetaItems.find { + metaName.contains(it.name) && it.path + ?.reference + ?.resolve() + ?.containingCrate + ?.normName == "ic_cdk_macros" + } ?: return null + + return icCdkMetaItem.metaItemArgsList.find { it.name == "name" }?.value + ?: rsFunction.name + } + } +} \ No newline at end of file diff --git a/src/main/resources/META-INF/candid-rust.xml b/src/main/resources/META-INF/candid-rust.xml new file mode 100644 index 0000000..1825f56 --- /dev/null +++ b/src/main/resources/META-INF/candid-rust.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index 2db39d4..b2ad1f4 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -5,6 +5,7 @@ Alaanor com.intellij.modules.platform + org.rust.lang + From a1e1b2c1e464a19b04ed5dceaa8523b6404df01f Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sat, 28 May 2022 13:34:49 +0200 Subject: [PATCH 03/12] Candid method reference to resolve to rust function --- .../alaanor/candid/psi/CandidExtension.kt | 6 +++ .../candid/psi/mixin/CandidMethodMixin.kt | 6 +++ .../psi/stub/type/CandidMethodStubType.kt | 3 +- .../candid/reference/CandidMethodReference.kt | 42 +++++++++++++++++++ .../candid/util/CandidRustIcCdkUtil.kt | 18 +++++--- src/main/resources/META-INF/candid-rust.xml | 1 + 6 files changed, 70 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt b/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt index d4dc91a..da8b4bd 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/CandidExtension.kt @@ -39,4 +39,10 @@ fun CandidElement.deleteWithSurroundSemicolon() { fun CandidStringLiteral.getTextRangeWithoutQuote(): TextRange { return TextRange.create(node.textRange.startOffset + 1, node.textRange.endOffset - 1) +} + +fun CandidMethodType.methodNameText(): String { + if (this.methodName.stringLiteral !== null) + return this.methodName.stringLiteral?.textWithoutQuote() ?: this.methodName.text + return this.methodName.text } \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt index 1f06395..591151e 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt @@ -5,9 +5,11 @@ import com.github.alaanor.candid.psi.* import com.github.alaanor.candid.psi.primitive.CandidStubBasedElementBase import com.github.alaanor.candid.psi.stub.CandidStubBasedPsiElement import com.github.alaanor.candid.psi.stub.impl.CandidMethodStub +import com.github.alaanor.candid.reference.CandidMethodReference import com.intellij.lang.ASTNode import com.intellij.navigation.ItemPresentation import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiReference import com.intellij.psi.stubs.IStubElementType import javax.swing.Icon @@ -35,6 +37,10 @@ abstract class CandidMethodMixin : return methodName.textRange } + override fun getReference(): PsiReference? { + return CandidMethodReference(this, TextRange.create(0, methodName.textLength)) + } + override fun getIcon(unused: Boolean): Icon { return when (getMethodType()) { Type.Update -> CandidIcons.MethodUpdate diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt index 11d40ec..fd24a9f 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/type/CandidMethodStubType.kt @@ -2,6 +2,7 @@ package com.github.alaanor.candid.psi.stub.type import com.github.alaanor.candid.psi.CandidMethodType import com.github.alaanor.candid.psi.impl.CandidMethodTypeImpl +import com.github.alaanor.candid.psi.methodNameText import com.github.alaanor.candid.psi.stub.impl.CandidMethodStub import com.intellij.psi.PsiElement import com.intellij.psi.stubs.StubElement @@ -18,6 +19,6 @@ object CandidMethodStubType : CandidStubTypeBase?): CandidMethodStub { - return CandidMethodStub(psi.methodName.text, parentStub) + return CandidMethodStub(psi.methodNameText(), parentStub) } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt b/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt new file mode 100644 index 0000000..92f3a82 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt @@ -0,0 +1,42 @@ +package com.github.alaanor.candid.reference + +import com.github.alaanor.candid.psi.CandidMethodType +import com.github.alaanor.candid.psi.methodNameText +import com.github.alaanor.candid.util.CandidRustIcCdkUtil +import com.intellij.openapi.util.TextRange +import com.intellij.psi.PsiElement +import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.stubs.StubIndex +import org.rust.lang.core.psi.RsFunction +import org.rust.lang.core.psi.ext.RsNamedElement +import org.rust.lang.core.stubs.index.RsNamedElementIndex + +class CandidMethodReference(method: CandidMethodType, private var textRange: TextRange) : + PsiReferenceBase(method, textRange) { + + override fun getAbsoluteRange(): TextRange = textRange + + override fun resolve(): PsiElement? { + val namedElements = StubIndex.getElements( + RsNamedElementIndex.KEY, + element.methodNameText(), + element.project, + GlobalSearchScope.projectScope(element.project), + RsNamedElement::class.java + ) + + val rsFunction = namedElements + .mapNotNull { + val rsFunction = it as? RsFunction ?: return@mapNotNull null + if (CandidRustIcCdkUtil.getName(rsFunction) != element.methodNameText()) { + return@mapNotNull null + } + + rsFunction + } + .firstOrNull() + + return rsFunction?.identifier + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt index 214b8b9..fe57523 100644 --- a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt +++ b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt @@ -1,6 +1,7 @@ package com.github.alaanor.candid.util import org.rust.lang.core.psi.RsFunction +import org.rust.lang.core.psi.RsMetaItem import org.rust.lang.core.psi.ext.containingCrate import org.rust.lang.core.psi.ext.name @@ -9,16 +10,23 @@ class CandidRustIcCdkUtil { private val metaName = arrayOf("update", "query") fun getName(rsFunction: RsFunction): String? { - val icCdkMetaItem = rsFunction.rawMetaItems.find { + val rsMetaItem = getMetaItem(rsFunction) ?: return null + return getName(rsMetaItem, rsFunction) + } + + private fun getName (rsMetaItem: RsMetaItem, rsFunction: RsFunction): String? { + return rsMetaItem.metaItemArgsList.find { it.name == "name" }?.value + ?: rsFunction.name + } + + private fun getMetaItem(rsFunction: RsFunction): RsMetaItem? { + return rsFunction.rawMetaItems.find { metaName.contains(it.name) && it.path ?.reference ?.resolve() ?.containingCrate ?.normName == "ic_cdk_macros" - } ?: return null - - return icCdkMetaItem.metaItemArgsList.find { it.name == "name" }?.value - ?: rsFunction.name + } } } } \ No newline at end of file diff --git a/src/main/resources/META-INF/candid-rust.xml b/src/main/resources/META-INF/candid-rust.xml index 1825f56..f3bba79 100644 --- a/src/main/resources/META-INF/candid-rust.xml +++ b/src/main/resources/META-INF/candid-rust.xml @@ -1,6 +1,7 @@ \ No newline at end of file From fc739a13395b2441220e6cbe80a6fcb294bd1575 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sat, 28 May 2022 14:41:34 +0200 Subject: [PATCH 04/12] fix candid method text range and better ic cdk macro detection --- .../candid/psi/mixin/CandidMethodMixin.kt | 5 ++++- .../candid/util/CandidRustIcCdkUtil.kt | 21 ++++++++++++------- 2 files changed, 17 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt index 591151e..4f72199 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/mixin/CandidMethodMixin.kt @@ -38,7 +38,10 @@ abstract class CandidMethodMixin : } override fun getReference(): PsiReference? { - return CandidMethodReference(this, TextRange.create(0, methodName.textLength)) + return CandidMethodReference( + this, + TextRange.create(0, methodNameText().length) + ) } override fun getIcon(unused: Boolean): Icon { diff --git a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt index fe57523..312fbf8 100644 --- a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt +++ b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt @@ -1,5 +1,6 @@ package com.github.alaanor.candid.util +import org.rust.ide.refactoring.move.common.RsMoveUtil.textNormalized import org.rust.lang.core.psi.RsFunction import org.rust.lang.core.psi.RsMetaItem import org.rust.lang.core.psi.ext.containingCrate @@ -7,25 +8,29 @@ import org.rust.lang.core.psi.ext.name class CandidRustIcCdkUtil { companion object { - private val metaName = arrayOf("update", "query") - fun getName(rsFunction: RsFunction): String? { val rsMetaItem = getMetaItem(rsFunction) ?: return null return getName(rsMetaItem, rsFunction) } - private fun getName (rsMetaItem: RsMetaItem, rsFunction: RsFunction): String? { + private fun getName(rsMetaItem: RsMetaItem, rsFunction: RsFunction): String? { return rsMetaItem.metaItemArgsList.find { it.name == "name" }?.value ?: rsFunction.name } private fun getMetaItem(rsFunction: RsFunction): RsMetaItem? { return rsFunction.rawMetaItems.find { - metaName.contains(it.name) && it.path - ?.reference - ?.resolve() - ?.containingCrate - ?.normName == "ic_cdk_macros" + when (it.path?.textNormalized) { + "ic_cdk_macros::update", "ic_cdk_macros::query" -> true + "update", "query" -> { + it.path + ?.reference + ?.resolve() + ?.containingCrate + ?.normName == "ic_cdk_macros" + } + else -> false + } } } } From 47280fcfa4a44dd1baf2a5bd60f84a6f54484e76 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sat, 28 May 2022 17:29:54 +0200 Subject: [PATCH 05/12] update test files with the new psi structure --- src/test/resources/CommonTestData.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/resources/CommonTestData.txt b/src/test/resources/CommonTestData.txt index bf9aabf..741e09c 100644 --- a/src/test/resources/CommonTestData.txt +++ b/src/test/resources/CommonTestData.txt @@ -114,7 +114,7 @@ Candid File(0,531) PsiWhiteSpace(' ')(225,226) PsiElement({)('{')(226,227) PsiWhiteSpace('\n ')(227,232) - CandidMethTypeImpl(METH_TYPE)(232,528) + {}(232,528) CandidMethodNameImpl(METHOD_NAME)(232,244) PsiElement(id)('http_request')(232,244) PsiWhiteSpace(' ')(244,245) From 8dc329ea7048642eb2945599d0164c63f7ba35c5 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sat, 4 Jun 2022 13:03:43 +0200 Subject: [PATCH 06/12] scope search per rust package according to dfx.json --- .../CandidMethodRsLineMarkerProvider.kt | 5 +- .../github/alaanor/candid/project/DfxJson.kt | 97 +++++++++++++++++++ 2 files changed, 101 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt diff --git a/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt b/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt index 2069856..586f986 100644 --- a/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt +++ b/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt @@ -1,6 +1,7 @@ package com.github.alaanor.candid.marker import com.github.alaanor.candid.icon.CandidIcons +import com.github.alaanor.candid.project.DfxJson import com.github.alaanor.candid.psi.CandidMethodType import com.github.alaanor.candid.psi.stub.index.CandidStubMethodIndex import com.github.alaanor.candid.util.CandidRustIcCdkUtil @@ -20,11 +21,13 @@ class CandidMethodRsLineMarkerProvider : RelatedItemLineMarkerProvider() { ) { val rsFunction = element as? RsFunction ?: return val name = CandidRustIcCdkUtil.getName(rsFunction) ?: return + val candidFile = DfxJson.getCandidFileFromRustFile(element.containingFile) ?: return + val candidMethod = StubIndex.getElements( CandidStubMethodIndex.Key, name, element.project, - GlobalSearchScope.projectScope(element.project), + GlobalSearchScope.fileScope(element.project, candidFile.virtualFile), CandidMethodType::class.java ).firstOrNull() ?: return diff --git a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt new file mode 100644 index 0000000..73e89c0 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt @@ -0,0 +1,97 @@ +package com.github.alaanor.candid.project + +import com.github.alaanor.candid.psi.CandidFile +import com.github.alaanor.candid.util.filePath +import com.github.alaanor.candid.util.projectFilePath +import com.intellij.find.findUsages.FindUsagesStatisticsCollector +import com.intellij.json.psi.JsonFile +import com.intellij.json.psi.JsonObject +import com.intellij.openapi.fileEditor.FileDocumentManager +import com.intellij.openapi.project.Project +import com.intellij.openapi.roots.FileIndex +import com.intellij.openapi.vfs.LocalFileSystem +import com.intellij.openapi.vfs.VfsUtilCore +import com.intellij.openapi.vfs.VirtualFile +import com.intellij.psi.PsiDocumentManager +import com.intellij.psi.PsiFile +import com.intellij.psi.PsiManager +import com.intellij.psi.impl.file.impl.FileManager +import com.intellij.psi.search.* +import org.rust.cargo.project.model.CargoProjectsService +import org.rust.cargo.project.workspace.CargoWorkspace +import org.rust.cargo.project.workspace.PackageOrigin +import org.rust.lang.core.psi.ext.findCargoPackage +import org.rust.openapiext.document +import java.io.File +import java.nio.file.Paths + +// @TODO cache it with lazy + AsyncFileListener +object DfxJson { + + fun getRustCanisterScopeFromCandidFile(candidFile: CandidFile): GlobalSearchScope? { + return getRustCanisters(candidFile.project) + ?.find { it.candidFile.filePath() === candidFile.filePath() } + ?.getScope() + } + + fun getCandidFileFromRustFile(psiFile: PsiFile): CandidFile? { + return getRustCanisters(psiFile.project) + ?.find { it.cargoPackage === psiFile.findCargoPackage() } + ?.candidFile + } + + private fun getRustCanisters(project: Project): List? { + // find dfx json + val document = FilenameIndex + .getFilesByName(project, "dfx.json", GlobalSearchScope.projectScope(project), false) + .find { it.projectFilePath() == "dfx.json" } + ?.run { virtualFile.document } ?: return null + + // retrieving the list of canisters declared from the dfx.json + val json = PsiDocumentManager.getInstance(project).getPsiFile(document) as? JsonFile ?: return null + val topLevel = json.topLevelValue as? JsonObject ?: return null + val canistersProperty = topLevel.findProperty("canisters") ?: return null + val canisters = (canistersProperty.value as? JsonObject)?.propertyList ?: return null + + // retrieving the list of all existing cargo package in this project + val cargoProjectsService = project.getServiceIfCreated(CargoProjectsService::class.java) ?: return null + val allCargoWorkspaces = cargoProjectsService.allProjects.mapNotNull { cargoProject -> cargoProject.workspace } + val allPackages = allCargoWorkspaces.flatMap { workspace -> + workspace.packages.filter { item -> item.origin == PackageOrigin.WORKSPACE } + } + + // finding a matching pair of rust type canister + cargo package + return canisters.mapNotNull { jsonProperty -> + // filter by canister type of rust + val typeField = (jsonProperty.value as? JsonObject)?.findProperty("type") ?: return@mapNotNull null + if (typeField.value?.text?.trim('"') != "rust") { + return@mapNotNull null + } + + // get the corresponding cargo package + val crateName = jsonProperty.nameElement.text + val cargoPackage = allPackages.find { item -> crateName.trim('"') == item.name } ?: return@mapNotNull null + + // get the corresponding candid file + val candidField = (jsonProperty.value as? JsonObject)?.findProperty("candid") ?: return@mapNotNull null + val candidPath = candidField.value?.text?.trim('"') ?: return@mapNotNull null + val candidVirtualFile = LocalFileSystem.getInstance() + .findFileByPath(Paths.get(project.basePath, candidPath).toString()) ?: return@mapNotNull null + val candidFile = PsiManager + .getInstance(project) + .findFile(candidVirtualFile) as? CandidFile ?: return@mapNotNull null + + RustCanister(project, cargoPackage, candidFile) + } + } + + private data class RustCanister( + val project: Project, + val cargoPackage: CargoWorkspace.Package, + val candidFile: CandidFile + ) { + fun getScope(): GlobalSearchScope { + return GlobalSearchScopes.directoriesScope(project, true, cargoPackage.contentRoot) + } + } +} \ No newline at end of file From 5deb12b9a00660a8e05653c54661d6f06098f3e3 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sat, 4 Jun 2022 19:39:44 +0200 Subject: [PATCH 07/12] cached lookup for dfxjson and scoped search candid method reference --- .../candid/listener/DfxFileListener.kt | 23 +++++++++ .../CandidMethodRsLineMarkerProvider.kt | 2 +- .../github/alaanor/candid/project/DfxJson.kt | 48 ++++++++++--------- .../candid/reference/CandidMethodReference.kt | 9 +++- src/main/resources/META-INF/candid-rust.xml | 1 + 5 files changed, 57 insertions(+), 26 deletions(-) create mode 100644 src/main/kotlin/com/github/alaanor/candid/listener/DfxFileListener.kt diff --git a/src/main/kotlin/com/github/alaanor/candid/listener/DfxFileListener.kt b/src/main/kotlin/com/github/alaanor/candid/listener/DfxFileListener.kt new file mode 100644 index 0000000..5f5ab78 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/listener/DfxFileListener.kt @@ -0,0 +1,23 @@ +package com.github.alaanor.candid.listener + +import com.github.alaanor.candid.project.DfxJson +import com.intellij.openapi.vfs.AsyncFileListener +import com.intellij.openapi.vfs.newvfs.events.VFileEvent + +class DfxFileListener : AsyncFileListener { + override fun prepareChange(events: MutableList): AsyncFileListener.ChangeApplier? { + var hasInterestingEvent = false + + for (event in events) + if (event.path.endsWith("dfx.json")) + hasInterestingEvent = true + + if (!hasInterestingEvent) return null + + return object : AsyncFileListener.ChangeApplier { + override fun afterVfsChange() { + DfxJson.updateCache() + } + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt b/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt index 586f986..da93cfd 100644 --- a/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt +++ b/src/main/kotlin/com/github/alaanor/candid/marker/CandidMethodRsLineMarkerProvider.kt @@ -27,7 +27,7 @@ class CandidMethodRsLineMarkerProvider : RelatedItemLineMarkerProvider() { CandidStubMethodIndex.Key, name, element.project, - GlobalSearchScope.fileScope(element.project, candidFile.virtualFile), + GlobalSearchScope.fileScope(element.project, candidFile), CandidMethodType::class.java ).firstOrNull() ?: return diff --git a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt index 73e89c0..770010e 100644 --- a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt +++ b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt @@ -1,45 +1,45 @@ package com.github.alaanor.candid.project -import com.github.alaanor.candid.psi.CandidFile -import com.github.alaanor.candid.util.filePath import com.github.alaanor.candid.util.projectFilePath -import com.intellij.find.findUsages.FindUsagesStatisticsCollector import com.intellij.json.psi.JsonFile import com.intellij.json.psi.JsonObject -import com.intellij.openapi.fileEditor.FileDocumentManager import com.intellij.openapi.project.Project -import com.intellij.openapi.roots.FileIndex import com.intellij.openapi.vfs.LocalFileSystem -import com.intellij.openapi.vfs.VfsUtilCore import com.intellij.openapi.vfs.VirtualFile import com.intellij.psi.PsiDocumentManager import com.intellij.psi.PsiFile -import com.intellij.psi.PsiManager -import com.intellij.psi.impl.file.impl.FileManager import com.intellij.psi.search.* import org.rust.cargo.project.model.CargoProjectsService import org.rust.cargo.project.workspace.CargoWorkspace import org.rust.cargo.project.workspace.PackageOrigin import org.rust.lang.core.psi.ext.findCargoPackage import org.rust.openapiext.document -import java.io.File import java.nio.file.Paths -// @TODO cache it with lazy + AsyncFileListener object DfxJson { - fun getRustCanisterScopeFromCandidFile(candidFile: CandidFile): GlobalSearchScope? { - return getRustCanisters(candidFile.project) - ?.find { it.candidFile.filePath() === candidFile.filePath() } + private val cachedRustCanister: MutableMap?> = mutableMapOf() + + fun getRustCanisterScopeFromCandidFile(project: Project, candidFile: VirtualFile): GlobalSearchScope? { + return getCached(project) + ?.find { it.candidFile.path == candidFile.path } ?.getScope() } - fun getCandidFileFromRustFile(psiFile: PsiFile): CandidFile? { - return getRustCanisters(psiFile.project) - ?.find { it.cargoPackage === psiFile.findCargoPackage() } + fun getCandidFileFromRustFile(psiFile: PsiFile): VirtualFile? { + return getCached(psiFile.project) + ?.find { it.cargoPackage == psiFile.findCargoPackage() } ?.candidFile } + private fun getCached(project: Project): List? { + if (!cachedRustCanister.containsKey(project)) { + cachedRustCanister[project] = getRustCanisters(project) + } + + return cachedRustCanister[project] + } + private fun getRustCanisters(project: Project): List? { // find dfx json val document = FilenameIndex @@ -69,26 +69,28 @@ object DfxJson { } // get the corresponding cargo package - val crateName = jsonProperty.nameElement.text - val cargoPackage = allPackages.find { item -> crateName.trim('"') == item.name } ?: return@mapNotNull null + val packageField = (jsonProperty.value as? JsonObject)?.findProperty("package") ?: return@mapNotNull null + val crateName = packageField.value?.text?.trim('"') ?: return@mapNotNull null + val cargoPackage = allPackages.find { item -> crateName == item.name } ?: return@mapNotNull null // get the corresponding candid file val candidField = (jsonProperty.value as? JsonObject)?.findProperty("candid") ?: return@mapNotNull null val candidPath = candidField.value?.text?.trim('"') ?: return@mapNotNull null val candidVirtualFile = LocalFileSystem.getInstance() .findFileByPath(Paths.get(project.basePath, candidPath).toString()) ?: return@mapNotNull null - val candidFile = PsiManager - .getInstance(project) - .findFile(candidVirtualFile) as? CandidFile ?: return@mapNotNull null - RustCanister(project, cargoPackage, candidFile) + RustCanister(project, cargoPackage, candidVirtualFile) } } + fun updateCache() { + cachedRustCanister.mapValues { getRustCanisters(it.key) } + } + private data class RustCanister( val project: Project, val cargoPackage: CargoWorkspace.Package, - val candidFile: CandidFile + val candidFile: VirtualFile ) { fun getScope(): GlobalSearchScope { return GlobalSearchScopes.directoriesScope(project, true, cargoPackage.contentRoot) diff --git a/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt b/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt index 92f3a82..a9a8b23 100644 --- a/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt +++ b/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt @@ -1,12 +1,12 @@ package com.github.alaanor.candid.reference +import com.github.alaanor.candid.project.DfxJson import com.github.alaanor.candid.psi.CandidMethodType import com.github.alaanor.candid.psi.methodNameText import com.github.alaanor.candid.util.CandidRustIcCdkUtil import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiReferenceBase -import com.intellij.psi.search.GlobalSearchScope import com.intellij.psi.stubs.StubIndex import org.rust.lang.core.psi.RsFunction import org.rust.lang.core.psi.ext.RsNamedElement @@ -18,11 +18,16 @@ class CandidMethodReference(method: CandidMethodType, private var textRange: Tex override fun getAbsoluteRange(): TextRange = textRange override fun resolve(): PsiElement? { + val rustPackageScope = DfxJson.getRustCanisterScopeFromCandidFile( + element.project, + element.containingFile.virtualFile + ) ?: return null + val namedElements = StubIndex.getElements( RsNamedElementIndex.KEY, element.methodNameText(), element.project, - GlobalSearchScope.projectScope(element.project), + rustPackageScope, RsNamedElement::class.java ) diff --git a/src/main/resources/META-INF/candid-rust.xml b/src/main/resources/META-INF/candid-rust.xml index f3bba79..15b3ec5 100644 --- a/src/main/resources/META-INF/candid-rust.xml +++ b/src/main/resources/META-INF/candid-rust.xml @@ -3,5 +3,6 @@ + \ No newline at end of file From 7e02c09a65dccfd9aab40e03369fbbfce6efaad2 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sun, 5 Jun 2022 13:29:11 +0200 Subject: [PATCH 08/12] If resolving by the method name fail, fallback by looking on the rsMetaItem --- .../candid/reference/CandidMethodReference.kt | 39 ++++++++++++++++++- .../candid/util/CandidRustIcCdkUtil.kt | 33 +++++++++------- 2 files changed, 57 insertions(+), 15 deletions(-) diff --git a/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt b/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt index a9a8b23..f02b6ad 100644 --- a/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt +++ b/src/main/kotlin/com/github/alaanor/candid/reference/CandidMethodReference.kt @@ -4,11 +4,16 @@ import com.github.alaanor.candid.project.DfxJson import com.github.alaanor.candid.psi.CandidMethodType import com.github.alaanor.candid.psi.methodNameText import com.github.alaanor.candid.util.CandidRustIcCdkUtil +import com.intellij.openapi.project.Project import com.intellij.openapi.util.TextRange import com.intellij.psi.PsiElement import com.intellij.psi.PsiReferenceBase +import com.intellij.psi.search.GlobalSearchScope +import com.intellij.psi.search.PsiSearchHelper import com.intellij.psi.stubs.StubIndex +import com.intellij.psi.util.PsiTreeUtil import org.rust.lang.core.psi.RsFunction +import org.rust.lang.core.psi.RsMetaItem import org.rust.lang.core.psi.ext.RsNamedElement import org.rust.lang.core.stubs.index.RsNamedElementIndex @@ -23,9 +28,15 @@ class CandidMethodReference(method: CandidMethodType, private var textRange: Tex element.containingFile.virtualFile ) ?: return null + val lookupName = element.methodNameText() + return resolveByMethodName(rustPackageScope, lookupName) + ?: resolveByMetaItem(element.project, rustPackageScope, lookupName) + } + + private fun resolveByMethodName(rustPackageScope: GlobalSearchScope, lookupName: String): PsiElement? { val namedElements = StubIndex.getElements( RsNamedElementIndex.KEY, - element.methodNameText(), + lookupName, element.project, rustPackageScope, RsNamedElement::class.java @@ -44,4 +55,30 @@ class CandidMethodReference(method: CandidMethodType, private var textRange: Tex return rsFunction?.identifier } + + private fun resolveByMetaItem( + project: Project, + rustPackageScope: GlobalSearchScope, + lookupName: String + ): PsiElement? { + var rsMetaItem: RsMetaItem? = null + + PsiSearchHelper.getInstance(project) + .processAllFilesWithWordInLiterals(lookupName, rustPackageScope) processor@{ file -> + if (!rustPackageScope.contains(file.virtualFile)) { + return@processor false + } + + val possibleMetaItem = PsiTreeUtil.findChildrenOfType(file, RsMetaItem::class.java) + .filter { CandidRustIcCdkUtil.isIcCdkUpdateQuery(it) } + .firstOrNull { CandidRustIcCdkUtil.getName(it) == lookupName } + ?: return@processor false + + rsMetaItem = possibleMetaItem + return@processor true + } + + return rsMetaItem?.parent?.parent as? RsFunction ?: return null + } + } \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt index 312fbf8..2702844 100644 --- a/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt +++ b/src/main/kotlin/com/github/alaanor/candid/util/CandidRustIcCdkUtil.kt @@ -13,25 +13,30 @@ class CandidRustIcCdkUtil { return getName(rsMetaItem, rsFunction) } - private fun getName(rsMetaItem: RsMetaItem, rsFunction: RsFunction): String? { + fun getName(rsMetaItem: RsMetaItem): String? { return rsMetaItem.metaItemArgsList.find { it.name == "name" }?.value - ?: rsFunction.name } - private fun getMetaItem(rsFunction: RsFunction): RsMetaItem? { - return rsFunction.rawMetaItems.find { - when (it.path?.textNormalized) { - "ic_cdk_macros::update", "ic_cdk_macros::query" -> true - "update", "query" -> { - it.path - ?.reference - ?.resolve() - ?.containingCrate - ?.normName == "ic_cdk_macros" - } - else -> false + fun isIcCdkUpdateQuery(rsMetaItem: RsMetaItem): Boolean { + return when (rsMetaItem.path?.textNormalized) { + "ic_cdk_macros::update", "ic_cdk_macros::query" -> true + "update", "query" -> { + rsMetaItem.path + ?.reference + ?.resolve() + ?.containingCrate + ?.normName == "ic_cdk_macros" } + else -> false } } + + private fun getName(rsMetaItem: RsMetaItem, rsFunction: RsFunction): String? { + return getName(rsMetaItem) ?: rsFunction.name + } + + private fun getMetaItem(rsFunction: RsFunction): RsMetaItem? { + return rsFunction.rawMetaItems.find { isIcCdkUpdateQuery(it) } + } } } \ No newline at end of file From 14bd52cd1a10b585bd50acde62ee506fb1af8b40 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sun, 5 Jun 2022 14:06:29 +0200 Subject: [PATCH 09/12] unused method inspection --- .../CandidUnusedMethodInspection.kt | 30 +++++++++++++++++++ .../github/alaanor/candid/project/DfxJson.kt | 4 +++ src/main/resources/META-INF/candid-rust.xml | 4 +++ .../CandidUnusedMethod.html | 7 +++++ 4 files changed, 45 insertions(+) create mode 100644 src/main/kotlin/com/github/alaanor/candid/inspection/CandidUnusedMethodInspection.kt create mode 100644 src/main/resources/inspectionDescriptions/CandidUnusedMethod.html diff --git a/src/main/kotlin/com/github/alaanor/candid/inspection/CandidUnusedMethodInspection.kt b/src/main/kotlin/com/github/alaanor/candid/inspection/CandidUnusedMethodInspection.kt new file mode 100644 index 0000000..f83bd94 --- /dev/null +++ b/src/main/kotlin/com/github/alaanor/candid/inspection/CandidUnusedMethodInspection.kt @@ -0,0 +1,30 @@ +package com.github.alaanor.candid.inspection + +import com.github.alaanor.candid.project.DfxJson +import com.github.alaanor.candid.psi.CandidMethodType +import com.intellij.codeInspection.* +import com.intellij.psi.PsiFile +import com.intellij.psi.util.PsiTreeUtil + +class CandidUnusedMethodInspection : LocalInspectionTool() { + override fun checkFile(file: PsiFile, manager: InspectionManager, isOnTheFly: Boolean): Array? { + if (!DfxJson.isRustCanister(file.project, file.virtualFile)) + return null + + val problemsHolder = ProblemsHolder(manager, file, isOnTheFly) + val methods = PsiTreeUtil.findChildrenOfType(file, CandidMethodType::class.java) + + methods.forEach { method -> + if (method.reference?.resolve() != null) + return@forEach + + problemsHolder.registerProblem( + method.originalElement, + "No matching rust method found for this candid method", + ProblemHighlightType.LIKE_UNUSED_SYMBOL + ) + } + + return problemsHolder.resultsArray + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt index 770010e..beec955 100644 --- a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt +++ b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt @@ -20,6 +20,10 @@ object DfxJson { private val cachedRustCanister: MutableMap?> = mutableMapOf() + fun isRustCanister(project: Project, candidFile: VirtualFile): Boolean { + return getCached(project)?.find { it.candidFile.path == candidFile.path } != null + } + fun getRustCanisterScopeFromCandidFile(project: Project, candidFile: VirtualFile): GlobalSearchScope? { return getCached(project) ?.find { it.candidFile.path == candidFile.path } diff --git a/src/main/resources/META-INF/candid-rust.xml b/src/main/resources/META-INF/candid-rust.xml index 15b3ec5..d4cf497 100644 --- a/src/main/resources/META-INF/candid-rust.xml +++ b/src/main/resources/META-INF/candid-rust.xml @@ -4,5 +4,9 @@ language="Rust" implementationClass="com.github.alaanor.candid.marker.CandidMethodRsLineMarkerProvider"/> + \ No newline at end of file diff --git a/src/main/resources/inspectionDescriptions/CandidUnusedMethod.html b/src/main/resources/inspectionDescriptions/CandidUnusedMethod.html new file mode 100644 index 0000000..6dd46e9 --- /dev/null +++ b/src/main/resources/inspectionDescriptions/CandidUnusedMethod.html @@ -0,0 +1,7 @@ +Detect unused candid method +
+For a candid method to be unused it must be unresolved. +This mean no matching rust method has been found. +
+A rust method must be annotated with something like #[query(name='matching-name')]. +If the name attribute is not used, the fallback is the rust method name. \ No newline at end of file From 0586f459bfe1c2a520be398f55244d50584e2357 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sun, 5 Jun 2022 14:41:04 +0200 Subject: [PATCH 10/12] fix the cache - actually assign the new value on update --- .../com/github/alaanor/candid/project/DfxJson.kt | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt index beec955..2207b84 100644 --- a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt +++ b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt @@ -18,7 +18,7 @@ import java.nio.file.Paths object DfxJson { - private val cachedRustCanister: MutableMap?> = mutableMapOf() + private var cachedRustCanister: MutableMap?> = mutableMapOf() fun isRustCanister(project: Project, candidFile: VirtualFile): Boolean { return getCached(project)?.find { it.candidFile.path == candidFile.path } != null @@ -46,10 +46,9 @@ object DfxJson { private fun getRustCanisters(project: Project): List? { // find dfx json - val document = FilenameIndex - .getFilesByName(project, "dfx.json", GlobalSearchScope.projectScope(project), false) - .find { it.projectFilePath() == "dfx.json" } - ?.run { virtualFile.document } ?: return null + val document = LocalFileSystem.getInstance() + .findFileByPath(Paths.get(project.basePath, "dfx.json").toString()) + ?.document ?: return null // retrieving the list of canisters declared from the dfx.json val json = PsiDocumentManager.getInstance(project).getPsiFile(document) as? JsonFile ?: return null @@ -88,7 +87,9 @@ object DfxJson { } fun updateCache() { - cachedRustCanister.mapValues { getRustCanisters(it.key) } + cachedRustCanister = cachedRustCanister + .mapValues { getRustCanisters(it.key) } + .toMutableMap() } private data class RustCanister( From 21e928aaf1f348762f74cafe5f7af4d3a5e6b1ee Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sun, 5 Jun 2022 15:01:52 +0200 Subject: [PATCH 11/12] bump for release & update readme for rust integration --- CHANGELOG.md | 30 +++++++++++++++++------------- README.md | 20 ++++++++++++++++++-- gradle.properties | 2 +- 3 files changed, 36 insertions(+), 16 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 01548a2..fe06aae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,21 +3,25 @@ # candid-intellij-plugin Changelog ## [Unreleased] +### Added +- 🔧 Line marker on rust files when a matching candid method is found +- 🔧 Resolve candid method to their corresponding rust method +- 🧐 Unused candid method inspection ## [0.2.0] -### Added -- 🪄 Suggest missing import whenever possible -- ️🧐 Marking self import as invalid -- ️🧐 Marking duplicated type name as invalid -- ️🧐 Marking empty and invalid import as invalid -- ✨ Comment code through shortcut -- 📝 Documentation for type reference -- 🔎 Go to symbol for candid type -- 📝 Structure view - -### Fixed -- Added missing top level keyword import -- Missing keyword completion for query and oneway +### Added +- 🪄 Suggest missing import whenever possible +- ️🧐 Marking self import as invalid +- ️🧐 Marking duplicated type name as invalid +- ️🧐 Marking empty and invalid import as invalid +- ✨ Comment code through shortcut +- 📝 Documentation for type reference +- 🔎 Go to symbol for candid type +- 📝 Structure view + +### Fixed +- Added missing top level keyword import +- Missing keyword completion for query and oneway - Stop suggesting top level keyword inside a service ## [0.1.1] diff --git a/README.md b/README.md index 85e514d..38024ce 100644 --- a/README.md +++ b/README.md @@ -17,11 +17,27 @@ Features supported so far: - ⌨️ Auto Completion - 🔍 Find Usage - 💄 Code Format +- 🦀 Rust integration -## Early stage +## 🦀 Rust integration -This plugin is in the early stage and while this already has some basic feature it might as well miss some. It is in my plan to work on it for the next 5 weeks and submit as an entry for the [Supernova hackathon](https://dfinity.org/supernova/). If you have any feedback or issue feel free to open an issue or directly get in touch with me on discord Alaanor#9999. +For the sake of correctness, the plugin will only enable rust integration for a given candid file if the followings are found in `dfx.json`: + +```json +{ + "canisters": { + "foobar-canister": { + "type": "rust", + "candid": "correct/path/to/candid-file.did", + "package": "rust-package-name" + } + } +} +``` + +All three `type`, `candid` and `package` fields are required to enable rust integration. `dfx.json` is expected to be found at the root of the project. +The type `custom` will not be supported because of the lack of explicit information that the plugin require to correctly resolve items. ## Installation diff --git a/gradle.properties b/gradle.properties index a10e8e3..c71e97b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.github.alaanor.candid pluginName = candid-intellij-plugin # SemVer format -> https://semver.org -pluginVersion = 0.2.0 +pluginVersion = 0.3.0 # See https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html # for insight into build numbers and IntelliJ Platform versions. From 6a576e314694dbc6df066b97a6b0fc63306a2e34 Mon Sep 17 00:00:00 2001 From: Maxime Bonvin Date: Sun, 5 Jun 2022 16:21:41 +0200 Subject: [PATCH 12/12] fix qodana lint --- .../candid/formatter/CandidCodeStyleSettingsProvider.kt | 2 +- .../alaanor/candid/inspection/CandidSelfImportInspection.kt | 2 +- src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt | 1 - .../candid/psi/primitive/CandidStubBasedElementBase.kt | 1 - .../com/github/alaanor/candid/psi/stub/CandidFileStub.kt | 2 +- .../alaanor/candid/psi/stub/CandidStubBasedPsiElement.kt | 4 +--- .../alaanor/candid/quickfix/action/CandidAddImportAction.kt | 2 +- 7 files changed, 5 insertions(+), 9 deletions(-) diff --git a/src/main/kotlin/com/github/alaanor/candid/formatter/CandidCodeStyleSettingsProvider.kt b/src/main/kotlin/com/github/alaanor/candid/formatter/CandidCodeStyleSettingsProvider.kt index fa86e29..9ffd327 100644 --- a/src/main/kotlin/com/github/alaanor/candid/formatter/CandidCodeStyleSettingsProvider.kt +++ b/src/main/kotlin/com/github/alaanor/candid/formatter/CandidCodeStyleSettingsProvider.kt @@ -12,7 +12,7 @@ import com.intellij.psi.codeStyle.CustomCodeStyleSettings class CandidCodeStyleSettingsProvider : CodeStyleSettingsProvider() { override fun getConfigurableDisplayName(): String = "Candid" - override fun createCustomSettings(settings: CodeStyleSettings): CustomCodeStyleSettings? { + override fun createCustomSettings(settings: CodeStyleSettings): CustomCodeStyleSettings { return CandidCodeStyleSettings(settings) } diff --git a/src/main/kotlin/com/github/alaanor/candid/inspection/CandidSelfImportInspection.kt b/src/main/kotlin/com/github/alaanor/candid/inspection/CandidSelfImportInspection.kt index 24cb20d..9de0b61 100644 --- a/src/main/kotlin/com/github/alaanor/candid/inspection/CandidSelfImportInspection.kt +++ b/src/main/kotlin/com/github/alaanor/candid/inspection/CandidSelfImportInspection.kt @@ -29,6 +29,6 @@ class CandidSelfImportInspection : LocalInspectionTool() { } } - return problemsHolder.resultsArray; + return problemsHolder.resultsArray } } \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt index 2207b84..3f349bf 100644 --- a/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt +++ b/src/main/kotlin/com/github/alaanor/candid/project/DfxJson.kt @@ -1,6 +1,5 @@ package com.github.alaanor.candid.project -import com.github.alaanor.candid.util.projectFilePath import com.intellij.json.psi.JsonFile import com.intellij.json.psi.JsonObject import com.intellij.openapi.project.Project diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/primitive/CandidStubBasedElementBase.kt b/src/main/kotlin/com/github/alaanor/candid/psi/primitive/CandidStubBasedElementBase.kt index 4939be0..8f98bf6 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/primitive/CandidStubBasedElementBase.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/primitive/CandidStubBasedElementBase.kt @@ -1,7 +1,6 @@ package com.github.alaanor.candid.psi.primitive import com.github.alaanor.candid.psi.stub.impl.CandidStubBase -import com.intellij.extapi.psi.ASTWrapperPsiElement import com.intellij.extapi.psi.StubBasedPsiElementBase import com.intellij.lang.ASTNode import com.intellij.navigation.ItemPresentation diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidFileStub.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidFileStub.kt index 2a66f57..72faeb6 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidFileStub.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidFileStub.kt @@ -8,7 +8,7 @@ import com.intellij.psi.tree.IStubFileElementType interface CandidFileStub : PsiFileStub, CandidStub { object Type : IStubFileElementType("CANDID_FILE", CandidLanguage.INSTANCE) { override fun getStubVersion(): Int { - return 1; + return 1 } override fun getExternalId(): String { diff --git a/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidStubBasedPsiElement.kt b/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidStubBasedPsiElement.kt index 98465bc..50465ba 100644 --- a/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidStubBasedPsiElement.kt +++ b/src/main/kotlin/com/github/alaanor/candid/psi/stub/CandidStubBasedPsiElement.kt @@ -4,6 +4,4 @@ import com.intellij.psi.PsiElement import com.intellij.psi.StubBasedPsiElement import com.intellij.psi.stubs.StubElement -interface CandidStubBasedPsiElement, TPsi: PsiElement> : StubBasedPsiElement { - -} \ No newline at end of file +interface CandidStubBasedPsiElement, TPsi: PsiElement> : StubBasedPsiElement \ No newline at end of file diff --git a/src/main/kotlin/com/github/alaanor/candid/quickfix/action/CandidAddImportAction.kt b/src/main/kotlin/com/github/alaanor/candid/quickfix/action/CandidAddImportAction.kt index 14a3cf1..9990724 100644 --- a/src/main/kotlin/com/github/alaanor/candid/quickfix/action/CandidAddImportAction.kt +++ b/src/main/kotlin/com/github/alaanor/candid/quickfix/action/CandidAddImportAction.kt @@ -40,7 +40,7 @@ class CandidAddImportAction( return "${value.name} (${value.projectFilePath()})" } - override fun getIconFor(value: CandidIdentifierDeclaration?): Icon? { + override fun getIconFor(value: CandidIdentifierDeclaration?): Icon { return CandidIcons.Type }