///|
fn validate_lint_policy(policy : RomanLintPolicy) -> Result[Unit, ConfigError] {
if policy.max_candidate_length < 1 {
Err(InvalidCandidateLength(policy.max_candidate_length))
} else {
Ok(())
}
}
///|
fn has_ascii_lowercase_roman(input : String) -> Bool {
for character in input.to_array() {
if is_lowercase_roman_letter(character) {
return true
}
}
false
}
///|
fn has_ascii_uppercase_roman(input : String) -> Bool {
for character in input.to_array() {
if is_ascii_roman_letter(character) && !is_lowercase_roman_letter(character) {
return true
}
}
false
}
///|
fn case_style_diagnostic(
input : String,
requirement : RomanCaseRequirement,
) -> RomanDiagnostic? {
let code_and_replacement : (DiagnosticCode, String)? = match requirement {
AnyRomanCase => None
RequireRomanUppercase =>
if has_ascii_lowercase_roman(input) {
Some((UppercaseRequiredCode, input.to_upper()))
} else {
None
}
RequireRomanLowercase =>
if has_ascii_uppercase_roman(input) {
Some((LowercaseRequiredCode, input.to_lower()))
} else {
None
}
}
match code_and_replacement {
None => None
Some(item) =>
Some({
code: item.0,
severity: DiagnosticWarning,
message: diagnostic_message(item.0),
span: { start: 0, end: input.to_array().length() },
replacement: Some(item.1),
})
}
}
///|
fn unicode_style_diagnostic(input : String) -> RomanDiagnostic? {
if !contains_supported_unicode_roman(input) {
return None
}
let chars = input.to_array()
let mut start = 0
while start < chars.length() &&
!(chars[start] >= '\u{2160}' && chars[start] <= '\u{217f}') {
start = start + 1
}
let mut end = chars.length()
while end > start &&
!(chars[end - 1] >= '\u{2160}' && chars[end - 1] <= '\u{217f}') {
end = end - 1
}
Some({
code: UnicodeSourceNotAllowedCode,
severity: DiagnosticWarning,
message: diagnostic_message(UnicodeSourceNotAllowedCode),
span: { start, end },
replacement: None,
})
}
///|
fn whitespace_style_diagnostic(input : String) -> RomanDiagnostic? {
if !has_outer_whitespace(input) {
return None
}
Some({
code: OuterWhitespaceNotAllowedCode,
severity: DiagnosticWarning,
message: diagnostic_message(OuterWhitespaceNotAllowedCode),
span: { start: 0, end: input.to_array().length() },
replacement: None,
})
}
///|
fn append_optional_diagnostic(
diagnostics : Array[RomanDiagnostic],
diagnostic : RomanDiagnostic?,
) -> Unit {
match diagnostic {
Some(value) => diagnostics.push(value)
None => ()
}
}
///|
fn lint_style_diagnostics(
input : String,
policy : RomanLintPolicy,
) -> Array[RomanDiagnostic] {
let diagnostics : Array[RomanDiagnostic] = []
if !policy.allow_unicode {
append_optional_diagnostic(diagnostics, unicode_style_diagnostic(input))
}
if !policy.allow_outer_whitespace {
append_optional_diagnostic(diagnostics, whitespace_style_diagnostic(input))
}
append_optional_diagnostic(
diagnostics,
case_style_diagnostic(input, policy.case_requirement),
)
diagnostics
}
///|
fn lint_analysis_diagnostics(
analysis : RomanAnalysisReport,
require_canonical : Bool,
) -> Array[RomanDiagnostic] {
let diagnostics : Array[RomanDiagnostic] = []
for diagnostic in analysis.diagnostics {
if diagnostic.code == NonCanonicalCode && !require_canonical {
diagnostics.push({ ..diagnostic, severity: DiagnosticWarning })
} else {
diagnostics.push(diagnostic)
}
}
diagnostics
}
///|
fn count_diagnostic_severity(
diagnostics : Array[RomanDiagnostic],
) -> (Int, Int) {
let mut errors = 0
let mut warnings = 0
for diagnostic in diagnostics {
match diagnostic.severity {
DiagnosticError => errors = errors + 1
DiagnosticWarning => warnings = warnings + 1
}
}
(errors, warnings)
}
///|
fn lint_output_case(requirement : RomanCaseRequirement) -> LetterCase {
match requirement {
RequireRomanLowercase => Lowercase
AnyRomanCase | RequireRomanUppercase => Uppercase
}
}
///|
fn lint_replacement(
input : String,
analysis : RomanAnalysisReport,
policy : RomanLintPolicy,
has_diagnostics : Bool,
) -> String? {
if !has_diagnostics {
return None
}
let base : String? = match analysis.status {
AnalysisAccepted(report) =>
match
format_with_config(report.value, {
mode: policy.parse_config.mode,
letter_case: lint_output_case(policy.case_requirement),
}) {
Ok(text) => Some(text)
Err(_) => None
}
AnalysisRejected(NonCanonicalRoman(expected)) => Some(expected)
_ => None
}
match base {
None => None
Some(text) => {
let target = match policy.case_requirement {
RequireRomanLowercase => text.to_lower()
RequireRomanUppercase => text.to_upper()
AnyRomanCase => text
}
if target == input {
None
} else {
Some(target)
}
}
}
}
///|
/// Lint one configured Roman input and retain parser and style diagnostics.
pub fn lint_roman(
input : String,
policy : RomanLintPolicy,
) -> Result[RomanLintReport, ConfigError] {
match validate_lint_policy(policy) {
Err(error) => return Err(error)
Ok(_) => ()
}
let analysis = analyze_roman(input, policy.parse_config)
let style_diagnostics = lint_style_diagnostics(input, policy)
let diagnostics = lint_analysis_diagnostics(
analysis,
policy.require_canonical,
)
for diagnostic in style_diagnostics {
diagnostics.push(diagnostic)
}
let counts = count_diagnostic_severity(diagnostics)
let replacement = lint_replacement(
input,
analysis,
policy,
diagnostics.length() > 0,
)
Ok({
original: input,
policy,
analysis,
style_diagnostics,
diagnostics,
replacement,
error_count: counts.0,
warning_count: counts.1,
conforms: diagnostics.length() == 0,
})
}