Skip to content

Commit 690ad4e

Browse files
committed
Add SQL completion tests for top element before at-sign and static property after other element
1 parent 19b6233 commit 690ad4e

File tree

8 files changed

+145
-15
lines changed

8 files changed

+145
-15
lines changed

src/main/kotlin/org/domaframework/doma/intellij/common/psi/PsiPatternUtil.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ import com.intellij.patterns.PatternCondition
1919
import com.intellij.patterns.PlatformPatterns
2020
import com.intellij.patterns.PsiElementPattern
2121
import com.intellij.psi.PsiElement
22+
import com.intellij.psi.PsiWhiteSpace
2223
import com.intellij.psi.TokenType
24+
import com.intellij.psi.tree.IElementType
2325
import com.intellij.psi.util.PsiTreeUtil
2426
import com.intellij.psi.util.elementType
2527
import com.intellij.psi.util.prevLeaf
@@ -148,4 +150,21 @@ object PsiPatternUtil {
148150
.substringAfter(symbol)
149151
return prefix
150152
}
153+
154+
fun getBindSearchWord(
155+
element: PsiElement,
156+
targetType: IElementType?,
157+
): MutableList<PsiElement> {
158+
var prevElement = PsiTreeUtil.prevLeaf(element, true)
159+
var prevElements = mutableListOf<PsiElement>()
160+
while (prevElement != null &&
161+
prevElement !is PsiWhiteSpace &&
162+
prevElement.elementType != targetType &&
163+
prevElement.elementType != SqlTypes.BLOCK_COMMENT_START
164+
) {
165+
prevElements.add(prevElement)
166+
prevElement = PsiTreeUtil.prevLeaf(prevElement, true)
167+
}
168+
return prevElements
169+
}
151170
}

src/main/kotlin/org/domaframework/doma/intellij/common/sql/directive/StaticDirectiveHandler.kt

Lines changed: 40 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ package org.domaframework.doma.intellij.common.sql.directive
1818
import com.intellij.codeInsight.completion.CompletionResultSet
1919
import com.intellij.openapi.module.Module
2020
import com.intellij.psi.PsiElement
21+
import com.intellij.psi.PsiWhiteSpace
2122
import com.intellij.psi.util.PsiTreeUtil
2223
import com.intellij.psi.util.elementType
2324
import org.domaframework.doma.intellij.common.psi.PsiPatternUtil
@@ -65,17 +66,48 @@ class StaticDirectiveHandler(
6566
* Determines whether code completion is needed for [SqlElClass] elements.
6667
*/
6768
private fun isSqlElClassCompletion(): Boolean {
68-
val elClassPattern = "^([a-zA-Z]*(\\.)+)*"
69+
if (element.elementType == SqlTypes.AT_SIGN &&
70+
PsiTreeUtil.prevLeaf(element)?.elementType == SqlTypes.AT_SIGN
71+
) {
72+
return true
73+
}
74+
75+
val elClassPattern = "^([a-zA-Z]*(\\.)+)*$"
6976
val regex = Regex(elClassPattern)
70-
val prevWords = PsiPatternUtil.getBindSearchWord(element.containingFile, element, "@")
77+
val prevElements = PsiPatternUtil.getBindSearchWord(element, SqlTypes.AT_SIGN)
78+
val topAtSign = PsiTreeUtil.prevLeaf(prevElements.lastOrNull() ?: element, true)
79+
val prevWords = prevElements.reversed().joinToString("") { it.text }
80+
81+
// If the cursor is in the middle of [SqlElClass],
82+
// search for the following @ and ensure that code completion is within [SqlElClass].
83+
if (element.elementType != SqlTypes.AT_SIGN) {
84+
var nextElement = PsiTreeUtil.nextLeaf(element, true)
85+
while (nextElement != null &&
86+
nextElement !is PsiWhiteSpace &&
87+
nextElement.elementType != SqlTypes.BLOCK_COMMENT_END &&
88+
nextElement.elementType != SqlTypes.AT_SIGN
89+
) {
90+
nextElement = PsiTreeUtil.nextLeaf(nextElement, true)
91+
}
92+
val lastAtSign = PsiTreeUtil.nextLeaf(nextElement ?: element, true)
93+
if (regex.matches(prevWords) &&
94+
(
95+
lastAtSign == null ||
96+
nextElement.elementType != SqlTypes.AT_SIGN ||
97+
lastAtSign.elementType == SqlTypes.BLOCK_COMMENT_END
98+
)
99+
) {
100+
return false
101+
}
102+
}
103+
104+
// Check if there is a partially entered class package name ahead and ensure that input is in [SqlElClass].
105+
if (prevElements.isEmpty()) return false
71106
return (
72-
(PsiTreeUtil.nextLeaf(element)?.elementType == SqlTypes.AT_SIGN || element.elementType == SqlTypes.AT_SIGN) &&
107+
topAtSign?.elementType == SqlTypes.AT_SIGN &&
108+
PsiTreeUtil.prevLeaf(topAtSign, true)?.elementType != SqlTypes.EL_IDENTIFIER &&
73109
regex.matches(prevWords)
74-
) ||
75-
(
76-
element.elementType == SqlTypes.AT_SIGN &&
77-
PsiTreeUtil.prevLeaf(element)?.elementType == SqlTypes.AT_SIGN
78-
)
110+
)
79111
}
80112

