Skip to content

Commit 0356975

Browse files
committed
Add A reference to the SQL symbol type definer
1 parent b176773 commit 0356975

File tree

9 files changed

+386
-3
lines changed

9 files changed

+386
-3
lines changed

src/main/java/org/domaframework/doma/intellij/Sql.bnf

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@
6868
extends("el_invocation_expr_group|el_primary_expr")=el_factor_expr
6969
extends("el_logical_expr_group|el_factor_expr")=el_term_expr
7070
extends(".*expr")=el_expr
71+
mixin("el_primary_expr|el_class")="org.domaframework.doma.intellij.psi.SqlCustomExprImpl"
7172
consumeTokenMethod("literal|word|.*directive|.*expr")="consumeTokenFast"
7273
}
7374

@@ -166,5 +167,8 @@ el_parameters ::= "(" (el_expr ("," el_expr)*)? ")" {pin=1}
166167
// primary
167168
el_primary_expr ::= el_literal_expr | el_id_expr | el_paren_expr
168169
private el_literal_expr ::= EL_NULL | BOOLEAN | (EL_PLUS | EL_MINUS)? EL_NUMBER | EL_STRING | EL_CHAR
169-
private el_id_expr ::= EL_IDENTIFIER
170+
el_id_expr ::= EL_IDENTIFIER
171+
{
172+
mixin="org.domaframework.doma.intellij.psi.SqlElPrimaryExprImpl"
173+
}
170174
private el_paren_expr ::= "(" el_expr ")" {pin=1}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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.psi;
17+
18+
import com.intellij.psi.PsiElement;
19+
20+
public interface SqlCustomElExpr extends PsiElement {}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
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.psi;
17+
18+
import com.intellij.extapi.psi.ASTWrapperPsiElement;
19+
import com.intellij.lang.ASTNode;
20+
import com.intellij.psi.PsiReference;
21+
import com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry;
22+
import org.jetbrains.annotations.NotNull;
23+
24+
public class SqlCustomExprImpl extends ASTWrapperPsiElement implements SqlCustomElExpr {
25+
26+
public SqlCustomExprImpl(@NotNull ASTNode node) {
27+
super(node);
28+
}
29+
30+
@Override
31+
public PsiReference @NotNull [] getReferences() {
32+
return ReferenceProvidersRegistry.getReferencesFromProviders(this);
33+
}
34+
}

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import com.intellij.psi.PsiComment
2323
import com.intellij.psi.PsiElement
2424
import org.domaframework.doma.intellij.common.psi.PsiPatternUtil
2525
import org.domaframework.doma.intellij.contributor.sql.provider.SqlParameterCompletionProvider
26+
import org.domaframework.doma.intellij.setting.SqlLanguage
2627
import org.jetbrains.kotlin.idea.completion.or
2728

