diff --git a/gradle.properties b/gradle.properties index 1957193..629536b 100644 --- a/gradle.properties +++ b/gradle.properties @@ -3,7 +3,7 @@ pluginGroup=com.semgrep.idea pluginName=semgrep pluginRepositoryUrl=https://github.com/returntocorp/semgrep-intellij # SemVer format -> https://semver.org -pluginVersion=0.1.3 +pluginVersion=0.1.4 # Supported build number ranges and IntelliJ Platform versions -> https://plugins.jetbrains.com/docs/intellij/build-number-ranges.html pluginSinceBuild=232 pluginUntilBuild=232.* diff --git a/src/main/kotlin/com/semgrep/idea/actions/LoginAction.kt b/src/main/kotlin/com/semgrep/idea/actions/LoginAction.kt index 9ea8d83..c59a0ce 100644 --- a/src/main/kotlin/com/semgrep/idea/actions/LoginAction.kt +++ b/src/main/kotlin/com/semgrep/idea/actions/LoginAction.kt @@ -7,7 +7,7 @@ import com.intellij.platform.lsp.api.LspServer import com.semgrep.idea.lsp.custom_notifications.LoginFinishRequest import com.semgrep.idea.lsp.custom_requests.LoginRequest -class LoginAction(private val notification: Notification? = null) : LspAction("Sign In to Semgrep Code") { +class LoginAction(private val notification: Notification? = null) : LspAction("Sign In with Semgrep") { override fun actionPerformed(e: AnActionEvent, servers: List) { val loginRequest = LoginRequest(servers.first()) val response = (servers.first() as LspServer).requestExecutor.sendRequestSync(loginRequest) ?: return diff --git a/src/main/kotlin/com/semgrep/idea/actions/LogoutAction.kt b/src/main/kotlin/com/semgrep/idea/actions/LogoutAction.kt index 6a9eef2..8c7a350 100644 --- a/src/main/kotlin/com/semgrep/idea/actions/LogoutAction.kt +++ b/src/main/kotlin/com/semgrep/idea/actions/LogoutAction.kt @@ -3,7 +3,7 @@ package com.semgrep.idea.actions import com.intellij.openapi.actionSystem.AnActionEvent import com.semgrep.idea.lsp.custom_notifications.LogoutNotifcation -class LogoutAction : LspAction("Sign out of Semgrep Code") { +class LogoutAction : LspAction("Sign out of Semgrep") { override fun actionPerformed(e: AnActionEvent, servers: List) { servers.map { LogoutNotifcation(it).sendNotification() diff --git a/src/main/kotlin/com/semgrep/idea/lsp/SemgrepInstaller.kt b/src/main/kotlin/com/semgrep/idea/lsp/SemgrepInstaller.kt index 99a65e6..1955fd2 100644 --- a/src/main/kotlin/com/semgrep/idea/lsp/SemgrepInstaller.kt +++ b/src/main/kotlin/com/semgrep/idea/lsp/SemgrepInstaller.kt @@ -73,7 +73,7 @@ object SemgrepInstaller { return if (result == "") null else result } - fun isWindows():Boolean{ + fun isWindows(): Boolean { return SystemInfo.isWindows } diff --git a/src/main/kotlin/com/semgrep/idea/lsp/SemgrepLspServerListener.kt b/src/main/kotlin/com/semgrep/idea/lsp/SemgrepLspServerListener.kt index 9be3b19..5d939dc 100644 --- a/src/main/kotlin/com/semgrep/idea/lsp/SemgrepLspServerListener.kt +++ b/src/main/kotlin/com/semgrep/idea/lsp/SemgrepLspServerListener.kt @@ -30,7 +30,8 @@ class SemgrepLspServerListener(val project: Project) : LspServerListener { val current = SemgrepInstaller.getCliVersion() val needed = SemVer.parseFromText(SemgrepLspServer.MIN_SEMGREP_VERSION) val latest = SemgrepInstaller.getMostUpToDateCliVersion() - if (current != null) { + val settings = AppState.getInstance() + if (current != null && !settings.lspSettings.ignoreCliVersion) { if (needed != null && current < needed) { SemgrepNotifier(project).notifyUpdateNeeded(needed, current) } else if (latest != null && current < latest) { diff --git a/src/main/kotlin/com/semgrep/idea/settings/AppSettingsComponent.kt b/src/main/kotlin/com/semgrep/idea/settings/AppSettingsComponent.kt index 0c886ce..848ceaa 100644 --- a/src/main/kotlin/com/semgrep/idea/settings/AppSettingsComponent.kt +++ b/src/main/kotlin/com/semgrep/idea/settings/AppSettingsComponent.kt @@ -1,39 +1,129 @@ package com.semgrep.idea.settings import com.intellij.openapi.ui.ComboBox +import com.intellij.openapi.ui.DialogPanel +import com.intellij.ui.components.JBCheckBox import com.intellij.ui.components.JBTextField -import com.intellij.util.ui.FormBuilder -import javax.swing.JPanel +import com.intellij.ui.dsl.builder.* class AppSettingsComponent(settings: SemgrepLspSettings) { - private var panel: JPanel? = null + private var panel: DialogPanel? = null // Really we should use reflection to generate this, but I'm lazy private val traceChooser = ComboBox(arrayOf("off", "messages", "verbose")) private val pathTextField = JBTextField() + private val ignoreCliVersion = JBCheckBox() + private val doHover = JBCheckBox() + + // Scan Settings + private val configuration = JBTextField() + private val exclude = JBTextField() + private val include = JBTextField() + private val jobs = JBTextField("1") + private val maxMemory = JBTextField("0") + private val maxTargetBytes = JBTextField("1000000") + private val timeout = JBTextField("30") + private val timeoutThreshold = JBTextField("3") + private val onlyGitDirty = JBCheckBox() + + // Metric Settings + private val metricsEnabled = JBCheckBox() + private var lspSettings: SemgrepLspSettings = AppState.getInstance().lspSettings init { - panel = FormBuilder.createFormBuilder().apply { - addLabeledComponent("Trace Level", traceChooser) - addLabeledComponent("Semgrep Path", pathTextField) - addComponentFillVertically(JPanel(), 0) - }.panel - setFieldValues(settings) - } + panel = panel { + group("General") { + row("Trace Level") { + comboBox(TraceLevel.values().toList()) + .bindItem(lspSettings.trace::server.toNullableProperty()) + .comment("Traces the communication between IntelliJ and the language server.") + } + row("Semgrep Path") { + textField() + .bindText(lspSettings::path) + .comment("Path to the semgrep executable.") + } + row { + checkBox("Ignore Cli Version") + .bindSelected(lspSettings::ignoreCliVersion) + .comment("Ignore CLI Version, and enable all extension features (Warning: this is mainly for extension development, and can break things if enabled!)") + } + row { + checkBox("Do Hover") + .bindSelected(lspSettings::doHover) + .comment("Enable hovering for AST node viewing (requires restart)") + } + } + group("Scan") { + row("Configuration") { + textField() + .bindText({ lspSettings.scan.configuration.joinToString(",") }, { s -> + lspSettings.scan.configuration = + s.split(",").map { it.trim() }.toTypedArray() + }) + .comment("Each item can be a YAML configuration file, directory of YAML files ending in .yml | .yaml, URL of a configuration file, or Semgrep registry entry name. Use \"auto\" to automatically obtain rules tailored to this project; your project URL will be used to log in to the Semgrep registry. Must be a comma-separated list.") - fun getPanel(): JPanel? { - return panel - } + } + row("Exclude") { + textField() + .bindText({ lspSettings.scan.exclude.joinToString(",") }, { s -> + lspSettings.scan.exclude = + s.split(",").map { it.trim() }.toTypedArray() + }) + .comment("List of files or directories to exclude. Must be a comma-separated list.") + } + row("Include") { + textField() + .bindText({ lspSettings.scan.include.joinToString(",") }, { s -> + lspSettings.scan.include = + s.split(",").map { it.trim() }.toTypedArray() + }) + .comment("List of files or directories to include. Must be a comma-separated list.") + } + row("Jobs") { + spinner(0..100) + .bindIntValue(lspSettings.scan::jobs) + .comment("Number of parallel jobs to run.") + } + row("Max Memory") { + spinner(0..1000) + .bindIntValue(lspSettings.scan::maxMemory) + .comment("Maximum memory to use in megabytes.") + } + row("Max Target Bytes") { + spinner(0..(Math.pow(10.0, 10.0).toInt())) + .bindIntValue(lspSettings.scan::maxTargetBytes) + .comment("Maximum size of target in bytes to scan.") + } + row("Timeout") { + spinner(0..1000) + .bindIntValue(lspSettings.scan::timeout) + .comment("Maximum time to scan in seconds.") + } + row("Timeout Threshold") { + spinner(0..1000) + .bindIntValue(lspSettings.scan::timeoutThreshold) + .comment("Maximum number of rules that can timeout on a file before the file is skipped. If set to 0 will not have limit. Defaults to 3.") + } + row { + checkBox("Only Git Dirty") + .bindSelected(lspSettings.scan::onlyGitDirty) + .comment("Only scan lines changed since the last commit") + } + } + group("Metrics") { + row { + checkBox("Enabled") + .bindSelected(lspSettings.metrics::enabled) + .comment("Enable metrics reporting") + } + } + } - fun getSettings(): SemgrepLspSettings { - val traceLevelStr = traceChooser.selectedItem as String - val traceLevel = TraceLevel.valueOf(traceLevelStr.uppercase()) - return SemgrepLspSettings(trace = SemgrepLspSettings.Trace(traceLevel), path = pathTextField.text) } - fun setFieldValues(settings: SemgrepLspSettings) { - traceChooser.selectedItem = settings.trace.server.name.lowercase() - pathTextField.text = settings.path + fun getPanel(): DialogPanel? { + return panel } } \ No newline at end of file diff --git a/src/main/kotlin/com/semgrep/idea/settings/AppSettingsConfigurable.kt b/src/main/kotlin/com/semgrep/idea/settings/AppSettingsConfigurable.kt index 9cfa9ad..a58b495 100644 --- a/src/main/kotlin/com/semgrep/idea/settings/AppSettingsConfigurable.kt +++ b/src/main/kotlin/com/semgrep/idea/settings/AppSettingsConfigurable.kt @@ -14,19 +14,15 @@ class AppSettingsConfigurable : Configurable { } override fun isModified(): Boolean { - return settingsComponent?.getSettings() != AppState.getInstance().lspSettings + return settingsComponent?.getPanel()?.isModified() ?: false } override fun apply() { - val appSettingsState = AppState.getInstance() - val newSettings = settingsComponent?.getSettings() ?: return - appSettingsState.lspSettings = newSettings - settingsComponent?.setFieldValues(appSettingsState.lspSettings) + settingsComponent?.getPanel()?.apply() } override fun reset() { - val settings = AppState.getInstance().lspSettings - settingsComponent?.setFieldValues(settings) + settingsComponent?.getPanel()?.reset() } override fun getDisplayName(): String { diff --git a/src/main/kotlin/com/semgrep/idea/settings/LspSettings.kt b/src/main/kotlin/com/semgrep/idea/settings/LspSettings.kt index 429f6ca..0185b31 100644 --- a/src/main/kotlin/com/semgrep/idea/settings/LspSettings.kt +++ b/src/main/kotlin/com/semgrep/idea/settings/LspSettings.kt @@ -1,10 +1,8 @@ package com.semgrep.idea.settings import com.intellij.ide.plugins.PluginManager -import com.intellij.internal.statistic.DeviceIdManager -import com.intellij.internal.statistic.DeviceIdManager.DeviceIdToken import com.intellij.openapi.extensions.PluginId -import java.util.UUID +import java.util.* enum class TraceLevel { OFF, diff --git a/src/main/kotlin/com/semgrep/idea/ui/SemgrepInstallBanner.kt b/src/main/kotlin/com/semgrep/idea/ui/SemgrepInstallBanner.kt index b712add..6ba612d 100644 --- a/src/main/kotlin/com/semgrep/idea/ui/SemgrepInstallBanner.kt +++ b/src/main/kotlin/com/semgrep/idea/ui/SemgrepInstallBanner.kt @@ -40,7 +40,7 @@ class SemgrepInstallBannerProvider : EditorNotifications.Provider