81113
/**

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

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ class SqlParameterCompletionProvider : CompletionProvider<CompletionParameters>(
8080
try {
8181
val originalFile = parameters.originalFile
8282
val pos = parameters.originalPosition ?: return
83-
val bindText = StringUtil.replaceBlockCommentStartEnd(cleanString(pos.text))
83+
val bindText = cleanString(pos.text)
8484

8585
val handler = DirectiveCompletion(originalFile, bindText, pos, caretNextText, result)
8686
val directiveSymbols = DirectiveCompletion.directiveSymbols
@@ -342,6 +342,14 @@ class SqlParameterCompletionProvider : CompletionProvider<CompletionParameters>(
342342
?: clazz.findStaticMethod(topText)?.returnType
343343
}
344344

345+
/**
346+
* Retrieves the class type from the previous element class words.
347+
* If the class is not found, returns null.
348+
* @param project The current project.
349+
* @param fqdn The fully qualified class name.
350+
* @param topText The name of the static property search that is being called first.
351+
* @return The class type of the static property, or null if not found.
352+
*/
345353
private fun getElementTypeByPrevSqlElClassWords(
346354
project: Project,
347355
fqdn: String,

src/test/kotlin/org/domaframework/doma/intellij/complate/sql/SqlCompleteTest.kt

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,13 +32,15 @@ class SqlCompleteTest : DomaSqlTest() {
3232
"$testDaoName/completeDaoArgument.sql",
3333
"$testDaoName/completeInstancePropertyFromDaoArgumentClass.sql",
3434
"$testDaoName/completeInstancePropertyWithMethodParameter.sql",
35+
"$testDaoName/completeTopElementBeforeAtsign.sql",
3536
"$testDaoName/completeJavaPackageClass.sql",
3637
"$testDaoName/completeDirective.sql",
3738
"$testDaoName/completeBatchInsert.sql",
3839
"$testDaoName/completeStaticPropertyFromStaticPropertyCall.sql",
3940
"$testDaoName/completePropertyAfterStaticPropertyCall.sql",
4041
"$testDaoName/completePropertyAfterStaticPropertyCallWithMethodParameter.sql",
4142
"$testDaoName/completePropertyAfterStaticMethodCall.sql",
43+
"$testDaoName/completeStaticPropertyAfterOtherElement.sql",
4244
"$testDaoName/completeBuiltinFunction.sql",
4345
"$testDaoName/completeDirectiveInsideIf.sql",
4446
"$testDaoName/completeDirectiveFieldInsideIfWithMethodParameter.sql",
@@ -131,6 +133,24 @@ class SqlCompleteTest : DomaSqlTest() {
131133
"isBlank()",
132134
),
133135
)
136+
innerDirectiveCompleteTest(
137+
"$testDaoName/completeTopElementBeforeAtsign.sql",
138+
listOf(
139+
"employee",
140+
"id",
141+
"userIds",
142+
"userId",
143+
"userId_has_next",
144+
"userId_index",
145+
),
146+
listOf(
147+
"doma",
148+
"example",
149+
"projectNumber",
150+
"subProjects",
151+
"valueOf()",
152+
),
153+
)
134154
}
135155

