use pedant_syntax::go::{GoBuildConditionKind, GoFileFacts};
const TEST_SUFFIX: &str = "_test";
const FOREIGN_IMPORT: &str = "C";
const PLATFORM_SYSTEMS: &[&str] = &[
"aix",
"android",
"darwin",
"dragonfly",
"freebsd",
"hurd",
"illumos",
"ios",
"js",
"linux",
"nacl",
"netbsd",
"openbsd",
"plan9",
"solaris",
"wasip1",
"windows",
"zos",
];
const PLATFORM_ARCHITECTURES: &[&str] = &[
"386",
"amd64",
"amd64p32",
"arm",
"arm64",
"arm64be",
"armbe",
"loong64",
"mips",
"mips64",
"mips64le",
"mips64p32",
"mips64p32le",
"mipsle",
"ppc",
"ppc64",
"ppc64le",
"riscv",
"riscv64",
"s390",
"s390x",
"sparc",
"sparc64",
"wasm",
];
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum GoBuildCondition {
Stated {
kind: GoBuildConditionKind,
constraint: Box<str>,
},
PlatformSuffix {
term: Box<str>,
},
TestContext,
ForeignFunctions,
}
impl GoBuildCondition {
pub fn token(&self) -> Box<str> {
match self {
Self::Stated { kind, constraint } => {
format!("stated:{}:{constraint}", stated_token(*kind)).into_boxed_str()
}
Self::PlatformSuffix { term } => format!("suffix:{term}").into_boxed_str(),
Self::TestContext => Box::from("test"),
Self::ForeignFunctions => Box::from("foreign"),
}
}
}
fn stated_token(kind: GoBuildConditionKind) -> &'static str {
match kind {
GoBuildConditionKind::GoBuild => "directive",
GoBuildConditionKind::LegacyBuildTag => "legacy",
}
}
pub(super) fn is_test_source(name: &str) -> bool {
stem(name).ends_with(TEST_SUFFIX)
}
pub(super) fn conditions_of(name: &str, facts: &GoFileFacts<'_>) -> Box<[GoBuildCondition]> {
let mut stated: Vec<GoBuildCondition> = facts
.build_conditions()
.iter()
.map(|condition| GoBuildCondition::Stated {
kind: condition.kind(),
constraint: Box::from(condition.constraint()),
})
.collect();
stated.extend(platform_suffixes(name));
stated.extend(is_test_source(name).then_some(GoBuildCondition::TestContext));
stated.extend(imports_foreign_bridge(facts).then_some(GoBuildCondition::ForeignFunctions));
stated.into_boxed_slice()
}
fn platform_suffixes(name: &str) -> Box<[GoBuildCondition]> {
let stem = stem(name);
let base = stem.strip_suffix(TEST_SUFFIX).unwrap_or(stem);
let tail = base.rsplit('_').next().unwrap_or_default();
let leading = base.strip_suffix(tail).unwrap_or_default();
trailing_terms(leading, tail)
.into_iter()
.map(|term| GoBuildCondition::PlatformSuffix {
term: Box::from(term),
})
.collect()
}
fn trailing_terms<'name>(leading: &'name str, tail: &'name str) -> Vec<&'name str> {
match (
leading.is_empty(),
PLATFORM_ARCHITECTURES.contains(&tail),
PLATFORM_SYSTEMS.contains(&tail),
) {
(true, _, _) => Vec::new(),
(false, true, _) => system_before(leading)
.into_iter()
.chain(std::iter::once(tail))
.collect(),
(false, false, true) => vec![tail],
(false, false, false) => Vec::new(),
}
}
fn system_before(leading: &str) -> Option<&str> {
let stated = leading.strip_suffix('_')?;
let previous = stated.rsplit('_').next()?;
let named = stated.strip_suffix(previous)?;
match named.is_empty() {
true => None,
false => PLATFORM_SYSTEMS.contains(&previous).then_some(previous),
}
}
fn imports_foreign_bridge(facts: &GoFileFacts<'_>) -> bool {
facts
.imports()
.iter()
.any(|import| import.path() == FOREIGN_IMPORT)
}
fn stem(name: &str) -> &str {
name.strip_suffix(".go").unwrap_or(name)
}