Kotlin Facet: Validate "additional arguments"

Validate consistency of "additional arguments" with respect to settings specified in other UI controls
This commit is contained in:
Alexey Sedunov
2017-03-14 21:18:24 +03:00
parent fa06965ed6
commit 88a394e892
4 changed files with 125 additions and 64 deletions
@@ -32,6 +32,8 @@ class CompilerSettings {
}
val CompilerSettings.additionalArgumentsAsList: List<String>
get() = StringUtil.splitHonorQuotes(additionalArguments, ' ').map {
if (it.startsWith('"')) StringUtil.unescapeChar(StringUtil.unquoteString(it), '"') else it
}
get() = splitArgumentString(additionalArguments)
fun splitArgumentString(arguments: String) = StringUtil.splitHonorQuotes(arguments, ' ').map {
if (it.startsWith('"')) StringUtil.unescapeChar(StringUtil.unquoteString(it), '"') else it
}
@@ -388,8 +388,12 @@ public class KotlinCompilerConfigurableTab implements SearchableConfigurable, Co
return getLanguageVersionOrDefault((String) apiVersionComboBox.getSelectedItem());
}
@Override
public void apply() throws ConfigurationException {
public void applyTo(
CommonCompilerArguments commonCompilerArguments,
K2JVMCompilerArguments k2jvmCompilerArguments,
K2JSCompilerArguments k2jsCompilerArguments,
CompilerSettings compilerSettings
) throws ConfigurationException {
if (isProjectSettings) {
boolean shouldInvalidateCaches =
commonCompilerArguments.languageVersion != getSelectedLanguageVersion() ||
@@ -450,6 +454,11 @@ public class KotlinCompilerConfigurableTab implements SearchableConfigurable, Co
BuildManager.getInstance().clearState(project);
}
@Override
public void apply() throws ConfigurationException {
applyTo(commonCompilerArguments, k2jvmCompilerArguments, k2jsCompilerArguments, compilerSettings);
}
@Override
public void reset() {
generateNoWarningsCheckBox.setSelected(commonCompilerArguments.suppressWarnings);
@@ -25,21 +25,20 @@ import com.intellij.ui.DocumentAdapter
import com.intellij.ui.HoverHyperlinkLabel
import com.intellij.util.ui.FormBuilder
import com.intellij.util.ui.ThreeStateCheckBox
import com.intellij.util.ui.UIUtil
import org.jetbrains.kotlin.cli.common.arguments.*
import org.jetbrains.kotlin.cli.common.parser.com.sampullara.cli.Argument
import org.jetbrains.kotlin.config.*
import org.jetbrains.kotlin.idea.compiler.configuration.*
import org.jetbrains.kotlin.utils.addToStdlib.firstIsInstanceOrNull
import java.awt.BorderLayout
import javax.swing.JComboBox
import javax.swing.JComponent
import javax.swing.JPanel
import javax.swing.*
import javax.swing.border.EmptyBorder
import javax.swing.event.DocumentEvent
class KotlinFacetEditorGeneralTab(
private val configuration: KotlinFacetConfiguration,
private val editorContext: FacetEditorContext,
validatorsManager: FacetValidatorsManager
private val validatorsManager: FacetValidatorsManager
) : FacetEditorTab() {
class EditorComponent(
private val project: Project,
@@ -164,20 +163,56 @@ class KotlinFacetEditorGeneralTab(
}
}
inner class CoroutineContradictionValidator : FacetEditorValidator() {
inner class ArgumentConsistencyValidator : FacetEditorValidator() {
override fun check(): ValidationResult {
val selectedOption = editor.compilerConfigurable.coroutineSupportComboBox.selectedItem as? LanguageFeature.State
?: return ValidationResult.OK
val parsedArguments = configuration.settings.compilerArguments?.javaClass?.newInstance()
?: return ValidationResult.OK
parseArguments(
editor.compilerConfigurable.additionalArgsOptionsField.text.split(Regex("\\s")).toTypedArray(),
parsedArguments,
true
)
val parsedOption = CoroutineSupport.byCompilerArgumentsOrNull(parsedArguments) ?: return ValidationResult.OK
if (parsedOption != selectedOption) {
return ValidationResult("Coroutine support setting specified as an additional argument differs from the one in \"Coroutines\" field", null)
val platform = editor.targetPlatformComboBox.selectedItem as TargetPlatformKind<*>? ?: return ValidationResult.OK
val primaryArguments = platform.createCompilerArguments().apply {
editor.compilerConfigurable.applyTo(
this,
this as? K2JVMCompilerArguments ?: K2JVMCompilerArguments(),
this as? K2JSCompilerArguments ?: K2JSCompilerArguments(),
CompilerSettings()
)
}
val argumentClass = primaryArguments.javaClass
val additionalArguments = argumentClass.newInstance().apply {
try {
parseArguments(splitArgumentString(editor.compilerConfigurable.additionalArgsOptionsField.text).toTypedArray(), this)
}
catch(e: IllegalArgumentException) {
return ValidationResult(e.message)
}
}
val emptyArguments = argumentClass.newInstance()
val fieldNamesToCheck = when (platform) {
is TargetPlatformKind.Jvm -> jvmUIExposedFields
is TargetPlatformKind.JavaScript -> jsUIExposedFields
else -> commonUIExposedFields
}
val fieldsToCheck = collectFieldsToCopy(argumentClass, false).filter { it.name in fieldNamesToCheck }
val overridingArguments = ArrayList<String>()
val redundantArguments = ArrayList<String>()
for (field in fieldsToCheck) {
val additionalValue = field[additionalArguments]
if (additionalValue != field[emptyArguments]) {
val argumentInfo = field.annotations.firstIsInstanceOrNull<Argument>() ?: continue
val addTo = if (additionalValue != field[primaryArguments]) overridingArguments else redundantArguments
addTo += "<strong>" + argumentInfo.value + "</strong>"
}
}
if (overridingArguments.isNotEmpty() || redundantArguments.isNotEmpty()) {
val message = buildString {
if (overridingArguments.isNotEmpty()) {
append("Following arguments override facet settings: ${overridingArguments.joinToString()}")
}
if (redundantArguments.isNotEmpty()) {
if (isNotEmpty()) {
append("<br/>")
}
append("Following arguments are redundant: ${redundantArguments.joinToString()}")
}
}
return ValidationResult(message)
}
return ValidationResult.OK
@@ -188,7 +223,7 @@ class KotlinFacetEditorGeneralTab(
private val libraryValidator: FrameworkLibraryValidator
private val versionValidator = VersionValidator()
private val coroutineValidator = CoroutineContradictionValidator()
private val coroutineValidator = ArgumentConsistencyValidator()
init {
libraryValidator = FrameworkLibraryValidatorWithDynamicDescription(
@@ -201,38 +236,48 @@ class KotlinFacetEditorGeneralTab(
validatorsManager.registerValidator(versionValidator)
validatorsManager.registerValidator(coroutineValidator)
editor.compilerConfigurable.languageVersionComboBox.addActionListener {
validatorsManager.validate()
with(editor.compilerConfigurable) {
generateNoWarningsCheckBox.validateOnChange()
additionalArgsOptionsField.textField.validateOnChange()
generateSourceMapsCheckBox.validateOnChange()
outputPrefixFile.textField.validateOnChange()
outputPostfixFile.textField.validateOnChange()
outputDirectory.textField.validateOnChange()
copyRuntimeFilesCheckBox.validateOnChange()
moduleKindComboBox.validateOnChange()
languageVersionComboBox.validateOnChange()
apiVersionComboBox.validateOnChange()
coroutineSupportComboBox.validateOnChange()
}
editor.compilerConfigurable.apiVersionComboBox.addActionListener {
validatorsManager.validate()
}
editor.targetPlatformComboBox.addActionListener {
validatorsManager.validate()
}
editor.compilerConfigurable.coroutineSupportComboBox.addActionListener {
validatorsManager.validate()
}
editor.compilerConfigurable.additionalArgsOptionsField.textField.document.addDocumentListener(
object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
val text = e.document.getText(0, e.document.length)
if (text.contains("-Xcoroutine")) {
validatorsManager.validate()
}
}
}
)
editor.targetPlatformComboBox.validateOnChange()
editor.updateCompilerConfigurable()
reset()
}
private fun JTextField.validateOnChange() {
document.addDocumentListener(
object : DocumentAdapter() {
override fun textChanged(e: DocumentEvent) {
doValidate()
}
}
)
}
private fun AbstractButton.validateOnChange() {
addChangeListener { doValidate() }
}
private fun JComboBox<*>.validateOnChange() {
addActionListener { doValidate() }
}
private fun doValidate() {
validatorsManager.validate()
}
override fun isModified(): Boolean {
if (editor.useProjectSettingsCheckBox.isSelected != configuration.settings.useProjectSettings) return true
if (editor.targetPlatformComboBox.selectedItem != configuration.settings.targetPlatformKind) return true
@@ -133,21 +133,26 @@ fun KotlinFacet.configureFacet(
// "Primary" fields are written to argument beans directly and thus not presented in the "additional arguments" string
// Update these lists when facet/project settings UI changes
private val commonPrimaryFields = listOf("languageVersion",
"apiVersion",
"suppressWarnings",
"coroutinesEnable",
"coroutinesWarn",
"coroutinesError",
"pluginClasspaths",
"pluginOptions")
private val jvmPrimaryFields = commonPrimaryFields +
listOf("jvmTarget")
private val jsPrimaryFields = commonPrimaryFields +
listOf("sourceMap",
"outputPrefix",
"outputPostfix",
"moduleKind")
val commonUIExposedFields = listOf("languageVersion",
"apiVersion",
"suppressWarnings",
"coroutinesEnable",
"coroutinesWarn",
"coroutinesError")
private val commonUIHiddenFields = listOf("pluginClasspaths",
"pluginOptions")
private val commonPrimaryFields = commonUIExposedFields + commonUIHiddenFields
private val jvmSpecificUIExposedFields = listOf("jvmTarget")
val jvmUIExposedFields = commonUIExposedFields + jvmSpecificUIExposedFields
private val jvmPrimaryFields = commonPrimaryFields + jvmSpecificUIExposedFields
private val jsSpecificUIExposedFields = listOf("sourceMap",
"outputPrefix",
"outputPostfix",
"moduleKind")
val jsUIExposedFields = commonUIExposedFields + jsSpecificUIExposedFields
private val jsPrimaryFields = commonPrimaryFields + jsSpecificUIExposedFields
private val CommonCompilerArguments.primaryFields: List<String>
get() = when (this) {