Skip to content

Commit

Permalink
Merge pull request #40 from DanielSchiavini/master
Browse files Browse the repository at this point in the history
External compiler annotator
  • Loading branch information
DanielSchiavini authored Jul 2, 2024
2 parents 33610ad + 6feb3c3 commit a405217
Show file tree
Hide file tree
Showing 24 changed files with 354 additions and 209 deletions.
18 changes: 18 additions & 0 deletions .idea/modules/vyper-plugin.test.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@

## [Unreleased]

- Add support for vyi files ([#17](https://github.com/NikitaMishin/vyper-plugin/issues/17)), including specific errors to vyi vs vyper files
- Add automatic compilation errors ([#36](https://github.com/NikitaMishin/vyper-plugin/issues/36))
- Add support for `vyi` files ([#17](https://github.com/NikitaMishin/vyper-plugin/issues/17)), including specific errors to `vyi` files
- Remove MythX and SmartCheck as they are deprecated ([#26](https://github.com/NikitaMishin/vyper-plugin/issues/26))
- Remove run action and update compile action ([#27](https://github.com/NikitaMishin/vyper-plugin/issues/27))
- Remove deprecated decorators from syntax (i.e. `@public`, `@private`).
- Add warning for deprecated @nonreentrant with entrancy key.
- New file icons

## [0.2.0-alpha.2] - 2024-06-26
Expand Down
9 changes: 6 additions & 3 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dependencies {
implementation group: "com.github.docker-java", name: "docker-java", version: "3.3.6"

// https://mvnrepository.com/artifact/com.github.docker-java/docker-java-transport-httpclient5
implementation "com.github.docker-java:docker-java-transport-httpclient5:3.3.6"
implementation group: "com.github.docker-java", name: "docker-java-transport-httpclient5", version: "3.3.6"

// https://mvnrepository.com/artifact/com.github.kittinunf.fuel/fuel-coroutines
implementation group: "com.github.kittinunf.fuel", name: "fuel-coroutines", version: "2.3.1"
Expand All @@ -62,10 +62,13 @@ dependencies {
implementation group: "com.google.code.gson", name: "gson", version: "2.11.0"

// https://mvnrepository.com/artifact/junit/junit
testImplementation group: "junit", name: "junit", version: "4.13.2"
testImplementation group: "junit", name: "junit", version: "4.13.2" // v5 not supported by base JetBrains tests

// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
testImplementation group: "org.jetbrains.kotlinx", name: "kotlinx-coroutines-core", version: "1.8.1"

// https://mvnrepository.com/artifact/io.mockk/mockk
testImplementation group: "io.mockk", name: "mockk", version: "1.13.11"
}

configurations {
Expand Down
8 changes: 3 additions & 5 deletions src/main/kotlin/org/vyperlang/plugin/VyperStubGenerator.kt
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import java.io.File
object VyperStubGenerator {

fun createStubInGenSourceFolder(
data: List<String>, module: Module, project: Project,
data: String, module: Module, project: Project,
originalFile: VirtualFile, extension: String
): File {
generateFolder(module, project)
Expand All @@ -36,12 +36,10 @@ object VyperStubGenerator {
file = File(filePath) // update because need refresh
val virtualFile = LocalFileSystem.getInstance().refreshAndFindFileByIoFile(file)!!
val document: Document = FileDocumentManager.getInstance().getDocument(virtualFile)!!

val text = data.joinToString("\n")
if (isFileExists) {
document.setText(text)
document.setText(data)
} else {
document.replaceString(0, document.textLength, text)
document.replaceString(0, document.textLength, data)
}

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,15 @@ import com.intellij.openapi.project.Project
import com.intellij.psi.PsiDirectory
import org.vyperlang.plugin.VyperIcons

private const val name = "Create empty Vyper file"
private const val name = "Vyper contract"
private const val description = "Empty Vyper file"
private const val kind = "Empty file"
private const val templateName = "Vyper File"

class CreateVyperFileAction : CreateFileFromTemplateAction(name, description, VyperIcons.FILE) {
// override fun getActionName(directory: PsiDirectory?, newName: String?, templateName: String?): String = name
// override fun buildDialog(project: Project?, directory: PsiDirectory?, builder: CreateFileFromTemplateDialog.Builder) {
// builder.setTitle(name).addKind("Empty file", VyperIcons.FILE, "Vyper File")
//
// }

override fun buildDialog(project: Project, directory: PsiDirectory, builder: CreateFileFromTemplateDialog.Builder) {
builder.setTitle(name).addKind("Empty file", VyperIcons.FILE, "Vyper File")
builder.setTitle(name).addKind(kind, VyperIcons.FILE, templateName)
}

override fun getActionName(directory: PsiDirectory?, newName: String, templateName: String?): String = name
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ class VyperColorAnnotator : Annotator {
if (element is VyperFunctionBody && element.text == "...") {
addError(holder, "Ellipsis is only allowed in `.vyi` files")
}
if (element is VyperFunctionEntrancyKey) {
holder.newAnnotation(HighlightSeverity.WARNING, "Entrancy key is deprecated").create()
}
// if (element is VyperFunctionEntrancyKey) {
// holder.newAnnotation(HighlightSeverity.WARNING, "Entrancy key is deprecated").create()
// }
}

private fun highlightInterfaceFile(element: PsiElement, holder: AnnotationHolder) {
Expand Down
123 changes: 77 additions & 46 deletions src/main/kotlin/org/vyperlang/plugin/annotators/CompilerAnnotator.kt
Original file line number Diff line number Diff line change
@@ -1,61 +1,92 @@
package org.vyperlang.plugin.annotators

import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer
import com.intellij.lang.annotation.AnnotationHolder
import com.intellij.lang.annotation.Annotator
import com.intellij.lang.annotation.ExternalAnnotator
import com.intellij.lang.annotation.HighlightSeverity
import com.intellij.openapi.application.ApplicationManager
import com.intellij.openapi.editor.Editor
import com.intellij.openapi.progress.ProgressIndicator
import com.intellij.openapi.project.DumbAware
import com.intellij.psi.PsiFile
import org.vyperlang.plugin.docker.StatusDocker
import org.vyperlang.plugin.docker.VyperCompilerDocker
import com.intellij.openapi.diagnostic.Logger
import com.intellij.openapi.project.Project
import com.intellij.openapi.util.TextRange
import com.intellij.psi.PsiDocumentManager
import com.intellij.psi.PsiElement
import com.intellij.psi.PsiManager
import org.vyperlang.plugin.compile.VyperCompiler
import org.vyperlang.plugin.psi.VyperFile
import java.beans.PropertyChangeEvent
import java.beans.PropertyChangeListener

class VyperCompilerListener(val project: Project) : PropertyChangeListener {
override fun propertyChange(evt: PropertyChangeEvent?) {
val data = evt!!.newValue as VyperCompiler.CompilerMessage

ApplicationManager.getApplication().runReadAction {

val psiFile = PsiManager.getInstance(project).findFile(data.file)
//what if user picks another file?
val document = PsiDocumentManager.getInstance(project).getDocument(psiFile!!)
for (report in data.error) {
val start = document!!.getLineStartOffset(report.line - 1)
val end = document.getLineEndOffset(report.line - 1)
val message = report.msg
CompilerOutput.messages.add(CompilerMessage(TextRange(start, end), message))
}
DaemonCodeAnalyzer.getInstance(project).restart()
}
}
import com.intellij.openapi.vfs.VirtualFile
import org.vyperlang.plugin.docker.CompilerMissingError

data class FileInfo(val project: Project, val file: VirtualFile, val indicator: ProgressIndicator? = null)

fun listenAnalysis() {
VyperCompiler.addListener(this)
}
}
val VYPER_ERROR_REGEX = listOf(
// ErrorType: error message\n
"(\\w+): ([^\\n]+)\\n+",
// (hint: optional)\n
"( +\\(hint: [^)]+\\)\\n+)?",
// contract "x", function "y", line 1:1
" +(?:contract \"[^\"]+\", )?(?:function \"[^\"]+\", )?line (\\d+):(\\d+)"
).joinToString("").toRegex(RegexOption.MULTILINE)

val LOG: Logger = Logger.getInstance(CompilerAnnotator::class.java)

/**
* Annotator that listens to the compiler output and annotates the file accordingly
* Annotator that calls the compiler and annotates the file accordingly.
* By using the ExternalAnnotator, the compiler is only called when the file is saved and all background processes are finished.
*/
class CompilerAnnotator : Annotator {
override fun annotate(element: PsiElement, holder: AnnotationHolder) {
if (element is VyperFile) {
for (message in CompilerOutput.messages) {
holder.newAnnotation(HighlightSeverity.ERROR, message.message)
}
CompilerOutput.messages = mutableListOf()
class CompilerAnnotator : ExternalAnnotator<FileInfo, List<CompilerError>>(), DumbAware {

/** 1st step of the external annotator: Collect information needed to run the compiler. */
override fun collectInformation(file: PsiFile) = FileInfo(file.project, file.virtualFile)

/** 1st step of the external annotator: Collect information needed to run the compiler. */
override fun collectInformation(file: PsiFile, editor: Editor, hasErrors: Boolean) = collectInformation(file)

/** 2nd step of the external annotator: Run the compiler and return the result. */
override fun doAnnotate(info: FileInfo?): List<CompilerError> {
val result = try {
VyperCompilerDocker(info!!.project, info.file, info.indicator).run()
} catch (e: CompilerMissingError) {
LOG.error("Error while running compiler annotator", e)
null
}
if (result?.statusDocker == StatusDocker.FAILED) {
return parseErrors(result.stderr);
}
return emptyList()
}
}

object CompilerOutput {
var messages: MutableList<CompilerMessage> = mutableListOf()
/** 3rd step of the external annotator: Apply the annotations to the file. */
override fun apply(file: PsiFile, annotationResult: List<CompilerError>, holder: AnnotationHolder) {
annotationResult.forEach {
val offset = file.text.lines().take(it.line - 1).sumOf { it.length + 1 } + it.column
val element = file.findReferenceAt(file.textOffset + offset)?.element
?: file.findElementAt(offset)
?: file.findElementAt(offset - 1)
?: file.findElementAt(offset - 2)
?: file
holder.newAnnotation(HighlightSeverity.ERROR, it.message)
.range(element.textRange)
.tooltip("${it.message} (${if (it.hint.isNullOrBlank()) it.errorType else it.hint})")
.create()
}
}

/** Parse the compiler stderr, return list of errors. */
private fun parseErrors(stderr: String): List<CompilerError> {
val messages = VYPER_ERROR_REGEX.findAll(stderr).map {
val (errorType, message, hint, line, column) = it.destructured
CompilerError(errorType, message.trim(), hint, line.toInt(), column.toInt())
}.toList()
if (messages.isEmpty()) {
LOG.warn("No error messages found in compiler output: $stderr")
}
return messages
}
}

data class CompilerMessage(val range: TextRange, val message: String)
/** Data class to store compiler error information. */
data class CompilerError(
val errorType: String,
val message: String,
val hint: String?,
val line: Int,
val column: Int
)
Loading

0 comments on commit a405217

Please sign in to comment.