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

feat: todo annotation #537

Merged
merged 34 commits into from
Jun 13, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
9ec9ed3
feat: process description annotations in backend
lars-reimann Jun 3, 2022
2d18515
feat: update validation in backend
lars-reimann Jun 3, 2022
e129011
feat(backend): actually trigger processing
lars-reimann Jun 3, 2022
2cfafbc
style: apply automatic fixes of linters
lars-reimann Jun 3, 2022
23cc007
feat(backend): process todo annotations
lars-reimann Jun 3, 2022
44a3e6f
feat(backend): update validation
lars-reimann Jun 3, 2022
936f3d4
feat(backend): code generation for todo comments
lars-reimann Jun 3, 2022
a8ad3c5
style: apply automatic fixes of linters
lars-reimann Jun 3, 2022
b8cf808
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 4, 2022
4fc0cc9
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 4, 2022
0cb506d
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 5, 2022
497c8d7
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 5, 2022
51ffbac
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 5, 2022
6c269b9
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 5, 2022
5ed6c6f
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 11, 2022
69a658b
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 11, 2022
4634cd9
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 12, 2022
fe6f8a4
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 12, 2022
dee4cb8
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 12, 2022
3e0f97e
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 12, 2022
b8c7ed9
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 13, 2022
ce2c659
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 13, 2022
3599178
feat: Adding description annotation (WIP)
Masara Jun 13, 2022
4d81cc2
Merge remote-tracking branch 'origin/517-description-annotation' into…
Masara Jun 13, 2022
3ab3f55
revert: changes to Kotlin files
lars-reimann Jun 13, 2022
0a21539
Merge branch 'main' into 517-description-annotation
lars-reimann Jun 13, 2022
c1726ca
feat: Added description annotation, a filter for it and adjusted the …
Masara Jun 13, 2022
788ef0a
Merge branch '517-description-annotation' into 515-todo-annotation
Masara Jun 13, 2022
a25246a
feat: Added todo annotation, a filter for it and adjusted the heatmap…
Masara Jun 13, 2022
baf7366
Merge branch 'main' into 515-todo-annotation
lars-reimann Jun 13, 2022
7fd935d
fix: issues after merge
lars-reimann Jun 13, 2022
7bd0dfa
fix: build errors
lars-reimann Jun 13, 2022
e60983d
fix: server crash
lars-reimann Jun 13, 2022
fb7346c
style: apply automatic fixes of linters
lars-reimann Jun 13, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,14 @@ fun PythonAttribute.toPythonCode() = buildString {
}

