Skip to content
This repository was archived by the owner on Jan 19, 2025. It is now read-only.

Commit 04062d1

Browse files
authored
feat: add new annotation @expert (#982)
* feat(backend): process `@expert` annotations * refactor(backend): rename file * feat(gui): add `@expert` annotation * feat(backend): update code generation * fix(backend): send annotations to backend * fix(backend): annotation:any filter * style(backend): fix linter error * style: apply automatic fixes of linters * feat(gui): `is:public !is:removed` as default filter (#983) * fix(backend): validation of combinations of annotations * docs(backend): add a comment Co-authored-by: lars-reimann <[email protected]>
1 parent c29d12b commit 04062d1

29 files changed

+603
-78
lines changed

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/codegen/StubCodeGenerator.kt

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import com.larsreimann.safeds.serializer.SerializationResult
4747
import com.larsreimann.safeds.serializer.serializeToFormattedString
4848

4949
/**
50-
* Create Simple-ML stub code for the Python module.
50+
* Create Safe-DS stub code for the Python module.
5151
*/
5252
fun PythonModule.toStubCode(): String {
5353
val compilationUnit = toSdsCompilationUnit()
@@ -66,7 +66,7 @@ fun PythonModule.toStubCode(): String {
6666
}
6767

6868
/**
69-
* Creates a Simple-ML compilation unit that corresponds to the Python module.
69+
* Creates a Safe-DS compilation unit that corresponds to the Python module.
7070
*/
7171
fun PythonModule.toSdsCompilationUnit(): SdsCompilationUnit {
7272
val classes = classes.map { it.toSdsClass() }
@@ -80,7 +80,7 @@ fun PythonModule.toSdsCompilationUnit(): SdsCompilationUnit {
8080
}
8181

8282
/**
83-
* Creates a Simple-ML class that corresponds to the Python class.
83+
* Creates a Safe-DS class that corresponds to the Python class.
8484
*/
8585
fun PythonClass.toSdsClass(): SdsClass {
8686
val stubName = name.snakeCaseToUpperCamelCase()
@@ -91,6 +91,9 @@ fun PythonClass.toSdsClass(): SdsClass {
9191
return createSdsClass(
9292
name = stubName,
9393
annotationCalls = buildList {
94+
if (isExpert) {
95+
add(createSdsAnnotationCall("Expert"))
96+
}
9497
if (name != stubName) {
9598
add(createSdsPythonNameAnnotationUse(name))
9699
}
@@ -111,7 +114,7 @@ private fun PythonClass.buildConstructor(): List<SdsParameter> {
111114
}
112115

113116
/**
114-
* Creates a Simple-ML attribute that corresponds to the Python attribute.
117+
* Creates a Safe-DS attribute that corresponds to the Python attribute.
115118
*/
116119
fun PythonAttribute.toSdsAttribute(): SdsAttribute {
117120
val stubName = name.snakeCaseToLowerCamelCase()
@@ -137,6 +140,9 @@ fun PythonFunction.toSdsFunction(): SdsFunction {
137140
name = stubName,
138141
isStatic = isStaticMethod(),
139142
annotationCalls = buildList {
143+
if (isExpert) {
144+
add(createSdsAnnotationCall("Expert"))
145+
}
140146
if (isPure) {
141147
add(createSdsAnnotationCall("Pure"))
142148
}
@@ -176,6 +182,9 @@ fun PythonParameter.toSdsParameterOrNull(): SdsParameter? {
176182
return createSdsParameter(
177183
name = stubName,
178184
annotationCalls = buildList {
185+
if (isExpert) {
186+
add(createSdsAnnotationCall("Expert"))
187+
}
179188
if (name != stubName) {
180189
add(createSdsPythonNameAnnotationUse(name))
181190
}
@@ -206,7 +215,7 @@ fun PythonResult.toSdsResult(): SdsResult {
206215
}
207216

208217
/**
209-
* Creates a Simple-ML enum that corresponds to the Python enum.
218+
* Creates a Safe-DS enum that corresponds to the Python enum.
210219
*/
211220
fun PythonEnum.toSdsEnum(): SdsEnum {
212221
val stubName = name.snakeCaseToUpperCamelCase()
@@ -226,7 +235,7 @@ fun PythonEnum.toSdsEnum(): SdsEnum {
226235
}
227236

228237
/**
229-
* Creates a Simple-ML enum variant that corresponds to the Python enum instance.
238+
* Creates a Safe-DS enum variant that corresponds to the Python enum instance.
230239
*/
231240
fun PythonEnumInstance.toSdsEnumVariant(): SdsEnumVariant {
232241
val stubName = name.snakeCaseToUpperCamelCase()

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/model/EditorAnnotations.kt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ data class GroupAnnotation(val groupName: String, val parameters: MutableList<St
7777
override val validTargets = FUNCTIONS
7878
}
7979

80+
@Serializable
81+
object ExpertAnnotation : EditorAnnotation() {
82+
83+
@Transient
84+
override val validTargets = ANY_DECLARATION
85+
}
86+
8087
@Serializable
8188
data class MoveAnnotation(val destination: String) : EditorAnnotation() {
8289

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/mutableModel/PythonAst.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ class PythonClass(
6565
var isPublic: Boolean = true,
6666
var description: String = "",
6767
var todo: String = "",
68+
var isExpert: Boolean = false,
6869
override val annotations: MutableList<EditorAnnotation> = mutableListOf(),
6970
var originalClass: OriginalPythonClass? = null,
7071
) : PythonDeclaration() {
@@ -132,6 +133,7 @@ class PythonFunction(
132133
var description: String = "",
133134
var todo: String = "",
134135
var isPure: Boolean = false,
136+
var isExpert: Boolean = false,
135137
override val annotations: MutableList<EditorAnnotation> = mutableListOf(),
136138
var callToOriginalAPI: PythonCall? = null,
137139
) : PythonDeclaration() {
@@ -192,6 +194,7 @@ class PythonParameter(
192194
var description: String = "",
193195
var todo: String = "",
194196
var boundary: Boundary? = null,
197+
var isExpert: Boolean = false,
195198
override val annotations: MutableList<EditorAnnotation> = mutableListOf(),
196199
) : PythonDeclaration() {
197200

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package com.larsreimann.apiEditor.transformation
2+
3+
import com.larsreimann.apiEditor.model.ExpertAnnotation
4+
import com.larsreimann.apiEditor.mutableModel.PythonClass
5+
import com.larsreimann.apiEditor.mutableModel.PythonFunction
6+
import com.larsreimann.apiEditor.mutableModel.PythonPackage
7+
import com.larsreimann.apiEditor.mutableModel.PythonParameter
8+
import com.larsreimann.modeling.descendants
9+
10+
/**
11+
* Processes and removes `@expert` annotations.
12+
*/
13+
fun PythonPackage.processExpertAnnotations() {
14+
this.descendants()
15+
.forEach {
16+
when (it) {
17+
is PythonClass -> it.processExpertAnnotations()
18+
is PythonFunction -> it.processExpertAnnotations()
19+
is PythonParameter -> it.processExpertAnnotations()
20+
}
21+
}
22+
}
23+
24+
private fun PythonClass.processExpertAnnotations() {
25+
this.annotations
26+
.filterIsInstance<ExpertAnnotation>()
27+
.forEach {
28+
this.isExpert = true
29+
this.annotations.remove(it)
30+
}
31+
}
32+
33+
private fun PythonFunction.processExpertAnnotations() {
34+
this.annotations
35+
.filterIsInstance<ExpertAnnotation>()
36+
.forEach {
37+
this.isExpert = true
38+
this.annotations.remove(it)
39+
}
40+
}
41+
42+
private fun PythonParameter.processExpertAnnotations() {
43+
this.annotations
44+
.filterIsInstance<ExpertAnnotation>()
45+
.forEach {
46+
this.isExpert = true
47+
this.annotations.remove(it)
48+
}
49+
}
File renamed without changes.

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/transformation/TransformationPlan.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ private fun PythonPackage.processAnnotations() {
3434
processMoveAnnotations()
3535
processBoundaryAnnotations()
3636
processValueAnnotations()
37+
processExpertAnnotations()
3738
processPureAnnotations()
3839
processEnumAnnotations()
3940
processGroupAnnotations()

api-editor/backend/src/main/kotlin/com/larsreimann/apiEditor/validation/AnnotationValidator.kt

Lines changed: 32 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -165,77 +165,48 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython
165165
) {
166166
val firstAnnotationName = firstAnnotation.type
167167
val secondAnnotationName = secondAnnotation.type
168-
if (firstAnnotationName !in possibleCombinations || secondAnnotationName !in possibleCombinations[firstAnnotationName]!!) {
168+
val annotationPair = createAnnotationPair(firstAnnotationName, secondAnnotationName)
169+
170+
if (annotationPair in forbiddenCombinations) {
169171
validationErrors.add(AnnotationCombinationError(qualifiedName, firstAnnotationName, secondAnnotationName))
170172
}
171173
}
172174

173175
private fun validateGroupCombinations(qualifiedName: String, editorAnnotations: List<EditorAnnotation>) {
174176
editorAnnotations.forEach {
175-
val annotationName = it.type
176-
if (annotationName !in possibleCombinations[groupAnnotationName]!!) {
177-
validationErrors.add(GroupAnnotationCombinationError(qualifiedName, annotationName))
177+
val firstAnnotationName = it.type
178+
val secondAnnotationName = groupAnnotationName
179+
val annotationPair = createAnnotationPair(firstAnnotationName, secondAnnotationName)
180+
181+
if (annotationPair in forbiddenCombinations) {
182+
validationErrors.add(GroupAnnotationCombinationError(qualifiedName, firstAnnotationName))
178183
}
179184
}
180185
}
181186

182-
companion object {
183-
private var possibleCombinations = buildMap<String, Set<String>> {
184-
this["Boundary"] = mutableSetOf("Description", "Group", "Optional", "Rename", "Required", "Todo")
185-
this["CalledAfter"] = mutableSetOf("CalledAfter", "Description", "Group", "Move", "Pure", "Rename")
186-
this["Constant"] = mutableSetOf()
187-
this["Description"] = mutableSetOf(
188-
"Boundary",
189-
"CalledAfter",
190-
"Enum",
191-
"Group",
192-
"Move",
193-
"Optional",
194-
"Pure",
195-
"Rename",
196-
"Required",
197-
"Todo",
198-
)
199-
this["Enum"] = mutableSetOf("Description", "Group", "Rename", "Required", "Todo")
200-
this["Group"] =
201-
mutableSetOf(
202-
"Boundary",
203-
"CalledAfter",
204-
"Description",
205-
"Enum",
206-
"Group",
207-
"Move",
208-
"Optional",
209-
"Pure",
210-
"Rename",
211-
"Required",
212-
"Todo",
213-
)
214-
this["Move"] = mutableSetOf("CalledAfter", "Description", "Group", "Pure", "Rename")
215-
this["Optional"] = mutableSetOf("Boundary", "Description", "Group", "Rename", "Todo")
216-
this["Pure"] = mutableSetOf("CalledAfter", "Description", "Group", "Move", "Rename")
217-
this["Remove"] = mutableSetOf()
218-
this["Rename"] = mutableSetOf(
219-
"Boundary",
220-
"CalledAfter",
221-
"Description",
222-
"Enum",
223-
"Group",
224-
"Move",
225-
"Optional",
226-
"Pure",
227-
"Required", "Todo",
228-
)
229-
this["Required"] = mutableSetOf("Boundary", "Description", "Enum", "Group", "Rename", "Todo")
230-
this["Todo"] = mutableSetOf(
231-
"Boundary",
232-
"Description",
233-
"Enum",
234-
"Group",
235-
"Optional",
236-
"Rename",
237-
"Required",
238-
)
187+
private fun createAnnotationPair(firstAnnotationName: String, secondAnnotationName: String): Pair<String, String> {
188+
return if (firstAnnotationName < secondAnnotationName) {
189+
firstAnnotationName to secondAnnotationName
190+
} else {
191+
secondAnnotationName to firstAnnotationName
239192
}
240193
}
194+
195+
companion object {
196+
197+
// We assume that the lookup function sorts the annotation names. The right one should either be identical to
198+
// the left or come after it alphabetically.
199+
private val forbiddenCombinations = setOf(
200+
"Constant" to "Constant",
201+
"Constant" to "Enum",
202+
"Constant" to "Group",
203+
"Constant" to "Omitted",
204+
"Constant" to "Optional",
205+
"Constant" to "Required",
206+
"Enum" to "Optional",
207+
"Omitted" to "Omitted",
208+
"Optional" to "Optional",
209+
"Required" to "Required",
210+
)
211+
}
241212
}

api-editor/backend/src/test/kotlin/com/larsreimann/apiEditor/codegen/StubCodeGeneratorTest.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -360,6 +360,19 @@ class StubCodeGeneratorTest {
360360
.map { it.name }
361361
.shouldContainExactly("testMethod")
362362
}
363+
364+
@Test
365+
fun `should mark expert classes with annotation`() {
366+
val pythonClass = PythonClass(
367+
name = "testClass",
368+
isExpert = true,
369+
)
370+
371+
pythonClass
372+
.toSdsClass()
373+
.uniqueAnnotationCallOrNull(QualifiedName.create("Expert"))
374+
.shouldNotBeNull()
375+
}
363376
}
364377

365378
@Nested
@@ -495,6 +508,19 @@ class StubCodeGeneratorTest {
495508
}
496509
}
497510

511+
@Test
512+
fun `should mark expert functions with annotation`() {
513+
val pythonFunction = PythonFunction(
514+
name = "testFunction",
515+
isExpert = true,
516+
)
517+
518+
pythonFunction
519+
.toSdsFunction()
520+
.uniqueAnnotationCallOrNull(QualifiedName.create("Expert"))
521+
.shouldNotBeNull()
522+
}
523+
498524
@Test
499525
fun `should mark pure functions with annotation`() {
500526
val pythonFunction = PythonFunction(
@@ -762,6 +788,20 @@ class StubCodeGeneratorTest {
762788
.defaultValue
763789
.shouldBeInstanceOf<SdsNull>()
764790
}
791+
792+
@Test
793+
fun `should mark expert parameters with annotation`() {
794+
val pythonParameter = PythonParameter(
795+
name = "testParameter",
796+
isExpert = true,
797+
)
798+
799+
pythonParameter
800+
.toSdsParameterOrNull()
801+
.shouldNotBeNull()
802+
.uniqueAnnotationCallOrNull(QualifiedName.create("Expert"))
803+
.shouldNotBeNull()
804+
}
765805
}
766806

767807
@Nested

0 commit comments

Comments
 (0)