Skip to content

Commit 2dcabe8

Browse files
authored
Merge pull request #150 from domaframework/feature/for-directive-specific-word-element-handling
for directive specific word element handling
2 parents 53cbaf9 + 8ee159a commit 2dcabe8

File tree

18 files changed

+397
-136
lines changed

18 files changed

+397
-136
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/util/ForDirectiveUtil.kt

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import com.intellij.psi.PsiClass
2020
import com.intellij.psi.PsiClassType
2121
import com.intellij.psi.PsiElement
2222
import com.intellij.psi.PsiType
23+
import com.intellij.psi.search.GlobalSearchScope
2324
import com.intellij.psi.util.CachedValue
2425
import com.intellij.psi.util.CachedValueProvider
2526
import com.intellij.psi.util.CachedValuesManager
@@ -225,7 +226,8 @@ class ForDirectiveUtil {
225226
var isBatchAnnotation = false
226227
val forItemDeclarationBlocks =
227228
if (forDirectiveDeclaration.element is SqlElStaticFieldAccessExpr) {
228-
val staticFieldAccessExpr = forDirectiveDeclaration.element as SqlElStaticFieldAccessExpr
229+
val staticFieldAccessExpr =
230+
forDirectiveDeclaration.element as SqlElStaticFieldAccessExpr
229231
staticFieldAccessExpr.accessElements
230232
} else {
231233
forDirectiveDeclaration.getDeclarationChildren()
@@ -234,18 +236,25 @@ class ForDirectiveUtil {
234236
// Defined by StaticFieldAccess
235237
if (forDirectiveDeclaration.element is SqlElStaticFieldAccessExpr) {
236238
val file = topForDirectiveItem.containingFile
237-
val staticFieldAccessExpr = forDirectiveDeclaration.element as SqlElStaticFieldAccessExpr
239+
val staticFieldAccessExpr =
240+
forDirectiveDeclaration.element as SqlElStaticFieldAccessExpr
238241
val clazz = staticFieldAccessExpr.elClass
239242
val staticElement = PsiStaticElement(clazz.elIdExprList, file)
240243
val referenceClazz = staticElement.getRefClazz() ?: return null
241244

242245
// In the case of staticFieldAccess, the property that is called first is retrieved.
243-
fieldAccessTopParentClass = getStaticFieldAccessTopElementClassType(staticFieldAccessExpr, referenceClazz)
246+
fieldAccessTopParentClass =
247+
getStaticFieldAccessTopElementClassType(
248+
staticFieldAccessExpr,
249+
referenceClazz,
250+
)
244251
} else {
245252
// Defined by Dao parameter
246253
val file = topForDirectiveItem.containingFile ?: return null
247254
val daoMethod = findDaoMethod(file) ?: return null
248-
val topElementText = forDirectiveDeclaration.getDeclarationChildren().firstOrNull()?.text ?: return null
255+
val topElementText =
256+
forDirectiveDeclaration.getDeclarationChildren().firstOrNull()?.text
257+
?: return null
249258
isBatchAnnotation = PsiDaoMethod(project, daoMethod).daoType.isBatchAnnotation()
250259

251260
val matchParam = daoMethod.findParameter(cleanString(topElementText))
@@ -450,5 +459,28 @@ class ForDirectiveUtil {
450459
} else {
451460
""
452461
}
462+
463+
fun resolveForDirectiveClassTypeIfSuffixExists(
464+
project: Project,
465+
searchName: String,
466+
): PsiType? {
467+
if (searchName.endsWith("_has_next")) {
468+
return PsiType.getTypeByName(
469+
"java.lang.Boolean",
470+
project,
471+
GlobalSearchScope.allScope(project),
472+
)
473+
}
474+
475+
if (searchName.endsWith("_index")) {
476+
return PsiType.getTypeByName(
477+
"java.lang.Integer",
478+
project,
479+
GlobalSearchScope.allScope(project),
480+
)
481+
}
482+
483+
return null
484+
}
453485
}
454486
}

src/main/kotlin/org/domaframework/doma/intellij/contributor/sql/provider/SqlParameterCompletionProvider.kt

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -419,20 +419,28 @@ class SqlParameterCompletionProvider : CompletionProvider<CompletionParameters>(
419419
positionText: String,
420420
result: CompletionResultSet,
421421
): Boolean {
422+
val searchWord = cleanString(positionText)
422423
val project = top.project
423424
val forDirectiveBlocks = ForDirectiveUtil.getForDirectiveBlocks(top)
424425
ForDirectiveUtil.findForItem(top, forDirectives = forDirectiveBlocks) ?: return false
425426

426427
val forItemClassType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectiveBlocks) ?: return false
428+
val specifiedClassType = ForDirectiveUtil.resolveForDirectiveClassTypeIfSuffixExists(project, top.text)
429+
val topClassType =
430+
if (specifiedClassType != null) {
431+
PsiParentClass(specifiedClassType)
432+
} else {
433+
forItemClassType
434+
}
435+
427436
val result =
428437
ForDirectiveUtil.getFieldAccessLastPropertyClassType(
429438
elements,
430439
project,
431-
forItemClassType,
440+
topClassType,
432441
shortName = "",
433442
dropLastIndex = 1,
434443
complete = { lastType ->
435-
val searchWord = cleanString(positionText)
436444
setFieldsAndMethodsCompletionResultSet(
437445
lastType.searchField(searchWord)?.toTypedArray() ?: emptyArray(),
438446
lastType.searchMethod(searchWord)?.toTypedArray() ?: emptyArray(),

src/main/kotlin/org/domaframework/doma/intellij/document/ForItemElementDocumentationProvider.kt

Lines changed: 17 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -16,23 +16,10 @@
1616
package org.domaframework.doma.intellij.document
1717

1818
import com.intellij.lang.documentation.AbstractDocumentationProvider
19-
import com.intellij.openapi.project.Project
2019
import com.intellij.psi.PsiElement
21-
import com.intellij.psi.PsiFile
2220
import com.intellij.psi.util.PsiTreeUtil
23-
import org.domaframework.doma.intellij.common.dao.findDaoMethod
24-
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
25-
import org.domaframework.doma.intellij.common.psi.PsiParentClass
26-
import org.domaframework.doma.intellij.common.psi.PsiStaticElement
27-
import org.domaframework.doma.intellij.common.sql.foritem.ForItem
28-
import org.domaframework.doma.intellij.common.util.ForDirectiveUtil
29-
import org.domaframework.doma.intellij.extension.expr.accessElements
30-
import org.domaframework.doma.intellij.extension.expr.accessElementsPrevOriginalElement
31-
import org.domaframework.doma.intellij.extension.psi.findParameter
32-
import org.domaframework.doma.intellij.extension.psi.getForItem
33-
import org.domaframework.doma.intellij.extension.psi.psiClassType
34-
import org.domaframework.doma.intellij.psi.SqlElClass
35-
import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr
21+
import org.domaframework.doma.intellij.document.generator.DocumentDaoParameterGenerator
22+
import org.domaframework.doma.intellij.document.generator.DocumentStaticFieldGenerator
3623
import org.domaframework.doma.intellij.psi.SqlElIdExpr
3724
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
3825
import org.domaframework.doma.intellij.psi.SqlTypes
@@ -56,93 +43,26 @@ class ForItemElementDocumentationProvider : AbstractDocumentationProvider() {
5643

5744
val staticFieldAccessExpr =
5845
PsiTreeUtil.getParentOfType(originalElement, SqlElStaticFieldAccessExpr::class.java)
59-
if (staticFieldAccessExpr != null) {
60-
generateStaticFieldDocument(
61-
staticFieldAccessExpr,
62-
file,
63-
originalElement,
64-
project,
65-
result,
66-
)
67-
} else {
68-
generateDaoFieldAccessDocument(originalElement, project, result)
69-
}
70-
return result.joinToString("\n")
71-
}
72-
73-
private fun generateDaoFieldAccessDocument(
74-
originalElement: PsiElement,
75-
project: Project,
76-
result: MutableList<String?>,
77-
) {
78-
var topParentType: PsiParentClass? = null
79-
val selfSkip = isSelfSkip(originalElement)
80-
val forDirectives = ForDirectiveUtil.getForDirectiveBlocks(originalElement, selfSkip)
81-
val fieldAccessExpr =
82-
PsiTreeUtil.getParentOfType(
83-
originalElement,
84-
SqlElFieldAccessExpr::class.java,
85-
)
86-
val fieldAccessBlocks =
87-
fieldAccessExpr?.accessElementsPrevOriginalElement(originalElement.textOffset)
88-
val searchElement = fieldAccessBlocks?.firstOrNull() ?: originalElement
89-
90-
var isBatchAnnotation = false
91-
if (ForDirectiveUtil.findForItem(searchElement, forDirectives = forDirectives) != null) {
92-
topParentType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectives)
93-
} else {
94-
val daoMethod = findDaoMethod(originalElement.containingFile) ?: return
95-
val param = daoMethod.findParameter(originalElement.text) ?: return
96-
isBatchAnnotation = PsiDaoMethod(project, daoMethod).daoType.isBatchAnnotation()
97-
topParentType = PsiParentClass(param.type)
98-
}
99-
if (fieldAccessExpr != null && fieldAccessBlocks != null) {
100-
topParentType?.let {
101-
ForDirectiveUtil.getFieldAccessLastPropertyClassType(
102-
fieldAccessBlocks,
46+
val generator =
47+
if (staticFieldAccessExpr != null) {
48+
DocumentStaticFieldGenerator(
49+
originalElement,
50+
project,
51+
result,
52+
staticFieldAccessExpr,
53+
file,
54+
)
55+
} else {
56+
DocumentDaoParameterGenerator(
57+
originalElement,
10358
project,
104-
it,
105-
isBatchAnnotation = isBatchAnnotation,
106-
complete = { lastType ->
107-
result.add("${generateTypeLink(lastType)} ${originalElement.text}")
108-
},
59+
result,
10960
)
11061
}
111-
return
112-
}
113-
result.add("${generateTypeLink(topParentType)} ${originalElement.text}")
114-
}
115-
116-
private fun generateStaticFieldDocument(
117-
staticFieldAccessExpr: SqlElStaticFieldAccessExpr,
118-
file: PsiFile,
119-
originalElement: PsiElement,
120-
project: Project,
121-
result: MutableList<String?>,
122-
) {
123-
val fieldAccessBlocks = staticFieldAccessExpr.accessElements
124-
val staticElement = PsiStaticElement(fieldAccessBlocks, file)
125-
val referenceClass = staticElement.getRefClazz() ?: return
126-
if (PsiTreeUtil.getParentOfType(originalElement, SqlElClass::class.java) != null) {
127-
val clazzType = PsiParentClass(referenceClass.psiClassType)
128-
result.add("${generateTypeLink(clazzType)} ${originalElement.text}")
129-
return
130-
}
13162

132-
ForDirectiveUtil.getFieldAccessLastPropertyClassType(
133-
fieldAccessBlocks.filter { it.textOffset <= originalElement.textOffset },
134-
project,
135-
PsiParentClass(referenceClass.psiClassType),
136-
complete = { lastType ->
137-
result.add("${generateTypeLink(lastType)} ${originalElement.text}")
138-
},
139-
)
140-
}
63+
generator.generateDocument()
14164

142-
private fun isSelfSkip(targetElement: PsiElement): Boolean {
143-
val forItem = ForItem(targetElement)
144-
val forDirectiveExpr = forItem.getParentForDirectiveExpr()
145-
return !(forDirectiveExpr != null && forDirectiveExpr.getForItem()?.textOffset == targetElement.textOffset)
65+
return result.joinToString("\n")
14666
}
14767

14868
override fun generateHoverDoc(
@@ -159,36 +79,4 @@ class ForItemElementDocumentationProvider : AbstractDocumentationProvider() {
15979
result.add(typeDocument)
16080
return result.joinToString("\n")
16181
}
162-
163-
private fun generateTypeLink(parentClass: PsiParentClass?): String {
164-
if (parentClass?.type != null) {
165-
return generateTypeLinkFromCanonicalText(parentClass.type.canonicalText)
166-
}
167-
return ""
168-
}
169-
170-
private fun generateTypeLinkFromCanonicalText(canonicalText: String): String {
171-
val regex = Regex("([a-zA-Z0-9_]+\\.)*([a-zA-Z0-9_]+)")
172-
val result = StringBuilder()
173-
var lastIndex = 0
174-
175-
for (match in regex.findAll(canonicalText)) {
176-
val fullMatch = match.value
177-
val typeName = match.groups[2]?.value ?: fullMatch
178-
val startIndex = match.range.first
179-
val endIndex = match.range.last + 1
180-
181-
if (lastIndex < startIndex) {
182-
result.append(canonicalText.substring(lastIndex, startIndex))
183-
}
184-
result.append("<a href=\"psi_element://$fullMatch\">$typeName</a>")
185-
lastIndex = endIndex
186-
}
187-
188-
if (lastIndex < canonicalText.length) {
189-
result.append(canonicalText.substring(lastIndex))
190-
}
191-
192-
return result.toString()
193-
}
19482
}
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/*
2+
* Copyright Doma Tools Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.domaframework.doma.intellij.document.generator
17+
18+
import com.intellij.openapi.project.Project
19+
import com.intellij.psi.PsiElement
20+
import com.intellij.psi.util.PsiTreeUtil
21+
import org.domaframework.doma.intellij.common.dao.findDaoMethod
22+
import org.domaframework.doma.intellij.common.psi.PsiDaoMethod
23+
import org.domaframework.doma.intellij.common.psi.PsiParentClass
24+
import org.domaframework.doma.intellij.common.util.ForDirectiveUtil
25+
import org.domaframework.doma.intellij.extension.expr.accessElementsPrevOriginalElement
26+
import org.domaframework.doma.intellij.extension.psi.findParameter
27+
import org.domaframework.doma.intellij.psi.SqlElFieldAccessExpr
28+
29+
class DocumentDaoParameterGenerator(
30+
val originalElement: PsiElement,
31+
val project: Project,
32+
val result: MutableList<String?>,
33+
) : DocumentGenerator() {
34+
override fun generateDocument() {
35+
var topParentType: PsiParentClass? = null
36+
val selfSkip = isSelfSkip(originalElement)
37+
val forDirectives = ForDirectiveUtil.getForDirectiveBlocks(originalElement, selfSkip)
38+
val fieldAccessExpr =
39+
PsiTreeUtil.getParentOfType(
40+
originalElement,
41+
SqlElFieldAccessExpr::class.java,
42+
)
43+
val fieldAccessBlocks =
44+
fieldAccessExpr?.accessElementsPrevOriginalElement(originalElement.textOffset)
45+
val searchElement = fieldAccessBlocks?.firstOrNull() ?: originalElement
46+
47+
var isBatchAnnotation = false
48+
if (ForDirectiveUtil.findForItem(searchElement, forDirectives = forDirectives) != null) {
49+
val forItemClassType = ForDirectiveUtil.getForDirectiveItemClassType(project, forDirectives)
50+
val specifiedClassType = ForDirectiveUtil.resolveForDirectiveClassTypeIfSuffixExists(project, searchElement.text)
51+
topParentType =
52+
if (specifiedClassType != null) {
53+
PsiParentClass(specifiedClassType)
54+
} else {
55+
forItemClassType
56+
}
57+
} else {
58+
val daoMethod = findDaoMethod(originalElement.containingFile) ?: return
59+
val param = daoMethod.findParameter(originalElement.text) ?: return
60+
isBatchAnnotation = PsiDaoMethod(project, daoMethod).daoType.isBatchAnnotation()
61+
topParentType = PsiParentClass(param.type)
62+
}
63+
64+
if (fieldAccessExpr != null && fieldAccessBlocks != null) {
65+
topParentType?.let {
66+
ForDirectiveUtil.getFieldAccessLastPropertyClassType(
67+
fieldAccessBlocks,
68+
project,
69+
it,
70+
isBatchAnnotation = isBatchAnnotation,
71+
complete = { lastType ->
72+
result.add("${generateTypeLink(lastType)} ${originalElement.text}")
73+
},
74+
)
75+
}
76+
return
77+
}
78+
result.add("${generateTypeLink(topParentType)} ${originalElement.text}")
79+
}
80+
}

0 commit comments

Comments
 (0)