CLI: refactor argument validation out of parsing
This commit is contained in:
+1
-10
@@ -109,16 +109,7 @@ public abstract class CommonCompilerArguments implements Serializable {
|
||||
|
||||
public List<String> freeArgs = new SmartList<>();
|
||||
|
||||
public List<String> unknownArgs = new SmartList<>();
|
||||
|
||||
public List<String> unknownExtraFlags = new SmartList<>();
|
||||
|
||||
// Names of extra (-X...) arguments which have been passed in an obsolete form ("-Xaaa bbb", instead of "-Xaaa=bbb")
|
||||
public List<String> extraArgumentsPassedInObsoleteForm = new SmartList<>();
|
||||
|
||||
// Non-boolean arguments which have been passed multiple times, possibly with different values.
|
||||
// The key in the map is the name of the argument, the value is the last passed value.
|
||||
public Map<String, String> duplicateArguments = new LinkedHashMap<>();
|
||||
public ArgumentParseErrors errors = new ArgumentParseErrors();
|
||||
|
||||
@NotNull
|
||||
public static CommonCompilerArguments createDefaultInstance() {
|
||||
|
||||
-10
@@ -20,16 +20,6 @@ import java.lang.reflect.Field
|
||||
import java.lang.reflect.Modifier
|
||||
import java.util.*
|
||||
|
||||
@JvmOverloads
|
||||
fun <A : CommonCompilerArguments> parseArguments(args: Array<String>, arguments: A, ignoreInvalidArguments: Boolean = false) {
|
||||
parseCommandLineArguments(args, arguments)
|
||||
|
||||
val invalidArgument = arguments.unknownArgs.firstOrNull()?.takeUnless { ignoreInvalidArguments }
|
||||
if (invalidArgument != null) {
|
||||
throw IllegalArgumentException("Invalid argument: $invalidArgument")
|
||||
}
|
||||
}
|
||||
|
||||
fun <T : Any> copyBean(bean: T) = copyFields(bean, bean::class.java.newInstance(), true, collectFieldsToCopy(bean::class.java, false))
|
||||
|
||||
fun <From : Any, To : From> mergeBeans(from: From, to: To): To {
|
||||
|
||||
+42
-8
@@ -16,7 +16,9 @@
|
||||
|
||||
package org.jetbrains.kotlin.cli.common.arguments
|
||||
|
||||
import com.intellij.util.SmartList
|
||||
import java.lang.reflect.Field
|
||||
import java.util.LinkedHashMap
|
||||
|
||||
annotation class Argument(
|
||||
val value: String,
|
||||
@@ -32,6 +34,21 @@ val Argument.isAdvanced: Boolean
|
||||
private val ADVANCED_ARGUMENT_PREFIX = "-X"
|
||||
private val FREE_ARGS_DELIMITER = "--"
|
||||
|
||||
class ArgumentParseErrors {
|
||||
val unknownArgs: MutableList<String> = SmartList<String>()
|
||||
|
||||
val unknownExtraFlags: MutableList<String> = SmartList<String>()
|
||||
|
||||
// Names of extra (-X...) arguments which have been passed in an obsolete form ("-Xaaa bbb", instead of "-Xaaa=bbb")
|
||||
val extraArgumentsPassedInObsoleteForm: MutableList<String> = SmartList<String>()
|
||||
|
||||
// Non-boolean arguments which have been passed multiple times, possibly with different values.
|
||||
// The key in the map is the name of the argument, the value is the last passed value.
|
||||
val duplicateArguments: MutableMap<String, String> = LinkedHashMap<String, String>()
|
||||
|
||||
var argumentWithoutValue: String? = null
|
||||
}
|
||||
|
||||
// Parses arguments in the passed [result] object, or throws an [IllegalArgumentException] with the message to be displayed to the user
|
||||
fun <A : CommonCompilerArguments> parseCommandLineArguments(args: Array<String>, result: A) {
|
||||
data class ArgumentField(val field: Field, val argument: Argument)
|
||||
@@ -41,11 +58,12 @@ fun <A : CommonCompilerArguments> parseCommandLineArguments(args: Array<String>,
|
||||
if (argument != null) ArgumentField(field, argument) else null
|
||||
}
|
||||
|
||||
val errors = result.errors
|
||||
val visitedArgs = mutableSetOf<String>()
|
||||
var freeArgsStarted = false
|
||||
|
||||
var i = 0
|
||||
while (i < args.size) {
|
||||
loop@ while (i < args.size) {
|
||||
val arg = args[i++]
|
||||
|
||||
if (freeArgsStarted) {
|
||||
@@ -65,8 +83,8 @@ fun <A : CommonCompilerArguments> parseCommandLineArguments(args: Array<String>,
|
||||
|
||||
if (argumentField == null) {
|
||||
when {
|
||||
arg.startsWith(ADVANCED_ARGUMENT_PREFIX) -> result.unknownExtraFlags.add(arg)
|
||||
arg.startsWith("-") -> result.unknownArgs.add(arg)
|
||||
arg.startsWith(ADVANCED_ARGUMENT_PREFIX) -> errors.unknownExtraFlags.add(arg)
|
||||
arg.startsWith("-") -> errors.unknownArgs.add(arg)
|
||||
else -> result.freeArgs.add(arg)
|
||||
}
|
||||
continue
|
||||
@@ -80,17 +98,20 @@ fun <A : CommonCompilerArguments> parseCommandLineArguments(args: Array<String>,
|
||||
}
|
||||
else -> {
|
||||
if (i == args.size) {
|
||||
throw IllegalArgumentException("No value passed for argument $arg")
|
||||
errors.argumentWithoutValue = arg
|
||||
break@loop
|
||||
}
|
||||
if (argument.isAdvanced) {
|
||||
result.extraArgumentsPassedInObsoleteForm.add(arg)
|
||||
else {
|
||||
if (argument.isAdvanced) {
|
||||
errors.extraArgumentsPassedInObsoleteForm.add(arg)
|
||||
}
|
||||
args[i++]
|
||||
}
|
||||
args[i++]
|
||||
}
|
||||
}
|
||||
|
||||
if (!field.type.isArray && !visitedArgs.add(argument.value) && value is String && field.get(result) != value) {
|
||||
result.duplicateArguments.put(argument.value, value)
|
||||
errors.duplicateArguments.put(argument.value, value)
|
||||
}
|
||||
|
||||
updateField(field, result, value, argument.delimiter)
|
||||
@@ -109,3 +130,16 @@ private fun <A : CommonCompilerArguments> updateField(field: Field, result: A, v
|
||||
else -> throw IllegalStateException("Unsupported argument type: ${field.type}")
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @return error message if arguments are parsed incorrectly, null otherwise
|
||||
*/
|
||||
fun validateArguments(errors: ArgumentParseErrors): String? {
|
||||
if (errors.argumentWithoutValue != null) {
|
||||
return "No value passed for argument ${errors.argumentWithoutValue}"
|
||||
}
|
||||
if (errors.unknownArgs.isNotEmpty()) {
|
||||
return "Invalid argument: ${errors.unknownArgs.first()}"
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ import kotlin.collections.ArraysKt;
|
||||
import org.fusesource.jansi.AnsiConsole;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.ArgumentUtilsKt;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.ArgumentParseErrors;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.CommonCompilerArguments;
|
||||
import org.jetbrains.kotlin.cli.common.arguments.ParseCommandLineArgumentsKt;
|
||||
import org.jetbrains.kotlin.cli.common.messages.*;
|
||||
import org.jetbrains.kotlin.cli.jvm.K2JVMCompiler;
|
||||
import org.jetbrains.kotlin.cli.jvm.compiler.CompileEnvironmentException;
|
||||
@@ -82,7 +83,11 @@ public abstract class CLICompiler<A extends CommonCompilerArguments> {
|
||||
|
||||
// Used in kotlin-maven-plugin (KotlinCompileMojoBase) and in kotlin-gradle-plugin (KotlinJvmOptionsImpl, KotlinJsOptionsImpl)
|
||||
public void parseArguments(@NotNull String[] args, @NotNull A arguments) {
|
||||
ArgumentUtilsKt.parseArguments(args, arguments);
|
||||
ParseCommandLineArgumentsKt.parseCommandLineArguments(args, arguments);
|
||||
String message = ParseCommandLineArgumentsKt.validateArguments(arguments.errors);
|
||||
if (message != null) {
|
||||
throw new IllegalArgumentException(message);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -142,7 +147,7 @@ public abstract class CLICompiler<A extends CommonCompilerArguments> {
|
||||
messageCollector = new FilteringMessageCollector(messageCollector, Predicate.isEqual(WARNING));
|
||||
}
|
||||
|
||||
reportArgumentParseProblems(messageCollector, arguments);
|
||||
reportArgumentParseProblems(messageCollector, arguments.errors);
|
||||
|
||||
GroupingMessageCollector groupingCollector = new GroupingMessageCollector(messageCollector);
|
||||
|
||||
@@ -320,15 +325,15 @@ public abstract class CLICompiler<A extends CommonCompilerArguments> {
|
||||
@NotNull CompilerConfiguration configuration, @NotNull A arguments, @NotNull Services services
|
||||
);
|
||||
|
||||
private void reportArgumentParseProblems(@NotNull MessageCollector collector, @NotNull A arguments) {
|
||||
for (String flag : arguments.unknownExtraFlags) {
|
||||
private static void reportArgumentParseProblems(@NotNull MessageCollector collector, @NotNull ArgumentParseErrors errors) {
|
||||
for (String flag : errors.getUnknownExtraFlags()) {
|
||||
collector.report(STRONG_WARNING, "Flag is not supported by this version of the compiler: " + flag, null);
|
||||
}
|
||||
for (String argument : arguments.extraArgumentsPassedInObsoleteForm) {
|
||||
for (String argument : errors.getExtraArgumentsPassedInObsoleteForm()) {
|
||||
collector.report(STRONG_WARNING, "Advanced option value is passed in an obsolete form. Please use the '=' character " +
|
||||
"to specify the value: " + argument + "=...", null);
|
||||
}
|
||||
for (Map.Entry<String, String> argument : arguments.duplicateArguments.entrySet()) {
|
||||
for (Map.Entry<String, String> argument : errors.getDuplicateArguments().entrySet()) {
|
||||
collector.report(STRONG_WARNING, "Argument " + argument.getKey() + " is passed multiple times. " +
|
||||
"Only the last value will be used: " + argument.getValue(), null);
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.jetbrains.jps.model.module.JpsModuleSourceRootType
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JSCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2JVMCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.K2MetadataCompilerArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.parseArguments
|
||||
import org.jetbrains.kotlin.cli.common.arguments.parseCommandLineArguments
|
||||
import org.jetbrains.kotlin.compilerRunner.ArgumentUtils
|
||||
import org.jetbrains.kotlin.config.JvmTarget
|
||||
import org.jetbrains.kotlin.config.LanguageFeature
|
||||
@@ -139,7 +139,7 @@ class KotlinMavenImporter : MavenImporter(KOTLIN_PLUGIN_GROUP_ID, KOTLIN_PLUGIN_
|
||||
}
|
||||
|
||||
val additionalArgs = configuration.getChild("args")?.getChildren("arg")?.mapNotNull { it.text } ?: emptyList()
|
||||
parseArguments(additionalArgs.toTypedArray(), arguments, true)
|
||||
parseCommandLineArguments(additionalArgs.toTypedArray(), arguments)
|
||||
|
||||
return ArgumentUtils.convertArgumentsToStringList(arguments)
|
||||
}
|
||||
@@ -269,4 +269,4 @@ class KotlinImporterComponent : PersistentStateComponent<KotlinImporterComponent
|
||||
}
|
||||
|
||||
private val Module.kotlinImporterComponent: KotlinImporterComponent
|
||||
get() = getComponent(KotlinImporterComponent::class.java) ?: throw IllegalStateException("No maven importer state configured")
|
||||
get() = getComponent(KotlinImporterComponent::class.java) ?: throw IllegalStateException("No maven importer state configured")
|
||||
|
||||
@@ -177,12 +177,10 @@ class KotlinFacetEditorGeneralTab(
|
||||
}
|
||||
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)
|
||||
}
|
||||
parseCommandLineArguments(
|
||||
splitArgumentString(editor.compilerConfigurable.additionalArgsOptionsField.text).toTypedArray(), this
|
||||
)
|
||||
validateArguments(errors)?.let { message -> return ValidationResult(message) }
|
||||
}
|
||||
val emptyArguments = argumentClass.newInstance()
|
||||
val fieldNamesToCheck = when (platform) {
|
||||
|
||||
@@ -175,10 +175,10 @@ fun parseCompilerArgumentsToFacet(arguments: List<String>, defaultArguments: Lis
|
||||
val compilerArguments = this.compilerArguments ?: return
|
||||
|
||||
val defaultCompilerArguments = compilerArguments::class.java.newInstance()
|
||||
parseArguments(defaultArguments.toTypedArray(), defaultCompilerArguments, true)
|
||||
parseCommandLineArguments(defaultArguments.toTypedArray(), defaultCompilerArguments)
|
||||
defaultCompilerArguments.convertPathsToSystemIndependent()
|
||||
|
||||
parseArguments(argumentArray, compilerArguments, true)
|
||||
parseCommandLineArguments(argumentArray, compilerArguments)
|
||||
|
||||
compilerArguments.convertPathsToSystemIndependent()
|
||||
|
||||
|
||||
Reference in New Issue
Block a user