diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidator.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidator.scala index f85ff3427..7f4032a52 100644 --- a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidator.scala +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidator.scala @@ -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 = @@ -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 diff --git a/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/error/ErrorDependencyPathIsWithinContractPath.scala b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/error/ErrorDependencyPathIsWithinContractPath.scala new file mode 100644 index 000000000..99564ce92 --- /dev/null +++ b/presentation-compiler/src/main/scala/org/alephium/ralph/lsp/pc/workspace/build/error/ErrorDependencyPathIsWithinContractPath.scala @@ -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." +} diff --git a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidatorSpec.scala b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidatorSpec.scala index c85cef31d..83ce5eb35 100644 --- a/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidatorSpec.scala +++ b/presentation-compiler/src/test/scala/org/alephium/ralph/lsp/pc/workspace/build/BuildValidatorSpec.scala @@ -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", @@ -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) + } + } } }