// SPDX-License-Identifier: MIT
// SPDX-FileCopyrightText: 2026 clbbbb
///|
pub struct FileRule {
path : String
kind : String
should_scan : Bool
reason : String
} derive(Debug, Eq)
///|
pub fn file_rule(
path : String,
kind : String,
should_scan : Bool,
reason : String,
) -> FileRule {
{ path, kind, should_scan, reason }
}
///|
pub fn classify_path(path : String) -> FileRule {
let lower = lower_ascii(path)
if lower.find("_build/") is Some(_) || lower.find("\\_build\\") is Some(_) {
file_rule(path, "build-artifact", false, "MoonBit build output")
} else if lower.find("node_modules/") is Some(_) ||
lower.find("\\node_modules\\") is Some(_) {
file_rule(path, "dependency-cache", false, "external dependency cache")
} else if lower.find("vendor/") is Some(_) ||
lower.find("\\vendor\\") is Some(_) {
file_rule(
path, "vendored-source", true, "vendored source should keep license headers",
)
} else if lower.has_suffix(".mbt") {
file_rule(path, "moonbit-source", true, "MoonBit source file")
} else if lower.has_suffix(".md") {
file_rule(path, "documentation", false, "documentation checked separately")
} else if lower.has_suffix(".yml") || lower.has_suffix(".yaml") {
file_rule(path, "configuration", false, "workflow/config file")
} else if lower.has_suffix(".json") || lower.has_suffix(".toml") {
file_rule(path, "metadata", false, "metadata file")
} else {
file_rule(path, "other", false, "not a source file")
}
}
///|
pub fn paths_to_scan(paths : Array[String]) -> Array[String] {
let rows : Array[String] = []
for path in paths {
let rule = classify_path(path)
if rule.should_scan {
rows.push(path)
}
}
rows
}
///|
pub fn should_scan_path(path : String) -> Bool {
classify_path(path).should_scan
}
///|
pub fn file_rules_report(paths : Array[String]) -> String {
let rows : Array[String] = []
for path in paths {
let rule = classify_path(path)
rows.push(
rule.path +
"," +
rule.kind +
"," +
bool_word(rule.should_scan) +
"," +
rule.reason,
)
}
join_lines(rows)
}
///|
pub fn filter_sources_by_rule(
paths : Array[String],
sources : Array[String],
) -> (Array[String], Array[String]) {
let kept_paths : Array[String] = []
let kept_sources : Array[String] = []
let limit = min_int(paths.length(), sources.length())
for index = 0; index < limit; index = index + 1 {
if should_scan_path(paths[index]) {
kept_paths.push(paths[index])
kept_sources.push(sources[index])
}
}
(kept_paths, kept_sources)
}
///|
pub fn ignored_paths_report(paths : Array[String]) -> String {
let rows : Array[String] = []
for path in paths {
let rule = classify_path(path)
if !rule.should_scan {
rows.push(path + ": " + rule.reason)
}
}
if rows.is_empty() {
"no ignored paths"
} else {
join_lines(rows)
}
}