collective

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

diff --git a/CLAUDE.md b/CLAUDE.md--- a/CLAUDE.md+++ b/CLAUDE.md@@ -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 appdiff --git a/Cargo.toml b/Cargo.toml--- a/Cargo.toml+++ b/Cargo.toml@@ -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" diff --git a/clippy.toml b/clippy.toml--- a/clippy.toml+++ b/clippy.toml@@ -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 = 30diff --git a/crates/raster/src/inpaint.rs b/crates/raster/src/inpaint.rs--- a/crates/raster/src/inpaint.rs+++ b/crates/raster/src/inpaint.rs@@ -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);diff --git a/crates/script/src/prelude.js b/crates/script/src/prelude.js--- a/crates/script/src/prelude.js+++ b/crates/script/src/prelude.js@@ -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; },diff --git a/crates/script/src/tests.rs b/crates/script/src/tests.rs--- a/crates/script/src/tests.rs+++ b/crates/script/src/tests.rs@@ -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()));diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml--- a/xtask/Cargo.toml+++ b/xtask/Cargo.toml@@ -5,6 +5,9 @@ edition.workspace = true license.workspace = true publish = false +[lints]+workspace = true+ [dependencies] serde_json = { workspace = true }