@@ -250,3 +250,53 @@ func (c *CargoUdepsCheck) Run(ctx *CheckContext) error {
250250 }
251251 return nil
252252}
253+
254+ // JscpdRustCheck detects code duplication in Rust files.
255+ type JscpdRustCheck struct {}
256+
257+ func (c * JscpdRustCheck ) Name () string {
258+ return "jscpd-rust"
259+ }
260+
261+ func (c * JscpdRustCheck ) Run (ctx * CheckContext ) error {
262+ rustSrcDir := filepath .Join (ctx .RootDir , "apps" , "desktop" , "src-tauri" , "src" )
263+
264+ // Check if jscpd is available via npx
265+ cmd := exec .Command ("npx" , "jscpd" , "--version" )
266+ if _ , err := runCommand (cmd , true ); err != nil {
267+ fmt .Printf ("%sInstalling jscpd...%s " , colorYellow , colorReset )
268+ installCmd := exec .Command ("npm" , "install" , "-g" , "jscpd" )
269+ if _ , err := runCommand (installCmd , true ); err != nil {
270+ return fmt .Errorf ("failed to install jscpd: %w" , err )
271+ }
272+ }
273+
274+ // Run jscpd on Rust source files
275+ // --min-lines 5: minimum 5 lines to consider as duplicate
276+ // --min-tokens 100: minimum 100 tokens (filters out small trait impls and test data)
277+ // --threshold 2: fail if >2% duplication
278+ // --ignore-pattern: ignore trait implementations (impl X for Y) which often have
279+ // similar accessor methods that can't be deduplicated
280+ cmd = exec .Command ("npx" , "jscpd" ,
281+ rustSrcDir ,
282+ "--format" , "rust" ,
283+ "--min-lines" , "5" ,
284+ "--min-tokens" , "100" ,
285+ "--threshold" , "2" ,
286+ "--ignore" , "**/test*.rs,**/*_test.rs" ,
287+ "--reporters" , "console" ,
288+ )
289+ output , err := runCommand (cmd , true )
290+ if err != nil {
291+ // jscpd returns non-zero when threshold exceeded
292+ if strings .Contains (output , "duplicated lines" ) || strings .Contains (output , "threshold" ) {
293+ fmt .Println ()
294+ fmt .Print (indentOutput (output , " " ))
295+ return fmt .Errorf ("code duplication exceeds threshold (2%%)" )
296+ }
297+ fmt .Println ()
298+ fmt .Print (indentOutput (output , " " ))
299+ return fmt .Errorf ("jscpd failed" )
300+ }
301+ return nil
302+ }
0 commit comments