Skip to content

Commit

Permalink
feat: Feature flag autocompletion
Browse files Browse the repository at this point in the history
  • Loading branch information
shyim committed Sep 23, 2021
1 parent 3c2367a commit 222371d
Show file tree
Hide file tree
Showing 12 changed files with 379 additions and 5 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ platformDownloadSources = true

# Plugin Dependencies -> https://plugins.jetbrains.com/docs/intellij/plugin-dependencies.html
# Example: platformPlugins = com.intellij.java, com.jetbrains.php:203.4449.22
platformPlugins=com.jetbrains.php:212.4746.100,com.jetbrains.twig:212.4746.2,JavaScriptLanguage,Git4Idea,fr.adrienbrault.idea.symfony2plugin:0.23.212,de.espend.idea.php.annotation:8.0.0
platformPlugins=com.jetbrains.php:212.4746.100,com.jetbrains.twig:212.4746.2,JavaScriptLanguage,Git4Idea,fr.adrienbrault.idea.symfony2plugin:0.23.212,de.espend.idea.php.annotation:8.0.0,org.jetbrains.plugins.yaml

# Java language level used to compile sources and to generate the files for - Java 11 is required since 2020.3
javaVersion = 11
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package de.shyim.shopware6.completion

import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext
import com.intellij.util.indexing.FileBasedIndex
import com.jetbrains.php.lang.psi.elements.ParameterList
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression
import de.shyim.shopware6.index.FeatureFlagIndex
import de.shyim.shopware6.util.PHPPattern
import icons.ShopwareToolBoxIcons


class PhpCompletionProvider : CompletionContributor() {
init {
extend(
CompletionType.BASIC,
PlatformPatterns.psiElement().withParent(
PlatformPatterns.psiElement(StringLiteralExpression::class.java).inside(
PlatformPatterns.psiElement(ParameterList::class.java)
)
),
object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {
val element = parameters.originalPosition ?: return
val project = element.project

if (PHPPattern.isFeatureFlagFunction(element)) {
for (key in FileBasedIndex.getInstance().getAllKeys(FeatureFlagIndex.key, project)) {
val vals = FileBasedIndex.getInstance()
.getValues(FeatureFlagIndex.key, key, GlobalSearchScope.allScope(project))

vals.forEach {
result.addElement(
LookupElementBuilder.create(it.name).withTypeText(it.description)
.withIcon(ShopwareToolBoxIcons.SHOPWARE)
)
}
}
}
}
}
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.ProcessingContext
import com.intellij.util.indexing.FileBasedIndex
import de.shyim.shopware6.index.FeatureFlagIndex
import de.shyim.shopware6.index.FrontendSnippetIndex
import de.shyim.shopware6.util.TwigPattern
import icons.ShopwareToolBoxIcons
Expand Down Expand Up @@ -39,5 +40,34 @@ class TwigCompletionProvider() : CompletionContributor() {
}
}
)

extend(
CompletionType.BASIC,
TwigPattern.getPrintBlockOrTagFunctionPattern("feature")!!,
object : CompletionProvider<CompletionParameters>() {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {
for (key in FileBasedIndex.getInstance()
.getAllKeys(FeatureFlagIndex.key, parameters.position.project)) {
val vals = FileBasedIndex.getInstance()
.getValues(
FeatureFlagIndex.key,
key,
GlobalSearchScope.allScope(parameters.position.project)
)

vals.forEach {
result.addElement(
LookupElementBuilder.create(it.name).withTypeText(it.description)
.withIcon(ShopwareToolBoxIcons.SHOPWARE)
)
}
}
}
}
)
}
}
121 changes: 121 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/index/FeatureFlagIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package de.shyim.shopware6.index

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.util.indexing.*
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import de.shyim.shopware6.index.dict.FeatureFlag
import de.shyim.shopware6.index.externalizer.ObjectStreamDataExternalizer
import gnu.trove.THashMap
import org.jetbrains.yaml.YAMLFileType
import org.jetbrains.yaml.psi.YAMLKeyValue
import org.jetbrains.yaml.psi.impl.YAMLSequenceItemImpl

class FeatureFlagIndex : FileBasedIndexExtension<String, FeatureFlag>() {
private val EXTERNALIZER = ObjectStreamDataExternalizer<FeatureFlag>()

override fun getName(): ID<String, FeatureFlag> {
return FeatureFlagIndex.key
}

override fun getVersion(): Int {
return 1
}

override fun dependsOnFileContent(): Boolean {
return true
}

override fun getIndexer(): DataIndexer<String, FeatureFlag, FileContent> {
return DataIndexer { inputData ->
if (!inputData.getFile().getName().equals("shopware.yaml")) {
return@DataIndexer mapOf()
}

if (!inputData.file.parent.name.equals("packages")) {
return@DataIndexer mapOf()
}

val flags = THashMap<String, FeatureFlag>()

inputData.psiFile.acceptChildren(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is YAMLKeyValue) {
if (element.firstChild.text != "shopware" && element.firstChild.text != "feature" && element.firstChild.text != "flags") {
return
}

if (element.firstChild.text == "flags") {
element.acceptChildren(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is YAMLSequenceItemImpl) {
val map = HashMap<String, String>()

element.acceptChildren(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is YAMLKeyValue) {
map.set(element.firstChild.text, element.lastChild.firstChild.text)
}

super.visitElement(element)
}
})

if (map.containsKey("name")) {
var default = false
var major = false

if (map.containsKey("default") && map.get("default") == "true") {
default = true
}

if (map.containsKey("major") && map.get("major") == "true") {
major = true
}

flags[map.get("name")] = FeatureFlag(
map.getOrDefault("name", ""),
default,
major,
map.getOrDefault("description", ""),
inputData.file.path
)
}

return
}

super.visitElement(element)
}
})

