Browse / github.com/storytold/effectcraft / patch
Enforce Rust coding standards via Clippy
Enforce Rust coding standards via Clippy Denies `clippy::cognitive_complexity` (threshold 30, matches Sonar rust:S3776), `too_many_arguments` and `wildcard_imports` workspace-wide. Existing violations are fixed in follow-up commits. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> <!-- coderabbit-ai-generated:v1 --> ## Summary by CodeRabbit * **Bug Fixes** * Improved path handling so folder names, folder paths, and joined paths consistently trim trailing slash and backslash characters, including paths made up only of separators. * **Chores** * Updated automated code checks to flag overly complex code,
- Status
- open
- Score
- 0.00
- Adopters
- 0 other operators
- Author
- dylanmtaylor (imported from pull request #2496, open)
- Base
dd12ed8f9c341a6081e08c8fd840aa3bb4325537- Size
- +78 −27 in 7 files
- Review
- pending
- Shared
- 2026-10-28
- Id
9e1a64309ac77d5cf64fbdb2
How it went
No other operator has reported using it yet.
Diff
raw · CLAUDE.md Cargo.toml clippy.toml crates/raster/src/inpaint.rs crates/script/src/prelude.js crates/script/src/tests.rs xtask/Cargo.toml
@@ -22,6 +22,7 @@ EffectCraft is a clean-room, open-source, pure-Rust motion graphics and visual e - **Everything is agent-drivable:** every interactive widget registers an automation id; UI state is serde. - **Community links** (Help menu, About, header Discord button): https://discord.gg/artcraft, https://getartcraft.com, https://getartcraft.com/apps/effectcraft, https://github.com/storytold/effectcraft. - **Quality gates** before every commit: `cargo xtask ci` (fmt, clippy -D warnings, tests, layers, assets, wasm).+- **New workspace crates** must include `[lints] workspace = true` in `Cargo.toml` to inherit `[workspace.lints]`. Production crate roots must declare the required crate-level lint attributes before modules or other crate items; crate-level `//!` documentation may precede them. - **Commits:** one task id per commit (`M6.3: trim paths`). Only green states. ## Running and looking at the app@@ -135,9 +135,11 @@ sys-locale = "0.3" unsafe_code = "forbid" [workspace.lints.clippy]+cognitive_complexity = "deny"+too_many_arguments = "deny"+wildcard_imports = "deny" dbg_macro = "warn" todo = "warn"-too_many_arguments = "allow" needless_range_loop = "allow" type_complexity = "allow" @@ -5,3 +5,4 @@ allow-indexing-slicing-in-tests = true disallowed-types = [ { path = "egui::ComboBox", reason = "use craft_ui::ComboBox: egui's combo box menu can't grow back while open (egui#5225)" }, ]+cognitive-complexity-threshold = 30@@ -607,38 +607,44 @@ pub struct FillInput { pub references: Vec<(usize, Image)>, } -/// Trace pixel (x, y) of frame `t` through `flows` (`step` = +1 forward with `fwd`, −1 backward-/// with `bwd`) to the nearest frame where it is visible: (sampled colour, frames travelled).-fn trace(- t: usize,+/// Shared frame and flow data for tracing pixels through a sequence.+struct TraceContext<'a> {+ fwd: &'a [Flow],+ bwd: &'a [Flow],+ known: &'a (dyn Fn(usize, i64, i64) -> bool + Sync),+ frames: &'a [&'a Image],+ max: usize,+}++/// Starting frame, point, and direction for a flow trace.+struct TraceStart {+ frame: usize, x: f64, y: f64, step: i64,- fwd: &[Flow],- bwd: &[Flow],- known: &dyn Fn(usize, i64, i64) -> bool,- frames: &[&Image],- max: usize,-) -> Option<([f32; 4], usize)> {- let n = frames.len();- let (w, h) = (frames[0].width as f64, frames[0].height as f64);- let (mut px, mut py) = (x, y);- let mut k = t as i64;- for d in 1..=max {- let v = if step > 0 {+}++/// Trace a point to the nearest visible frame: (sampled colour, frames travelled).+fn trace(context: &TraceContext<'_>, start: TraceStart) -> Option<([f32; 4], usize)> {+ let n = context.frames.len();+ let (w, h) = (context.frames[0].width as f64, context.frames[0].height as f64);+ let (mut px, mut py) = (start.x, start.y);+ let mut k = start.frame as i64;+ for d in 1..=context.max {+ let v = if start.step > 0 { if k as usize + 1 >= n { return None; }- fwd[k as usize].at(px, py)+ context.fwd[k as usize].at(px, py) } else { if k == 0 { return None; }- bwd[k as usize - 1].at(px, py)+ context.bwd[k as usize - 1].at(px, py) }; px += v[0] as f64; py += v[1] as f64;- k += step;+ k += start.step; if px < 0.0 || py < 0.0 || px >= w || py >= h { return None; }@@ -646,9 +652,9 @@ fn trace( let (x0, y0) = ((px - 0.5).floor() as i64, (py - 0.5).floor() as i64); let ok = [(x0, y0), (x0 + 1, y0), (x0, y0 + 1), (x0 + 1, y0 + 1)] .iter()- .all(|&(xx, yy)| known(k as usize, xx.clamp(0, w as i64 - 1), yy.clamp(0, h as i64 - 1)));+ .all(|&(xx, yy)| (context.known)(k as usize, xx.clamp(0, w as i64 - 1), yy.clamp(0, h as i64 - 1))); if ok {- return Some((frames[k as usize].sample_bilinear_clamped(px, py), d));+ return Some((context.frames[k as usize].sample_bilinear_clamped(px, py), d)); } } None@@ -697,6 +703,7 @@ pub fn fill_sequence(input: &FillInput, o: &FillOpts, progress: &mut dyn FnMut(u // 2. Propagation (and the lighting-correction border ring). let refs: Vec<&Image> = frames.iter().collect(); let known = |k: usize, x: i64, y: i64| !holes[k][y as usize * w + x as usize];+ let trace_context = TraceContext { fwd: &fwd, bwd: &bwd, known: &known, frames: &refs, max: n }; let ring_r = 2usize; let propagated: Vec<(Image, Vec<bool>)> = (0..n) .into_par_iter()@@ -708,8 +715,8 @@ pub fn fill_sequence(input: &FillInput, o: &FillOpts, progress: &mut dyn FnMut(u } let sample = |i: usize| -> Option<[f32; 4]> { let (x, y) = ((i % w) as f64 + 0.5, (i / w) as f64 + 0.5);- let f = trace(t, x, y, 1, &fwd, &bwd, &known, &refs, n);- let b = trace(t, x, y, -1, &fwd, &bwd, &known, &refs, n);+ let f = trace(&trace_context, TraceStart { frame: t, x, y, step: 1 });+ let b = trace(&trace_context, TraceStart { frame: t, x, y, step: -1 }); match (f, b) { (Some((a, da)), Some((c, dc))) => { let (wa, wc) = (1.0 / da as f32, 1.0 / dc as f32);@@ -189,7 +189,12 @@ BridgeTalk.pump = function () { return false; }; // ------------------------------------------------------------------ File / Folder -function __join(a, b) { return a.replace(/[\/\\]+$/, "") + "/" + b; }+function __trimPathSeparators(path) {+ var end = path.length;+ while (end > 0 && (path.charAt(end - 1) === "/" || path.charAt(end - 1) === "\\")) end--;+ return path.slice(0, end);+}+function __join(a, b) { return __trimPathSeparators(a) + "/" + b; } function File(path) { if (!(this instanceof File)) return new File(path);@@ -292,9 +297,9 @@ Folder.prototype = { get fsName() { return this.__path; }, get fullName() { return this.__path; }, get absoluteURI() { return this.__path; },- get name() { return this.__path.replace(/[\/\\]+$/, "").split(/[\/\\]/).pop(); },+ get name() { return __trimPathSeparators(this.__path).split(/[\/\\]/).pop(); }, get displayName() { return this.name; },- get path() { var p = this.__path.replace(/[\/\\]+$/, "").split(/[\/\\]/); p.pop(); return p.join("/"); },+ get path() { var p = __trimPathSeparators(this.__path).split(/[\/\\]/); p.pop(); return p.join("/"); }, get parent() { return new Folder(this.path); }, get exists() { return JSON.parse(__file("isDir", this.__path)) === true; }, toString: function () { return this.__path; },@@ -456,6 +456,38 @@ fn layers_items_and_markers() { ); } +#[test]+fn folder_paths_trim_trailing_separators() {+ let mut s = session();+ let o = ok(+ &mut s,+ r#"+ var paths = ['', '/', '///', 'a/b/', 'a\\b\\/', 'a/b', 'a/\n'];+ var result = paths.map(function (path) {+ var folder = new Folder(path);+ return [folder.name, folder.path, __join(path, 'child')];+ });+ // A long run followed by a non-separator used to cause quadratic regex backtracking.+ var longPath = Array(20001).join('/') + 'x';+ result.push([new Folder(longPath).name, new Folder(longPath).path.length, __join(longPath, 'child').length]);+ result+ "#,+ );+ assert_eq!(+ o.result,+ json!([+ ["", "", "/child"],+ ["", "", "/child"],+ ["", "", "/child"],+ ["b", "a", "a/b/child"],+ ["b", "a", "a\\b/child"],+ ["b", "a", "a/b/child"],+ ["\n", "a", "a/\n/child"],+ ["x", 19999, 20007]+ ])+ );+}+ #[test] fn security_gate_for_files() { let dir = std::env::temp_dir().join(format!("ec-script-gate-{}", std::process::id()));@@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true publish = false +[lints]+workspace = true+ [dependencies] serde_json = { workspace = true }