Skip to content

Commit 44cf81d

Browse files
committed
Support arrays too (#105)
1 parent 039075c commit 44cf81d

File tree

16 files changed

+182
-22
lines changed

16 files changed

+182
-22
lines changed

parser/src/main/antlr/com/github/bjansen/pebble/parser/PebbleParser.g4

+2-6
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ expression
6464
| expression filters
6565
| unary_op expression
6666
| parenthesized_expression
67-
| expression list_expression
67+
| expression LBRACKET expression RBRACKET
6868
| expression WITH map_expression
6969
| expression OR expression
7070
| expression AND expression
@@ -79,7 +79,7 @@ expression
7979
| map_expression
8080
| in_expression
8181
| function_call_expression
82-
| qualified_expression
82+
| expression OP_MEMBER (function_call_expression | identifier)
8383
| term
8484
;
8585

@@ -107,10 +107,6 @@ map_element
107107
: string_literal OP_COLON (map_expression | expression)
108108
;
109109

110-
qualified_expression
111-
: (function_call_expression | term | parenthesized_expression) (OP_MEMBER (function_call_expression | identifier))+
112-
;
113-
114110
function_call_expression
115111
: identifier argument_list
116112
;

pebble-intellij-test/pom.xml

+5
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,10 @@
4141
<artifactId>spring-context</artifactId>
4242
<version>${spring.version}</version>
4343
</dependency>
44+
<dependency>
45+
<groupId>org.slf4j</groupId>
46+
<artifactId>slf4j-simple</artifactId>
47+
<version>1.7.25</version>
48+
</dependency>
4449
</dependencies>
4550
</project>
+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{# @pebvariable name="trucs" type="foo.bar.SomeClass[]" #}
2+
"{{ trucs[0].publicField.byteValue() }}"
3+
4+
{% for truc in trucs %}
5+
{{ truc.intMethod() }}
6+
{{ truc.integerMethod().byteValue() }}
7+
{% endfor %}
8+
9+
{{ trucs[0].toString().length() }}
10+
{{ trucs. }}

src/main/kotlin/com/github/bjansen/intellij/pebble/ext/SpringExtension.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ class PebbleSpringReference(private val psi: PsiElement, private val range: Text
172172
} else if (qualifyingMember is PomTargetPsiElement) {
173173
val springBeanType = getBeanType(SpringBeanPomTargetUtils.getSpringBean(qualifyingMember))
174174
if (springBeanType is PsiClassType) {
175-
return buildPsiTypeLookups(springBeanType)
175+
return buildPsiTypeLookups(springBeanType, psi.project)
176176
}
177177
}
178178

src/main/kotlin/com/github/bjansen/intellij/pebble/lang/PebbleCore.kt

+4-3
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,17 @@ import com.intellij.openapi.project.Project
55
import com.intellij.openapi.util.Key
66
import com.intellij.psi.PsiClass
77
import com.intellij.psi.PsiMethod
8+
import com.jetbrains.rd.util.ConcurrentHashMap
89

910
object PebbleCore {
1011

1112
private val filtersKey = Key.create<PsiClass>("PEBBLE_FILTERS_CLASS")
1213
private val functionsKey = Key.create<PsiClass>("PEBBLE_FUNCTIONS_CLASS")
1314
private val testsKey = Key.create<PsiClass>("PEBBLE_TESTS_CLASS")
1415

15-
private val filtersByProject = hashMapOf<Project, Map<String, Filter>>()
16-
private val functionsByProject = hashMapOf<Project, Map<String, Filter>>()
17-
private val testsByProject = hashMapOf<Project, Map<String, Test>>()
16+
private val filtersByProject = ConcurrentHashMap<Project, Map<String, Filter>>()
17+
private val functionsByProject = ConcurrentHashMap<Project, Map<String, Filter>>()
18+
private val testsByProject = ConcurrentHashMap<Project, Map<String, Test>>()
1819

1920
fun getFilters(project: Project): Collection<Filter> {
2021
return filtersByProject.computeIfAbsent(project, this::initFilters).values

src/main/kotlin/com/github/bjansen/intellij/pebble/psi/ExpressionTypeVisitor.kt

+7-4
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,16 @@
11
package com.github.bjansen.intellij.pebble.psi
22

3-
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.rules
43
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.tokens
54
import com.github.bjansen.intellij.pebble.psi.PebbleReferencesHelper.findMembersByName
65
import com.github.bjansen.pebble.parser.PebbleLexer
7-
import com.github.bjansen.pebble.parser.PebbleParser
86
import com.intellij.psi.*
7+
import com.intellij.psi.util.PsiTreeUtil
98

109
class ExpressionTypeVisitor : PsiRecursiveElementVisitor(false) {
1110
var type: PsiType? = null
1211

1312
override fun visitElement(element: PsiElement) {
14-
if (element.node.elementType == rules[PebbleParser.RULE_qualified_expression]) {
13+
if (isQualifiedExpression(element)) {
1514
type = typeOfQualifiedExpression(element)
1615
}
1716

@@ -23,6 +22,10 @@ class ExpressionTypeVisitor : PsiRecursiveElementVisitor(false) {
2322
super.visitElement(element)
2423
}
2524

25+
private fun isQualifiedExpression(element: PsiElement): Boolean {
26+
return element.children.any { it.node.elementType == tokens[PebbleLexer.OP_MEMBER] }
27+
}
28+
2629
private fun typeOfIdentifier(element: PebbleIdentifier): PsiType? {
2730
return typeOf(element.reference?.resolve())
2831
}
@@ -35,7 +38,7 @@ class ExpressionTypeVisitor : PsiRecursiveElementVisitor(false) {
3538
qualifierType = if (child is PebbleIdentifier) {
3639
typeOfIdentifier(child)
3740
} else {
38-
typeOfIdentifier(child.firstChild as PebbleIdentifier)
41+
typeOfIdentifier(PsiTreeUtil.findChildOfType(child, PebbleIdentifier::class.java)!!)
3942
}
4043
} else if (child.node.elementType == tokens[PebbleLexer.OP_MEMBER]) {
4144
continue

src/main/kotlin/com/github/bjansen/intellij/pebble/psi/PebbleIdentifierReference.kt

+9-5
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ class PebbleIdentifierReference(private val psi: PsiElement, private val range:
3232
return createResults(findMembersByName(getPsiClassFromType(qualifyingMember.type), referenceText))
3333
} else if (qualifyingMember is PsiMethod) {
3434
return createResults(findMembersByName(getPsiClassFromType(qualifyingMember.returnType), referenceText))
35+
} else if (qualifyingMember is PebbleArrayAccess) {
36+
return createResults(findMembersByName(getPsiClassFromType(qualifyingMember.getType()), referenceText))
3537
} else {
3638
val parentTag = PsiTreeUtil.getParentOfType(psi, PebbleTagDirective::class.java)
3739

@@ -104,15 +106,17 @@ class PebbleIdentifierReference(private val psi: PsiElement, private val range:
104106
val qualifyingMember = findQualifyingMember(psi)
105107

106108
if (qualifyingMember is PebbleInVariable) {
107-
return buildPsiTypeLookups(qualifyingMember.getType())
109+
return buildPsiTypeLookups(qualifyingMember.getType(), psi.project)
108110
} else if (qualifyingMember is PebbleLiteral) {
109-
return buildPsiTypeLookups(qualifyingMember.getType())
111+
return buildPsiTypeLookups(qualifyingMember.getType(), psi.project)
110112
} else if (qualifyingMember is PsiField) {
111-
return buildPsiTypeLookups(qualifyingMember.type)
113+
return buildPsiTypeLookups(qualifyingMember.type, psi.project)
112114
} else if (qualifyingMember is PsiVariable) {
113-
return buildPsiTypeLookups(qualifyingMember.type)
115+
return buildPsiTypeLookups(qualifyingMember.type, psi.project)
114116
} else if (qualifyingMember is PsiMethod) {
115-
return buildPsiTypeLookups(qualifyingMember.returnType)
117+
return buildPsiTypeLookups(qualifyingMember.returnType, psi.project)
118+
} else if (qualifyingMember is PebbleArrayAccess) {
119+
return buildPsiTypeLookups(qualifyingMember.getType(), psi.project)
116120
} else if (qualifyingMember == null) {
117121
val file = psi.containingFile
118122
if (file is PebbleFile) {

src/main/kotlin/com/github/bjansen/intellij/pebble/psi/PebbleReferencesHelper.kt

+20-3
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,10 @@ import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler
77
import com.intellij.codeInsight.lookup.LookupElement
88
import com.intellij.codeInsight.lookup.LookupElementBuilder
99
import com.intellij.icons.AllIcons
10+
import com.intellij.openapi.project.Project
1011
import com.intellij.openapi.util.text.StringUtil
1112
import com.intellij.psi.*
13+
import com.intellij.psi.search.GlobalSearchScope
1214
import com.intellij.psi.search.searches.SuperMethodsSearch
1315
import com.intellij.psi.util.PropertyUtil
1416
import com.intellij.psi.util.PsiTreeUtil
@@ -17,7 +19,7 @@ object PebbleReferencesHelper {
1719
private fun isOverride(method: PsiMethod)
1820
= SuperMethodsSearch.search(method, null, true, false).findFirst() != null
1921

20-
fun buildPsiTypeLookups(type: PsiType?): Array<Any> {
22+
fun buildPsiTypeLookups(type: PsiType?, project: Project): Array<Any> {
2123
if (type is PsiClassType) {
2224
val clazz = type.resolve() ?: return emptyArray()
2325
val resolveResult = type.resolveGenerics()
@@ -52,6 +54,20 @@ object PebbleReferencesHelper {
5254
)
5355

5456
return lookups.toTypedArray()
57+
} else if (type is PsiArrayType) {
58+
val objectType = PsiType.getJavaLangObject(
59+
PsiManager.getInstance(project),
60+
GlobalSearchScope.allScope(project)
61+
)
62+
63+
val objectLookups = arrayListOf(*buildPsiTypeLookups(objectType, project))
64+
objectLookups.add(
65+
LookupElementBuilder.create("length")
66+
.withTypeText("int")
67+
.withIcon(AllIcons.Nodes.Property)
68+
)
69+
70+
return objectLookups.toTypedArray()
5571
}
5672

5773
return emptyArray()
@@ -65,7 +81,7 @@ object PebbleReferencesHelper {
6581
for (prefix in listOf("get", "is", "has")) {
6682
for (method in clazz.findMethodsByName(prefix + capitalizedName, true)) {
6783
if (method.parameterList.parametersCount == 0) {
68-
return listOf(method);
84+
return listOf(method)
6985
}
7086
}
7187
}
@@ -108,12 +124,13 @@ object PebbleReferencesHelper {
108124
val prevLeaf = PsiTreeUtil.prevVisibleLeaf(psi)
109125

110126
if (prevLeaf != null && prevLeaf.node.elementType == PebbleParserDefinition.tokens[PebbleLexer.OP_MEMBER]) {
111-
val qualifier = prevLeaf.prevSibling
127+
val qualifier = prevLeaf.prevSibling?.lastChild
112128

113129
if (qualifier != null) {
114130
val identifier = when (qualifier.node.elementType) {
115131
PebbleParserDefinition.rules[PebbleParser.RULE_function_call_expression] -> qualifier.firstChild
116132
PebbleParserDefinition.rules[PebbleParser.RULE_term] -> qualifier.firstChild
133+
PebbleParserDefinition.rules[PebbleParser.RULE_parenthesized_expression] -> qualifier.firstChild
117134
else -> qualifier
118135
}
119136

src/main/kotlin/com/github/bjansen/intellij/pebble/psi/PsiElementFactory.kt

+11
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ package com.github.bjansen.intellij.pebble.psi
22

33
import com.github.bjansen.intellij.pebble.lang.PebbleLanguage
44
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.rules
5+
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.tokens
6+
import com.github.bjansen.pebble.parser.PebbleLexer
57
import com.github.bjansen.pebble.parser.PebbleParser
68
import com.intellij.lang.ASTNode
79
import com.intellij.openapi.project.Project
@@ -61,9 +63,18 @@ object PsiElementFactory {
6163
elType == rules[PebbleParser.RULE_numeric_literal]
6264
) {
6365
return PebbleLiteral(node)
66+
} else if (elType == rules[PebbleParser.RULE_expression] && isArrayAccess(node)) {
67+
return PebbleArrayAccess(node)
6468
}
6569

6670
return PebblePsiElement(node)
6771
}
72+
73+
private fun isArrayAccess(node: ASTNode): Boolean {
74+
val children = node.getChildren(null)
75+
return children.size == 4
76+
&& children[1].elementType == tokens[PebbleLexer.LBRACKET]
77+
&& children[3].elementType == tokens[PebbleLexer.RBRACKET]
78+
}
6879
}
6980

src/main/kotlin/com/github/bjansen/intellij/pebble/psi/elements.kt

+42
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,10 @@ class PebbleInVariable(node: ASTNode) : PebblePsiElement(node), PsiNamedElement
159159
&& it is PsiClassType && it.parameters.isNotEmpty()) {
160160
iteratedType = it.parameters[0]
161161
}
162+
if (it is PsiArrayType) {
163+
iteratedType = it.componentType
164+
return@processSuperTypes false
165+
}
162166

163167
true
164168
}
@@ -196,3 +200,41 @@ class PebbleLiteral(node: ASTNode) : PebblePsiElement(node) {
196200
return PsiType.getTypeByName(typeName, project, scope)
197201
}
198202
}
203+
204+
class PebbleArrayAccess(node: ASTNode) : PebblePsiElement(node) {
205+
206+
fun getType(): PsiType? {
207+
val expr = node.treeParent.findChildByType(rules[PebbleParser.RULE_expression], node)
208+
209+
return if (expr != null) inferVariableType(expr.psi) else null
210+
}
211+
212+
private fun inferVariableType(iterableExpression: PsiElement): PsiType {
213+
val visitor = ExpressionTypeVisitor()
214+
iterableExpression.accept(visitor)
215+
216+
val type = visitor.type
217+
218+
if (type != null) {
219+
var iteratedType: PsiType? = null
220+
221+
InheritanceUtil.processSuperTypes(type, true) {
222+
if (it is PsiArrayType) {
223+
iteratedType = it.componentType
224+
return@processSuperTypes false
225+
}
226+
227+
true
228+
}
229+
230+
if (iteratedType != null) {
231+
return iteratedType as PsiType
232+
}
233+
}
234+
235+
return PsiType.getJavaLangObject(
236+
PsiManager.getInstance(containingFile.project),
237+
GlobalSearchScope.allScope(containingFile.project)
238+
)
239+
}
240+
}

src/test/kotlin/com/github/bjansen/intellij/pebble/editor/completion/PebbleIdentifierCompletionTest.kt

+24
Original file line numberDiff line numberDiff line change
@@ -257,4 +257,28 @@ class PebbleIdentifierCompletionTest : AbstractCompletionTest() {
257257

258258
assertNoLookups()
259259
}
260+
261+
fun testCompletionOfArrayElement() {
262+
myFixture.configureByFile("array.peb")
263+
myFixture.addClass(File("$testDataPath/Double.java").readText(Charsets.UTF_8))
264+
myFixture.complete(CompletionType.BASIC)
265+
266+
assertLookupsContain(listOf("intValue"))
267+
}
268+
269+
fun testCompletionOfArrayElement2() {
270+
myFixture.configureByFile("array2.peb")
271+
myFixture.addClass(File("$testDataPath/Double.java").readText(Charsets.UTF_8))
272+
myFixture.complete(CompletionType.BASIC)
273+
274+
assertLookupsContain(listOf("intValue"))
275+
}
276+
277+
fun testCompletionOfArray() {
278+
myFixture.configureByFile("array3.peb")
279+
myFixture.addClass(File("$testDataPath/Double.java").readText(Charsets.UTF_8))
280+
myFixture.complete(CompletionType.BASIC)
281+
282+
assertLookupsContain(listOf("length"))
283+
}
260284
}

src/test/kotlin/com/github/bjansen/intellij/pebble/psi/IdentifierReferencesTest.kt

+27
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import com.intellij.psi.PsiComment
44
import com.intellij.psi.PsiElement
55
import com.intellij.psi.PsiField
66
import com.intellij.psi.PsiMethod
7+
import com.intellij.psi.PsiNamedElement
78
import java.io.File
89

910
class IdentifierReferencesTest : AbstractReferencesTest() {
@@ -327,4 +328,30 @@ class IdentifierReferencesTest : AbstractReferencesTest() {
327328
fail("Reference resolved to nothing")
328329
}
329330
}
331+
332+
fun testArrayComponents() {
333+
initFile("arrays.peb")
334+
myFixture.addClass(File("src/test/resources/completion/identifiers/MyClass.java").readText(Charsets.UTF_8))
335+
336+
// `foo in foos`
337+
assertElementAtResolvesTo<PsiMethod>(100, "getProperty")
338+
assertElementAtResolvesTo<PsiMethod>(125, "getChild")
339+
340+
// `foos[0]`
341+
assertElementAtResolvesTo<PsiMethod>(160, "getProperty")
342+
assertElementAtResolvesTo<PsiMethod>(190, "getChild")
343+
}
344+
345+
private inline fun <reified T : PsiNamedElement> assertElementAtResolvesTo(offset: Int, expectedName: String) {
346+
moveCaret(offset)
347+
348+
val resolved = resolveRefAtCaret()
349+
350+
if (resolved != null) {
351+
assert(resolved is T)
352+
assert((resolved as T).name == expectedName)
353+
} else {
354+
fail("Reference resolved to nothing")
355+
}
356+
}
330357
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{# @pebvariable name="stuff" type="java.lang.Double[]" #}
2+
3+
{% for thing in stuff %}
4+
{{ thing.<caret> }}
5+
{% endfor %}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{# @pebvariable name="stuff" type="java.lang.Double[]" #}
2+
3+
{{ stuff[0].<caret> }}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{# @pebvariable name="stuff" type="java.lang.Double[]" #}
2+
3+
{{ stuff.<caret> }}
+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{# @pebvariable name="foos" type="pebble.tests.MyClass[]" #}
2+
3+
{% for foo in foos %}
4+
{{ foo.property }}
5+
{{ foo.child.child }}
6+
{% endfor %}
7+
8+
{{ foos[0].property }}
9+
{{ foos[0].child.child }}

0 commit comments

Comments
 (0)