1
+ package org.utbot.intellij.plugin.javadoc
2
+
3
+ import com.intellij.codeInsight.documentation.DocumentationManagerUtil
4
+ import com.intellij.codeInsight.javadoc.JavaDocUtil
5
+ import com.intellij.lang.documentation.DocumentationMarkup
6
+ import com.intellij.openapi.project.DumbService
7
+ import com.intellij.openapi.project.IndexNotReadyException
8
+ import com.intellij.openapi.util.text.StringUtil
9
+ import com.intellij.psi.*
10
+ import com.intellij.psi.javadoc.PsiDocComment
11
+ import com.intellij.psi.javadoc.PsiDocTag
12
+ import com.intellij.psi.javadoc.PsiDocToken
13
+ import com.intellij.psi.javadoc.PsiInlineDocTag
14
+ import mu.KotlinLogging
15
+
16
+ private const val LINK_TAG = " link"
17
+ private const val LINKPLAIN_TAG = " linkplain"
18
+ private const val LITERAL_TAG = " literal"
19
+ private const val CODE_TAG = " code"
20
+ private const val SYSTEM_PROPERTY_TAG = " systemProperty"
21
+ private const val MESSAGE_SEPARATOR = " :"
22
+
23
+ private val logger = KotlinLogging .logger {}
24
+
25
+ class UtJavaDocInfoGenerator {
26
+ /* *
27
+ * Generates UtBot specific sections to include them to rendered JavaDoc comment.
28
+ */
29
+ fun addUtBotSpecificSectionsToJavaDoc (javadoc : String? , comment : PsiDocComment ): String {
30
+ val builder: StringBuilder = StringBuilder (javadoc)
31
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .ClassUnderTest )
32
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .MethodUnderTest )
33
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .Invokes )
34
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .Executes )
35
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .ExpectedResult )
36
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .ActualResult )
37
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .ReturnsFrom )
38
+ generateUtTagSection(builder, comment, UtCustomJavaDocTagProvider .UtCustomTag .ThrowsException )
39
+ return builder.toString()
40
+ }
41
+
42
+ /* *
43
+ * Searches for UtBot tag in the comment and generates a related section for it.
44
+ */
45
+ private fun generateUtTagSection (
46
+ builder : StringBuilder ,
47
+ comment : PsiDocComment ? ,
48
+ utTag : UtCustomJavaDocTagProvider .UtCustomTag
49
+ ) {
50
+ if (comment != null ) {
51
+ val tag = comment.findTagByName(utTag.name) ? : return
52
+ startHeaderSection(builder, utTag.getMessage())?.append(" <p>" )
53
+ val tmp = StringBuilder ()
54
+ generateValue(tmp, tag.dataElements)
55
+ builder.append(tmp.toString().trim { it <= ' ' })
56
+ builder.append(DocumentationMarkup .SECTION_END )
57
+ }
58
+ }
59
+
60
+ private fun startHeaderSection (builder : StringBuilder , message : String ): StringBuilder ? {
61
+ return builder.append(DocumentationMarkup .SECTION_HEADER_START )
62
+ .append(message)
63
+ .append(MESSAGE_SEPARATOR )
64
+ .append(DocumentationMarkup .SECTION_SEPARATOR )
65
+ }
66
+
67
+ /* *
68
+ * Generates info depending on tag's value type.
69
+ */
70
+ private fun generateValue (builder : StringBuilder , elements : Array <PsiElement >) {
71
+ var offset = if (elements.isNotEmpty()) {
72
+ elements[0 ].textOffset + elements[0 ].text.length
73
+ } else 0
74
+
75
+ for (i in elements.indices) {
76
+ if (elements[i].textOffset > offset) builder.append(' ' )
77
+ offset = elements[i].textOffset + elements[i].text.length
78
+ val element = elements[i]
79
+ if (element is PsiInlineDocTag ) {
80
+ when (element.name) {
81
+ LITERAL_TAG -> generateLiteralValue(builder, element)
82
+ CODE_TAG , SYSTEM_PROPERTY_TAG -> generateCodeValue(element, builder)
83
+ LINK_TAG -> generateLinkValue(element, builder, false )
84
+ LINKPLAIN_TAG -> generateLinkValue(element, builder, true )
85
+ }
86
+ } else {
87
+ appendPlainText(builder, element.text)
88
+ }
89
+ }
90
+ }
91
+
92
+ private fun appendPlainText (builder : StringBuilder , text : String ) {
93
+ builder.append(StringUtil .replaceUnicodeEscapeSequences(text))
94
+ }
95
+
96
+ private fun collectElementText (builder : StringBuilder , element : PsiElement ) {
97
+ element.accept(object : PsiRecursiveElementWalkingVisitor () {
98
+ override fun visitElement (element : PsiElement ) {
99
+ super .visitElement(element)
100
+ if (element is PsiWhiteSpace ||
101
+ element is PsiJavaToken ||
102
+ element is PsiDocToken && element.tokenType != = JavaDocTokenType .DOC_COMMENT_LEADING_ASTERISKS
103
+ ) {
104
+ builder.append(element.text)
105
+ }
106
+ }
107
+ })
108
+ }
109
+
110
+ private fun generateCodeValue (tag : PsiInlineDocTag , builder : StringBuilder ) {
111
+ builder.append(" <code>" )
112
+ val pos = builder.length
113
+ generateLiteralValue(builder, tag)
114
+ builder.append(" </code>" )
115
+ if (builder[pos] == ' \n ' ) builder.insert(
116
+ pos,
117
+ ' '
118
+ ) // line break immediately after opening tag is ignored by JEditorPane
119
+ }
120
+
121
+ private fun generateLiteralValue (builder : StringBuilder , tag : PsiDocTag ) {
122
+ val tmpBuilder = StringBuilder ()
123
+ val children = tag.children
124
+ for (i in 2 until children.size - 1 ) { // process all children except tag opening/closing elements
125
+ val child = children[i]
126
+ if (child is PsiDocToken && child.tokenType == = JavaDocTokenType .DOC_COMMENT_LEADING_ASTERISKS ) continue
127
+ var elementText = child.text
128
+ if (child is PsiWhiteSpace ) {
129
+ val pos = elementText.lastIndexOf(' \n ' )
130
+ if (pos >= 0 ) elementText = elementText.substring(0 , pos + 1 ) // skip whitespace before leading asterisk
131
+ }
132
+ appendPlainText(tmpBuilder, StringUtil .escapeXmlEntities(elementText))
133
+ }
134
+ builder.append(StringUtil .trimLeading(tmpBuilder))
135
+ }
136
+
137
+ private fun generateLinkValue (tag : PsiInlineDocTag , builder : StringBuilder , plainLink : Boolean ) {
138
+ val tagElements = tag.dataElements
139
+ val linkText: String = createLinkText(tagElements)
140
+ if (linkText.isNotEmpty()) {
141
+ val index = JavaDocUtil .extractReference(linkText)
142
+ val referenceText = linkText.substring(0 , index).trim { it <= ' ' }
143
+ val label = StringUtil .nullize(linkText.substring(index).trim { it <= ' ' })
144
+ generateLink(builder, referenceText, label, tagElements[0 ], plainLink)
145
+ }
146
+ }
147
+
148
+ private fun createLinkText (tagElements : Array <PsiElement >): String {
149
+ var offset = if (tagElements.isNotEmpty()) {
150
+ tagElements[0 ].textOffset + tagElements[0 ].text.length
151
+ } else {
152
+ 0
153
+ }
154
+
155
+ val builder = StringBuilder ()
156
+ for (i in tagElements.indices) {
157
+ val tagElement = tagElements[i]
158
+ if (tagElement.textOffset > offset) builder.append(' ' )
159
+ offset = tagElement.textOffset + tagElement.text.length
160
+ collectElementText(builder, tagElement)
161
+ if (i < tagElements.size - 1 ) {
162
+ builder.append(' ' )
163
+ }
164
+ }
165
+ return builder.toString().trim { it <= ' ' }
166
+ }
167
+
168
+ private fun generateLink (
169
+ builder : StringBuilder ,
170
+ refText : String? ,
171
+ label : String? ,
172
+ context : PsiElement ,
173
+ plainLink : Boolean
174
+ ) {
175
+ var linkLabel = label
176
+ if (label == null ) {
177
+ val manager = context.manager
178
+ linkLabel = JavaDocUtil .getLabelText(manager.project, manager, refText, context)
179
+ }
180
+
181
+ var target: PsiElement ? = null
182
+ try {
183
+ if (refText != null ) {
184
+ target = JavaDocUtil .findReferenceTarget(context.manager, refText, context)
185
+ }
186
+ } catch (e: IndexNotReadyException ) {
187
+ logger.info(e) { " Failed to find a reference while generating JavaDoc comment. Details: ${e.message} " }
188
+ }
189
+
190
+ if (target == null && DumbService .isDumb(context.project)) {
191
+ builder.append(linkLabel)
192
+ } else if (target == null ) {
193
+ builder.append(" <font color=red>" ).append(linkLabel).append(" </font>" )
194
+ } else {
195
+ val referenceText = JavaDocUtil .getReferenceText(target.project, target)
196
+ if (referenceText != null ) {
197
+ DocumentationManagerUtil .createHyperlink(builder, target, referenceText, linkLabel, plainLink)
198
+ }
199
+ }
200
+ }
201
+ }
0 commit comments