use serde::{Deserialize, Serialize};
use crate::build_spec::{BuildSpec, PackageKind, PackageSource};
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "rule", rename_all = "kebab-case")]
pub enum Violation {
StaleSchemaVersion { found: u32, expected: u32 },
RootNotInPackages { key: String },
WorkspaceMemberNotInPackages { key: String },
WorkspaceMemberNotMain { key: String },
UnresolvedImport { from: String, missing_key: String },
StdNodeHasVendoredSource { key: String },
NonStdNodeHasStdSource { key: String },
NodeMissingSourceHash { key: String },
RelativePathEscapes { key: String, relative_path: String },
EmbedPatternsWithoutFiles { key: String },
}
#[must_use]
pub fn check(spec: &BuildSpec) -> Vec<Violation> {
let mut out = Vec::new();
if spec.version < crate::build_spec::SCHEMA_VERSION {
out.push(Violation::StaleSchemaVersion {
found: spec.version,
expected: crate::build_spec::SCHEMA_VERSION,
});
}
if !spec.root_package.is_empty() && !spec.packages.contains_key(&spec.root_package) {
out.push(Violation::RootNotInPackages { key: spec.root_package.clone() });
}
for m in &spec.workspace_members {
match spec.packages.get(m) {
None => out.push(Violation::WorkspaceMemberNotInPackages { key: m.clone() }),
Some(p) if !p.kind.is_main() => {
out.push(Violation::WorkspaceMemberNotMain { key: m.clone() });
}
_ => {}
}
}
for (key, p) in &spec.packages {
match (p.kind, &p.source) {
(PackageKind::Std, PackageSource::Vendored { .. }) => {
out.push(Violation::StdNodeHasVendoredSource { key: key.clone() });
}
(k, PackageSource::Std) if !k.is_std() => {
out.push(Violation::NonStdNodeHasStdSource { key: key.clone() });
}
_ => {}
}
if !p.kind.is_std() && p.source_hash.is_empty() {
out.push(Violation::NodeMissingSourceHash { key: key.clone() });
}
if let PackageSource::Vendored { relative_path } = &p.source {
if relative_path.starts_with('/')
|| relative_path.split('/').any(|c| c == "..")
{
out.push(Violation::RelativePathEscapes {
key: key.clone(),
relative_path: relative_path.clone(),
});
}
}
for edge in &p.imports {
if !spec.packages.contains_key(edge) {
out.push(Violation::UnresolvedImport {
from: key.clone(),
missing_key: edge.clone(),
});
}
}
if !p.embed.patterns.is_empty() && p.embed.files.is_empty() {
out.push(Violation::EmbedPatternsWithoutFiles { key: key.clone() });
}
}
out
}
#[must_use]
pub fn violation_locus(v: &Violation) -> (&'static str, Option<String>) {
use Violation::*;
match v {
StaleSchemaVersion { .. } => ("stale-schema-version", None),
RootNotInPackages { key } => ("root-not-in-packages", Some(key.clone())),
WorkspaceMemberNotInPackages { key } => {
("workspace-member-not-in-packages", Some(key.clone()))
}
WorkspaceMemberNotMain { key } => ("workspace-member-not-main", Some(key.clone())),
UnresolvedImport { from, .. } => ("unresolved-import", Some(from.clone())),
StdNodeHasVendoredSource { key } => ("std-node-has-vendored-source", Some(key.clone())),
NonStdNodeHasStdSource { key } => ("non-std-node-has-std-source", Some(key.clone())),
NodeMissingSourceHash { key } => ("node-missing-source-hash", Some(key.clone())),
RelativePathEscapes { key, .. } => ("relative-path-escapes", Some(key.clone())),
EmbedPatternsWithoutFiles { key } => ("embed-patterns-without-files", Some(key.clone())),
}
}
pub struct GomodInvariants;
impl gen_types::Invariants for GomodInvariants {
type Spec = BuildSpec;
type Violation = Violation;
fn check(spec: &Self::Spec) -> Vec<Self::Violation> {
check(spec)
}
}