Skip to content

Commit

Permalink
Merge pull request #127 from alephium/goto_enum
Browse files Browse the repository at this point in the history
GoTo `enum` type, field and usages
  • Loading branch information
simerplaha authored Mar 8, 2024
2 parents 24a0db9 + b5478ee commit 00fcd7a
Show file tree
Hide file tree
Showing 7 changed files with 554 additions and 7 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,62 @@ import scala.collection.immutable.ArraySeq
private object GoToIdent {

/**
* Navigate to the nearest argument for the given identifier.
* Navigate to the argument(s) for the given identifier.
*
* @param identNode The node representing the identifier in the AST.
* @param ident The identifier for which the argument definition is sought.
* @param source The source tree to search within.
* @return An option containing the closest argument if found, otherwise None.
* @return An array sequence of positioned ASTs matching the search result.
* */
def goTo(identNode: Node[Positioned],
ident: Ast.Ident,
source: Tree.Source): ArraySeq[Ast.Argument] =
source: Tree.Source): ArraySeq[Ast.Positioned] =
identNode
.parent // take one step up to check the type of ident node.
.map(_.data)
.to(ArraySeq)
.collect {
case variable: Ast.Variable[_] if variable.id == ident => // Is it a variable?
case Node(variable: Ast.Variable[_], _) if variable.id == ident => // Is it a variable?
// The user clicked on a variable. Take 'em there!
goToVariable(
variableNode = identNode,
variable = variable,
source = source
)

case Node(fieldSelector: Ast.EnumFieldSelector[_], _) if fieldSelector.field == ident =>
// The user clicked on an enum field. Take 'em there!
goToEnumField(
fieldSelector = fieldSelector,
source = source
)

case node @ Node(field: Ast.EnumField, _) if field.ident == ident =>
// The user clicked on an enum field.
// Check the parent to find the enum type.
node
.parent
.map(_.data)
.to(ArraySeq)
.collect {
// Check: Parent is an enum definition which contains the enum field.
case enumDef: Ast.EnumDef if enumDef.fields.exists(_.ident == field.ident) =>
goToEnumFieldCalls(
enumType = enumDef.id,
enumField = field,
source = source
)
}
.flatten
}
.flatten

/** Navigate to the nearest argument for the given variable.
/**
* Navigate to the argument(s) for the given variable.
*
* @param variableNode The node representing the variable.
* @param variable The variable to find the argument for.
* @param source The source tree to search within.
* @return An option containing the closest argument if found, otherwise None.
* @return An array sequence of [[Ast.Argument]]s matching the search result.
* */
private def goToVariable(variableNode: Node[Positioned],
variable: Ast.Variable[_],
Expand All @@ -61,4 +86,46 @@ private object GoToIdent {
}
.map(_.typeDef)
}

/**
* Navigate to the enum field(s) for the given selected enum field.
*
* @param fieldSelector The selected enum field to find.
* @param source The source tree to search within.
* @return An array sequence of [[Ast.EnumField]]s matching the search result.
* */
private def goToEnumField(fieldSelector: Ast.EnumFieldSelector[_],
source: Tree.Source): ArraySeq[Ast.EnumField] =
source.ast match {
case contract: Ast.Contract =>
contract
.enums
.filter(_.id == fieldSelector.enumId)
.flatMap(_.fields.find(_.ident == fieldSelector.field))
.to(ArraySeq)

case _: Ast.ContractInterface | _: Ast.TxScript =>
ArraySeq.empty
}

/**
* Navigate to all enum calls for the given enum type and field.
*
* @param enumType The enum type to find.
* @param enumField The enum field to find.
* @param source The source tree to search within.
* @return An array sequence of enum field identities matching the search result.
* */
private def goToEnumFieldCalls(enumType: Ast.TypeId,
enumField: Ast.EnumField,
source: Tree.Source): ArraySeq[Ast.Ident] =
source
.rootNode
.walkDown
.collect {
// find all the selections matching the enum and the enum's field type.
case Node(selector: Ast.EnumFieldSelector[_], _) if selector.enumId == enumType && selector.field == enumField.ident =>
selector.field
}
.to(ArraySeq)
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,14 @@ private object GoToSource {
funcId = funcId,
source = source
)

case typIdNode @ Node(typeId: Ast.TypeId, _) =>
// the clicked/closest node is TypeId
GoToTypeId.goTo(
identNode = typIdNode,
typeId = typeId,
source = source
)
}
.flatten

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package org.alephium.ralph.lsp.pc.search.gotodef

import org.alephium.ralph.Ast
import org.alephium.ralph.Ast.Positioned
import org.alephium.ralph.lsp.access.compiler.ast.Tree
import org.alephium.ralph.lsp.access.compiler.ast.node.Node

import scala.collection.immutable.ArraySeq

