-
Notifications
You must be signed in to change notification settings - Fork 323
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Update Project Name Validation (#1570)
PR moves all the project validation logic to the `org.enso.pkg.validation` module and updates the project manager to use the logic from the new module
- Loading branch information
Showing
12 changed files
with
345 additions
and
153 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
25 changes: 25 additions & 0 deletions
25
lib/scala/pkg/src/main/scala/org/enso/pkg/validation/InvalidNameError.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package org.enso.pkg.validation | ||
|
||
/** Base trait for a project name validation errors. */ | ||
sealed trait InvalidNameError | ||
object InvalidNameError { | ||
|
||
/** Indicates that project name is empty. */ | ||
case object Empty extends InvalidNameError | ||
|
||
/** Indicates that project name should start with a capital letter. */ | ||
case object ShouldStartWithCapitalLetter extends InvalidNameError | ||
|
||
/** Indicates that projet name contains invalid characters. | ||
* | ||
* @param invalidCharacters the list of invalid characters | ||
*/ | ||
case class ContainsInvalidCharacters(invalidCharacters: Set[Char]) | ||
extends InvalidNameError | ||
|
||
/** Indicates that project name should be in upper shane case. | ||
* | ||
* @param validName initial project name rewritten in upper snake case | ||
*/ | ||
case class ShouldBeUpperSnakeCase(validName: String) extends InvalidNameError | ||
} |
81 changes: 81 additions & 0 deletions
81
lib/scala/pkg/src/main/scala/org/enso/pkg/validation/NameValidation.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,81 @@ | ||
package org.enso.pkg.validation | ||
|
||
import scala.collection.immutable.ListSet | ||
|
||
object NameValidation { | ||
|
||
/** Transforms the given string into a valid package name (i.e. a CamelCased identifier). | ||
* | ||
* @param name the original name. | ||
* @return the transformed name conforming to the specification. | ||
*/ | ||
def normalizeName(name: String): String = { | ||
val startingWithLetter = | ||
if (name.isEmpty || !name.head.isLetter) "Project_" ++ name else name | ||
val startingWithUppercase = startingWithLetter.capitalize | ||
val onlyAlphanumeric = startingWithUppercase.filter(isAllowedNameCharacter) | ||
toUpperSnakeCase(onlyAlphanumeric) | ||
} | ||
|
||
/** Validate the project name. | ||
* | ||
* @param name the project name to validate | ||
* @return either a validation error or a project name if it's valid | ||
*/ | ||
def validateName(name: String): Either[InvalidNameError, String] = | ||
if (name.isEmpty) { | ||
Left(InvalidNameError.Empty) | ||
} else if (!name.head.isLetter || !name.head.isUpper) { | ||
Left(InvalidNameError.ShouldStartWithCapitalLetter) | ||
} else if (!name.forall(isAllowedNameCharacter)) { | ||
val invalidCharacters = name.filterNot(isAllowedNameCharacter) | ||
Left( | ||
InvalidNameError.ContainsInvalidCharacters( | ||
ListSet(invalidCharacters: _*) | ||
) | ||
) | ||
} else if (name != toUpperSnakeCase(name)) { | ||
Left(InvalidNameError.ShouldBeUpperSnakeCase(toUpperSnakeCase(name))) | ||
} else { | ||
Right(name) | ||
} | ||
|
||
/** Checks if a character is allowed in a project name. | ||
* | ||
* @param char the char to validate | ||
* @return `true` if it's allowed, `false` otherwise | ||
*/ | ||
private def isAllowedNameCharacter(char: Char): Boolean = { | ||
char.isLetterOrDigit || char == '_' | ||
} | ||
|
||
/** Takes a name containing letters, digits, and `_` characters and makes it | ||
* a proper `Upper_Snake_Case` name. | ||
* | ||
* @param string the input string | ||
* @return the transformed string | ||
*/ | ||
private def toUpperSnakeCase(string: String): String = { | ||
val beginMarker = '#' | ||
val chars = string.toList | ||
val charPairs = (beginMarker :: chars).zip(chars) | ||
charPairs | ||
.map { case (previous, current) => | ||
if (previous == beginMarker) { | ||
current.toString | ||
} else if (previous.isLower && current.isUpper) { | ||
s"_$current" | ||
} else if (previous.isLetter && current.isDigit) { | ||
s"_$current" | ||
} else if (previous == '_' && current == '_') { | ||
"" | ||
} else if (previous.isDigit && current.isLetter) { | ||
s"_${current.toUpper}" | ||
} else { | ||
current.toString | ||
} | ||
} | ||
.mkString("") | ||
} | ||
|
||
} |
46 changes: 34 additions & 12 deletions
46
lib/scala/pkg/src/test/scala/org/enso/pkg/NameSanitizationSpec.scala
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,23 +1,45 @@ | ||
package org.enso.pkg | ||
|
||
import java.io.File | ||
|
||
import org.enso.filesystem.FileSystem | ||
import org.enso.pkg.validation.{InvalidNameError, NameValidation} | ||
import org.scalatest.matchers.should.Matchers | ||
import org.scalatest.wordspec.AnyWordSpec | ||
|
||
import scala.collection.immutable.ListSet | ||
|
||
class NameSanitizationSpec extends AnyWordSpec with Matchers { | ||
"Creating a new project" should { | ||
"sanitize the name of the project" in { | ||
implicit val fileSystem: FileSystem[File] = FileSystem.Default | ||
|
||
val manager = new PackageManager() | ||
"Name Validation" should { | ||
|
||
"sanitize the name of the project" in { | ||
normalizeName("My_Project") shouldEqual "My_Project" | ||
normalizeName("My___Project") shouldEqual "My_Project" | ||
normalizeName("myProject") shouldEqual "My_Project" | ||
normalizeName("myPro??^ject123") shouldEqual "My_Project_123" | ||
normalizeName("???%$6543lib") shouldEqual "Project_6543_Lib" | ||
} | ||
|
||
manager.normalizeName("My_Project") shouldEqual "My_Project" | ||
manager.normalizeName("My___Project") shouldEqual "My_Project" | ||
manager.normalizeName("myProject") shouldEqual "My_Project" | ||
manager.normalizeName("myPro??^ject123") shouldEqual "My_Project_123" | ||
manager.normalizeName("???%$6543lib") shouldEqual "Project_6543_Lib" | ||
"validate the project name" in { | ||
validateName("My") shouldEqual Right("My") | ||
validateName("My_Project") shouldEqual Right("My_Project") | ||
validateName("") shouldEqual Left(InvalidNameError.Empty) | ||
validateName("My___Project") shouldEqual Left( | ||
InvalidNameError.ShouldBeUpperSnakeCase("My_Project") | ||
) | ||
validateName("FooBar") shouldEqual Left( | ||
InvalidNameError.ShouldBeUpperSnakeCase("Foo_Bar") | ||
) | ||
validateName("myProject") shouldEqual Left( | ||
InvalidNameError.ShouldStartWithCapitalLetter | ||
) | ||
validateName("MyPro??^ject123") shouldEqual Left( | ||
InvalidNameError.ContainsInvalidCharacters(ListSet('?', '^')) | ||
) | ||
} | ||
} | ||
|
||
private def normalizeName(name: String): String = | ||
NameValidation.normalizeName(name) | ||
|
||
private def validateName(name: String): Either[InvalidNameError, String] = | ||
NameValidation.validateName(name) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.