diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java index 65a12b87be77..552b17e32f8e 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/ProgrammingExerciseService.java @@ -104,18 +104,27 @@ public class ProgrammingExerciseService { * (https://docs.oracle.com/javase/specs/jls/se14/html/jls-7.html#jls-7.4.1) * with the restriction to a-z,A-Z,_ as "Java letter" and 0-9 as digits due to JavaScript/Browser Unicode character class limitations */ - private static final String PACKAGE_NAME_REGEX = "^(?!.*(?:\\.|^)(?:abstract|continue|for|new|switch|assert|default|if|package|synchronized|boolean|do|goto|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while|_|true|false|null)(?:\\.|$))[A-Z_a-z]\\w*(?:\\.[A-Z_a-z]\\w*)*$"; + private static final String PACKAGE_NAME_REGEX_FOR_JAVA_KOTLIN = "^(?!.*(?:\\.|^)(?:abstract|continue|for|new|switch|assert|default|if|package|synchronized|boolean|do|goto|private|this|break|double|implements|protected|throw|byte|else|import|public|throws|case|enum|instanceof|return|transient|catch|extends|int|short|try|char|final|interface|static|void|class|finally|long|strictfp|volatile|const|float|native|super|while|_|true|false|null)(?:\\.|$))[A-Z_a-z]\\w*(?:\\.[A-Z_a-z]\\w*)*$"; /** * Swift package name Regex derived from * (https://docs.swift.org/swift-book/ReferenceManual/LexicalStructure.html#ID412), * with the restriction to a-z,A-Z as "Swift letter" and 0-9 as digits where no separators are allowed */ - private static final String SWIFT_PACKAGE_NAME_REGEX = "^(?!(?:associatedtype|class|deinit|enum|extension|fileprivate|func|import|init|inout|internal|let|open|operator|private|protocol|public|rethrows|static|struct|subscript|typealias|var|break|case|continue|default|defer|do|else|fallthrough|for|guard|if|in|repeat|return|switch|where|while|as|Any|catch|false|is|nil|super|self|Self|throw|throws|true|try|_|[sS]wift)$)[A-Za-z][\\dA-Za-z]*$"; + private static final String PACKAGE_NAME_REGEX_FOR_SWIFT = "^(?!(?:associatedtype|class|deinit|enum|extension|fileprivate|func|import|init|inout|internal|let|open|operator|private|protocol|public|rethrows|static|struct|subscript|typealias|var|break|case|continue|default|defer|do|else|fallthrough|for|guard|if|in|repeat|return|switch|where|while|as|Any|catch|false|is|nil|super|self|Self|throw|throws|true|try|_|[sS]wift)$)[A-Za-z][\\dA-Za-z]*$"; - private final Pattern packageNamePattern = Pattern.compile(PACKAGE_NAME_REGEX); + /** + * Go package name Regex derived from The Go Programming Language Specification limited to ASCII. Package names are + * identifiers. + * They allow letters, digits and underscore. They cannot start with a digit. The package name cannot be a keyword or "_". + */ + private static final String PACKAGE_NAME_REGEX_FOR_GO = "^(?!(?:break|default|func|interface|select|case|defer|go|map|struct|chan|else|goto|package|switch|const|fallthrough|if|range|type|continue|for|import|return|var|_)$)[A-Za-z_][A-Za-z0-9_]*$"; + + private static final Pattern PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN = Pattern.compile(PACKAGE_NAME_REGEX_FOR_JAVA_KOTLIN); - private final Pattern packageNamePatternForSwift = Pattern.compile(SWIFT_PACKAGE_NAME_REGEX); + private static final Pattern PACKAGE_NAME_PATTERN_FOR_SWIFT = Pattern.compile(PACKAGE_NAME_REGEX_FOR_SWIFT); + + private static final Pattern PACKAGE_NAME_PATTERN_FOR_GO = Pattern.compile(PACKAGE_NAME_REGEX_FOR_GO); private static final Logger log = LoggerFactory.getLogger(ProgrammingExerciseService.class); @@ -426,12 +435,12 @@ private void validatePackageName(ProgrammingExercise programmingExercise, Progra } // Check if package name matches regex - Matcher packageNameMatcher; - switch (programmingExercise.getProgrammingLanguage()) { - case JAVA, KOTLIN -> packageNameMatcher = packageNamePattern.matcher(programmingExercise.getPackageName()); - case SWIFT -> packageNameMatcher = packageNamePatternForSwift.matcher(programmingExercise.getPackageName()); + Matcher packageNameMatcher = switch (programmingExercise.getProgrammingLanguage()) { + case JAVA, KOTLIN -> PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN.matcher(programmingExercise.getPackageName()); + case SWIFT -> PACKAGE_NAME_PATTERN_FOR_SWIFT.matcher(programmingExercise.getPackageName()); + case GO -> PACKAGE_NAME_PATTERN_FOR_GO.matcher(programmingExercise.getPackageName()); default -> throw new IllegalArgumentException("Programming language not supported"); - } + }; if (!packageNameMatcher.matches()) { throw new BadRequestAlertException("The package name is invalid", "Exercise", "packagenameInvalid"); } diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java index c4ffd9f71bcb..cca2995cdba0 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/jenkins/JenkinsProgrammingLanguageFeatureService.java @@ -41,7 +41,7 @@ public JenkinsProgrammingLanguageFeatureService() { programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, false, true, false, false, List.of(FACT, GCC), false, false)); programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false, false)); programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false, false)); - programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, false, false, List.of(), false, false)); + programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, true, false, List.of(), false, false)); programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, false, false, false, false, true, List.of(), false, false)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN, MAVEN_BLACKBOX), true, false)); diff --git a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java index 1828ed874f48..4a62b227323a 100644 --- a/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java +++ b/src/main/java/de/tum/cit/aet/artemis/programming/service/localci/LocalCIProgrammingLanguageFeatureService.java @@ -48,7 +48,7 @@ public LocalCIProgrammingLanguageFeatureService() { programmingLanguageFeatures.put(C, new ProgrammingLanguageFeature(C, false, true, true, false, false, List.of(FACT, GCC), false, true)); programmingLanguageFeatures.put(C_PLUS_PLUS, new ProgrammingLanguageFeature(C_PLUS_PLUS, false, false, true, false, false, List.of(), false, true)); programmingLanguageFeatures.put(C_SHARP, new ProgrammingLanguageFeature(C_SHARP, false, false, true, false, false, List.of(), false, true)); - programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, false, false, List.of(), false, true)); + programmingLanguageFeatures.put(GO, new ProgrammingLanguageFeature(GO, false, false, true, true, false, List.of(), false, true)); programmingLanguageFeatures.put(HASKELL, new ProgrammingLanguageFeature(HASKELL, true, false, false, false, true, List.of(), false, true)); programmingLanguageFeatures.put(JAVA, new ProgrammingLanguageFeature(JAVA, true, true, true, true, false, List.of(PLAIN_GRADLE, GRADLE_GRADLE, PLAIN_MAVEN, MAVEN_MAVEN), false, true)); diff --git a/src/main/resources/templates/go/exercise/bubblesort.go b/src/main/resources/templates/go/exercise/bubblesort.go index c52d8b6ba229..2a05e9ca9e13 100644 --- a/src/main/resources/templates/go/exercise/bubblesort.go +++ b/src/main/resources/templates/go/exercise/bubblesort.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} type BubbleSort struct{} diff --git a/src/main/resources/templates/go/exercise/context.go b/src/main/resources/templates/go/exercise/context.go index ebbd2cd85e9d..aced7eb6bea0 100644 --- a/src/main/resources/templates/go/exercise/context.go +++ b/src/main/resources/templates/go/exercise/context.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} type Context struct{} diff --git a/src/main/resources/templates/go/exercise/go.mod b/src/main/resources/templates/go/exercise/go.mod index 928bbea04c33..224363e133d4 100644 --- a/src/main/resources/templates/go/exercise/go.mod +++ b/src/main/resources/templates/go/exercise/go.mod @@ -1,3 +1,3 @@ -module artemis/assignment +module artemis/${packageName} go 1.23.2 diff --git a/src/main/resources/templates/go/exercise/mergesort.go b/src/main/resources/templates/go/exercise/mergesort.go index f2d48f09e350..e3c8f12f29d6 100644 --- a/src/main/resources/templates/go/exercise/mergesort.go +++ b/src/main/resources/templates/go/exercise/mergesort.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} type MergeSort struct{} diff --git a/src/main/resources/templates/go/exercise/policy.go b/src/main/resources/templates/go/exercise/policy.go index 4156b94a456b..7676b8bfbaeb 100644 --- a/src/main/resources/templates/go/exercise/policy.go +++ b/src/main/resources/templates/go/exercise/policy.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} type Policy struct{} diff --git a/src/main/resources/templates/go/exercise/sortstrategy.go b/src/main/resources/templates/go/exercise/sortstrategy.go index e008b5f52afb..3f3884429007 100644 --- a/src/main/resources/templates/go/exercise/sortstrategy.go +++ b/src/main/resources/templates/go/exercise/sortstrategy.go @@ -1,3 +1,3 @@ -package assignment +package ${packageName} type SortStrategy interface{} diff --git a/src/main/resources/templates/go/solution/bubblesort.go b/src/main/resources/templates/go/solution/bubblesort.go index 2c5a46334348..2ee261ec5c20 100644 --- a/src/main/resources/templates/go/solution/bubblesort.go +++ b/src/main/resources/templates/go/solution/bubblesort.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} import "time" diff --git a/src/main/resources/templates/go/solution/client/client.go b/src/main/resources/templates/go/solution/client/client.go index 4489670b760c..0d7238e95968 100644 --- a/src/main/resources/templates/go/solution/client/client.go +++ b/src/main/resources/templates/go/solution/client/client.go @@ -5,7 +5,7 @@ import ( "math/rand" "time" - "artemis/assignment" + "artemis/${packageName}" ) // Constants define iteration and random date generation bounds. @@ -18,8 +18,8 @@ const ( // main demonstrates the sorting process. func main() { // Init Context and Policy - context := assignment.NewContext() - policy := assignment.NewPolicy(context) + context := ${packageName}.NewContext() + policy := ${packageName}.NewPolicy(context) // Run multiple times to simulate different sorting strategies for i := 0; i < Iterations; i++ { diff --git a/src/main/resources/templates/go/solution/context.go b/src/main/resources/templates/go/solution/context.go index 910ca6e6f611..8165ff883668 100644 --- a/src/main/resources/templates/go/solution/context.go +++ b/src/main/resources/templates/go/solution/context.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} import "time" diff --git a/src/main/resources/templates/go/solution/go.mod b/src/main/resources/templates/go/solution/go.mod index 928bbea04c33..224363e133d4 100644 --- a/src/main/resources/templates/go/solution/go.mod +++ b/src/main/resources/templates/go/solution/go.mod @@ -1,3 +1,3 @@ -module artemis/assignment +module artemis/${packageName} go 1.23.2 diff --git a/src/main/resources/templates/go/solution/mergesort.go b/src/main/resources/templates/go/solution/mergesort.go index e60da39480ab..7722813d0b47 100644 --- a/src/main/resources/templates/go/solution/mergesort.go +++ b/src/main/resources/templates/go/solution/mergesort.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} import "time" diff --git a/src/main/resources/templates/go/solution/policy.go b/src/main/resources/templates/go/solution/policy.go index 7b06e8c3ee30..fedd5da02aba 100644 --- a/src/main/resources/templates/go/solution/policy.go +++ b/src/main/resources/templates/go/solution/policy.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} type Policy struct { context *Context diff --git a/src/main/resources/templates/go/solution/sortstrategy.go b/src/main/resources/templates/go/solution/sortstrategy.go index 79ea803d6903..f61ad7d8f9ad 100644 --- a/src/main/resources/templates/go/solution/sortstrategy.go +++ b/src/main/resources/templates/go/solution/sortstrategy.go @@ -1,4 +1,4 @@ -package assignment +package ${packageName} import "time" diff --git a/src/main/resources/templates/go/test/behavior/behavior_test.go b/src/main/resources/templates/go/test/behavior/behavior_test.go index f1be13b8cea5..fcd84bafe90f 100644 --- a/src/main/resources/templates/go/test/behavior/behavior_test.go +++ b/src/main/resources/templates/go/test/behavior/behavior_test.go @@ -5,7 +5,7 @@ import ( "testing" "time" - "artemis/assignment" + "artemis/${packageName}" ) type SortStrategy interface { @@ -15,8 +15,8 @@ type SortStrategy interface { type Context interface { GetDates() []time.Time SetDates(dates []time.Time) - GetSortAlgorithm() assignment.SortStrategy - SetSortAlgorithm(strategy assignment.SortStrategy) + GetSortAlgorithm() ${packageName}.SortStrategy + SetSortAlgorithm(strategy ${packageName}.SortStrategy) } type Policy interface { @@ -28,7 +28,7 @@ func TestBubbleSort(t *testing.T) { dates, datesWithCorrectOrder := createTestDates() - var bubbleSortAny interface{} = assignment.NewBubbleSort() + var bubbleSortAny interface{} = ${packageName}.NewBubbleSort() bubbleSort := bubbleSortAny.(SortStrategy) bubbleSort.PerformSort(dates) @@ -42,7 +42,7 @@ func TestMergeSort(t *testing.T) { dates, datesWithCorrectOrder := createTestDates() - var mergeSortAny interface{} = assignment.NewMergeSort() + var mergeSortAny interface{} = ${packageName}.NewMergeSort() mergeSort := mergeSortAny.(SortStrategy) mergeSort.PerformSort(dates) @@ -76,7 +76,7 @@ func TestUseMergeSortForBigList(t *testing.T) { bigList = append(bigList, time.Unix(0, 0)) } chosenSortStrategy := configurePolicyAndContext(bigList) - _, ok := chosenSortStrategy.(*assignment.MergeSort) + _, ok := chosenSortStrategy.(*${packageName}.MergeSort) if !ok { t.Fatal("The sort algorithm of Context was not MergeSort for a list with more than 10 dates.") } @@ -90,19 +90,19 @@ func TestUseBubbleSortForSmallList(t *testing.T) { bigList = append(bigList, time.Unix(0, 0)) } chosenSortStrategy := configurePolicyAndContext(bigList) - _, ok := chosenSortStrategy.(*assignment.BubbleSort) + _, ok := chosenSortStrategy.(*${packageName}.BubbleSort) if !ok { t.Fatal("The sort algorithm of Context was not MergeSort for a list with more than 10 dates.") } } func configurePolicyAndContext(dates []time.Time) interface{} { - contextOriginal := assignment.NewContext() + contextOriginal := ${packageName}.NewContext() var contextAny interface{} = contextOriginal context := contextAny.(Context) context.SetDates(dates) - var policyAny interface{} = assignment.NewPolicy(contextOriginal) + var policyAny interface{} = ${packageName}.NewPolicy(contextOriginal) policy := policyAny.(Policy) policy.Configure() diff --git a/src/main/resources/templates/go/test/go.mod b/src/main/resources/templates/go/test/go.mod index 85c0afed131a..8ac02f91c2ce 100644 --- a/src/main/resources/templates/go/test/go.mod +++ b/src/main/resources/templates/go/test/go.mod @@ -2,6 +2,6 @@ module artemis/test go 1.23.2 -replace artemis/assignment => ${studentParentWorkingDirectoryName} +replace artemis/${packageName} => ${studentParentWorkingDirectoryName} -require artemis/assignment v0.0.0-00010101000000-000000000000 +require artemis/${packageName} v0.0.0-00010101000000-000000000000 diff --git a/src/main/resources/templates/go/test/structural/structural_test.go b/src/main/resources/templates/go/test/structural/structural_test.go index 726a33c7a9c1..815a5ed70a0d 100644 --- a/src/main/resources/templates/go/test/structural/structural_test.go +++ b/src/main/resources/templates/go/test/structural/structural_test.go @@ -4,7 +4,7 @@ import ( "testing" "time" - "artemis/assignment" + "artemis/${packageName}" ) type SortStrategy interface { @@ -14,8 +14,8 @@ type SortStrategy interface { type Context interface { GetDates() []time.Time SetDates(dates []time.Time) - GetSortAlgorithm() assignment.SortStrategy - SetSortAlgorithm(strategy assignment.SortStrategy) + GetSortAlgorithm() ${packageName}.SortStrategy + SetSortAlgorithm(strategy ${packageName}.SortStrategy) } type Policy interface { @@ -25,7 +25,7 @@ type Policy interface { func TestBubbleSort(t *testing.T) { defer handlePanic(t) - var bubbleSort interface{} = new(assignment.BubbleSort) + var bubbleSort interface{} = new(${packageName}.BubbleSort) _ = bubbleSort.(SortStrategy) } @@ -33,7 +33,7 @@ func TestBubbleSort(t *testing.T) { func TestMergeSort(t *testing.T) { defer handlePanic(t) - var mergeSort interface{} = new(assignment.MergeSort) + var mergeSort interface{} = new(${packageName}.MergeSort) _ = mergeSort.(SortStrategy) } @@ -41,7 +41,7 @@ func TestMergeSort(t *testing.T) { func TestContext(t *testing.T) { defer handlePanic(t) - var context interface{} = new(assignment.Context) + var context interface{} = new(${packageName}.Context) _ = context.(Context) } @@ -49,7 +49,7 @@ func TestContext(t *testing.T) { func TestPolicy(t *testing.T) { defer handlePanic(t) - var policy interface{} = new(assignment.Policy) + var policy interface{} = new(${packageName}.Policy) _ = policy.(Policy) } diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts index 36c9b8df3a8c..51b9056d0b15 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-creation-config.ts @@ -22,7 +22,6 @@ export type ProgrammingExerciseCreationConfig = { invalidRepositoryNamePattern: RegExp; isImportFromExistingExercise: boolean; isImportFromFile: boolean; - appNamePatternForSwift: string; modePickerOptions?: ModePickerOption[]; withDependencies: boolean; onWithDependenciesChanged: (withDependencies: boolean) => boolean; diff --git a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts index 5bdde2d8c65c..b5aa5d3bb090 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts +++ b/src/main/webapp/app/exercises/programming/manage/update/programming-exercise-update.component.ts @@ -23,6 +23,7 @@ import { INVALID_DIRECTORY_NAME_PATTERN, INVALID_REPOSITORY_NAME_PATTERN, MAX_PENALTY_PATTERN, + PACKAGE_NAME_PATTERN_FOR_GO, PACKAGE_NAME_PATTERN_FOR_JAVA_BLACKBOX, PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN, PROGRAMMING_EXERCISE_SHORT_NAME_PATTERN, @@ -58,12 +59,13 @@ export const LOCAL_STORAGE_KEY_IS_SIMPLE_MODE = 'isSimpleMode'; export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDestroy, OnInit { protected readonly documentationType: DocumentationType = 'Programming'; protected readonly maxPenaltyPattern = MAX_PENALTY_PATTERN; - protected readonly packageNamePatternForJavaBlackbox = PACKAGE_NAME_PATTERN_FOR_JAVA_BLACKBOX; protected readonly invalidRepositoryNamePattern = INVALID_REPOSITORY_NAME_PATTERN; protected readonly invalidDirectoryNamePattern = INVALID_DIRECTORY_NAME_PATTERN; protected readonly shortNamePattern = PROGRAMMING_EXERCISE_SHORT_NAME_PATTERN; - readonly packageNamePatternForJavaKotlin = PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN; - readonly appNamePatternForSwift = APP_NAME_PATTERN_FOR_SWIFT; + private readonly packageNameRegexForJavaKotlin = RegExp(PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN); + private readonly packageNameRegexForJavaBlackbox = RegExp(PACKAGE_NAME_PATTERN_FOR_JAVA_BLACKBOX); + private readonly appNameRegexForSwift = RegExp(APP_NAME_PATTERN_FOR_SWIFT); + private readonly packageNameRegexForGo = RegExp(PACKAGE_NAME_PATTERN_FOR_GO); @ViewChild(ProgrammingExerciseInformationComponent) exerciseInfoComponent?: ProgrammingExerciseInformationComponent; @ViewChild(ProgrammingExerciseModeComponent) exerciseDifficultyComponent?: ProgrammingExerciseModeComponent; @@ -825,10 +827,17 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest * @param useBlackboxPattern whether to allow points in the regex */ setPackageNamePattern(language: ProgrammingLanguage, useBlackboxPattern = false) { - if (language === ProgrammingLanguage.SWIFT) { - this.packageNamePattern = this.appNamePatternForSwift; - } else { - this.packageNamePattern = useBlackboxPattern ? this.packageNamePatternForJavaBlackbox : this.packageNamePatternForJavaKotlin; + switch (language) { + case ProgrammingLanguage.SWIFT: + this.packageNamePattern = APP_NAME_PATTERN_FOR_SWIFT; + break; + case ProgrammingLanguage.JAVA: + case ProgrammingLanguage.KOTLIN: + this.packageNamePattern = useBlackboxPattern ? PACKAGE_NAME_PATTERN_FOR_JAVA_BLACKBOX : PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN; + break; + case ProgrammingLanguage.GO: + this.packageNamePattern = PACKAGE_NAME_PATTERN_FOR_GO; + break; } } @@ -1062,13 +1071,26 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest } private validateExercisePackageName(validationErrorReasons: ValidationReason[]): void { - // validation on package name has only to be performed for Java, Kotlin and Swift - if ( - this.programmingExercise.programmingLanguage !== ProgrammingLanguage.JAVA && - this.programmingExercise.programmingLanguage !== ProgrammingLanguage.KOTLIN && - this.programmingExercise.programmingLanguage !== ProgrammingLanguage.SWIFT - ) { - return; + let regex; + switch (this.programmingExercise.programmingLanguage) { + case ProgrammingLanguage.JAVA: + if (this.programmingExercise.projectType === ProjectType.MAVEN_BLACKBOX) { + regex = this.packageNameRegexForJavaBlackbox; + } else { + regex = this.packageNameRegexForJavaKotlin; + } + break; + case ProgrammingLanguage.KOTLIN: + regex = this.packageNameRegexForJavaKotlin; + break; + case ProgrammingLanguage.SWIFT: + regex = this.appNameRegexForSwift; + break; + case ProgrammingLanguage.GO: + regex = this.packageNameRegexForGo; + break; + default: + return; } if (this.programmingExercise.packageName === undefined || this.programmingExercise.packageName === '') { @@ -1076,39 +1098,19 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest translateKey: 'artemisApp.exercise.form.packageName.undefined', translateValues: {}, }); - } else { - const patternMatchJavaKotlin: RegExpMatchArray | null = this.programmingExercise.packageName.match(this.packageNamePatternForJavaKotlin); - const patternMatchJavaBlackbox: RegExpMatchArray | null = this.programmingExercise.packageName.match(this.packageNamePatternForJavaBlackbox); - const patternMatchSwift: RegExpMatchArray | null = this.programmingExercise.packageName.match(this.appNamePatternForSwift); - const projectTypeDependentPatternMatch: RegExpMatchArray | null = - this.programmingExercise.projectType === ProjectType.MAVEN_BLACKBOX ? patternMatchJavaBlackbox : patternMatchJavaKotlin; - - if ( - this.programmingExercise.programmingLanguage === ProgrammingLanguage.JAVA && - (projectTypeDependentPatternMatch === null || projectTypeDependentPatternMatch.length === 0) - ) { - if (this.programmingExercise.projectType === ProjectType.MAVEN_BLACKBOX) { - validationErrorReasons.push({ - translateKey: 'artemisApp.exercise.form.packageName.pattern.JAVA_BLACKBOX', - translateValues: {}, - }); - } else { - validationErrorReasons.push({ - translateKey: 'artemisApp.exercise.form.packageName.pattern.JAVA', - translateValues: {}, - }); - } - } else if (this.programmingExercise.programmingLanguage === ProgrammingLanguage.KOTLIN && (patternMatchJavaKotlin === null || patternMatchJavaKotlin.length === 0)) { - validationErrorReasons.push({ - translateKey: 'artemisApp.exercise.form.packageName.pattern.KOTLIN', - translateValues: {}, - }); - } else if (this.programmingExercise.programmingLanguage === ProgrammingLanguage.SWIFT && (patternMatchSwift === null || patternMatchSwift.length === 0)) { - validationErrorReasons.push({ - translateKey: 'artemisApp.exercise.form.packageName.pattern.SWIFT', - translateValues: {}, - }); - } + return; + } + + const packageNameDoesMatch = regex.test(this.programmingExercise.packageName); + if (!packageNameDoesMatch) { + const translateKey = + this.programmingExercise.projectType === ProjectType.MAVEN_BLACKBOX + ? `artemisApp.exercise.form.packageName.pattern.${this.programmingExercise.programmingLanguage}_BLACKBOX` + : `artemisApp.exercise.form.packageName.pattern.${this.programmingExercise.programmingLanguage}`; + validationErrorReasons.push({ + translateKey, + translateValues: {}, + }); } } @@ -1244,7 +1246,6 @@ export class ProgrammingExerciseUpdateComponent implements AfterViewInit, OnDest exerciseCategories: this.exerciseCategories, existingCategories: this.existingCategories, updateCategories: this.categoriesChanged, - appNamePatternForSwift: this.appNamePatternForSwift, modePickerOptions: this.modePickerOptions, withDependencies: this.withDependencies, onWithDependenciesChanged: this.withDependenciesChanged, diff --git a/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html b/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html index 7b60308b7091..4dd22d1e9b35 100644 --- a/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html +++ b/src/main/webapp/app/exercises/programming/manage/update/update-components/language/programming-exercise-language.component.html @@ -122,7 +122,7 @@ setTimeout(() => this.calculateFormValid()))); } diff --git a/src/main/webapp/app/shared/constants/input.constants.ts b/src/main/webapp/app/shared/constants/input.constants.ts index 6748c2507b07..d220eae6bb11 100644 --- a/src/main/webapp/app/shared/constants/input.constants.ts +++ b/src/main/webapp/app/shared/constants/input.constants.ts @@ -47,3 +47,7 @@ export const PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN = // with the restriction to a-z,A-Z as "Swift letter" and 0-9 as digits where no separators are allowed export const APP_NAME_PATTERN_FOR_SWIFT = '^(?!(?:associatedtype|class|deinit|enum|extension|fileprivate|func|import|init|inout|internal|let|open|operator|private|protocol|public|rethrows|static|struct|subscript|typealias|var|break|case|continue|default|defer|do|else|fallthrough|for|guard|if|in|repeat|return|switch|where|while|as|Any|catch|false|is|nil|super|self|Self|throw|throws|true|try|_|[sS]wift)$)[A-Za-z][0-9A-Za-z]*$'; +// Go package name Regex derived from (https://go.dev/ref/spec#Package_clause) limited to ASCII. Package names are identifiers. +// They allow letters, digits and underscore. They cannot start with a digit. The package name cannot be a keyword or "_". +export const PACKAGE_NAME_PATTERN_FOR_GO = + '^(?!(?:break|default|func|interface|select|case|defer|go|map|struct|chan|else|goto|package|switch|const|fallthrough|if|range|type|continue|for|import|return|var|_)$)[A-Za-z_][A-Za-z0-9_]*$'; diff --git a/src/main/webapp/i18n/de/exercise.json b/src/main/webapp/i18n/de/exercise.json index 00cc0421b9db..5890170a7668 100644 --- a/src/main/webapp/i18n/de/exercise.json +++ b/src/main/webapp/i18n/de/exercise.json @@ -239,7 +239,8 @@ "JAVA": "Der Package-Name muss aus einem oder mehreren gültigen Java-Bezeichnern bestehen, die mit '.' getrennt sind, z.B. \"net.java\"!", "KOTLIN": "Der Package-Name muss aus einem oder mehreren gültigen Kotlin-Bezeichnern bestehen, die mit '.' getrennt sind, z.B. \"net.kotlin\"!", "SWIFT": "Der Package-Name muss aus einem oder mehreren gültigen Swift-Bezeichnern bestehen, die nicht voneinander separiert sind, z.B. \"SwiftEx\"!", - "JAVA_BLACKBOX": "Der Paketname muss ein gültiger Java-Bezeicher sein. Zusätzlich sind Trennpunkte im Paketnamen für den DejaGnu Aufgabentyp nicht erlaubt." + "JAVA_BLACKBOX": "Der Paketname muss ein gültiger Java-Bezeicher sein. Zusätzlich sind Trennpunkte im Paketnamen für den DejaGnu Aufgabentyp nicht erlaubt.", + "GO": "Der Package-Name muss ein gültiger Go-Identifier sein, z.B. \"goExercise\"." } }, "points": { diff --git a/src/main/webapp/i18n/en/exercise.json b/src/main/webapp/i18n/en/exercise.json index 82144be9ffe5..acf92030b0c5 100644 --- a/src/main/webapp/i18n/en/exercise.json +++ b/src/main/webapp/i18n/en/exercise.json @@ -239,7 +239,8 @@ "JAVA": "The package name must consist of one or more valid Java identifiers separated by '.', e.g. \"net.java\"!", "KOTLIN": "The package name must consist of one or more valid Kotlin identifiers separated by '.', e.g. \"net.kotlin\"!", "SWIFT": "The package name must consist of one or more valid Swift identifiers without any separator, e.g. \"SwiftEx\"!", - "JAVA_BLACKBOX": "The package name must be a valid Java identifier. In addition, no dots are allowed in the package name of DejaGnu projects" + "JAVA_BLACKBOX": "The package name must be a valid Java identifier. In addition, no dots are allowed in the package name of DejaGnu projects", + "GO": "The package name must be a valid Go identifier, e.g. \"goExercise\"." } }, "points": { diff --git a/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts b/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts index 3e49d0bd49ff..c4cbdf912f8a 100644 --- a/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts +++ b/src/test/javascript/spec/component/programming-exercise/programming-exercise-update.component.spec.ts @@ -76,6 +76,7 @@ import { TitleChannelNameComponent } from 'app/shared/form/title-channel-name/ti import { ExerciseTitleChannelNameModule } from 'app/exercises/shared/exercise-title-channel-name/exercise-title-channel-name.module'; import { CustomNotIncludedInValidatorDirective } from '../../../../../main/webapp/app/shared/validators/custom-not-included-in-validator.directive'; import { ProgrammingExerciseDifficultyComponent } from '../../../../../main/webapp/app/exercises/programming/manage/update/update-components/difficulty/programming-exercise-difficulty.component'; +import { APP_NAME_PATTERN_FOR_SWIFT, PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN } from '../../../../../main/webapp/app/shared/constants/input.constants'; describe('ProgrammingExerciseUpdateComponent', () => { const courseId = 1; @@ -534,7 +535,7 @@ describe('ProgrammingExerciseUpdateComponent', () => { expect(courseService.find).toHaveBeenCalledWith(courseId); expect(comp.selectedProgrammingLanguage).toBe(ProgrammingLanguage.SWIFT); expect(comp.staticCodeAnalysisAllowed).toBeTrue(); - expect(comp.packageNamePattern).toBe(comp.appNamePatternForSwift); + expect(comp.packageNamePattern).toBe(APP_NAME_PATTERN_FOR_SWIFT); })); it('should activate SCA for C', fakeAsync(() => { @@ -560,7 +561,7 @@ describe('ProgrammingExerciseUpdateComponent', () => { // THEN expect(comp.selectedProgrammingLanguage).toBe(ProgrammingLanguage.JAVA); expect(comp.staticCodeAnalysisAllowed).toBeTrue(); - expect(comp.packageNamePattern).toBe(comp.packageNamePatternForJavaKotlin); + expect(comp.packageNamePattern).toBe(PACKAGE_NAME_PATTERN_FOR_JAVA_KOTLIN); })); it('should deactivate SCA for C (FACT)', fakeAsync(() => {