From 7efd0e2bac6918092a4e4af096e820195ee50bad Mon Sep 17 00:00:00 2001 From: Nazar Velychenko Date: Fri, 26 Jan 2024 00:18:23 +0200 Subject: [PATCH] Feat: eck content entity support. --- CHANGELOG.md | 3 +- gradle.properties | 2 +- .../DrupalContentEntityContributor.kt | 4 +- .../drupalextend/index/ContentEntityIndex.kt | 113 ++++++++++++------ 4 files changed, 81 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a88639a..fb3caee 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,6 @@ # drupal-extend Changelog -## [Unreleased] - ## [0.0.5] ### Added @@ -11,6 +9,7 @@ - Index caching - Static content entity provider e.g. Node::load() - Fields autocomplete only knowing entity interface e.g. NodeInterface +- ECK content entity type support. ### Fixed diff --git a/gradle.properties b/gradle.properties index 1fd4e3c..caf982d 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ pluginGroup = com.github.nvelychenko.drupalextend pluginName = drupal-extend pluginRepositoryUrl = https://github.com/nvelychenko/drupal-extend # SemVer format -> https://semver.org -pluginVersion = 0.0.4 +pluginVersion = 0.0.5 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild = 233.13135.108 diff --git a/src/main/kotlin/com/github/nvelychenko/drupalextend/completion/DrupalContentEntityContributor.kt b/src/main/kotlin/com/github/nvelychenko/drupalextend/completion/DrupalContentEntityContributor.kt index 4ecd1eb..9b3f702 100644 --- a/src/main/kotlin/com/github/nvelychenko/drupalextend/completion/DrupalContentEntityContributor.kt +++ b/src/main/kotlin/com/github/nvelychenko/drupalextend/completion/DrupalContentEntityContributor.kt @@ -37,10 +37,10 @@ class DrupalContentEntityContributor : CompletionContributor() { val parent: PsiElement val prevSibling = completionParameters.position.prevSibling parent = if (prevSibling != null && prevSibling.node.elementType === PhpTokenTypes.ARROW) { - // $foo-> + // $xex-> prevSibling.parent } else { - // $foo->ad + // $xex->ad psiElement.parent } diff --git a/src/main/kotlin/com/github/nvelychenko/drupalextend/index/ContentEntityIndex.kt b/src/main/kotlin/com/github/nvelychenko/drupalextend/index/ContentEntityIndex.kt index 523eb41..a71675e 100644 --- a/src/main/kotlin/com/github/nvelychenko/drupalextend/index/ContentEntityIndex.kt +++ b/src/main/kotlin/com/github/nvelychenko/drupalextend/index/ContentEntityIndex.kt @@ -2,7 +2,8 @@ package com.github.nvelychenko.drupalextend.index import com.github.nvelychenko.drupalextend.index.types.DrupalContentEntity import com.github.nvelychenko.drupalextend.util.isValidForIndex -import com.intellij.openapi.vfs.VirtualFile +import com.intellij.openapi.project.guessProjectDir +import com.intellij.openapi.vfs.VfsUtil import com.intellij.psi.util.PsiTreeUtil import com.intellij.util.indexing.* import com.intellij.util.io.DataExternalizer @@ -12,6 +13,10 @@ import com.jetbrains.php.lang.PhpFileType import com.jetbrains.php.lang.documentation.phpdoc.psi.PhpDocComment import com.jetbrains.php.lang.psi.PhpFile import com.jetbrains.php.lang.psi.elements.PhpClass +import org.jetbrains.yaml.YAMLFileType +import org.jetbrains.yaml.psi.YAMLDocument +import org.jetbrains.yaml.psi.YAMLFile +import org.jetbrains.yaml.psi.YAMLKeyValue import java.io.DataInput import java.io.DataOutput @@ -54,56 +59,92 @@ class ContentEntityIndex : FileBasedIndexExtension( override fun getIndexer(): DataIndexer { return DataIndexer { inputData -> val map = hashMapOf() - val phpFile = inputData.psiFile as PhpFile + val psiFile = inputData.psiFile if (!isValidForIndex(inputData)) { return@DataIndexer map } - val phpClass = PsiTreeUtil.findChildOfType(phpFile, PhpClass::class.java) ?: return@DataIndexer map - if (phpClass.docComment !is PhpDocComment) return@DataIndexer map + when (psiFile) { + is YAMLFile -> processYml(map, psiFile) + is PhpFile -> processPhp(map, psiFile) + } - val contentEntityTypes = (phpClass.docComment as PhpDocComment).getTagElementsByName("@ContentEntityType") - if (contentEntityTypes.isEmpty()) { - return@DataIndexer map + map + } + } + + private fun processYml(map: HashMap, psiFile: YAMLFile) { + val file = psiFile.virtualFile + val baseDir = psiFile.project.guessProjectDir() + + if (baseDir != null && file != null) { + val relativePath = VfsUtil.getRelativePath(file, baseDir, '/') ?: return + + if (!relativePath.contains("config/sync")) { + return } + } - val contentEntityTypeDocText = contentEntityTypes[0].text - - val id = getPhpDocParameter(contentEntityTypeDocText, "id") ?: return@DataIndexer map - - val hardcodedKeys = arrayOf( - "id", - "revision", - "bundle", - "label", - "langcode", - "uuid", - "status", - "published", - "uid", - "owner", - "revision_log_message", - "revision_created", - "revision_user", - ) - - val resolvedKeys = hashMapOf() - - // @todo Implement better parsing for phpdoc. - for (key in hardcodedKeys) { - resolvedKeys[key] = (getPhpDocParameter(contentEntityTypeDocText, key) ?: continue) + if (!psiFile.name.startsWith("eck.eck_entity_type.")) { + return + } + + PsiTreeUtil.getChildOfType(psiFile, YAMLDocument::class.java)?.topLevelValue?.children?.forEach { node -> + if (node is YAMLKeyValue && node.keyText == "id") { + map[node.valueText] = DrupalContentEntity( + node.valueText, + "\\Drupal\\eck\\Entity\\EckEntity", + hashMapOf(), + "\\Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage" + ) } + } + } - val sqlStorageHandler = getPhpDocParameter(contentEntityTypeDocText, "storage") ?: "\\Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage" + private fun processPhp(map: HashMap, phpFile: PhpFile) { + val phpClass = PsiTreeUtil.findChildOfType(phpFile, PhpClass::class.java) ?: return + if (phpClass.docComment !is PhpDocComment) return - map[id] = DrupalContentEntity(id, phpClass.fqn, resolvedKeys, sqlStorageHandler) + val contentEntityTypes = (phpClass.docComment as PhpDocComment).getTagElementsByName("@ContentEntityType") + if (contentEntityTypes.isEmpty()) { + return + } - map + val contentEntityTypeDocText = contentEntityTypes[0].text + + val id = getPhpDocParameter(contentEntityTypeDocText, "id") ?: return + + val hardcodedKeys = arrayOf( + "id", + "revision", + "bundle", + "label", + "langcode", + "uuid", + "status", + "published", + "uid", + "owner", + "revision_log_message", + "revision_created", + "revision_user", + ) + + val resolvedKeys = hashMapOf() + + // @todo Implement better parsing for phpdoc. + for (key in hardcodedKeys) { + resolvedKeys[key] = (getPhpDocParameter(contentEntityTypeDocText, key) ?: continue) } + + val sqlStorageHandler = getPhpDocParameter(contentEntityTypeDocText, "storage") ?: "\\Drupal\\Core\\Entity\\Sql\\SqlContentEntityStorage" + + map[id] = DrupalContentEntity(id, phpClass.fqn, resolvedKeys, sqlStorageHandler) } private fun getPhpDocParameter(phpDocText: String, id: String): String? { + @Suppress("RegExpUnnecessaryNonCapturingGroup") val entityTypeMatch = Regex("${id}(?:\"?)\\s*=\\s*\"([^\"]+)\"").find(phpDocText) return entityTypeMatch?.groups?.get(1)?.value @@ -114,7 +155,7 @@ class ContentEntityIndex : FileBasedIndexExtension( override fun getValueExternalizer(): DataExternalizer = myDataExternalizer override fun getInputFilter(): FileBasedIndex.InputFilter { - return FileBasedIndex.InputFilter { file: VirtualFile -> file.fileType == PhpFileType.INSTANCE } + return FileBasedIndex.InputFilter { file -> file.fileType == YAMLFileType.YML || file.fileType == PhpFileType.INSTANCE } } override fun dependsOnFileContent(): Boolean = true