Skip to content

Commit

Permalink
Support arrays too (#105)
Browse files Browse the repository at this point in the history
  • Loading branch information
bjansen committed Mar 9, 2024
1 parent 039075c commit 44cf81d
Show file tree
Hide file tree
Showing 16 changed files with 182 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ expression
| expression filters
| unary_op expression
| parenthesized_expression
| expression list_expression
| expression LBRACKET expression RBRACKET
| expression WITH map_expression
| expression OR expression
| expression AND expression
Expand All @@ -79,7 +79,7 @@ expression
| map_expression
| in_expression
| function_call_expression
| qualified_expression
| expression OP_MEMBER (function_call_expression | identifier)
| term
;

Expand Down Expand Up @@ -107,10 +107,6 @@ map_element
: string_literal OP_COLON (map_expression | expression)
;

qualified_expression
: (function_call_expression | term | parenthesized_expression) (OP_MEMBER (function_call_expression | identifier))+
;

function_call_expression
: identifier argument_list
;
Expand Down
5 changes: 5 additions & 0 deletions pebble-intellij-test/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -41,5 +41,10 @@
<artifactId>spring-context</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.25</version>
</dependency>
</dependencies>
</project>
10 changes: 10 additions & 0 deletions pebble-intellij-test/src/arrays.pebble
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{# @pebvariable name="trucs" type="foo.bar.SomeClass[]" #}
"{{ trucs[0].publicField.byteValue() }}"

{% for truc in trucs %}
{{ truc.intMethod() }}
{{ truc.integerMethod().byteValue() }}
{% endfor %}

{{ trucs[0].toString().length() }}
{{ trucs. }}
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ class PebbleSpringReference(private val psi: PsiElement, private val range: Text
} else if (qualifyingMember is PomTargetPsiElement) {
val springBeanType = getBeanType(SpringBeanPomTargetUtils.getSpringBean(qualifyingMember))
if (springBeanType is PsiClassType) {
return buildPsiTypeLookups(springBeanType)
return buildPsiTypeLookups(springBeanType, psi.project)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,17 @@ import com.intellij.openapi.project.Project
import com.intellij.openapi.util.Key
import com.intellij.psi.PsiClass
import com.intellij.psi.PsiMethod
import com.jetbrains.rd.util.ConcurrentHashMap

object PebbleCore {

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

private val filtersByProject = hashMapOf<Project, Map<String, Filter>>()
private val functionsByProject = hashMapOf<Project, Map<String, Filter>>()
private val testsByProject = hashMapOf<Project, Map<String, Test>>()
private val filtersByProject = ConcurrentHashMap<Project, Map<String, Filter>>()
private val functionsByProject = ConcurrentHashMap<Project, Map<String, Filter>>()
private val testsByProject = ConcurrentHashMap<Project, Map<String, Test>>()

fun getFilters(project: Project): Collection<Filter> {
return filtersByProject.computeIfAbsent(project, this::initFilters).values
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,16 @@
package com.github.bjansen.intellij.pebble.psi

import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.rules
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.tokens
import com.github.bjansen.intellij.pebble.psi.PebbleReferencesHelper.findMembersByName
import com.github.bjansen.pebble.parser.PebbleLexer
import com.github.bjansen.pebble.parser.PebbleParser
import com.intellij.psi.*
import com.intellij.psi.util.PsiTreeUtil

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

override fun visitElement(element: PsiElement) {
if (element.node.elementType == rules[PebbleParser.RULE_qualified_expression]) {
if (isQualifiedExpression(element)) {
type = typeOfQualifiedExpression(element)
}

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

private fun isQualifiedExpression(element: PsiElement): Boolean {
return element.children.any { it.node.elementType == tokens[PebbleLexer.OP_MEMBER] }
}

private fun typeOfIdentifier(element: PebbleIdentifier): PsiType? {
return typeOf(element.reference?.resolve())
}
Expand All @@ -35,7 +38,7 @@ class ExpressionTypeVisitor : PsiRecursiveElementVisitor(false) {
qualifierType = if (child is PebbleIdentifier) {
typeOfIdentifier(child)
} else {
typeOfIdentifier(child.firstChild as PebbleIdentifier)
typeOfIdentifier(PsiTreeUtil.findChildOfType(child, PebbleIdentifier::class.java)!!)
}
} else if (child.node.elementType == tokens[PebbleLexer.OP_MEMBER]) {
continue
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ class PebbleIdentifierReference(private val psi: PsiElement, private val range:
return createResults(findMembersByName(getPsiClassFromType(qualifyingMember.type), referenceText))
} else if (qualifyingMember is PsiMethod) {
return createResults(findMembersByName(getPsiClassFromType(qualifyingMember.returnType), referenceText))
} else if (qualifyingMember is PebbleArrayAccess) {
return createResults(findMembersByName(getPsiClassFromType(qualifyingMember.getType()), referenceText))
} else {
val parentTag = PsiTreeUtil.getParentOfType(psi, PebbleTagDirective::class.java)

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

if (qualifyingMember is PebbleInVariable) {
return buildPsiTypeLookups(qualifyingMember.getType())
return buildPsiTypeLookups(qualifyingMember.getType(), psi.project)
} else if (qualifyingMember is PebbleLiteral) {
return buildPsiTypeLookups(qualifyingMember.getType())
return buildPsiTypeLookups(qualifyingMember.getType(), psi.project)
} else if (qualifyingMember is PsiField) {
return buildPsiTypeLookups(qualifyingMember.type)
return buildPsiTypeLookups(qualifyingMember.type, psi.project)
} else if (qualifyingMember is PsiVariable) {
return buildPsiTypeLookups(qualifyingMember.type)
return buildPsiTypeLookups(qualifyingMember.type, psi.project)
} else if (qualifyingMember is PsiMethod) {
return buildPsiTypeLookups(qualifyingMember.returnType)
return buildPsiTypeLookups(qualifyingMember.returnType, psi.project)
} else if (qualifyingMember is PebbleArrayAccess) {
return buildPsiTypeLookups(qualifyingMember.getType(), psi.project)
} else if (qualifyingMember == null) {
val file = psi.containingFile
if (file is PebbleFile) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@ import com.intellij.codeInsight.completion.util.ParenthesesInsertHandler
import com.intellij.codeInsight.lookup.LookupElement
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.icons.AllIcons
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.text.StringUtil
import com.intellij.psi.*
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.search.searches.SuperMethodsSearch
import com.intellij.psi.util.PropertyUtil
import com.intellij.psi.util.PsiTreeUtil
Expand All @@ -17,7 +19,7 @@ object PebbleReferencesHelper {
private fun isOverride(method: PsiMethod)
= SuperMethodsSearch.search(method, null, true, false).findFirst() != null

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

return lookups.toTypedArray()
} else if (type is PsiArrayType) {
val objectType = PsiType.getJavaLangObject(
PsiManager.getInstance(project),
GlobalSearchScope.allScope(project)
)

val objectLookups = arrayListOf(*buildPsiTypeLookups(objectType, project))
objectLookups.add(
LookupElementBuilder.create("length")
.withTypeText("int")
.withIcon(AllIcons.Nodes.Property)
)

return objectLookups.toTypedArray()
}

return emptyArray()
Expand All @@ -65,7 +81,7 @@ object PebbleReferencesHelper {
for (prefix in listOf("get", "is", "has")) {
for (method in clazz.findMethodsByName(prefix + capitalizedName, true)) {
if (method.parameterList.parametersCount == 0) {
return listOf(method);
return listOf(method)
}
}
}
Expand Down Expand Up @@ -108,12 +124,13 @@ object PebbleReferencesHelper {
val prevLeaf = PsiTreeUtil.prevVisibleLeaf(psi)

if (prevLeaf != null && prevLeaf.node.elementType == PebbleParserDefinition.tokens[PebbleLexer.OP_MEMBER]) {
val qualifier = prevLeaf.prevSibling
val qualifier = prevLeaf.prevSibling?.lastChild

if (qualifier != null) {
val identifier = when (qualifier.node.elementType) {
PebbleParserDefinition.rules[PebbleParser.RULE_function_call_expression] -> qualifier.firstChild
PebbleParserDefinition.rules[PebbleParser.RULE_term] -> qualifier.firstChild
PebbleParserDefinition.rules[PebbleParser.RULE_parenthesized_expression] -> qualifier.firstChild
else -> qualifier
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.github.bjansen.intellij.pebble.psi

import com.github.bjansen.intellij.pebble.lang.PebbleLanguage
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.rules
import com.github.bjansen.intellij.pebble.psi.PebbleParserDefinition.Companion.tokens
import com.github.bjansen.pebble.parser.PebbleLexer
import com.github.bjansen.pebble.parser.PebbleParser
import com.intellij.lang.ASTNode
import com.intellij.openapi.project.Project
Expand Down Expand Up @@ -61,9 +63,18 @@ object PsiElementFactory {
elType == rules[PebbleParser.RULE_numeric_literal]
) {
return PebbleLiteral(node)
} else if (elType == rules[PebbleParser.RULE_expression] && isArrayAccess(node)) {
return PebbleArrayAccess(node)
}

return PebblePsiElement(node)
}

private fun isArrayAccess(node: ASTNode): Boolean {
val children = node.getChildren(null)
return children.size == 4
&& children[1].elementType == tokens[PebbleLexer.LBRACKET]
&& children[3].elementType == tokens[PebbleLexer.RBRACKET]
}
}

42 changes: 42 additions & 0 deletions src/main/kotlin/com/github/bjansen/intellij/pebble/psi/elements.kt
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,10 @@ class PebbleInVariable(node: ASTNode) : PebblePsiElement(node), PsiNamedElement
&& it is PsiClassType && it.parameters.isNotEmpty()) {
iteratedType = it.parameters[0]
}
if (it is PsiArrayType) {
iteratedType = it.componentType
return@processSuperTypes false
}

true
}
Expand Down Expand Up @@ -196,3 +200,41 @@ class PebbleLiteral(node: ASTNode) : PebblePsiElement(node) {
return PsiType.getTypeByName(typeName, project, scope)
}
}

class PebbleArrayAccess(node: ASTNode) : PebblePsiElement(node) {

fun getType(): PsiType? {
val expr = node.treeParent.findChildByType(rules[PebbleParser.RULE_expression], node)

return if (expr != null) inferVariableType(expr.psi) else null
}

private fun inferVariableType(iterableExpression: PsiElement): PsiType {
val visitor = ExpressionTypeVisitor()
iterableExpression.accept(visitor)

val type = visitor.type

if (type != null) {
var iteratedType: PsiType? = null

InheritanceUtil.processSuperTypes(type, true) {
if (it is PsiArrayType) {
iteratedType = it.componentType
return@processSuperTypes false
}

true
}

if (iteratedType != null) {
return iteratedType as PsiType
}
}

return PsiType.getJavaLangObject(
PsiManager.getInstance(containingFile.project),
GlobalSearchScope.allScope(containingFile.project)
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -257,4 +257,28 @@ class PebbleIdentifierCompletionTest : AbstractCompletionTest() {

assertNoLookups()
}

fun testCompletionOfArrayElement() {
myFixture.configureByFile("array.peb")
myFixture.addClass(File("$testDataPath/Double.java").readText(Charsets.UTF_8))
myFixture.complete(CompletionType.BASIC)

assertLookupsContain(listOf("intValue"))
}

fun testCompletionOfArrayElement2() {
myFixture.configureByFile("array2.peb")
myFixture.addClass(File("$testDataPath/Double.java").readText(Charsets.UTF_8))
myFixture.complete(CompletionType.BASIC)

assertLookupsContain(listOf("intValue"))
}

fun testCompletionOfArray() {
myFixture.configureByFile("array3.peb")
myFixture.addClass(File("$testDataPath/Double.java").readText(Charsets.UTF_8))
myFixture.complete(CompletionType.BASIC)

assertLookupsContain(listOf("length"))
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.psi.PsiComment
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiField
import com.intellij.psi.PsiMethod
import com.intellij.psi.PsiNamedElement
import java.io.File

class IdentifierReferencesTest : AbstractReferencesTest() {
Expand Down Expand Up @@ -327,4 +328,30 @@ class IdentifierReferencesTest : AbstractReferencesTest() {
fail("Reference resolved to nothing")
}
}

fun testArrayComponents() {
initFile("arrays.peb")
myFixture.addClass(File("src/test/resources/completion/identifiers/MyClass.java").readText(Charsets.UTF_8))

// `foo in foos`
assertElementAtResolvesTo<PsiMethod>(100, "getProperty")
assertElementAtResolvesTo<PsiMethod>(125, "getChild")

// `foos[0]`
assertElementAtResolvesTo<PsiMethod>(160, "getProperty")
assertElementAtResolvesTo<PsiMethod>(190, "getChild")
}

private inline fun <reified T : PsiNamedElement> assertElementAtResolvesTo(offset: Int, expectedName: String) {
moveCaret(offset)

val resolved = resolveRefAtCaret()

if (resolved != null) {
assert(resolved is T)
assert((resolved as T).name == expectedName)
} else {
fail("Reference resolved to nothing")
}
}
}
5 changes: 5 additions & 0 deletions src/test/resources/completion/identifiers/array.peb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{# @pebvariable name="stuff" type="java.lang.Double[]" #}

{% for thing in stuff %}
{{ thing.<caret> }}
{% endfor %}
3 changes: 3 additions & 0 deletions src/test/resources/completion/identifiers/array2.peb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{# @pebvariable name="stuff" type="java.lang.Double[]" #}

{{ stuff[0].<caret> }}
3 changes: 3 additions & 0 deletions src/test/resources/completion/identifiers/array3.peb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{# @pebvariable name="stuff" type="java.lang.Double[]" #}

{{ stuff.<caret> }}
9 changes: 9 additions & 0 deletions src/test/resources/references/arrays.peb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{# @pebvariable name="foos" type="pebble.tests.MyClass[]" #}

{% for foo in foos %}
{{ foo.property }}
{{ foo.child.child }}
{% endfor %}

{{ foos[0].property }}
{{ foos[0].child.child }}

0 comments on commit 44cf81d

Please sign in to comment.