2829
/**
@@ -41,7 +42,7 @@ open class SqlCompletionContributor : CompletionContributor() {
4142
.inFile(
4243
PlatformPatterns
4344
.psiFile()
44-
.withName(StandardPatterns.string().endsWith(".sql")),
45+
.withLanguage(SqlLanguage.INSTANCE),
4546
),
4647
PsiPatternUtil
4748
.createPattern(PsiComment::class.java)
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
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.reference
17+
18+
import com.intellij.openapi.util.TextRange
19+
import com.intellij.psi.AbstractElementManipulator
20+
import org.domaframework.doma.intellij.psi.SqlCustomElExpr
21+
22+
class SqlElExprManipulator : AbstractElementManipulator<SqlCustomElExpr>() {
23+
override fun handleContentChange(
24+
element: SqlCustomElExpr,
25+
textRange: TextRange,
26+
newName: String?,
27+
): SqlCustomElExpr? = element
28+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
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.reference
17+
18+
import com.intellij.psi.PsiElement
19+
import com.intellij.psi.PsiReference
20+
import com.intellij.psi.PsiReferenceProvider
21+
import com.intellij.util.ProcessingContext
22+
import org.domaframework.doma.intellij.psi.SqlCustomElExpr
23+
24+
class SqlPsiReferenceProvider : PsiReferenceProvider() {
25+
override fun getReferencesByElement(
26+
element: PsiElement,
27+
context: ProcessingContext,
28+
): Array<out PsiReference?> {
29+
if (element !is SqlCustomElExpr) return PsiReference.EMPTY_ARRAY
30+
return arrayOf(SqlReference(element))
31+
}
32+
}
Lines changed: 225 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,225 @@
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.reference
17+
18+
import com.intellij.psi.PsiElement
19+
import com.intellij.psi.PsiFile
20+
import com.intellij.psi.PsiMethod
21+
import com.intellij.psi.PsiReferenceBase
22+
import com.intellij.psi.PsiType
23+
import com.intellij.psi.util.PsiTreeUtil
24+
import com.intellij.psi.util.PsiTypesUtil
25+
import com.intellij.psi.util.elementType
26+
import org.domaframework.doma.intellij.common.PluginLoggerUtil
27+
import org.domaframework.doma.intellij.common.dao.findDaoMethod
28+
import org.domaframework.doma.intellij.common.isSupportFileType
29+
import org.domaframework.doma.intellij.common.psi.PsiParentClass
30+
import org.domaframework.doma.intellij.common.psi.PsiStaticElement
31+
import org.domaframework.doma.intellij.extension.psi.findParameter
32+
import org.domaframework.doma.intellij.extension.psi.getDomaAnnotationType
33+
import org.domaframework.doma.intellij.extension.psi.getIterableClazz
34+
import org.domaframework.doma.intellij.extension.psi.methodParameters
35+
import org.domaframework.doma.intellij.psi.SqlElClass
36+
import org.domaframework.doma.intellij.psi.SqlElPrimaryExpr
37+
import org.domaframework.doma.intellij.psi.SqlElStaticFieldAccessExpr
38+
import org.domaframework.doma.intellij.psi.SqlTypes
39+
40+
class SqlReference(
41+
element: PsiElement,
42+
) : PsiReferenceBase<PsiElement>(element) {
43+
val file: PsiFile? = element.containingFile
44+
45+
override fun resolve(): PsiElement? {
46+
if (file == null || !isSupportFileType(file)) return null
47+
val variableName = element.text
48+
49+
val startTime = System.nanoTime()
50+
val staticDirective = getStaticDirective(element, variableName)
51+
if (staticDirective != null) {
52+
PluginLoggerUtil.countLogging(
53+
this::class.java.simpleName,
54+
"ReferenceToStatic",
55+
"Reference",
56+
startTime,
57+
)
58+
return staticDirective
59+
}
60+
61+
val targetElement = getBlockCommentElements(element)
62+
if (targetElement.isEmpty()) return null
63+
64+
val daoMethod = findDaoMethod(file) ?: return null
65+
66+
return when (element.textOffset) {
67+
targetElement.first().textOffset ->
68+
jumpToDaoMethodParameter(
69+
daoMethod,
70+
element,
71+
startTime,
72+
)
73+
74+
else -> jumpToEntity(daoMethod, targetElement, startTime)
75+
}
76+
return null
77+
}
78+
79+
override fun getVariants(): Array<Any> = emptyArray()
80+
81+
private fun getStaticDirective(
82+
staticDirection: PsiElement?,
83+
elementName: String,
84+
): PsiElement? {
85+
if (staticDirection == null) return null
86+
val file: PsiFile = file ?: return null
87+
// Jump to class definition
88+
if (staticDirection is SqlElClass ||
89+
staticDirection.parent is SqlElClass
90+
) {
91+
val psiStaticElement = PsiStaticElement(staticDirection.text, file)
92+
return psiStaticElement.getRefClazz()
93+
}
94+
95+
// Jump from field or method to definition (assuming the top element is static)
96+
val staticAccessParent = staticDirection.parent
97+
if (staticDirection is SqlElStaticFieldAccessExpr ||
98+
staticAccessParent is SqlElStaticFieldAccessExpr
99+
) {
100+
val firstChildText =
101+
staticAccessParent.children
102+
.firstOrNull()
103+
?.text ?: ""
104+
val psiStaticElement =
105+
PsiStaticElement(
106+
firstChildText,
107+
file,
108+
)
109+
val javaClazz = psiStaticElement.getRefClazz() ?: return null
110+
val psiParentClass = PsiParentClass(PsiTypesUtil.getClassType(javaClazz))
111+
psiParentClass.findField(elementName)?.let {
112+
return it
113+
}
114+
psiParentClass.findMethod(elementName)?.let {
115+
return it
116+
}
117+
}
118+
return null
119+
}
120+
121+
private fun getBlockCommentElements(element: PsiElement): List<PsiElement> {
122+
val nodeElm =
123+
PsiTreeUtil
124+
.getChildrenOfType(element.parent, PsiElement::class.java)
125+
?.filter {
126+
(
127+
it.elementType == SqlTypes.EL_IDENTIFIER ||
128+
it is SqlElPrimaryExpr
129+
) &&
130+
it.textOffset <= element.textOffset
131+
}?.toList()
132+
?.sortedBy { it.textOffset } ?: emptyList()
133+
return nodeElm
134+
}
135+
136+
private fun jumpToDaoMethodParameter(
137+
daoMethod: PsiMethod,
138+
it: PsiElement,
139+
startTime: Long,
140+
): PsiElement? {
141+
daoMethod
142+
.let { method ->
143+
method.methodParameters.firstOrNull { param ->
144+
param.name == it.text
145+
}
146+
}?.originalElement
147+
?.let { originalElm ->
148+
PluginLoggerUtil.countLogging(
149+
this::class.java.simpleName,
150+
"ReferenceToDaoMethodParameter",
151+
"Reference",
152+
startTime,
153+
)
154+
return originalElm
155+
} ?: return null
156+
}
157+
158+
private fun jumpToEntity(
159+
daoMethod: PsiMethod,
160+
targetElement: List<PsiElement>,
161+
startTime: Long,
162+
): PsiElement? {
163+
val topParam = daoMethod.findParameter(targetElement.first().text) ?: return null
164+
val parentClass = topParam.getIterableClazz(daoMethod.getDomaAnnotationType())
165+
val bindEntity =
166+
getBindProperty(
167+
targetElement.toList(),
168+
parentClass,
169+
)
170+
171+
PluginLoggerUtil.countLogging(
172+
this::class.java.simpleName,
173+
"ReferenceToBindVariable",
174+
"Reference",
175+
startTime,
176+
)
177+
return bindEntity
178+
}
179+
180+
private fun getBindProperty(
181+
elementBlock: List<PsiElement>,
182+
topElementClass: PsiParentClass,
183+
): PsiElement? {
184+
// If the argument is List, get the element type
185+
var parentClass = topElementClass
186+
val accessElms = elementBlock.drop(1)
187+
var isExistProperty: Boolean
188+
189+
fun getBindVariableIfLastIndex(
190+
index: Int,
191+
type: PsiType,
192+
originalElement: PsiElement,
193+
): PsiElement? {
194+
isExistProperty = true
195+
if (index >= accessElms.size - 1) {
196+
return originalElement
197+
}
198+
parentClass = PsiParentClass(type)
199+
return null
200+
}
201+
202+
for (index in accessElms.indices) {
203+
isExistProperty = false
204+
val elm = accessElms[index]
205+
206+
parentClass
207+
.findField(elm.text)
208+
?.let {
209+
val bindVal = getBindVariableIfLastIndex(index, it.type, it.originalElement)
210+
if (bindVal != null) return bindVal
211+
}
212+
if (isExistProperty) continue
213+
parentClass
214+
.findMethod(elm.text)
215+
?.let {
216+
val returnType = it.returnType ?: return null
217+
val bindVal =
218+
getBindVariableIfLastIndex(index, returnType, it.originalElement)
219+
if (bindVal != null) return bindVal
220+
}
221+
if (!isExistProperty) return null
222+
}
223+
return null
224+
}
225+
}

0 commit comments

Comments
 (0)