mirror of
https://github.com/yairm210/Unciv.git
synced 2025-02-22 20:49:36 +07:00
chore: Split RulesetValidator into subfunctions for easier parsing, and separate class for unique validations
This commit is contained in:
parent
c15e72c25a
commit
6d56fafbbe
File diff suppressed because it is too large
Load Diff
201
core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt
Normal file
201
core/src/com/unciv/models/ruleset/validation/UniqueValidator.kt
Normal file
@ -0,0 +1,201 @@
|
||||
package com.unciv.models.ruleset.validation
|
||||
|
||||
import com.unciv.models.ruleset.IRulesetObject
|
||||
import com.unciv.models.ruleset.Ruleset
|
||||
import com.unciv.models.ruleset.RulesetCache
|
||||
import com.unciv.models.ruleset.unique.IHasUniques
|
||||
import com.unciv.models.ruleset.unique.Unique
|
||||
import com.unciv.models.ruleset.unique.UniqueComplianceError
|
||||
import com.unciv.models.ruleset.unique.UniqueParameterType
|
||||
import com.unciv.models.ruleset.unique.UniqueTarget
|
||||
import com.unciv.models.ruleset.unique.UniqueType
|
||||
import com.unciv.models.stats.INamed
|
||||
|
||||
class UniqueValidator(val ruleset: Ruleset) {
|
||||
|
||||
fun checkUniques(
|
||||
uniqueContainer: IHasUniques,
|
||||
lines: RulesetErrorList,
|
||||
severityToReport: UniqueType.UniqueComplianceErrorSeverity,
|
||||
tryFixUnknownUniques: Boolean
|
||||
) {
|
||||
for (unique in uniqueContainer.uniqueObjects) {
|
||||
val errors = checkUnique(
|
||||
unique,
|
||||
tryFixUnknownUniques,
|
||||
uniqueContainer as? INamed,
|
||||
severityToReport
|
||||
)
|
||||
lines.addAll(errors)
|
||||
}
|
||||
}
|
||||
|
||||
fun checkUnique(
|
||||
unique: Unique,
|
||||
tryFixUnknownUniques: Boolean,
|
||||
namedObj: INamed?,
|
||||
severityToReport: UniqueType.UniqueComplianceErrorSeverity
|
||||
): List<RulesetError> {
|
||||
val prefix by lazy { (if (namedObj is IRulesetObject) "${namedObj.originRuleset}: " else "") +
|
||||
(if (namedObj == null) "The" else "${namedObj.name}'s") }
|
||||
if (unique.type == null) return checkUntypedUnique(unique, tryFixUnknownUniques, prefix)
|
||||
|
||||
val rulesetErrors = RulesetErrorList()
|
||||
|
||||
if (namedObj is IHasUniques && !unique.type.canAcceptUniqueTarget(namedObj.getUniqueTarget()))
|
||||
rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" is not allowed on its target type", RulesetErrorSeverity.Warning))
|
||||
|
||||
val typeComplianceErrors = getComplianceErrors(unique)
|
||||
for (complianceError in typeComplianceErrors) {
|
||||
if (complianceError.errorSeverity <= severityToReport)
|
||||
rulesetErrors.add(RulesetError("$prefix unique \"${unique.text}\" contains parameter ${complianceError.parameterName}," +
|
||||
" which does not fit parameter type" +
|
||||
" ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
|
||||
complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
|
||||
))
|
||||
}
|
||||
|
||||
for (conditional in unique.conditionals) {
|
||||
if (conditional.type == null) {
|
||||
rulesetErrors.add(
|
||||
"$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
||||
" which is of an unknown type!",
|
||||
RulesetErrorSeverity.Warning
|
||||
)
|
||||
} else {
|
||||
if (conditional.type.targetTypes.none { it.modifierType != UniqueTarget.ModifierType.None })
|
||||
rulesetErrors.add("$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
||||
" which is a Unique type not allowed as conditional or trigger.",
|
||||
RulesetErrorSeverity.Warning)
|
||||
|
||||
if (conditional.type.targetTypes.contains(UniqueTarget.UnitActionModifier)
|
||||
&& unique.type.targetTypes.none { UniqueTarget.UnitAction.canAcceptUniqueTarget(it) })
|
||||
rulesetErrors.add("$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"," +
|
||||
" which as a UnitActionModifier is only allowed on UnitAciton uniques.",
|
||||
RulesetErrorSeverity.Warning)
|
||||
|
||||
val conditionalComplianceErrors =
|
||||
getComplianceErrors(conditional)
|
||||
for (complianceError in conditionalComplianceErrors) {
|
||||
if (complianceError.errorSeverity == severityToReport)
|
||||
rulesetErrors.add(RulesetError( "$prefix unique \"${unique.text}\" contains the conditional \"${conditional.text}\"." +
|
||||
" This contains the parameter ${complianceError.parameterName} which does not fit parameter type" +
|
||||
" ${complianceError.acceptableParameterTypes.joinToString(" or ") { it.parameterName }} !",
|
||||
complianceError.errorSeverity.getRulesetErrorSeverity(severityToReport)
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (severityToReport != UniqueType.UniqueComplianceErrorSeverity.RulesetSpecific)
|
||||
// If we don't filter these messages will be listed twice as this function is called twice on most objects
|
||||
// The tests are RulesetInvariant in nature, but RulesetSpecific is called for _all_ objects, invariant is not.
|
||||
return rulesetErrors
|
||||
|
||||
|
||||
val deprecationAnnotation = unique.getDeprecationAnnotation()
|
||||
if (deprecationAnnotation != null) {
|
||||
val replacementUniqueText = unique.getReplacementText(ruleset)
|
||||
val deprecationText =
|
||||
"$prefix unique \"${unique.text}\" is deprecated ${deprecationAnnotation.message}," +
|
||||
if (deprecationAnnotation.replaceWith.expression != "") " replace with \"${replacementUniqueText}\"" else ""
|
||||
val severity = if (deprecationAnnotation.level == DeprecationLevel.WARNING)
|
||||
RulesetErrorSeverity.WarningOptionsOnly // Not user-visible
|
||||
else RulesetErrorSeverity.Warning // User visible
|
||||
|
||||
rulesetErrors.add(deprecationText, severity)
|
||||
}
|
||||
|
||||
return rulesetErrors
|
||||
}
|
||||
|
||||
/** Maps uncompliant parameters to their required types */
|
||||
private fun getComplianceErrors(
|
||||
unique: Unique,
|
||||
): List<UniqueComplianceError> {
|
||||
if (unique.type==null) return emptyList()
|
||||
val errorList = ArrayList<UniqueComplianceError>()
|
||||
for ((index, param) in unique.params.withIndex()) {
|
||||
val acceptableParamTypes = unique.type.parameterTypeMap[index]
|
||||
val errorTypesForAcceptableParameters =
|
||||
acceptableParamTypes.map { getParamTypeErrorSeverityCached(it, param) }
|
||||
if (errorTypesForAcceptableParameters.any { it == null }) continue // This matches one of the types!
|
||||
val leastSevereWarning =
|
||||
errorTypesForAcceptableParameters.minByOrNull { it!!.ordinal }!!
|
||||
errorList += UniqueComplianceError(param, acceptableParamTypes, leastSevereWarning)
|
||||
}
|
||||
return errorList
|
||||
}
|
||||
|
||||
private val paramTypeErrorSeverityCache = HashMap<UniqueParameterType, HashMap<String, UniqueType.UniqueComplianceErrorSeverity?>>()
|
||||
private fun getParamTypeErrorSeverityCached(uniqueParameterType: UniqueParameterType, param:String): UniqueType.UniqueComplianceErrorSeverity? {
|
||||
if (!paramTypeErrorSeverityCache.containsKey(uniqueParameterType))
|
||||
paramTypeErrorSeverityCache[uniqueParameterType] = hashMapOf()
|
||||
val uniqueParamCache = paramTypeErrorSeverityCache[uniqueParameterType]!!
|
||||
|
||||
if (uniqueParamCache.containsKey(param)) return uniqueParamCache[param]
|
||||
|
||||
val severity = uniqueParameterType.getErrorSeverity(param, ruleset)
|
||||
uniqueParamCache[param] = severity
|
||||
return severity
|
||||
}
|
||||
|
||||
private fun checkUntypedUnique(unique: Unique, tryFixUnknownUniques: Boolean, prefix: String ): List<RulesetError> {
|
||||
// Malformed conditional is always bad
|
||||
if (unique.text.count { it == '<' } != unique.text.count { it == '>' })
|
||||
return listOf(RulesetError(
|
||||
"$prefix unique \"${unique.text}\" contains mismatched conditional braces!",
|
||||
RulesetErrorSeverity.Warning))
|
||||
|
||||
// Support purely filtering Uniques without actual implementation
|
||||
if (isFilteringUniqueAllowed(unique)) return emptyList()
|
||||
if (tryFixUnknownUniques) {
|
||||
val fixes = tryFixUnknownUnique(unique, prefix)
|
||||
if (fixes.isNotEmpty()) return fixes
|
||||
}
|
||||
|
||||
return listOf(RulesetError(
|
||||
"$prefix unique \"${unique.text}\" not found in Unciv's unique types.",
|
||||
RulesetErrorSeverity.OK))
|
||||
}
|
||||
|
||||
private fun isFilteringUniqueAllowed(unique: Unique): Boolean {
|
||||
// Isolate this decision, to allow easy change of approach
|
||||
// This says: Must have no conditionals or parameters, and is contained in GlobalUniques
|
||||
if (unique.conditionals.isNotEmpty() || unique.params.isNotEmpty()) return false
|
||||
return unique.text in ruleset.globalUniques.uniqueMap
|
||||
}
|
||||
|
||||
private fun tryFixUnknownUnique(unique: Unique, prefix: String): List<RulesetError> {
|
||||
val similarUniques = UniqueType.values().filter {
|
||||
getRelativeTextDistance(
|
||||
it.placeholderText,
|
||||
unique.placeholderText
|
||||
) <= RulesetCache.uniqueMisspellingThreshold
|
||||
}
|
||||
val equalUniques =
|
||||
similarUniques.filter { it.placeholderText == unique.placeholderText }
|
||||
return when {
|
||||
// This should only ever happen if a bug is or has been introduced that prevents Unique.type from being set for a valid UniqueType, I think.\
|
||||
equalUniques.isNotEmpty() -> listOf(RulesetError(
|
||||
"$prefix unique \"${unique.text}\" looks like it should be fine, but for some reason isn't recognized.",
|
||||
RulesetErrorSeverity.OK))
|
||||
|
||||
similarUniques.isNotEmpty() -> {
|
||||
val text =
|
||||
"$prefix unique \"${unique.text}\" looks like it may be a misspelling of:\n" +
|
||||
similarUniques.joinToString("\n") { uniqueType ->
|
||||
var text = "\"${uniqueType.text}"
|
||||
if (unique.conditionals.isNotEmpty())
|
||||
text += " " + unique.conditionals.joinToString(" ") { "<${it.text}>" }
|
||||
text += "\""
|
||||
if (uniqueType.getDeprecationAnnotation() != null) text += " (Deprecated)"
|
||||
return@joinToString text
|
||||
}.prependIndent("\t")
|
||||
listOf(RulesetError(text, RulesetErrorSeverity.OK))
|
||||
}
|
||||
else -> emptyList()
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user