Skip to content

Commit

Permalink
Add dataset completions and gotos
Browse files Browse the repository at this point in the history
  • Loading branch information
Oliver committed May 26, 2022
1 parent fdd8734 commit 8d012e1
Show file tree
Hide file tree
Showing 21 changed files with 463 additions and 44 deletions.
9 changes: 6 additions & 3 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
# PEST IntelliJ Changelog

## [Unreleased]

## [1.6.0-EAP.1]
### Added
### Added
- Added converting multiple `expect` to `and` calls instead
- Added dataset completion
- Added dataset goto

### Fixed
- Fixed automatic case changing on multicased string

## [1.5.0]
### Added
Expand Down
2 changes: 1 addition & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ plugins {
// Kotlin support
id("org.jetbrains.kotlin.jvm") version "1.6.21"
// Gradle IntelliJ Plugin
id("org.jetbrains.intellij") version "1.6.0-SNAPSHOT"
id("org.jetbrains.intellij") version "1.6.0"
// Gradle Changelog Plugin
id("org.jetbrains.changelog") version "1.3.1"
}
Expand Down
1 change: 1 addition & 0 deletions src/main/kotlin/com/pestphp/pest/PestIconProvider.kt
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.intellij.openapi.application.runReadAction
import com.intellij.openapi.project.DumbService
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.pestphp.pest.datasets.isPestDatasetFile
import javax.swing.Icon