return
}
}

super.visitElement(element)
}
})

return@DataIndexer flags
}
}

override fun getKeyDescriptor(): KeyDescriptor<String> {
return EnumeratorStringDescriptor.INSTANCE
}

override fun getValueExternalizer(): ObjectStreamDataExternalizer<FeatureFlag> {
return EXTERNALIZER
}

companion object {
val key = ID.create<String, FeatureFlag>("de.shyim.shopware6.core.feature_flag")
}

override fun getInputFilter(): FileBasedIndex.InputFilter {
return object : DefaultFileTypeSpecificInputFilter(YAMLFileType.YML) {
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import com.intellij.json.JsonFileType
import com.intellij.util.indexing.*
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import de.shyim.shopware6.index.dict.SnippetFile
import de.shyim.shopware6.index.externalizer.ObjectStreamDataExternalizer
import de.shyim.shopware6.util.SnippetUtil
import gnu.trove.THashMap
Expand All @@ -17,7 +18,7 @@ open class FrontendSnippetIndex : FileBasedIndexExtension<String, SnippetFile>()
}

override fun getVersion(): Int {
return 2
return 3
}

override fun dependsOnFileContent(): Boolean {
Expand Down
32 changes: 32 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/index/dict/FeatureFlag.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package de.shyim.shopware6.index.dict

import org.apache.commons.lang.builder.HashCodeBuilder
import java.io.Serializable
import java.util.*

class FeatureFlag(
var name: String,
var default: Boolean,
var major: Boolean,
var description: String,
var file: String
) : Serializable {
override fun hashCode(): Int {
return HashCodeBuilder()
.append(this.name)
.append(this.default)
.append(this.major)
.append(this.description)
.append(this.file)
.toHashCode()
}

override fun equals(obj: Any?): Boolean {
return obj is FeatureFlag &&
Objects.equals(obj.name, this.name) &&
Objects.equals(obj.default, this.default) &&
Objects.equals(obj.major, this.major) &&
Objects.equals(obj.description, this.description) &&
Objects.equals(obj.file, this.file)
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package de.shyim.shopware6.index
package de.shyim.shopware6.index.dict

import org.apache.commons.lang.builder.HashCodeBuilder
import java.io.Serializable
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package de.shyim.shopware6.navigation

import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.project.Project
import com.intellij.openapi.vfs.LocalFileSystem
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.PsiRecursiveElementWalkingVisitor
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.util.indexing.FileBasedIndex
import de.shyim.shopware6.index.FeatureFlagIndex
import de.shyim.shopware6.index.dict.FeatureFlag
import de.shyim.shopware6.util.PHPPattern
import de.shyim.shopware6.util.TwigPattern
import org.jetbrains.yaml.psi.impl.YAMLPlainTextImpl

class FeatureFlagGoToDeclareHandler : GotoDeclarationHandler {
override fun getGotoDeclarationTargets(
element: PsiElement?,
offset: Int,
editor: Editor?
): Array<PsiElement>? {
if (editor === null || editor.project === null || element == null) {
return null
}

val project = editor.project!!

val psiElements: MutableList<PsiElement> = ArrayList()

if (PHPPattern.isFeatureFlagFunction(element) || TwigPattern.getPrintBlockOrTagFunctionPattern("feature")!!
.accepts(element)
) {
for (key in FileBasedIndex.getInstance().getAllKeys(FeatureFlagIndex.key, project)) {
if (key == element.text) {
val vals = FileBasedIndex.getInstance()
.getValues(FeatureFlagIndex.key, key, GlobalSearchScope.allScope(project))

vals.forEach {
lookupPsiElementFromFile(psiElements, it, project)
}
}
}
}

return psiElements.toTypedArray()
}

private fun lookupPsiElementFromFile(
psiElements: MutableList<PsiElement>,
featureFlag: FeatureFlag,
project: Project
) {
val file = LocalFileSystem.getInstance().findFileByPath(featureFlag.file)

if (file != null) {
val psi = PsiManager.getInstance(project).findFile(file)

if (psi != null) {
psi.acceptChildren(object : PsiRecursiveElementWalkingVisitor() {
override fun visitElement(element: PsiElement) {
if (element is YAMLPlainTextImpl) {
if (element.text == featureFlag.name) {
psiElements.add(element)
return
}
}

super.visitElement(element)
}
})
}
}
}
}
21 changes: 21 additions & 0 deletions src/main/kotlin/de/shyim/shopware6/util/PHPPattern.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package de.shyim.shopware6.util

import com.intellij.psi.PsiElement
import fr.adrienbrault.idea.symfony2plugin.util.MethodMatcher

object PHPPattern {
private val FEATURE_FLAG_SIGNATURES: Array<MethodMatcher.CallToSignature> = arrayOf(
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "isActive"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "ifActive"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "ifActiveCall"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "skipTestIfInActive"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "skipTestIfActive"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "triggerDeprecated"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "throwException"),
MethodMatcher.CallToSignature("\\Shopware\\Core\\Framework\\Feature", "has"),
)

fun isFeatureFlagFunction(element: PsiElement): Boolean {
return MethodMatcher.getMatchedSignatureWithDepth(element.getContext(), FEATURE_FLAG_SIGNATURES) != null
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/de/shyim/shopware6/util/SnippetUtil.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package de.shyim.shopware6.util

import de.shyim.shopware6.index.SnippetFile
import de.shyim.shopware6.index.dict.SnippetFile
import gnu.trove.THashMap
import org.codehaus.jettison.json.JSONException
import org.codehaus.jettison.json.JSONObject
Expand Down
Loading

0 comments on commit 222371d

Please sign in to comment.