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

Commit 9f3ff94

Browse files
lars-reimannMasara
andauthored
feat: description annotation (#536)
* feat: process description annotations in backend * feat: update validation in backend * feat(backend): actually trigger processing * style: apply automatic fixes of linters * feat: Adding description annotation (WIP) * revert: changes to Kotlin files * feat: Added description annotation, a filter for it and adjusted the heatmap to also count description annotations. * style: apply automatic fixes of linters * feat: don't show new description in annotation view (long) * fix: build error * feat: consistent label for new description text area * refactor: sort stuff * fix: description annotations not sent to server * fix: remove log * refactor: sort stuff * refactor: sort stuff * style: apply automatic fixes of linters * fix: build error Co-authored-by: lars-reimann <[email protected]> Co-authored-by: Arsam Islami <[email protected]> Co-authored-by: Masara <[email protected]>
1 parent cca834d commit 9f3ff94

File tree

25 files changed

+361
-12
lines changed

25 files changed

+361
-12
lines changed

api-editor/backend/src/main/kotlin/com/larsreimann/api_editor/model/editorAnnotations.kt

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,13 @@ data class ConstantAnnotation(val defaultValue: DefaultValue) : EditorAnnotation
6060
override val validTargets = PARAMETERS
6161
}
6262

63+
@Serializable
64+
data class DescriptionAnnotation(val newDescription: String) : EditorAnnotation() {
65+
66+
@Transient
67+
override val validTargets = ANY_DECLARATION
68+
}
69+
6370
@Serializable
6471
data class EnumAnnotation(val enumName: String, val pairs: List<EnumPair>) : EditorAnnotation() {
6572

@@ -112,7 +119,7 @@ object RemoveAnnotation : EditorAnnotation() {
112119
@Serializable
113120
data class RenameAnnotation(val newName: String) : EditorAnnotation() {
114121
@Transient
115-
override val validTargets = CLASSES.union(FUNCTIONS).union(PARAMETERS)
122+
override val validTargets = ANY_DECLARATION
116123
}
117124

118125
@Serializable
@@ -164,6 +171,13 @@ enum class AnnotationTarget(private val target: String) {
164171
}
165172
}
166173

174+
val ANY_DECLARATION = setOf(
175+
CLASS,
176+
GLOBAL_FUNCTION,
177+
METHOD,
178+
CONSTRUCTOR_PARAMETER,
179+
FUNCTION_PARAMETER
180+
)
167181
val GLOBAL_DECLARATIONS = setOf(CLASS, GLOBAL_FUNCTION)
168182
val CLASSES = setOf(CLASS)
169183
val FUNCTIONS = setOf(GLOBAL_FUNCTION, METHOD)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package com.larsreimann.api_editor.transformation
2+
3+
import com.larsreimann.api_editor.model.DescriptionAnnotation
4+
import com.larsreimann.api_editor.mutable_model.PythonClass
5+
import com.larsreimann.api_editor.mutable_model.PythonDeclaration
6+
import com.larsreimann.api_editor.mutable_model.PythonFunction
7+
import com.larsreimann.api_editor.mutable_model.PythonPackage
8+
import com.larsreimann.api_editor.mutable_model.PythonParameter
9+
import com.larsreimann.modeling.descendants
10+
11+
/**
12+
* Processes and removes `@description` annotations.
13+
*/
14+
fun PythonPackage.processDescriptionAnnotations() {
15+
this.descendants()
16+
.filterIsInstance<PythonDeclaration>()
17+
.forEach { it.processDescriptionAnnotations() }
18+
}
19+
20+
private fun PythonDeclaration.processDescriptionAnnotations() {
21+
this.annotations
22+
.filterIsInstance<DescriptionAnnotation>()
23+
.forEach {
24+
when (this) {
25+
is PythonClass -> this.description = it.newDescription
26+
is PythonFunction -> this.description = it.newDescription
27+
is PythonParameter -> this.description = it.newDescription
28+
else -> {
29+
// Do nothing
30+
}
31+
}
32+
this.annotations.remove(it)
33+
}
34+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ private fun PythonPackage.preprocess(newPackageName: String) {
2828
*/
2929
private fun PythonPackage.processAnnotations() {
3030
processRemoveAnnotations()
31+
processDescriptionAnnotations()
3132
processRenameAnnotations()
3233
processMoveAnnotations()
3334
processBoundaryAnnotations()

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

Lines changed: 22 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,15 +181,28 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython
181181

182182
companion object {
183183
private var possibleCombinations = buildMap<String, Set<String>> {
184-
this["Attribute"] = mutableSetOf("Rename")
185-
this["Boundary"] = mutableSetOf("Group", "Optional", "Rename", "Required")
186-
this["CalledAfter"] = mutableSetOf("CalledAfter", "Group", "Move", "Pure", "Rename")
184+
this["Attribute"] = mutableSetOf("Description", "Rename")
185+
this["Boundary"] = mutableSetOf("Description", "Group", "Optional", "Rename", "Required")
186+
this["CalledAfter"] = mutableSetOf("CalledAfter", "Description", "Group", "Move", "Pure", "Rename")
187187
this["Constant"] = mutableSetOf()
188-
this["Enum"] = mutableSetOf("Group", "Rename", "Required")
188+
this["Description"] = mutableSetOf(
189+
"Attribute",
190+
"Boundary",
191+
"CalledAfter",
192+
"Enum",
193+
"Group",
194+
"Move",
195+
"Optional",
196+
"Pure",
197+
"Rename",
198+
"Required"
199+
)
200+
this["Enum"] = mutableSetOf("Description", "Group", "Rename", "Required")
189201
this["Group"] =
190202
mutableSetOf(
191203
"Boundary",
192204
"CalledAfter",
205+
"Description",
193206
"Enum",
194207
"Group",
195208
"Move",
@@ -198,22 +211,23 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython
198211
"Rename",
199212
"Required"
200213
)
201-
this["Move"] = mutableSetOf("CalledAfter", "Group", "Pure", "Rename")
202-
this["Optional"] = mutableSetOf("Boundary", "Group", "Rename")
203-
this["Pure"] = mutableSetOf("CalledAfter", "Group", "Move", "Rename")
214+
this["Move"] = mutableSetOf("CalledAfter", "Description", "Group", "Pure", "Rename")
215+
this["Optional"] = mutableSetOf("Boundary", "Description", "Group", "Rename")
216+
this["Pure"] = mutableSetOf("CalledAfter", "Description", "Group", "Move", "Rename")
204217
this["Remove"] = mutableSetOf()
205218
this["Rename"] = mutableSetOf(
206219
"Attribute",
207220
"Boundary",
208221
"CalledAfter",
222+
"Description",
209223
"Enum",
210224
"Group",
211225
"Move",
212226
"Optional",
213227
"Pure",
214228
"Required"
215229
)
216-
this["Required"] = mutableSetOf("Boundary", "Enum", "Group", "Rename")
230+
this["Required"] = mutableSetOf("Boundary", "Description", "Enum", "Group", "Rename")
217231
}
218232
}
219233
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
package com.larsreimann.api_editor.transformation
2+
3+
import com.larsreimann.api_editor.model.DescriptionAnnotation
4+
import com.larsreimann.api_editor.mutable_model.PythonClass
5+
import com.larsreimann.api_editor.mutable_model.PythonFunction
6+
import com.larsreimann.api_editor.mutable_model.PythonModule
7+
import com.larsreimann.api_editor.mutable_model.PythonPackage
8+
import com.larsreimann.api_editor.mutable_model.PythonParameter
9+
import io.kotest.matchers.collections.shouldBeEmpty
10+
import io.kotest.matchers.shouldBe
11+
import org.junit.jupiter.api.BeforeEach
12+
import org.junit.jupiter.api.Test
13+
14+
class DescriptionAnnotationProcessorTest {
15+
private lateinit var testClass: PythonClass
16+
private lateinit var testFunction: PythonFunction
17+
private lateinit var testParameter: PythonParameter
18+
private lateinit var testPackage: PythonPackage
19+
20+
@BeforeEach
21+
fun reset() {
22+
testClass = PythonClass(
23+
name = "TestClass",
24+
description = "Lorem ipsum",
25+
annotations = mutableListOf(DescriptionAnnotation("Important class"))
26+
)
27+
testParameter = PythonParameter(
28+
name = "testParameter",
29+
description = "Lorem ipsum",
30+
annotations = mutableListOf(DescriptionAnnotation("Important parameter"))
31+
)
32+
testFunction = PythonFunction(
33+
name = "testFunction",
34+
description = "Lorem ipsum",
35+
annotations = mutableListOf(DescriptionAnnotation("Important function")),
36+
parameters = mutableListOf(testParameter)
37+
)
38+
testPackage = PythonPackage(
39+
distribution = "testPackage",
40+
name = "testPackage",
41+
version = "1.0.0",
42+
modules = listOf(
43+
PythonModule(
44+
name = "testModule",
45+
classes = listOf(testClass),
46+
functions = listOf(testFunction)
47+
)
48+
)
49+
)
50+
}
51+
52+
@Test
53+
fun `should process DescriptionAnnotation of classes`() {
54+
testPackage.processDescriptionAnnotations()
55+
56+
testClass.description shouldBe "Important class"
57+
}
58+
59+
@Test
60+
fun `should remove DescriptionAnnotation of classes`() {
61+
testPackage.processDescriptionAnnotations()
62+
63+
testClass.annotations
64+
.filterIsInstance<DescriptionAnnotation>()
65+
.shouldBeEmpty()
66+
}
67+
68+
@Test
69+
fun `should process DescriptionAnnotation of functions`() {
70+
testPackage.processDescriptionAnnotations()
71+
72+
testFunction.description shouldBe "Important function"
73+
}
74+
75+
@Test
76+
fun `should remove DescriptionAnnotation of functions`() {
77+
testPackage.processDescriptionAnnotations()
78+
79+
testFunction.annotations
80+
.filterIsInstance<DescriptionAnnotation>()
81+
.shouldBeEmpty()
82+
}
83+
84+
@Test
85+
fun `should process DescriptionAnnotation of parameters`() {
86+
testPackage.processDescriptionAnnotations()
87+
88+
testParameter.description shouldBe "Important parameter"
89+
}
90+
91+
@Test
92+
fun `should remove DescriptionAnnotation of parameters`() {
93+
testPackage.processDescriptionAnnotations()
94+
95+
testParameter.annotations
96+
.filterIsInstance<DescriptionAnnotation>()
97+
.shouldBeEmpty()
98+
}
99+
}

api-editor/desktop/src/main/kotlin/com/larsreimann/python_api_editor/AnnotationDropdown.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ fun AnnotationDropdown(
2020
showBoundary: Boolean = false,
2121
showCalledAfter: Boolean = false,
2222
showConstant: Boolean = false,
23+
showDescription: Boolean = false,
2324
showEnum: Boolean = false,
2425
showGroup: Boolean = false,
2526
showMove: Boolean = false,
@@ -56,6 +57,11 @@ fun AnnotationDropdown(
5657
Text(labels.getString("AnnotationDropdown.Option.Constant"))
5758
}
5859
}
60+
if (showDescription) {
61+
DropdownMenuItem(onClick = {}) {
62+
Text(labels.getString("AnnotationDropdown.Option.Description"))
63+
}
64+
}
5965
if (showEnum) {
6066
DropdownMenuItem(onClick = {}) {
6167
Text(labels.getString("AnnotationDropdown.Option.Enum"))

api-editor/desktop/src/main/resources/i18n/labels.properties

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ AnnotationDropdown.Option.Attribute=@attribute
2828
AnnotationDropdown.Option.Boundary=@boundary
2929
AnnotationDropdown.Option.CalledAfter=@calledAfter
3030
AnnotationDropdown.Option.Constant=@constant
31+
AnnotationDropdown.Option.Description=@description
3132
AnnotationDropdown.Option.Enum=@enum
3233
AnnotationDropdown.Option.Group=@group
3334
AnnotationDropdown.Option.Move=@move

api-editor/gui/src/app/App.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { initializeAnnotations, persistAnnotations, selectAnnotations } from '..
1717
import { BoundaryForm } from '../features/annotations/forms/BoundaryForm';
1818
import { CalledAfterForm } from '../features/annotations/forms/CalledAfterForm';
1919
import { ConstantForm } from '../features/annotations/forms/ConstantForm';
20+
import { DescriptionForm } from '../features/annotations/forms/DescriptionForm';
2021
import { EnumForm } from '../features/annotations/forms/EnumForm';
2122
import { GroupForm } from '../features/annotations/forms/GroupForm';
2223
import { MoveForm } from '../features/annotations/forms/MoveForm';
@@ -47,6 +48,8 @@ import {
4748
selectFilteredPythonPackage,
4849
selectPythonPackage,
4950
} from '../features/packageData/apiSlice';
51+
import { PythonClass } from '../features/packageData/model/PythonClass';
52+
import { PythonParameter } from '../features/packageData/model/PythonParameter';
5053

5154
export const App: React.FC = function () {
5255
useIndexedDB();
@@ -103,6 +106,12 @@ export const App: React.FC = function () {
103106
{currentUserAction.type === 'constant' && (
104107
<ConstantForm target={userActionTarget || pythonPackage} />
105108
)}
109+
{currentUserAction.type === 'description' &&
110+
(userActionTarget instanceof PythonClass ||
111+
userActionTarget instanceof PythonFunction ||
112+
userActionTarget instanceof PythonParameter) && (
113+
<DescriptionForm target={userActionTarget} />
114+
)}
106115
{currentUserAction.type === 'enum' && <EnumForm target={userActionTarget || pythonPackage} />}
107116
{currentUserAction.type === 'group' && (
108117
<GroupForm

api-editor/gui/src/common/FilterHelpButton.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -86,8 +86,8 @@ export const FilterHelpButton = function () {
8686
Displays only elements that are annotated with the given type xy. Replace [type]
8787
with one of{' '}
8888
<em>
89-
@attribute, @boundary, @calledAfter, @constant, @enum, @group, @move, @optional,
90-
@pure, @remove, @renaming, @required
89+
@attribute, @boundary, @calledAfter, @constant, @description, @enum, @group,
90+
@move, @optional, @pure, @remove, @renaming, @required
9191
</em>
9292
.
9393
</ChakraText>

api-editor/gui/src/features/annotatedPackageData/model/AnnotatedPythonPackageBuilder.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
InferableRemoveAnnotation,
2626
InferableRenameAnnotation,
2727
InferableRequiredAnnotation,
28+
InferableDescriptionAnnotation,
2829
} from './InferableAnnotation';
2930

3031
export class AnnotatedPythonPackageBuilder {
@@ -140,6 +141,7 @@ export class AnnotatedPythonPackageBuilder {
140141
'Boundary',
141142
'CalledAfters',
142143
'Constant',
144+
'Description',
143145
'Enum',
144146
'Groups',
145147
'Move',
@@ -192,6 +194,12 @@ export class AnnotatedPythonPackageBuilder {
192194
return new InferableConstantAnnotation(constantAnnotation);
193195
}
194196
break;
197+
case 'Description':
198+
const descriptionAnnotation = this.annotationStore.descriptions[target];
199+
if (descriptionAnnotation) {
200+
return new InferableDescriptionAnnotation(descriptionAnnotation);
201+
}
202+
break;
195203
case 'Groups':
196204
const groupAnnotations = this.annotationStore.groups[target];
197205
if (!groupAnnotations) {

api-editor/gui/src/features/annotatedPackageData/model/InferableAnnotation.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
CalledAfterAnnotation,
55
ComparisonOperator,
66
ConstantAnnotation,
7+
DescriptionAnnotation,
78
DefaultType,
89
DefaultValue,
910
EnumAnnotation,
@@ -92,6 +93,15 @@ export class InferableConstantAnnotation extends InferableAnnotation {
9293
}
9394
}
9495

96+
export class InferableDescriptionAnnotation extends InferableAnnotation {
97+
readonly newDescription: string;
98+
99+
constructor(descriptionAnnotation: DescriptionAnnotation) {
100+
super(dataPathPrefix + 'DescriptionAnnotation');
101+
this.newDescription = descriptionAnnotation.newDescription;
102+
}
103+
}
104+
95105
export class InferableGroupAnnotation extends InferableAnnotation {
96106
readonly groupName: string;
97107
readonly parameters: string[];

api-editor/gui/src/features/annotations/AnnotationDropdown.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ import {
1313
showMoveAnnotationForm,
1414
showOptionalAnnotationForm,
1515
showRenameAnnotationForm,
16+
showDescriptionAnnotationForm,
1617
} from '../ui/uiSlice';
1718

1819
interface AnnotationDropdownProps {
1920
showAttribute?: boolean;
2021
showBoundary?: boolean;
2122
showCalledAfter?: boolean;
2223
showConstant?: boolean;
24+
showDescription?: boolean;
2325
showEnum?: boolean;
2426
showGroup?: boolean;
2527
showMove?: boolean;
@@ -36,6 +38,7 @@ export const AnnotationDropdown: React.FC<AnnotationDropdownProps> = function ({
3638
showBoundary = false,
3739
showCalledAfter = false,
3840
showConstant = false,
41+
showDescription = false,
3942
showGroup = false,
4043
showEnum = false,
4144
showMove = false,
@@ -79,6 +82,11 @@ export const AnnotationDropdown: React.FC<AnnotationDropdownProps> = function ({
7982
{showConstant && (
8083
<MenuItem onClick={() => dispatch(showConstantAnnotationForm(target))}>@constant</MenuItem>
8184
)}
85+
{showDescription && (
86+
<MenuItem onClick={() => dispatch(showDescriptionAnnotationForm(target))}>
87+
@description
88+
</MenuItem>
89+
)}
8290
{showEnum && <MenuItem onClick={() => dispatch(showEnumAnnotationForm(target))}>@enum</MenuItem>}
8391
{showGroup && (
8492
<MenuItem

0 commit comments

Comments
 (0)