class PestIconProvider : IconProvider() {
Expand Down
30 changes: 5 additions & 25 deletions src/main/kotlin/com/pestphp/pest/PestUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.jetbrains.php.lang.psi.PhpFile
import com.jetbrains.php.lang.psi.elements.PhpNamespace
import com.jetbrains.php.lang.psi.elements.PhpPsiElement
import com.jetbrains.php.lang.psi.elements.Statement
import com.jetbrains.php.lang.psi.elements.impl.FunctionReferenceImpl
import com.jetbrains.php.phpunit.PhpUnitUtil
Expand All @@ -17,16 +18,7 @@ fun PsiFile.isPestTestFile(): Boolean {
if (this !is PhpFile) return false

return try {
val element = this.firstChild

element.children.filterIsInstance<PhpNamespace>()
.mapNotNull { it.statements }
.getOrElse(
0
) { element }
.children
.filterIsInstance<Statement>()
.mapNotNull { it.firstChild }
this.getRootPhpPsiElements()
.any(PsiElement::isPestTestReference)
} catch (e: Exception) {
false
Expand All @@ -44,8 +36,8 @@ fun Project.isPestEnabled(): Boolean {
.any { StringUtil.isNotEmpty(it.executablePath) }
}

fun PsiFile.isPestDatasetFile(): Boolean {
if (this !is PhpFile) return false
fun PsiFile.getRootPhpPsiElements(): List<PhpPsiElement> {
if (this !is PhpFile) return listOf()

val element = this.firstChild

Expand All @@ -57,19 +49,7 @@ fun PsiFile.isPestDatasetFile(): Boolean {
.children
.filterIsInstance<Statement>()
.mapNotNull { it.firstChild }
.any(PsiElement::isPestDataset)
}

fun PsiElement?.isPestDataset(): Boolean {
return when (this) {
null -> false
is FunctionReferenceImpl -> this.isPestDatasetFunction()
else -> false
}
}

fun FunctionReferenceImpl.isPestDatasetFunction(): Boolean {
return this.canonicalText in setOf("dataset")
.filterIsInstance<PhpPsiElement>()
}

/**
Expand Down
19 changes: 19 additions & 0 deletions src/main/kotlin/com/pestphp/pest/PhpTestFolderInputFilter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.pestphp.pest

import com.intellij.openapi.project.ProjectManager
import com.intellij.openapi.roots.TestSourcesFilter
import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.indexing.DefaultFileTypeSpecificInputFilter
import com.jetbrains.php.lang.PhpFileType

open class PhpTestFolderInputFilter : DefaultFileTypeSpecificInputFilter(PhpFileType.INSTANCE) {
override fun acceptInput(file: VirtualFile): Boolean {
if (file.path.contains(""".*?test.*?/.*\..*""".toRegex())) {
return true
}

return ProjectManager.getInstance().openProjects.any {
TestSourcesFilter.isTestSources(file, it)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
package com.pestphp.pest.datasets

import com.intellij.codeInsight.completion.*
import com.intellij.codeInsight.lookup.LookupElementBuilder
import com.intellij.codeInsight.navigation.actions.GotoDeclarationHandler
import com.intellij.openapi.editor.Editor
import com.intellij.patterns.PlatformPatterns
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import com.intellij.psi.search.GlobalSearchScope
import com.intellij.psi.util.elementType
import com.intellij.util.ProcessingContext
import com.intellij.util.indexing.FileBasedIndex
import com.jetbrains.php.lang.lexer.PhpTokenTypes
import com.jetbrains.php.lang.psi.elements.FieldReference
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression
import com.jetbrains.php.lang.psi.elements.impl.FunctionReferenceImpl
import com.pestphp.pest.getAllBeforeThisAssignments
import com.pestphp.pest.getRootPhpPsiElements
import com.pestphp.pest.isAnyPestFunction
import com.pestphp.pest.isThisVariableInPest


class DatasetCompletionContributor: CompletionContributor() {
init {
extend(
CompletionType.BASIC,
PlatformPatterns.psiElement().withParent(
StringLiteralExpression::class.java
),
DataSetCompletionProvider()
)
}

class DataSetCompletionProvider: CompletionProvider<CompletionParameters>(), GotoDeclarationHandler {
override fun addCompletions(
parameters: CompletionParameters,
context: ProcessingContext,
result: CompletionResultSet
) {
val fileBasedIndex = FileBasedIndex.getInstance()

// Get all shared datasets
val sharedDatasets = fileBasedIndex.getAllKeys(DatasetIndex.key, parameters.originalFile.project)
.map { fileBasedIndex.getValues(
DatasetIndex.key,
it,
GlobalSearchScope.projectScope(parameters.originalFile.project)
) }
.flatten()
.flatten()

// Get all datasets in the same file
val localDatasets = parameters.originalFile
.getRootPhpPsiElements()
.filter { it.isPestDataset() }
.filterIsInstance<FunctionReferenceImpl>()
.mapNotNull { it.getPestDatasetName() }

listOf(
*sharedDatasets.toTypedArray(),
*localDatasets.toTypedArray(),
).forEach {
result.addElement(
LookupElementBuilder.create(it)
)
}
}

override fun getGotoDeclarationTargets(
sourceElement: PsiElement?,
offset: Int,
editor: Editor
): Array<PsiElement> {
if (sourceElement?.elementType !in listOf(PhpTokenTypes.STRING_LITERAL_SINGLE_QUOTE, PhpTokenTypes.STRING_LITERAL)) {
return PsiElement.EMPTY_ARRAY
}

val parent = sourceElement?.parent
if (parent !is StringLiteralExpression) {
return PsiElement.EMPTY_ARRAY
}

val fileBasedIndex = FileBasedIndex.getInstance()
val datasetName = parent.contents

val foundDatasets = mutableListOf<PsiElement>()

fileBasedIndex.getAllKeys(
DatasetIndex.key,
parent.project
).forEach { key ->
fileBasedIndex.processValues(
DatasetIndex.key,
key,
null,
{ file, datasets ->
if (datasetName !in datasets) {
return@processValues true
}

// Add all shared datasets which matches
PsiManager.getInstance(parent.project).findFile(file)!!
.getRootPhpPsiElements()
.filter { it.isPestDataset() }
.filterIsInstance<FunctionReferenceImpl>()
.filter { it.getPestDatasetName() == datasetName }
.forEach { foundDatasets.add(it) }

true
},
GlobalSearchScope.projectScope(parent.project)
)
}

// Add all local datasets which matches
parent.containingFile
.getRootPhpPsiElements()
.filter { it.isPestDataset() }
.filterIsInstance<FunctionReferenceImpl>()
.filter { it.getPestDatasetName() == datasetName }
.forEach { foundDatasets.add(it) }

return foundDatasets.toTypedArray()
}
}
}
71 changes: 71 additions & 0 deletions src/main/kotlin/com/pestphp/pest/datasets/DatasetIndex.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package com.pestphp.pest.datasets

import com.intellij.openapi.vfs.VirtualFile
import com.intellij.util.indexing.*
import com.intellij.util.io.DataExternalizer
import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import com.jetbrains.php.lang.psi.PhpFile
import com.jetbrains.php.lang.psi.elements.impl.FunctionReferenceImpl
import com.pestphp.pest.PhpTestFolderInputFilter
import com.pestphp.pest.customExpectations.externalizers.ListDataExternalizer
import com.pestphp.pest.getRootPhpPsiElements
import com.pestphp.pest.realPath

class DatasetIndex : FileBasedIndexExtension<String, List<String>>() {
companion object {
val key = ID.create<String, List<String>>("php.pest.datasets")
}

override fun getName(): ID<String, List<String>> {
return key
}

override fun getVersion(): Int {
return 1
}

override fun getIndexer(): DataIndexer<String, List<String>, FileContent> {
return DataIndexer { inputData ->
val file = inputData.psiFile

if (file !is PhpFile) {
return@DataIndexer mapOf()
}

val datasets = file
.getRootPhpPsiElements()
.filter { it.isPestDataset() }
.filterIsInstance<FunctionReferenceImpl>()
.mapNotNull { it.getPestDatasetName() }

if(datasets.isEmpty()) {
return@DataIndexer mapOf()
}

mapOf(
file.realPath to datasets
)
}
}

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

override fun getValueExternalizer(): DataExternalizer<List<String>> {
return ListDataExternalizer(EnumeratorStringDescriptor.INSTANCE)
}

override fun getInputFilter(): FileBasedIndex.InputFilter {
return object : PhpTestFolderInputFilter() {
override fun acceptInput(file: VirtualFile): Boolean {
return super.acceptInput(file) && file.parent.path.endsWith("/Datasets")
}
}
}

override fun dependsOnFileContent(): Boolean {
return true
}
}
28 changes: 28 additions & 0 deletions src/main/kotlin/com/pestphp/pest/datasets/DatasetUtil.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package com.pestphp.pest.datasets

import com.intellij.psi.PsiElement
import com.intellij.psi.PsiFile
import com.jetbrains.php.lang.psi.elements.StringLiteralExpression
import com.jetbrains.php.lang.psi.elements.impl.FunctionReferenceImpl
import com.pestphp.pest.getRootPhpPsiElements

fun PsiFile.isPestDatasetFile(): Boolean {
return this.getRootPhpPsiElements()
.any(PsiElement::isPestDataset)
}

fun PsiElement?.isPestDataset(): Boolean {
return when (this) {
null -> false
is FunctionReferenceImpl -> this.isPestDatasetFunction()
else -> false
}
}

fun FunctionReferenceImpl.isPestDatasetFunction(): Boolean {
return this.canonicalText in setOf("dataset")
}

fun FunctionReferenceImpl.getPestDatasetName(): String? {
return (getParameter(0) as? StringLiteralExpression)?.contents
}
13 changes: 2 additions & 11 deletions src/main/kotlin/com/pestphp/pest/indexers/PestTestIndex.kt
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import com.intellij.util.io.EnumeratorStringDescriptor
import com.intellij.util.io.KeyDescriptor
import com.jetbrains.php.lang.PhpFileType
import com.jetbrains.php.lang.psi.stubs.indexes.StringSetDataExternalizer
import com.pestphp.pest.PhpTestFolderInputFilter
import com.pestphp.pest.getPestTestName
import com.pestphp.pest.getPestTests
import com.pestphp.pest.isPestTestFile
Expand Down Expand Up @@ -53,17 +54,7 @@ class PestTestIndex : FileBasedIndexExtension<String, Set<String>>() {
}

override fun getInputFilter(): FileBasedIndex.InputFilter {
return object : DefaultFileTypeSpecificInputFilter(PhpFileType.INSTANCE) {
override fun acceptInput(file: VirtualFile): Boolean {
if (file.path.contains(""".*?test.*?/.*\..*""".toRegex())) {
return true
}

return ProjectManager.getInstance().openProjects.any {
TestSourcesFilter.isTestSources(file, it)
}
}
}
return PhpTestFolderInputFilter()
}

override fun getKeyDescriptor(): KeyDescriptor<String> {
Expand Down
Loading

0 comments on commit 8d012e1

Please sign in to comment.