Skip to content

Commit

Permalink
Merge pull request #115 from alephium/disallow_overlap
Browse files Browse the repository at this point in the history
Disallow `dependencyPath` and `contractPath` overlapping
  • Loading branch information
simerplaha authored Feb 26, 2024
2 parents 6a59ecd + 125e794 commit 704ad32
Show file tree
Hide file tree
Showing 3 changed files with 136 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,10 @@ object BuildValidator {
/** Validate that the configured paths are within the workspace directory */
private def validatePathsInWorkspace(parsed: BuildParsed): Option[BuildState.BuildErrored] = {
// absolute source paths
val (workspacePath, absoluteContractPath, absoluteArtifactPath, _) =
val (workspacePath, absoluteContractPath, absoluteArtifactPath, absoluteDependencyPath) =
Build.getAbsolutePaths(parsed)

val (contractPathIndex, artifactPathIndex, _) =
val (contractPathIndex, artifactPathIndex, dependencyPathIndex) =
Build.getPathIndexes(parsed)

val errors =
Expand All @@ -63,6 +63,17 @@ object BuildValidator {
index = artifactPathIndex
)

// Validate: DependencyPath and ContractPath should not overlap
absoluteDependencyPath foreach {
absoluteDependencyPath =>
if (URIUtil.contains(absoluteContractPath, absoluteDependencyPath) || URIUtil.contains(absoluteDependencyPath, absoluteContractPath)) {
// TODO: When `contractPath` and `dependencyPath` are identical then both errors will have the same index, which means
// both errors get reported at the same field. This will be fixed when an AST is available in issue #17.
errors addOne ErrorDependencyPathIsWithinContractPath(index = contractPathIndex)
errors addOne ErrorDependencyPathIsWithinContractPath(index = dependencyPathIndex)
}
}

// Check if errors exists
if (errors.isEmpty)
None
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.alephium.ralph.lsp.pc.workspace.build.error

import org.alephium.ralph.lsp.access.compiler.message.{CompilerMessage, SourceIndex}

case class ErrorDependencyPathIsWithinContractPath(index: SourceIndex) extends CompilerMessage.Error {
override def message: String =
"`dependencyPath` and `contractPath` cannot be identical to or nested within each other. Make sure that they are distinct and do not overlap."
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package org.alephium.ralph.lsp.pc.workspace.build

import org.alephium.ralph.lsp.access.compiler.message.SourceIndex
import org.alephium.ralph.lsp.access.file.FileAccess
import org.alephium.ralph.lsp.pc.workspace.build.error.ErrorDependencyPathIsWithinContractPath
import org.scalatest.OptionValues._
import org.scalatest.TryValues.convertTryToSuccessOrFailure
import org.scalatest.matchers.should.Matchers
import org.scalatest.wordspec.AnyWordSpec
import org.scalatest.TryValues.convertTryToSuccessOrFailure

import java.nio.file.Files
import scala.collection.immutable.ArraySeq

class BuildValidatorSpec extends AnyWordSpec with Matchers {

"BuildValidator" should {
"validate" should {
"normalize path" in {
val config1 = RalphcConfig.defaultParsedConfig.copy(
contractPath = "contracts",
Expand Down Expand Up @@ -46,5 +50,114 @@ class BuildValidatorSpec extends AnyWordSpec with Matchers {

BuildValidator.validate(parsed1) shouldBe BuildValidator.validate(parsed2)
}

"Fail: contractPath and dependencyPath" when {
def doTest(config: RalphcConfig.RalphcParsedConfig) = {
// create a build file for the config
val build =
TestBuild
.genParsed(config = config)
.sample
.get

TestBuild persist build

implicit val file: FileAccess =
FileAccess.disk

val actual =
BuildValidator.validate(build)

// errors should reported for both the field
actual.value.errors should contain theSameElementsAs
ArraySeq(
// error for field dependencyPath
ErrorDependencyPathIsWithinContractPath(SourceIndex(build.code.lastIndexOf(config.dependencyPath.value), config.dependencyPath.value.length)),
// error for field contractPath
ErrorDependencyPathIsWithinContractPath(SourceIndex(build.code.lastIndexOf(config.contractPath), config.contractPath.length))
)

TestBuild delete build
}

"they are identical" in {
val config =
RalphcConfig.defaultParsedConfig.copy(
contractPath = "contracts",
artifactPath = "artifacts",
dependencyPath = Some("contracts")
)

doTest(config)
}

"contractPath is within dependencyPath" in {
val config =
RalphcConfig.defaultParsedConfig.copy(
contractPath = "dependencies/contracts",
artifactPath = "artifacts",
dependencyPath = Some("dependencies")
)

doTest(config)
}

"dependencyPath is within contractPath" in {
val config =
RalphcConfig.defaultParsedConfig.copy(
contractPath = "contracts",
artifactPath = "artifacts",
dependencyPath = Some("contracts/dependencies")
)

doTest(config)
}
}

"Pass: contractPath and dependencyPath" when {
def doTest(config: RalphcConfig.RalphcParsedConfig) = {
// create a build file for the config
val build =
TestBuild
.genParsed(config = config)
.sample
.get

TestBuild persist build

implicit val file: FileAccess =
FileAccess.disk

val actual =
BuildValidator.validate(build)

// expect no errors
actual shouldBe empty

TestBuild delete build
}

"they are distinct" in {
val config =
RalphcConfig.defaultParsedConfig.copy(
contractPath = "contracts",
artifactPath = "artifacts",
dependencyPath = Some("dependencies")
)

doTest(config)
}

"they are distinct within a root folder" in {
val config =
RalphcConfig.defaultParsedConfig.copy(
contractPath = "my_code/contracts",
artifactPath = "artifacts",
dependencyPath = Some("my_code/dependencies")
)

doTest(config)
}
}
}
}

0 comments on commit 704ad32

Please sign in to comment.