private object GoToTypeId {

/**
* Navigate to the positioned ASTs for the given type identifier.
*
* @param identNode The node representing the type identifier in the AST.
* @param typeId The type identifier for which the [[Ast.TypeId]] is sought.
* @param source The source tree to search within.
* @return An array sequence of positioned ASTs matching the search result.
* */
def goTo(identNode: Node[Positioned],
typeId: Ast.TypeId,
source: Tree.Source): ArraySeq[Ast.Positioned] =
identNode
.parent // take one step up to check the type of TypeId node.
.map(_.data)
.to(ArraySeq)
.collect {
case enumFieldSelector: Ast.EnumFieldSelector[_] if enumFieldSelector.enumId == typeId =>
// The user clicked on an enum type. Take 'em there!
goToEnumType(
enumSelector = enumFieldSelector,
source = source
)

case enumDef: Ast.EnumDef if enumDef.id == typeId =>
// The user clicked on an enum definition. Take 'em there!
goToEnumTypeCalls(
enumDef = enumDef,
source = source
)
}
.flatten

/** Navigate to the enum types for the given enum field selector.
*
* @param enumSelector The enum type to find.
* @param source The source tree to search within.
* @return An array sequence of enum [[Ast.TypeId]]s matching the search result.
* */
private def goToEnumType(enumSelector: Ast.EnumFieldSelector[_],
source: Tree.Source): ArraySeq[Ast.TypeId] =
source.ast match {
case contract: Ast.Contract =>
contract
.enums
.filter(_.id == enumSelector.enumId)
.map(_.id)
.to(ArraySeq)

case _: Ast.ContractInterface | _: Ast.TxScript =>
ArraySeq.empty
}

/** Navigate to the enum type name calls.
*
* @param enumDef The enum definition contain the enum type identifier to find calls for.
* @param source The source tree to search within.
* @return An array sequence of enum type [[Ast.TypeId]]s matching the search result.
* */
private def goToEnumTypeCalls(enumDef: Ast.EnumDef,
source: Tree.Source): ArraySeq[Ast.TypeId] =
source
.rootNode
.walkDown
.collect {
case Node(selector: Ast.EnumFieldSelector[_], _) if selector.enumId == enumDef.id =>
selector.enumId
}
.to(ArraySeq)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
package org.alephium.ralph.lsp.pc.search.gotodef

import org.alephium.ralph.lsp.pc.search.TestCodeProvider._
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec

class GoToEnumFieldSpec extends AnyWordSpec with Matchers {

"return empty" when {
"enum type does not exist" in {
goTo(
"""
|Contract MyContract() {
| pub fn function() -> () {
| let field = EnumType.Fie@@ld0
| }
|}
|""".stripMargin
)
}
}

"return non-empty" when {
"user selects the first enum field" in {
goTo(
"""
|Contract MyContract() {
|
| enum EnumType {
| >>Field0 = 0<<
| Field1 = 1
| }
|
| pub fn function() -> () {
| let field0 = EnumType.Fie@@ld0
| let field1 = EnumType.Field1
| }
|}
|""".stripMargin
)
}

"user selects the second enum field" in {
goTo(
"""
|Contract MyContract() {
|
| enum EnumType {
| Field0 = 0
| >>Field1 = 1<<
| }
|
| pub fn function() -> () {
| let field0 = EnumType.Field0
| let field1 = EnumType.Fie@@ld1
| }
|}
|""".stripMargin
)
}

"there are duplicate enum types and fields" when {
"user selects the first enum field" in {
goTo(
"""
|Contract MyContract() {
|
| enum EnumType {
| >>Field0 = 0<<
| Field1 = 1
| }
|
| enum EnumType {
| >>Field0 = 0<<
| Field1 = 1
| }
|
| pub fn function() -> () {
| let field0 = EnumType.Fi@@eld0
| let field1 = EnumType.Field1
| }
|}
|""".stripMargin
)
}

"user selects the second enum field" in {
goTo(
"""
|Contract MyContract() {
|
| enum EnumType {
| Field0 = 0
| >>Field1 = 1<<
| }
|
| enum EnumType {
| Field0 = 0
| >>Field1 = 1<<
| }
|
| pub fn function() -> () {
| let field0 = EnumType.Field0
| let field1 = EnumType.Fi@@eld1
| }
|}
|""".stripMargin
)
}
}

"there are duplicate enum types with distinct fields" when {
"user selects the first enum field" in {
goTo(
"""
|Contract MyContract() {
|
| enum EnumType {
| >>Field0 = 0<<
| Field1 = 1
| }
|
| enum EnumType {
| Field2 = 2
| Field3 = 3
| }
|
| pub fn function() -> () {
| let field0 = EnumType.Fie@@ld0
| let field1 = EnumType.Field1
| let field2 = EnumType.Field2
| let field3 = EnumType.Field3
| }
|}
|""".stripMargin
)
}

"user selects the third enum field" in {
goTo(
"""
|Contract MyContract() {
|
| enum EnumType {
| Field0 = 0
| Field1 = 1
| }
|
| enum EnumType {
| >>Field2 = 2<<
| Field3 = 3
| }
|
| pub fn function() -> () {
| let field0 = EnumType.Field0
| let field1 = EnumType.Field1
| let field2 = EnumType.Fie@@ld2
| let field3 = EnumType.Field3
| }
|}
|""".stripMargin
)
}
}
}
}
Loading

0 comments on commit 00fcd7a

Please sign in to comment.