diff --git a/build.gradle.kts b/build.gradle.kts index d20288bf4..31ae017ac 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -259,7 +259,7 @@ allprojects { } task { systemProperty("org.move.debug.enabled", true) - systemProperty("org.move.types.highlight.unknown.as.error", true) + systemProperty("org.move.types.highlight.unknown.as.error", false) // systemProperty("org.move.external.linter.max.duration", 30) // 30 ms // systemProperty("org.move.aptos.bundled.force.unsupported", true) // systemProperty("idea.log.debug.categories", "org.move.cli") diff --git a/src/main/kotlin/org/move/cli/externalLinter/CompilerErrors.kt b/src/main/kotlin/org/move/cli/externalLinter/CompilerErrors.kt index 63a2e655b..a0a46aac0 100644 --- a/src/main/kotlin/org/move/cli/externalLinter/CompilerErrors.kt +++ b/src/main/kotlin/org/move/cli/externalLinter/CompilerErrors.kt @@ -4,38 +4,54 @@ import org.move.ide.annotator.AptosCompilerMessage import org.move.ide.annotator.AptosCompilerSpan fun parseCompilerErrors(outputLines: List): List { - val errorLists = splitErrors(outputLines) - val messages = errorLists.map(::errorLinesToCompilerMessage) + val rawMessages = splitMessages(outputLines) + val messages = rawMessages.map(::rawMessageToCompilerMessage) return messages } private val ERROR_START_RE = Regex("^error(\\[E\\d+\\])?:\\s+(.+)") -private fun splitErrors(outputLines: List): List> { - val errorLists = mutableListOf>() - var thisErrorLines: MutableList? = null +enum class ErrorType(val severityLevel: String) { + ERROR("error"), WARNING("warning"), WARNING_LINT("warning [lint]"); +} + +data class RawMessage(val errorType: ErrorType, val lines: MutableList) + +private fun splitMessages(outputLines: List): List { + val rawMessages = mutableListOf() + var message: RawMessage? = null for (line in outputLines) { if (line.startsWith("{")) { break } - if (ERROR_START_RE.find(line) != null) { -// if (line.startsWith("error:")) { + val newErrorType = when { + ERROR_START_RE.find(line) != null -> ErrorType.ERROR + line.startsWith("warning: [lint]") -> ErrorType.WARNING_LINT + line.startsWith("warning:") -> ErrorType.WARNING + else -> null + } + if (newErrorType != null) { // flush - thisErrorLines?.let { errorLists.add(it) } - thisErrorLines = mutableListOf() + message?.let { rawMessages.add(it) } + message = RawMessage(newErrorType, mutableListOf()) } - thisErrorLines?.add(line) + message?.lines?.add(line) } // flush - thisErrorLines?.let { errorLists.add(it) } - return errorLists + message?.let { rawMessages.add(it) } + return rawMessages } -private fun errorLinesToCompilerMessage(errorLines: List): AptosCompilerMessage { - val messageLine = errorLines.first() - val (_, message) = ERROR_START_RE.find(messageLine)!!.destructured - val spans = splitSpans(errorLines) - return AptosCompilerMessage(message, "error", spans) +private fun rawMessageToCompilerMessage(rawMessage: RawMessage): AptosCompilerMessage { + val messageLine = rawMessage.lines.first() + val message = when (rawMessage.errorType) { + ErrorType.ERROR -> ERROR_START_RE.find(messageLine)!!.destructured.component2() + ErrorType.WARNING -> messageLine.substringAfter("warning: ") + ErrorType.WARNING_LINT -> messageLine.substringAfter("warning: [lint] ") + } +// val (_, message) = ERROR_START_RE.find(messageLine)!!.destructured + val spans = splitSpans(rawMessage.lines) + return AptosCompilerMessage(message, rawMessage.errorType.severityLevel, spans) } private val FILE_POSITION_RE = diff --git a/src/main/kotlin/org/move/cli/externalLinter/ExternalLinter.kt b/src/main/kotlin/org/move/cli/externalLinter/ExternalLinter.kt index 3982823f8..0df850333 100644 --- a/src/main/kotlin/org/move/cli/externalLinter/ExternalLinter.kt +++ b/src/main/kotlin/org/move/cli/externalLinter/ExternalLinter.kt @@ -5,9 +5,9 @@ package org.move.cli.externalLinter -enum class ExternalLinter(val title: String) { - COMPILER("Aptos Compiler"); -// LINTER("Move Linter"); +enum class ExternalLinter(val title: String, val command: String) { + COMPILER("Aptos Compiler", "compile"), + LINTER("Aptos Linter", "lint"); override fun toString(): String = title diff --git a/src/main/kotlin/org/move/cli/externalLinter/MvExternalLinterConfigurable.kt b/src/main/kotlin/org/move/cli/externalLinter/MvExternalLinterConfigurable.kt index c01817890..8c55c10a4 100644 --- a/src/main/kotlin/org/move/cli/externalLinter/MvExternalLinterConfigurable.kt +++ b/src/main/kotlin/org/move/cli/externalLinter/MvExternalLinterConfigurable.kt @@ -26,6 +26,11 @@ class MvExternalLinterConfigurable(val project: Project): BoundConfigurable("Ext val settings = project.externalLinterSettings val state = settings.state.copy() + row { + checkBox("Run external linter on the fly") + .comment("Adds code highlighting based on the external linter results. May affect the IDE performance") + .bindSelected(state::runOnTheFly) + } row("External tool:") { comboBox(EnumComboBoxModel(ExternalLinter::class.java)) .comment("External tool for additional code analysis") @@ -35,7 +40,9 @@ class MvExternalLinterConfigurable(val project: Project): BoundConfigurable("Ext row("Additional arguments:") { fullWidthCell(additionalArguments) .resizableColumn() - .comment("Additional arguments to pass to aptos move compile command") + .comment( + "Additional arguments to pass to aptos move compile / aptos move lint" + ) .bind( componentGet = { it.text }, componentSet = { component, value -> component.text = value }, @@ -52,11 +59,6 @@ class MvExternalLinterConfigurable(val project: Project): BoundConfigurable("Ext ) } - row { - checkBox("Run external linter to analyze code on the fly") - .comment("Adds code highlighting based on the external linter results. May affect the IDE performance") - .bindSelected(state::runOnTheFly) - } separator() row { checkBox("Prevent duplicate errors") diff --git a/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt b/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt index b5e201ed9..e3f07dc15 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/aptos/Aptos.kt @@ -12,11 +12,11 @@ import com.intellij.openapi.vfs.VirtualFile import com.intellij.util.execution.ParametersListUtil import org.move.cli.MoveProject import org.move.cli.MvConstants +import org.move.cli.externalLinter.ExternalLinter import org.move.cli.runConfigurations.AptosCommandLine import org.move.cli.settings.moveSettings import org.move.openapiext.* import org.move.openapiext.common.isUnitTestMode -import org.move.stdext.CollectionBuilder import org.move.stdext.RsResult import org.move.stdext.RsResult.Err import org.move.stdext.RsResult.Ok @@ -70,7 +70,7 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp val commandLine = AptosCommandLine( subCommand = "move compile", - arguments = compileArguments(project), + arguments = compilerArguments(project), workingDirectory = packageRoot ) return executeAptosCommandLine(commandLine, colored = true, runner = runner) @@ -78,17 +78,28 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp fun checkProject( project: Project, - args: AptosCompileArgs + linterArgs: AptosExternalLinterArgs ): RsResult { -// val useClippy = args.linter == ExternalLinter.CLIPPY -// && !checkNeedInstallClippy(project, args.cargoProjectDirectory) -// val checkCommand = if (useClippy) "clippy" else "check" - val extraArguments = ParametersListUtil.parse(args.extraArguments) + val lintCommand = linterArgs.linter.command + val extraArguments = ParametersListUtil.parse(linterArgs.extraArguments) + val arguments = when (linterArgs.linter) { + ExternalLinter.COMPILER -> compilerArguments(project, extraArguments) + ExternalLinter.LINTER -> { + buildList { + addAll(extraArguments) + if (project.moveSettings.skipFetchLatestGitDeps + && "--skip-fetch-latest-git-deps" !in extraArguments + ) { + add("--skip-fetch-latest-git-deps") + } + } + } + } val commandLine = AptosCommandLine( - "move compile", - arguments = compileArguments(project) { addAll(extraArguments) }, - args.moveProjectDirectory, + "move $lintCommand", + arguments, + linterArgs.moveProjectDirectory, ) return executeCommandLine(commandLine).ignoreExitCode() } @@ -199,18 +210,17 @@ data class Aptos(val cliLocation: Path, val parentDisposable: Disposable?): Disp return Ok(aptosProcessOutput) } - private fun compileArguments( + private fun compilerArguments( project: Project, - builder: (CollectionBuilder.() -> Unit)? = null + extraArguments: List = emptyList(), ): List { val settings = project.moveSettings - val initialArguments = buildList { builder?.let { this.it() } } return buildList { - addAll(initialArguments) - if (settings.enableMove2 && "--move-2" !in initialArguments) { + addAll(extraArguments) + if (settings.enableMove2 && "--move-2" !in extraArguments) { add("--move-2") } - if (settings.skipFetchLatestGitDeps && "--skip-fetch-latest-git-deps" !in initialArguments) { + if (settings.skipFetchLatestGitDeps && "--skip-fetch-latest-git-deps" !in extraArguments) { add("--skip-fetch-latest-git-deps") } } diff --git a/src/main/kotlin/org/move/cli/runConfigurations/aptos/AptosCompileArgs.kt b/src/main/kotlin/org/move/cli/runConfigurations/aptos/AptosExternalLinterArgs.kt similarity index 87% rename from src/main/kotlin/org/move/cli/runConfigurations/aptos/AptosCompileArgs.kt rename to src/main/kotlin/org/move/cli/runConfigurations/aptos/AptosExternalLinterArgs.kt index 1f5e355a6..7558005ce 100644 --- a/src/main/kotlin/org/move/cli/runConfigurations/aptos/AptosCompileArgs.kt +++ b/src/main/kotlin/org/move/cli/runConfigurations/aptos/AptosExternalLinterArgs.kt @@ -6,7 +6,7 @@ import org.move.cli.externalLinter.externalLinterSettings import org.move.cli.settings.moveSettings import java.nio.file.Path -data class AptosCompileArgs( +data class AptosExternalLinterArgs( val linter: ExternalLinter, val moveProjectDirectory: Path, val extraArguments: String, @@ -15,7 +15,7 @@ data class AptosCompileArgs( val skipLatestGitDeps: Boolean, ) { companion object { - fun forMoveProject(moveProject: MoveProject): AptosCompileArgs { + fun forMoveProject(moveProject: MoveProject): AptosExternalLinterArgs { val linterSettings = moveProject.project.externalLinterSettings val moveSettings = moveProject.project.moveSettings @@ -23,7 +23,7 @@ data class AptosCompileArgs( val enviroment = linterSettings.envs val workingDirectory = moveProject.workingDirectory - return AptosCompileArgs( + return AptosExternalLinterArgs( linterSettings.tool, workingDirectory, additionalArguments, diff --git a/src/main/kotlin/org/move/ide/annotator/AptosCompilerMessage.kt b/src/main/kotlin/org/move/ide/annotator/AptosCompilerMessage.kt index 52a653deb..6036f47b2 100644 --- a/src/main/kotlin/org/move/ide/annotator/AptosCompilerMessage.kt +++ b/src/main/kotlin/org/move/ide/annotator/AptosCompilerMessage.kt @@ -5,7 +5,7 @@ import com.intellij.openapi.util.TextRange import org.jetbrains.annotations.TestOnly data class AptosCompilerMessage( - val message: String, + val text: String, val severityLevel: String, val spans: List ) { @@ -18,7 +18,7 @@ data class AptosCompilerMessage( } fun toTestString(): String { - return "$severityLevel: '$message' at ${mainSpan?.toTestString()}" + return "$severityLevel: '$text' at ${mainSpan?.toTestString()}" } companion object { diff --git a/src/main/kotlin/org/move/ide/annotator/RsExternalLinterPass.kt b/src/main/kotlin/org/move/ide/annotator/RsExternalLinterPass.kt index 4d4bac04a..04af94e9d 100644 --- a/src/main/kotlin/org/move/ide/annotator/RsExternalLinterPass.kt +++ b/src/main/kotlin/org/move/ide/annotator/RsExternalLinterPass.kt @@ -33,7 +33,7 @@ import com.intellij.psi.PsiFile import com.intellij.util.ui.update.MergingUpdateQueue import com.intellij.util.ui.update.Update import org.move.cli.externalLinter.externalLinterSettings -import org.move.cli.runConfigurations.aptos.AptosCompileArgs +import org.move.cli.runConfigurations.aptos.AptosExternalLinterArgs import org.move.cli.runConfigurations.aptos.workingDirectory import org.move.cli.settings.getAptosCli import org.move.ide.notifications.RsExternalLinterSlowRunNotifier @@ -66,7 +66,7 @@ class RsExternalLinterPass( .also { Disposer.register(moduleOrProject, it) } val aptos = myProject.getAptosCli(parentDisposable = disposable) ?: return - val args = AptosCompileArgs.forMoveProject(moveProject) + val args = AptosExternalLinterArgs.forMoveProject(moveProject) annotationInfo = RsExternalLinterUtils.checkLazily( aptos, myProject, diff --git a/src/main/kotlin/org/move/ide/annotator/RsExternalLinterUtils.kt b/src/main/kotlin/org/move/ide/annotator/RsExternalLinterUtils.kt index 33a29d67e..539fd852e 100644 --- a/src/main/kotlin/org/move/ide/annotator/RsExternalLinterUtils.kt +++ b/src/main/kotlin/org/move/ide/annotator/RsExternalLinterUtils.kt @@ -29,7 +29,7 @@ import org.move.cli.externalLinter.RsExternalLinterWidget import org.move.cli.externalLinter.externalLinterSettings import org.move.cli.externalLinter.parseCompilerErrors import org.move.cli.runConfigurations.aptos.Aptos -import org.move.cli.runConfigurations.aptos.AptosCompileArgs +import org.move.cli.runConfigurations.aptos.AptosExternalLinterArgs import org.move.ide.annotator.RsExternalLinterFilteredMessage.Companion.filterMessage import org.move.ide.annotator.RsExternalLinterUtils.TEST_MESSAGE import org.move.ide.notifications.logOrShowBalloon @@ -62,10 +62,13 @@ object RsExternalLinterUtils { aptosCli: Aptos, project: Project, workingDirectory: Path, - args: AptosCompileArgs + linterArgs: AptosExternalLinterArgs ): Lazy { checkReadAccessAllowed() - return externalLinterLazyResultCache.getOrPut(project, Key(aptosCli, workingDirectory, args)) { + return externalLinterLazyResultCache.getOrPut( + project, + Key(aptosCli, workingDirectory, linterArgs) + ) { // We want to run external linter in background thread and *without* read action. // And also we want to cache result of external linter because it is cargo package-global, // but annotator can be invoked separately for each file. @@ -84,7 +87,7 @@ object RsExternalLinterUtils { lazy { // This code will be executed out of read action in background thread if (!isUnitTestMode) checkReadAccessNotAllowed() - checkWrapped(aptosCli, project, args) + checkWrapped(aptosCli, project, linterArgs) } } } @@ -92,7 +95,7 @@ object RsExternalLinterUtils { private fun checkWrapped( aptosCli: Aptos, project: Project, - args: AptosCompileArgs + linterArgs: AptosExternalLinterArgs ): RsExternalLinterResult? { val widget = WriteAction.computeAndWait { saveAllDocumentsAsTheyAre() @@ -102,11 +105,15 @@ object RsExternalLinterUtils { val future = CompletableFuture() val task = - object: Task.Backgroundable(project, "Analyzing project with ${args.linter.title}...", true) { + object: Task.Backgroundable( + project, + "Analyzing project with ${linterArgs.linter.title}...", + true + ) { override fun run(indicator: ProgressIndicator) { widget?.inProgress = true - future.complete(check(project, aptosCli, args)) + future.complete(check(project, aptosCli, linterArgs)) } override fun onFinished() { @@ -119,13 +126,12 @@ object RsExternalLinterUtils { private fun check( project: Project, - aptosCli: Aptos, - aptosCompileArgs: AptosCompileArgs + aptos: Aptos, + linterArgs: AptosExternalLinterArgs ): RsExternalLinterResult? { ProgressManager.checkCanceled() val started = Instant.now() - val output = aptosCli - .checkProject(project, aptosCompileArgs) + val output = aptos.checkProject(project, linterArgs) .unwrapOrElse { e -> LOG.error(e) return null @@ -142,7 +148,7 @@ object RsExternalLinterUtils { private data class Key( val aptosCli: Aptos, val workingDirectory: Path, - val args: AptosCompileArgs + val args: AptosExternalLinterArgs ) private val externalLinterLazyResultCache = @@ -169,16 +175,12 @@ fun MessageBus.createDisposableOnAnyPsiChange(): CheckedDisposable { fun MutableList.addHighlightsForFile( file: MoveFile, annotationResult: RsExternalLinterResult, -// minApplicability: Applicability ) { -// val cargoPackageOrigin = file.containing?.origin -// if (cargoPackageOrigin != PackageOrigin.WORKSPACE) return - val doc = file.viewProvider.document ?: error("Can't find document for $file in external linter") val skipIdeErrors = file.project.externalLinterSettings.skipErrorsKnownToIde - val filteredMessages = annotationResult.messages + val filteredMessages = annotationResult.compilerMessages .mapNotNull { message -> filterMessage(file, doc, message, skipIdeErrors) } // Cargo can duplicate some error messages when `--all-targets` attribute is used .distinct() @@ -192,18 +194,6 @@ fun MutableList.addHighlightsForFile( .range(message.textRange) .needsUpdateOnTyping(true) -// message.quickFixes -// .singleOrNull { it.applicability <= minApplicability } -// ?.let { fix -> -// val element = fix.startElement ?: fix.endElement -// val lint = message.lint -// val actions = if (element != null && lint != null) createSuppressFixes(element, lint) else emptyArray() -// val options = convertBatchToSuppressIntentionActions(actions).toList() -// val displayName = "Aptos external linter" -// val key = HighlightDisplayKey.findOrRegister(APTOS_EXTERNAL_LINTER_ID, displayName) -// highlightBuilder.registerFix(fix, options, displayName, fix.textRange, key) -// } - highlightBuilder.create()?.let(::add) } } @@ -216,11 +206,9 @@ private fun convertSeverity(severity: HighlightSeverity): HighlightInfoType = wh else -> HighlightInfoType.INFORMATION } -private const val APTOS_EXTERNAL_LINTER_ID: String = "AptosExternalLinterOptions" - class RsExternalLinterResult(commandOutput: List, val executionTime: Long) { - val messages: List = parseCompilerErrors(commandOutput) + val compilerMessages: List = parseCompilerErrors(commandOutput) } private data class RsExternalLinterFilteredMessage( @@ -228,8 +216,6 @@ private data class RsExternalLinterFilteredMessage( val textRange: TextRange, @Nls val message: String, @Nls val htmlTooltip: String, -// val lint: RsLint.ExternalLinterLint?, -// val quickFixes: List ) { companion object { private val LOG = logger() @@ -246,6 +232,7 @@ private data class RsExternalLinterFilteredMessage( val severity = when (message.severityLevel) { "error" -> HighlightSeverity.ERROR "warning" -> HighlightSeverity.WEAK_WARNING + "warning [lint]" -> HighlightSeverity.WEAK_WARNING else -> HighlightSeverity.INFORMATION } @@ -256,7 +243,7 @@ private data class RsExternalLinterFilteredMessage( // drop syntax errors val syntaxErrors = listOf("unexpected token") // val syntaxErrors = listOf("expected pattern", "unexpected token") - if (syntaxErrors.any { it in span.label.orEmpty() || it in message.message }) { + if (syntaxErrors.any { it in span.label.orEmpty() || it in message.text }) { return null } @@ -270,9 +257,11 @@ private data class RsExternalLinterFilteredMessage( "too many arguments", "the function takes", // missing fields "too few arguments", "missing fields", + // unused imports + "unused alias", ) - if (errorsToIgnore.any { it in message.message }) { - LOG.logOrShowBalloon("ignore compiler error: ${message.toTestString()}") + if (errorsToIgnore.any { it in message.text }) { + LOG.logOrShowBalloon("ignore external linter error", message.toTestString()) return null } } @@ -283,14 +272,14 @@ private data class RsExternalLinterFilteredMessage( val textRange = span.toTextRange(document) ?: return null val tooltip = buildString { - append(formatMessage(StringEscapeUtils.escapeHtml4(message.message)).escapeUrls()) + append(formatMessage(StringEscapeUtils.escapeHtml4(message.text)).escapeUrls()) // val code = message.code.formatAsLink() // if (code != null) { // append(" [$code]") // } with(mutableListOf()) { - if (span.label != null && !message.message.startsWith(span.label)) { + if (span.label != null && !message.text.startsWith(span.label)) { add(StringEscapeUtils.escapeHtml4(span.label)) } @@ -306,7 +295,7 @@ private data class RsExternalLinterFilteredMessage( return RsExternalLinterFilteredMessage( severity, textRange, - message.message.capitalized(), + message.text.capitalized(), tooltip, // message.code?.code?.let { RsLint.ExternalLinterLint(it) }, // message.collectQuickFixes(file, document) diff --git a/src/main/kotlin/org/move/ide/inspections/MvExternalLinterInspection.kt b/src/main/kotlin/org/move/ide/inspections/MvExternalLinterInspection.kt index 2210b065f..a3b748463 100644 --- a/src/main/kotlin/org/move/ide/inspections/MvExternalLinterInspection.kt +++ b/src/main/kotlin/org/move/ide/inspections/MvExternalLinterInspection.kt @@ -22,7 +22,7 @@ import com.intellij.psi.PsiFile import com.intellij.util.containers.ContainerUtil import org.move.cli.MoveProject import org.move.cli.moveProjectsService -import org.move.cli.runConfigurations.aptos.AptosCompileArgs +import org.move.cli.runConfigurations.aptos.AptosExternalLinterArgs import org.move.cli.runConfigurations.aptos.workingDirectory import org.move.cli.settings.getAptosCli import org.move.ide.annotator.RsExternalLinterResult @@ -124,7 +124,7 @@ class MvExternalLinterInspection: GlobalSimpleInspectionTool() { aptosCli, project, moveProject.workingDirectory, - AptosCompileArgs.forMoveProject(moveProject) + AptosExternalLinterArgs.forMoveProject(moveProject) ) } diff --git a/src/main/kotlin/org/move/ide/notifications/Utils.kt b/src/main/kotlin/org/move/ide/notifications/Utils.kt index 102774f7c..62b42fb44 100644 --- a/src/main/kotlin/org/move/ide/notifications/Utils.kt +++ b/src/main/kotlin/org/move/ide/notifications/Utils.kt @@ -33,6 +33,21 @@ fun Logger.logOrShowBalloon(@NotificationContent content: String, productionLeve } } +fun Logger.logOrShowBalloon( + title: String, + @NotificationContent content: String, + productionLevel: LogLevel = LogLevel.DEBUG +) { + when { + isUnitTestMode -> this.warn("BALLOON: $title - $content") + isDebugModeEnabled() -> { + this.warn(content) + showBalloonWithoutProject(title, content, INFORMATION) + } + else -> this.log("$title - $content", productionLevel) + } +} + fun Project.showBalloon( @NotificationTitle title: String, @NotificationContent content: String,