136156
fun testCompleteJavaPackageClass() {
@@ -245,20 +265,36 @@ class SqlCompleteTest : DomaSqlTest() {
245265
"projectNumber",
246266
"projectName",
247267
"projectCategory",
268+
"status",
269+
"manager",
270+
"getFirstEmployee()",
271+
"getTermNumber()",
248272
),
249273
listOf(
250274
"projectDetailId",
251275
"members",
252-
"manager",
253-
"getFirstEmployee()",
254-
"getTermNumber()",
255276
"employeeId",
256277
"projects",
257-
"rank",
258278
"contains()",
259279
"isBlank()",
260280
),
261281
)
282+
innerDirectiveCompleteTest(
283+
"$testDaoName/completeStaticPropertyAfterOtherElement.sql",
284+
listOf(
285+
"projectNumber",
286+
"projectName",
287+
"projectCategory",
288+
"manager",
289+
"subProjects",
290+
"getTermNumber()",
291+
"getCustomNumber()",
292+
),
293+
listOf(
294+
"doma",
295+
"employee",
296+
),
297+
)
262298
}
263299

264300
fun testCompleteCallStaticPropertyClassPackage() {

src/test/testData/src/main/java/doma/example/dao/SqlCompleteTestDao.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@ interface SqlCompleteTestDao {
2222
@Select
2323
Employee completeInstancePropertyWithMethodParameter(Employee employee, String name);
2424

25+
@Select
26+
Employee completeTopElementBeforeAtsign(Employee employee, Integer id, List<Integer> userIds);
27+
2528
@Insert(sqlFile = true)
2629
int completeJavaPackageClass(Employee employee);
2730

@@ -43,6 +46,9 @@ interface SqlCompleteTestDao {
4346
@Select
4447
Project completePropertyAfterStaticMethodCall();
4548

49+
@Select
50+
Employee completeStaticPropertyAfterOtherElement(Employee employee);
51+
4652
@Select
4753
Project completeBuiltinFunction(ProjectDetail detail);
4854

src/test/testData/src/main/resources/META-INF/doma/example/dao/SqlCompleteTestDao/completePropertyAfterStaticPropertyCallWithMethodParameter.sql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,4 @@ from project p
77
inner join project_detail pd
88
on p.project_id = pd.project_id
99
where
10-
-- Code completion for static fields and methods
11-
and pd.manager_id = /* @doma.example.entity.ProjectDetail@getProject(employee.rank).<caret>pro */'TODO'
10+
and pd.manager_id = /* @doma.example.entity.ProjectDetail@getProject(employee.rank).<caret> */'TODO'
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
select
2+
p.project_id
3+
, p.statis
4+
, p.project_name
5+
, p.rank
6+
from project p
7+
inner join project_detail pd
8+
on p.project_id = pd.project_id
9+
where
10+
/*%for userId : userIds */
11+
pd.manager_id = /* id @doma.example.entity.ProjectDetail@<caret> */'TODO'
12+
/*%if userId_has_next */
13+
/*# "OR" */
14+
/*%end */
15+
/*%end */
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
select
2+
p.project_id
3+
, p.statis
4+
, p.project_name
5+
, p.rank
6+
from project p
7+
inner join project_detail pd
8+
on p.project_id = pd.project_id
9+
where
10+
/*%for userId : userIds */
11+
pd.manager_id = /* id <caret> @doma.example.entity.ProjectDetail@manager */'TODO'
12+
/*%if userId_has_next */
13+
/*# "OR" */
14+
/*%end */
15+
/*%end */

0 commit comments

Comments
 (0)