fun PythonClass.toPythonCode() = buildString {
val todoComment = todoComment(todo)
val docstring = docstring()
val constructorString = constructor?.toPythonCode() ?: ""
val methodsString = methods.joinToString("\n\n") { it.toPythonCode() }

if (todoComment.isNotBlank()) {
appendLine(todoComment)
}
appendLine("class $name:")
if (docstring.isNotBlank()) {
appendIndented("\"\"\"\n")
Expand All @@ -143,6 +147,7 @@ fun PythonClass.toPythonCode() = buildString {
}

fun PythonConstructor.toPythonCode() = buildString {
val todoComment = todoComment(todo)
val parametersString = parameters.toPythonCode()
val boundariesString = parameters
.mapNotNull { it.boundary?.toPythonCode(it.name) }
Expand All @@ -155,6 +160,9 @@ fun PythonConstructor.toPythonCode() = buildString {
?.let { "self.instance = ${it.toPythonCode()}" }
?: ""

if (todoComment.isNotBlank()) {
appendLine(todoComment)
}
appendLine("def __init__($parametersString):")
if (boundariesString.isNotBlank()) {
appendIndented(boundariesString)
Expand Down Expand Up @@ -202,6 +210,7 @@ fun PythonEnumInstance.toPythonCode(enumName: String): String {
}

fun PythonFunction.toPythonCode() = buildString {
val todoComment = todoComment(todo)
val parametersString = parameters.toPythonCode()
val docstring = docstring()
val boundariesString = parameters
Expand All @@ -211,6 +220,9 @@ fun PythonFunction.toPythonCode() = buildString {
?.let { "return ${it.toPythonCode()}" }
?: ""

if (todoComment.isNotBlank()) {
appendLine(todoComment)
}
if (isStaticMethod()) {
appendLine("@staticmethod")
}
Expand Down Expand Up @@ -357,3 +369,18 @@ fun Boundary.toPythonCode(parameterName: String) = buildString {
appendIndented("raise ValueError(f'Valid values of $parameterName must be less than or equal to $upperIntervalLimit, but {$parameterName} was assigned.')")
}
}

fun todoComment(message: String) = buildString {
if (message.isBlank()) {
return ""
}

val lines = message.lines()
val firstLine = lines.first()
val remainingLines = lines.drop(1)

appendLine("# TODO: $firstLine")
remainingLines.forEach {
appendIndented(it, indent = " ".repeat(8))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,7 @@ internal fun String.prependIndentUnlessBlank(indent: String = " "): String {
.joinToString("\n")
}

internal fun StringBuilder.appendIndented(init: StringBuilder.() -> Unit): StringBuilder {
val stringToIndent = StringBuilder().apply(init).toString()
append(stringToIndent.prependIndentUnlessBlank())
return this
}

internal fun StringBuilder.appendIndented(value: String): StringBuilder {
append(value.prependIndentUnlessBlank())
internal fun StringBuilder.appendIndented(value: String, indent: String = " "): StringBuilder {
append(value.prependIndentUnlessBlank(indent))
return this
}
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,12 @@ object RequiredAnnotation : EditorAnnotation() {
override val validTargets = PARAMETERS
}

@Serializable
data class TodoAnnotation(val message: String) : EditorAnnotation() {
@Transient
override val validTargets = ANY_DECLARATION
}

@Serializable
sealed class DefaultValue

Expand Down Expand Up @@ -179,7 +185,6 @@ val ANY_DECLARATION = setOf(
FUNCTION_PARAMETER
)
val GLOBAL_DECLARATIONS = setOf(CLASS, GLOBAL_FUNCTION)
val CLASSES = setOf(CLASS)
val FUNCTIONS = setOf(GLOBAL_FUNCTION, METHOD)
val PARAMETERS = setOf(
CONSTRUCTOR_PARAMETER,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ class PythonClass(
methods: List<PythonFunction> = emptyList(),
var isPublic: Boolean = true,
var description: String = "",
var todo: String = "",
override val annotations: MutableList<EditorAnnotation> = mutableListOf(),
var originalClass: OriginalPythonClass? = null
) : PythonDeclaration() {
Expand All @@ -81,7 +82,8 @@ class PythonClass(

class PythonConstructor(
parameters: List<PythonParameter> = emptyList(),
val callToOriginalAPI: PythonCall? = null
val callToOriginalAPI: PythonCall? = null,
var todo: String = ""
) : PythonAstNode() {

val parameters = MutableContainmentList(parameters)
Expand Down Expand Up @@ -126,6 +128,7 @@ class PythonFunction(
results: List<PythonResult> = emptyList(),
var isPublic: Boolean = true,
var description: String = "",
var todo: String = "",
var isPure: Boolean = false,
override val annotations: MutableList<EditorAnnotation> = mutableListOf(),
var callToOriginalAPI: PythonCall? = null
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package com.larsreimann.api_editor.transformation

import com.larsreimann.api_editor.model.TodoAnnotation
import com.larsreimann.api_editor.mutable_model.PythonClass
import com.larsreimann.api_editor.mutable_model.PythonDeclaration
import com.larsreimann.api_editor.mutable_model.PythonFunction
import com.larsreimann.api_editor.mutable_model.PythonPackage
import com.larsreimann.modeling.descendants

/**
* Processes and removes `@todo` annotations.
*/
fun PythonPackage.processTodoAnnotations() {
this.descendants()
.filterIsInstance<PythonDeclaration>()
.forEach { it.processTodoAnnotations() }
}

private fun PythonDeclaration.processTodoAnnotations() {
this.annotations
.filterIsInstance<TodoAnnotation>()
.forEach {
when (this) {
is PythonClass -> this.todo = it.message
is PythonFunction -> this.todo = it.message
else -> {
// Do nothing
}
}
this.annotations.remove(it)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ private fun PythonPackage.preprocess(newPackageName: String) {
private fun PythonPackage.processAnnotations() {
processRemoveAnnotations()
processDescriptionAnnotations()
processTodoAnnotations()
processRenameAnnotations()
processMoveAnnotations()
processBoundaryAnnotations()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,8 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython

companion object {
private var possibleCombinations = buildMap<String, Set<String>> {
this["Attribute"] = mutableSetOf("Description", "Rename")
this["Boundary"] = mutableSetOf("Description", "Group", "Optional", "Rename", "Required")
this["Attribute"] = mutableSetOf("Description", "Rename", "Todo")
this["Boundary"] = mutableSetOf("Description", "Group", "Optional", "Rename", "Required", "Todo")
this["CalledAfter"] = mutableSetOf("CalledAfter", "Description", "Group", "Move", "Pure", "Rename")
this["Constant"] = mutableSetOf()
this["Description"] = mutableSetOf(
Expand All @@ -195,9 +195,10 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython
"Optional",
"Pure",
"Rename",
"Required"
"Required",
"Todo"
)
this["Enum"] = mutableSetOf("Description", "Group", "Rename", "Required")
this["Enum"] = mutableSetOf("Description", "Group", "Rename", "Required", "Todo")
this["Group"] =
mutableSetOf(
"Boundary",
Expand All @@ -209,10 +210,11 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython
"Optional",
"Pure",
"Rename",
"Required"
"Required",
"Todo"
)
this["Move"] = mutableSetOf("CalledAfter", "Description", "Group", "Pure", "Rename")
this["Optional"] = mutableSetOf("Boundary", "Description", "Group", "Rename")
this["Optional"] = mutableSetOf("Boundary", "Description", "Group", "Rename", "Todo")
this["Pure"] = mutableSetOf("CalledAfter", "Description", "Group", "Move", "Rename")
this["Remove"] = mutableSetOf()
this["Rename"] = mutableSetOf(
Expand All @@ -225,9 +227,19 @@ class AnnotationValidator(private val annotatedPythonPackage: SerializablePython
"Move",
"Optional",
"Pure",
"Required", "Todo"
)
this["Required"] = mutableSetOf("Boundary", "Description", "Enum", "Group", "Rename", "Todo")
this["Todo"] = mutableSetOf(
"Attribute",
"Boundary",
"Description",
"Enum",
"Group",
"Optional",
"Rename",
"Required"
)
this["Required"] = mutableSetOf("Boundary", "Description", "Enum", "Group", "Rename")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,21 @@ class PythonCodeGeneratorTest {
| self.testAttribute2: int = 2
""".trimMargin()
}

@Test
fun `should store todo if it is not blank`() {
val testClass = PythonClass(
name = "TestClass",
todo = "Lorem ipsum\n\nDolor sit amet"
)

testClass.toPythonCode() shouldBe """
|# TODO: Lorem ipsum
| Dolor sit amet
|class TestClass:
| pass
""".trimMargin()
}
}

@Nested
Expand Down Expand Up @@ -441,6 +456,20 @@ class PythonCodeGeneratorTest {
| self.instance = OriginalClass()
""".trimMargin()
}

@Test
fun `should store todo if it is not blank`() {
val testConstructor = PythonConstructor(
todo = "Lorem ipsum\n\nDolor sit amet"
)

testConstructor.toPythonCode() shouldBe """
|# TODO: Lorem ipsum
| Dolor sit amet
|def __init__():
| pass
""".trimMargin()
}
}

@Nested
Expand Down Expand Up @@ -687,6 +716,21 @@ class PythonCodeGeneratorTest {
| pass
""".trimMargin()
}

@Test
fun `should store todo if it is not blank`() {
val testFunction = PythonFunction(
name = "testFunction",
todo = "Lorem ipsum\n\nDolor sit amet"
)

testFunction.toPythonCode() shouldBe """
|# TODO: Lorem ipsum
| Dolor sit amet
|def testFunction():
| pass
""".trimMargin()
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package com.larsreimann.api_editor.transformation

import com.larsreimann.api_editor.model.TodoAnnotation
import com.larsreimann.api_editor.mutable_model.PythonClass
import com.larsreimann.api_editor.mutable_model.PythonFunction
import com.larsreimann.api_editor.mutable_model.PythonModule
import com.larsreimann.api_editor.mutable_model.PythonPackage
import io.kotest.matchers.collections.shouldBeEmpty
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.BeforeEach
import org.junit.jupiter.api.Test

class TodoAnnotationProcessorTest {
private lateinit var testClass: PythonClass
private lateinit var testFunction: PythonFunction
private lateinit var testPackage: PythonPackage

@BeforeEach
fun reset() {
testClass = PythonClass(
name = "TestClass",
annotations = mutableListOf(TodoAnnotation("Refactor class"))
)
testFunction = PythonFunction(
name = "testFunction",
annotations = mutableListOf(TodoAnnotation("Refactor function"))
)
testPackage = PythonPackage(
distribution = "testPackage",
name = "testPackage",
version = "1.0.0",
modules = listOf(
PythonModule(
name = "testModule",
classes = listOf(testClass),
functions = listOf(testFunction)
)
)
)
}

@Test
fun `should process TodoAnnotation of classes`() {
testPackage.processTodoAnnotations()

testClass.todo shouldBe "Refactor class"
}

@Test
fun `should remove TodoAnnotation of classes`() {
testPackage.processTodoAnnotations()

testClass.annotations
.filterIsInstance<TodoAnnotation>()
.shouldBeEmpty()
}

@Test
fun `should process TodoAnnotation of functions`() {
testPackage.processTodoAnnotations()

testFunction.todo shouldBe "Refactor function"
}

@Test
fun `should remove TodoAnnotation of functions`() {
testPackage.processTodoAnnotations()

testFunction.annotations
.filterIsInstance<TodoAnnotation>()
.shouldBeEmpty()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ fun AnnotationDropdown(
showRemove: Boolean = false,
showRename: Boolean = false,
showRequired: Boolean = false,
showTodo: Boolean = false,
) {
var expanded by remember { mutableStateOf(false) }

Expand Down Expand Up @@ -102,6 +103,11 @@ fun AnnotationDropdown(
Text(labels.getString("AnnotationDropdown.Option.Required"))
}
}
if (showTodo) {
DropdownMenuItem(onClick = {}) {
Text(labels.getString("AnnotationDropdown.Option.Todo"))
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ AnnotationDropdown.Option.Pure=@pure
AnnotationDropdown.Option.Remove=@remove
AnnotationDropdown.Option.Rename=@rename
AnnotationDropdown.Option.Required=@required
AnnotationDropdown.Option.Todo=@todo
Loading