mirror of
https://github.com/bherbruck/solidcraft.git
synced 2026-11-10 18:55:14 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a50ad60ad0 | |||
| a531623d2d |
@@ -436,6 +436,10 @@ jobs:
|
||||
run: |
|
||||
cargo xtask mesh-decisions
|
||||
cargo test -p xtask mesh_decisions --locked
|
||||
- name: Apps compute no geometry
|
||||
run: |
|
||||
cargo xtask app-geometry
|
||||
cargo test -p xtask app_geometry --locked
|
||||
- name: Agent router and skills cite real files
|
||||
run: |
|
||||
cargo xtask skills
|
||||
|
||||
@@ -12,7 +12,7 @@ Work: ROADMAP.md, "Where help is wanted most" in CONTRIBUTING.md, issues labelle
|
||||
- Never crash: no unwrap/expect/panic/unsafe in non-test code, errors are `Result`, input is hostile: clippy `-D warnings`, `hostile_params_never_panic`.
|
||||
- Model decisions (what an operation does, which bodies it touches, which face or edge a point or name means, depths, nearness) use exact geometry; a mesh is for display, export, previews and mesh bodies (`xtask/mesh-decisions.txt` only shrinks): `cargo xtask mesh-decisions`.
|
||||
- Everything is a command and programmatic calls never open dialogs: `skills/solidcraft-commands/SKILL.md`.
|
||||
- Layering; only `crates/kernel` uses truck; solidcore only via `solidcore-ops`/`-tess`/`-record` (`xtask/solidcore-seam.txt`): `cargo xtask layers`; see `solidcore/docs/layers.md`.
|
||||
- Layering; only `crates/kernel` uses truck; solidcore only via `solidcore-ops`/`-tess`/`-record` (`xtask/solidcore-seam.txt`): `cargo xtask layers`/`app-geometry`; `solidcore/docs/layers.md`.
|
||||
- One outer UI container, one camera path (`navigation::handle`), colours from theme tokens: `cargo xtask ui-containers`.
|
||||
- Application code is Rust only and builds for wasm: `cargo xtask wasm`, `cargo xtask web-offline-test`.
|
||||
- The product name is SolidCraft; the old name lives only in `docs/compatibility.md`.
|
||||
|
||||
@@ -18,6 +18,7 @@ The authority is `solidcore/docs/layers.md`; this is its short form. Every job l
|
||||
|
||||
## Lists that only shrink
|
||||
- `xtask/mesh-decisions.txt`: model decisions made from a mesh (`cargo xtask mesh-decisions`).
|
||||
- `xtask/app-geometry.txt`: functions in the app crates that compute geometry (`cargo xtask app-geometry`). A new one fails CI; when you move one into `solidcore/`, delete its line.
|
||||
- `solidcore/plan/panic-sites.tsv`: panic-prone calls in solidcore (SolidCore checks in CI).
|
||||
|
||||
## Working in solidcore/
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,369 @@
|
||||
//! `cargo xtask app-geometry [--print]`: apps compute no geometry (`solidcore/docs/layers.md` §4,
|
||||
//! RFC 0003 row 0.1). Vector maths on coordinates, solves and mesh queries belong in the kernel
|
||||
//! (solidcore); the crates here hold state and call it. The check keeps a ledger of the functions
|
||||
//! that still compute geometry, [`LIST`], and the ledger only shrinks: a refactor PR deletes its
|
||||
//! lines and no PR adds one.
|
||||
//!
|
||||
//! The heuristic, deliberately plain. The check reads the non-test Rust of the crates in
|
||||
//! [`CRATES`], following each crate's module tree from its `lib.rs` (a `#[cfg(test)]` or
|
||||
//! `#[test]` item is left out), and flags every function whose body (closures, nested blocks and
|
||||
//! expression macros such as `format!` included) uses at least one geometry operation, named by
|
||||
//! [`geometry_use`]:
|
||||
//! - vector maths, as a method call: `.dot(v)`, `.cross(v)`, `.normalize()`, `.normalized()`,
|
||||
//! `.magnitude()`, `.magnitude2()`, `.distance(p)`, `.distance2(p)`, `.hypot(y)`, `.atan2(x)`,
|
||||
//! `.angle(v)`;
|
||||
//! - solves: `.solve(..)`;
|
||||
//! - mesh queries: `.raycast(..)`, `.tangent_chain(..)`, the `.triangles` field and
|
||||
//! `closest_on_triangle(..)`.
|
||||
//!
|
||||
//! It is by name, not by type: a method with one of those names counts whoever defines it. It
|
||||
//! does not grade `sqrt`, `sin`, `cos`, `length`, `lerp` or plain arithmetic (too common in
|
||||
//! layout, colour and timing code), so the ledger is a floor of the sites, not a complete count.
|
||||
//! A function is `<path>:<scope>`, the enclosing inline modules, impl type and function joined
|
||||
//! by `::`. Nested functions and closures count toward the function they sit in; two functions
|
||||
//! with one name in one impl type (trait impls) are one site.
|
||||
//!
|
||||
//! [`LIST`] holds one site a line, `<path>:<function> <the operations it uses>`, sorted (the
|
||||
//! kernel rig's known-failing format; the operations are for the reader and are not graded). The
|
||||
//! check fails on a site the list does not name, and on a listed site that no longer computes
|
||||
//! geometry or no longer exists: its refactor removes its line, as with
|
||||
//! `xtask/mesh-decisions.txt`. `--print` writes the current sites in the list's format, to see
|
||||
//! what a change did; it is not a way to refresh the list.
|
||||
|
||||
use std::collections::BTreeMap;
|
||||
use std::path::Path;
|
||||
|
||||
use syn::visit::{self, Visit};
|
||||
|
||||
use crate::mesh_decisions::{module_files, relative, test_only, type_name};
|
||||
|
||||
pub const LIST: &str = "xtask/app-geometry.txt";
|
||||
|
||||
/// The crates that are apps or sit between an app and the kernel: the document, the engine, the
|
||||
/// UI, file formats, and SolidCraft's own kernel and geometry layers, whose geometry moves into
|
||||
/// solidcore.
|
||||
pub const CRATES: &[&str] = &["crates/doc", "crates/engine", "crates/ui-egui", "crates/io", "crates/kernel", "crates/geom"];
|
||||
|
||||
/// The geometry operation `call` is, if it is one.
|
||||
fn geometry_use(call: &Call) -> Option<&'static str> {
|
||||
match (call, call.name()) {
|
||||
(Call::Method { args: 1, .. }, "dot") => Some("dot"),
|
||||
(Call::Method { args: 1, .. }, "cross") => Some("cross"),
|
||||
(Call::Method { args: 0, .. }, "normalize") => Some("normalize"),
|
||||
(Call::Method { args: 0, .. }, "normalized") => Some("normalized"),
|
||||
(Call::Method { args: 0, .. }, "magnitude") => Some("magnitude"),
|
||||
(Call::Method { args: 0, .. }, "magnitude2") => Some("magnitude2"),
|
||||
(Call::Method { args: 1, .. }, "distance") => Some("distance"),
|
||||
(Call::Method { args: 1, .. }, "distance2") => Some("distance2"),
|
||||
(Call::Method { args: 1, .. }, "hypot") => Some("hypot"),
|
||||
(Call::Method { args: 1, .. }, "atan2") => Some("atan2"),
|
||||
(Call::Method { args: 1, .. }, "angle") => Some("angle"),
|
||||
(Call::Method { .. }, "solve") => Some("solve"),
|
||||
(Call::Method { .. }, "raycast") => Some("raycast"),
|
||||
(Call::Method { .. }, "tangent_chain") => Some("tangent_chain"),
|
||||
(Call::Function { .. }, "closest_on_triangle") => Some("closest_on_triangle"),
|
||||
(Call::Field { .. }, "triangles") => Some("triangles"),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
enum Call {
|
||||
Method { name: String, args: usize },
|
||||
Function { name: String },
|
||||
Field { name: String },
|
||||
}
|
||||
|
||||
impl Call {
|
||||
fn name(&self) -> &str {
|
||||
match self {
|
||||
Call::Method { name, .. } | Call::Function { name } | Call::Field { name } => name,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn run(root: &Path, args: &[&str]) -> Result<(), String> {
|
||||
let found = scan(root)?;
|
||||
if args.contains(&"--print") {
|
||||
for (site, uses) in &found {
|
||||
println!("{site} {}", uses.join(","));
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
let text = std::fs::read_to_string(root.join(LIST)).map_err(|e| format!("{LIST}: {e}"))?;
|
||||
println!("{}", grade(&text, &found).map_err(|e| format!("{LIST}: {e}"))?);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Every site under `root`: `<path>:<function>` with the operations it uses, sorted.
|
||||
fn scan(root: &Path) -> Result<BTreeMap<String, Vec<&'static str>>, String> {
|
||||
let mut found = BTreeMap::new();
|
||||
for krate in CRATES {
|
||||
let mut files = Vec::new();
|
||||
module_files(&root.join(krate).join("src/lib.rs"), true, &mut files)?;
|
||||
files.sort();
|
||||
files.dedup();
|
||||
for path in files {
|
||||
let rel = relative(root, &path);
|
||||
let source = std::fs::read_to_string(&path).map_err(|e| format!("{rel}: {e}"))?;
|
||||
for (function, uses) in sites(&source).map_err(|e| format!("{rel}: {e}"))? {
|
||||
found.insert(format!("{rel}:{function}"), uses);
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
/// The functions of `source` that compute geometry, with the operations each uses (outside test
|
||||
/// code), sorted.
|
||||
pub fn sites(source: &str) -> Result<BTreeMap<String, Vec<&'static str>>, String> {
|
||||
let ast = syn::parse_file(source).map_err(|e| format!("cannot parse Rust source: {e}"))?;
|
||||
let mut finder = Finder { scope: Vec::new(), fns: 0, found: BTreeMap::new() };
|
||||
finder.visit_file(&ast);
|
||||
for uses in finder.found.values_mut() {
|
||||
uses.sort_unstable();
|
||||
}
|
||||
Ok(finder.found)
|
||||
}
|
||||
|
||||
struct Finder {
|
||||
/// The enclosing inline modules, impl type and function, outermost first.
|
||||
scope: Vec<String>,
|
||||
/// How many functions enclose the code being visited: a use belongs to the outermost one.
|
||||
fns: usize,
|
||||
found: BTreeMap<String, Vec<&'static str>>,
|
||||
}
|
||||
|
||||
impl Finder {
|
||||
fn note(&mut self, call: Call) {
|
||||
let Some(what) = geometry_use(&call) else { return };
|
||||
// Outside any function (a const or static initialiser) the use belongs to that item.
|
||||
let site = if self.scope.is_empty() { "<module>".to_owned() } else { self.scope.join("::") };
|
||||
let uses = self.found.entry(site).or_default();
|
||||
if !uses.contains(&what) {
|
||||
uses.push(what);
|
||||
}
|
||||
}
|
||||
|
||||
fn scoped(&mut self, name: String, f: impl FnOnce(&mut Self)) {
|
||||
self.scope.push(name);
|
||||
f(self);
|
||||
self.scope.pop();
|
||||
}
|
||||
|
||||
/// A function: inside another function it joins that one (a nested `fn` is part of its
|
||||
/// parent's site), otherwise it is a site of its own.
|
||||
fn function(&mut self, name: String, f: impl FnOnce(&mut Self)) {
|
||||
if self.fns > 0 {
|
||||
f(self);
|
||||
} else {
|
||||
self.fns += 1;
|
||||
self.scoped(name, f);
|
||||
self.fns -= 1;
|
||||
}
|
||||
}
|
||||
|
||||
/// A macro's arguments, when they are expressions (`format!`, `vec!`, `assert!`).
|
||||
fn macro_args(&mut self, mac: &syn::Macro) {
|
||||
let parser = syn::punctuated::Punctuated::<syn::Expr, syn::Token![,]>::parse_terminated;
|
||||
if let Ok(args) = mac.parse_body_with(parser) {
|
||||
for e in &args {
|
||||
self.visit_expr(e);
|
||||
}
|
||||
} else if let Ok(block) = mac.parse_body_with(syn::Block::parse_within) {
|
||||
for s in &block {
|
||||
self.visit_stmt(s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'ast> Visit<'ast> for Finder {
|
||||
fn visit_item_mod(&mut self, m: &'ast syn::ItemMod) {
|
||||
if !test_only(&m.attrs) && m.content.is_some() {
|
||||
self.scoped(m.ident.to_string(), |f| visit::visit_item_mod(f, m));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_fn(&mut self, i: &'ast syn::ItemFn) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.function(i.sig.ident.to_string(), |f| visit::visit_item_fn(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_impl(&mut self, i: &'ast syn::ItemImpl) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.scoped(type_name(&i.self_ty), |f| visit::visit_item_impl(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_impl_item_fn(&mut self, i: &'ast syn::ImplItemFn) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.function(i.sig.ident.to_string(), |f| visit::visit_impl_item_fn(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_trait(&mut self, i: &'ast syn::ItemTrait) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.scoped(i.ident.to_string(), |f| visit::visit_item_trait(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_trait_item_fn(&mut self, i: &'ast syn::TraitItemFn) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.function(i.sig.ident.to_string(), |f| visit::visit_trait_item_fn(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_const(&mut self, i: &'ast syn::ItemConst) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.scoped(i.ident.to_string(), |f| visit::visit_item_const(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_item_static(&mut self, i: &'ast syn::ItemStatic) {
|
||||
if !test_only(&i.attrs) {
|
||||
self.scoped(i.ident.to_string(), |f| visit::visit_item_static(f, i));
|
||||
}
|
||||
}
|
||||
|
||||
fn visit_stmt(&mut self, s: &'ast syn::Stmt) {
|
||||
if let syn::Stmt::Local(l) = s
|
||||
&& test_only(&l.attrs)
|
||||
{
|
||||
return;
|
||||
}
|
||||
visit::visit_stmt(self, s);
|
||||
}
|
||||
|
||||
fn visit_expr_method_call(&mut self, e: &'ast syn::ExprMethodCall) {
|
||||
self.note(Call::Method { name: e.method.to_string(), args: e.args.len() });
|
||||
visit::visit_expr_method_call(self, e);
|
||||
}
|
||||
|
||||
fn visit_expr_call(&mut self, e: &'ast syn::ExprCall) {
|
||||
if let syn::Expr::Path(p) = e.func.as_ref()
|
||||
&& let Some(last) = p.path.segments.last()
|
||||
{
|
||||
self.note(Call::Function { name: last.ident.to_string() });
|
||||
}
|
||||
visit::visit_expr_call(self, e);
|
||||
}
|
||||
|
||||
fn visit_expr_field(&mut self, e: &'ast syn::ExprField) {
|
||||
if let syn::Member::Named(name) = &e.member {
|
||||
self.note(Call::Field { name: name.to_string() });
|
||||
}
|
||||
visit::visit_expr_field(self, e);
|
||||
}
|
||||
|
||||
fn visit_macro(&mut self, mac: &'ast syn::Macro) {
|
||||
self.macro_args(mac);
|
||||
visit::visit_macro(self, mac);
|
||||
}
|
||||
}
|
||||
|
||||
/// The crate of a site (`crates/<name>/...`).
|
||||
fn crate_of(site: &str) -> &str {
|
||||
site.strip_prefix("crates/").and_then(|s| s.split('/').next()).unwrap_or("?")
|
||||
}
|
||||
|
||||
/// The per-crate counts of `sites`, in [`CRATES`] order: `doc 12, engine 3, ...`.
|
||||
fn counts<'a>(sites: impl Iterator<Item = &'a str>) -> String {
|
||||
let mut n: BTreeMap<&str, usize> = BTreeMap::new();
|
||||
for s in sites {
|
||||
*n.entry(crate_of(s)).or_default() += 1;
|
||||
}
|
||||
CRATES.iter().map(|c| c.trim_start_matches("crates/")).map(|c| format!("{c} {}", n.get(c).unwrap_or(&0))).collect::<Vec<_>>().join(", ")
|
||||
}
|
||||
|
||||
/// The gate: every site in `found` is listed in `list`, and every listed site is still found.
|
||||
fn grade(list: &str, found: &BTreeMap<String, Vec<&'static str>>) -> Result<String, String> {
|
||||
let listed = crate::kernel_cases::known_failing(list)?;
|
||||
let mut problems = Vec::new();
|
||||
for (site, uses) in found {
|
||||
if !listed.iter().any(|(s, _)| s == site) {
|
||||
problems.push(format!(
|
||||
"NEW: {site} computes geometry ({}): call the kernel (solidcore ops) for it instead; apps hold state and compute no geometry",
|
||||
uses.join(", ")
|
||||
));
|
||||
}
|
||||
}
|
||||
for (site, _) in &listed {
|
||||
if !found.contains_key(*site) {
|
||||
problems.push(format!("FIXED: {site} no longer computes geometry (or is gone): remove its line"));
|
||||
}
|
||||
}
|
||||
let by_crate = counts(listed.iter().map(|(s, _)| *s));
|
||||
if problems.is_empty() {
|
||||
Ok(format!("app-geometry: {} sites compute geometry ({by_crate}); no new ones", listed.len()))
|
||||
} else {
|
||||
Err(format!("{} problem(s) (listed: {by_crate}):\n {}", problems.len(), problems.join("\n ")))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn geometry_is_found_per_function_outside_tests() {
|
||||
let s = sites(
|
||||
r#"
|
||||
fn tilt(n: V, u: V) -> f64 { n.dot(u).acos() }
|
||||
struct Rig;
|
||||
impl Rig {
|
||||
fn frame(&self, a: V, b: V) -> V { let f = |x: V| x.cross(b); f(a).normalized() }
|
||||
fn nested(&self) { fn inner(a: V) -> f64 { a.magnitude() } let _ = inner; }
|
||||
fn plain(&self, a: f64) -> f64 { a.sqrt() + a.sin() }
|
||||
fn arity(&self, a: V) -> f64 { a.dot() }
|
||||
}
|
||||
fn in_macro(a: V, b: V) -> String { format!("{}", a.distance(b)) }
|
||||
fn mesh(m: &Mesh) -> usize { m.triangles.len() }
|
||||
#[cfg(test)] mod tests { fn fixture(a: V, b: V) { a.dot(b); } }
|
||||
#[test] fn t(a: V, b: V) { a.dot(b); }
|
||||
mod inner { fn nearest(p: V) -> V { closest_on_triangle(p, p, p, p) } }
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
let keys: Vec<&str> = s.keys().map(String::as_str).collect();
|
||||
assert_eq!(keys, ["Rig::frame", "Rig::nested", "in_macro", "inner::nearest", "mesh", "tilt"]);
|
||||
assert_eq!(s["Rig::frame"], ["cross", "normalized"]);
|
||||
assert_eq!(s["Rig::nested"], ["magnitude"], "a nested fn counts toward its parent");
|
||||
assert!(sites("invalid Rust {{").is_err(), "unparseable source fails closed");
|
||||
}
|
||||
|
||||
const LISTED: &str = "# comment\ncrates/doc/src/a.rs:tilt dot\ncrates/ui-egui/src/b.rs:near distance\n";
|
||||
|
||||
fn found(names: &[&str]) -> BTreeMap<String, Vec<&'static str>> {
|
||||
names.iter().map(|n| (n.to_string(), vec!["dot"])).collect()
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn listed_sites_pass_and_counts_are_per_crate() {
|
||||
let ok = grade(LISTED, &found(&["crates/doc/src/a.rs:tilt", "crates/ui-egui/src/b.rs:near"])).unwrap();
|
||||
assert!(ok.contains("doc 1, engine 0, ui-egui 1, io 0, kernel 0, geom 0"), "{ok}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_new_site_fails() {
|
||||
let e = grade(LISTED, &found(&["crates/doc/src/a.rs:tilt", "crates/ui-egui/src/b.rs:near", "crates/doc/src/a.rs:pick"])).unwrap_err();
|
||||
assert!(e.contains("NEW: crates/doc/src/a.rs:pick"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_gone_site_still_listed_fails() {
|
||||
let e = grade(LISTED, &found(&["crates/doc/src/a.rs:tilt"])).unwrap_err();
|
||||
assert!(e.contains("FIXED: crates/ui-egui/src/b.rs:near"), "{e}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_list_is_sorted_with_a_reason_each() {
|
||||
assert!(grade("b.rs:near x\na.rs:tilt x\n", &found(&["a.rs:tilt", "b.rs:near"])).is_err());
|
||||
assert!(grade("a.rs:tilt\n", &found(&["a.rs:tilt"])).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn the_workspace_has_no_new_app_geometry() {
|
||||
run(Path::new(env!("CARGO_MANIFEST_DIR")).parent().unwrap(), &[]).unwrap();
|
||||
}
|
||||
}
|
||||
+10
-1
@@ -3,6 +3,7 @@
|
||||
//! Rust workspace tooling. External tools (`cargo`, and Python 3.11+ for the
|
||||
//! original icon collection) are invoked through `std::process::Command`.
|
||||
|
||||
mod app_geometry;
|
||||
mod app_icons;
|
||||
mod assets;
|
||||
mod book;
|
||||
@@ -121,6 +122,12 @@ commands:
|
||||
geometry: fails on a function outside display, export, previews and mesh bodies
|
||||
that uses a mesh and xtask/mesh-decisions.txt does not list, and on a listed one
|
||||
that no longer does (the list only shrinks)
|
||||
app-geometry [--print]
|
||||
check that apps compute no geometry: fails on a function in crates/doc, engine,
|
||||
ui-egui, io, kernel or geom that does vector maths, solves or mesh queries and
|
||||
xtask/app-geometry.txt does not list, and on a listed one that no longer does
|
||||
(the list only shrinks); prints the listed count per crate. --print writes the
|
||||
sites found, in the list's format
|
||||
test [CARGO ARGS] [-- TEST ARGS]
|
||||
build the tests with the ci profile, then run every test binary at once
|
||||
(and the doc tests alongside); SOLIDCRAFT_TEST_JOBS=N bounds concurrent test
|
||||
@@ -136,7 +143,7 @@ commands:
|
||||
check in headless Chrome that the web app works offline and updates to a new
|
||||
build (a stand-in app, or the web build in DIR such as dist/web)
|
||||
ci fmt --check, assets, icons, licences --check, lists, skills, layers, shared,
|
||||
ui-containers, mesh-decisions, clippy -D warnings, then the tests
|
||||
ui-containers, mesh-decisions, app-geometry, clippy -D warnings, then the tests
|
||||
with the wasm check alongside, then web-offline-test when Chrome is installed
|
||||
(stops at the first failing step)
|
||||
";
|
||||
@@ -166,6 +173,7 @@ fn main() -> ExitCode {
|
||||
Some("fold-pending") => pending::run(&root()),
|
||||
Some("skills") => skills::run(&root()),
|
||||
Some("mesh-decisions") => mesh_decisions::run(&root()),
|
||||
Some("app-geometry") => app_geometry::run(&root(), &rest),
|
||||
Some("lists") => lists::run(&root(), rest.contains(&"--fix")),
|
||||
Some("test") => {
|
||||
let (cargo_args, test_args) = rest.iter().position(|a| *a == "--").map_or((&rest[..], &[][..]), |i| (&rest[..i], &rest[i + 1..]));
|
||||
@@ -477,6 +485,7 @@ fn cmd_ci() -> Result<(), String> {
|
||||
shared::run(&root())?;
|
||||
ui_containers::run(&root())?;
|
||||
mesh_decisions::run(&root())?;
|
||||
app_geometry::run(&root(), &[])?;
|
||||
let mut c = cargo();
|
||||
c.args(["clippy", "--workspace", "--all-targets", "--profile", "ci", "--", "-D", "warnings"]);
|
||||
run(c, "cargo clippy -D warnings")?;
|
||||
|
||||
@@ -178,14 +178,14 @@ fn scan_skipping(root: &Path, modules: &[(&str, &str)], functions: &[(&str, &str
|
||||
Ok(found)
|
||||
}
|
||||
|
||||
fn relative(root: &Path, path: &Path) -> String {
|
||||
pub(crate) fn relative(root: &Path, path: &Path) -> String {
|
||||
path.strip_prefix(root).unwrap_or(path).to_string_lossy().replace('\\', "/")
|
||||
}
|
||||
|
||||
/// The source files of the module at `file` and of every non-test module below it, into `out`.
|
||||
/// `owns_dir` is whether `file` is a `lib.rs` or `mod.rs` (its submodules sit beside it) rather
|
||||
/// than `name.rs` (its submodules sit in `name/`).
|
||||
fn module_files(file: &Path, owns_dir: bool, out: &mut Vec<PathBuf>) -> Result<(), String> {
|
||||
pub(crate) fn module_files(file: &Path, owns_dir: bool, out: &mut Vec<PathBuf>) -> Result<(), String> {
|
||||
let source = std::fs::read_to_string(file).map_err(|e| format!("{}: {e}", file.display()))?;
|
||||
let ast = syn::parse_file(&source).map_err(|e| format!("{}: cannot parse Rust source: {e}", file.display()))?;
|
||||
out.push(file.to_path_buf());
|
||||
@@ -244,7 +244,7 @@ fn path_attribute(attrs: &[syn::Attribute]) -> Option<String> {
|
||||
|
||||
/// Whether `attrs` keep an item to test builds: `#[test]`, `#[cfg(test)]` or
|
||||
/// `#[cfg(all(.., test, ..))]`.
|
||||
fn test_only(attrs: &[syn::Attribute]) -> bool {
|
||||
pub(crate) fn test_only(attrs: &[syn::Attribute]) -> bool {
|
||||
attrs.iter().any(|a| {
|
||||
if a.path().is_ident("test") {
|
||||
return true;
|
||||
@@ -310,7 +310,7 @@ impl Finder {
|
||||
}
|
||||
}
|
||||
|
||||
fn type_name(ty: &syn::Type) -> String {
|
||||
pub(crate) fn type_name(ty: &syn::Type) -> String {
|
||||
match ty {
|
||||
syn::Type::Path(p) => p.path.segments.last().map_or_else(|| "<type>".to_owned(), |s| s.ident.to_string()),
|
||||
syn::Type::Reference(r) => type_name(&r.elem),
|
||||
|
||||
Reference in New Issue
Block a user