Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: fallow-rs/fallow
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v2.62.0
Choose a base ref
...
head repository: fallow-rs/fallow
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v2.63.0
Choose a head ref
  • 11 commits
  • 82 files changed
  • 2 contributors

Commits on May 2, 2026

  1. Configuration menu
    Copy the full SHA
    331b141 View commit details
    Browse the repository at this point in the history

Commits on May 3, 2026

  1. fix(scripts): harden CI YAML scanner against shell/regex fragment mis…

    …classification (#262)
    
    The CI YAML scanner extracts shell command tokens from `run:` blocks
    into entry-pattern candidates and feeds them to globset. Several token
    classes are not valid filesystem paths and produce noisy
    `invalid entry pattern` warnings on every run:
    
    - GitHub Actions expressions: `${{ env.URL }}/api/health` whitespace-splits
      into chunks like `}}/api/health` (unclosed alternation).
    - jq array iterators: `jq -r '.[]'` produces the token `'.[]'`
      (empty character class in glob syntax).
    - Perl regex fragments: `grep -oP '(?<=Module )\\./[^ ]+...'` produces
      chunks like `)\\./[^` (backslash plus unclosed class).
    
    Two changes:
    
    1. Tighten `looks_like_file_path` with negative guards for tokens that
       cannot be Unix paths: `${{`/`}}`, backslashes, and `[` followed
       immediately by `]` or with no closing `]`. Next.js dynamic-route
       segments like `app/[id]/page.tsx` are unaffected because their `[`
       has at least one char before `]`.
    2. Filter `extract_ci_signals`'s `cmd.config_args`/`cmd.file_args` through
       the negative-only guard `could_be_file_path` before extending
       `entry_files`, so quoted shell tokens that `parse_script` classifies
       as args never reach globset compilation. Bare-name paths like
       `deploy.log` still pass through.
    
    Tests: 6 new unit tests covering each rejection class plus a Next.js
    dynamic-route positive case, 2 integration tests using verbatim
    in-the-wild fragments from real-world workflow YAML.
    fmguerreiro authored May 3, 2026
    Configuration menu
    Copy the full SHA
    db70a40 View commit details
    Browse the repository at this point in the history
  2. refactor(scripts): share could_be_file_path with parse_scripts (#264)

    PR #262 added `could_be_file_path` in `crates/core/src/scripts/mod.rs` to
    reject tokens whose syntax precludes a Unix path (GHA expressions,
    backslash escapes, malformed `[...]`) before they reach globset
    compilation. The sibling `looks_like_file_path` /
    `looks_like_script_file` in `crates/core/src/discover/parse_scripts.rs`
    classify the same kinds of tokens for package.json / Dockerfile /
    Procfile / fly.toml extraction, but feed `resolve_entry_path_with_tracking`
    (real-fs `is_file()` check) instead of globset, so they never produced
    the noisy `invalid entry pattern` warning.
    
    No behavioral bug exists today, but maintaining two near-identical
    classifiers invites drift. Promote `could_be_file_path` from private to
    `pub` and call it as a pre-filter from the discover-side classifiers.
    This is a single-source-of-truth refactor with a micro perf win
    (skip `Path::join` + `is_file` syscalls on tokens that cannot be paths)
    and zero behavioral change for users.
    
    Verified: total_issues on the next.js benchmark is unchanged (24825),
    no `invalid entry pattern` warnings emitted (already 0 after #262), all
    1917 fallow-core unit tests + 297 integration tests pass, full workspace
    test suite green.
    BartWaardenburg authored May 3, 2026
    Configuration menu
    Copy the full SHA
    371fc4b View commit details
    Browse the repository at this point in the history
  3. Configuration menu
    Copy the full SHA
    3b35832 View commit details
    Browse the repository at this point in the history
  4. fix(plugins): treat dynamic(import().then(m => m.X)) as re-export (#263)

    The Next.js code-splitting idiom
    
        // Foo.tsx
        export function Foo() { /* heavy */ }
    
        // Foo-lazy.tsx
        export const Foo = dynamic(
          () => import('./Foo').then(m => m.Foo),
          { ssr: false },
        );
    
    is semantically a re-export of `Foo`, equivalent to
    `export { Foo } from './Foo'`, which fallow already does not flag.
    Currently both files appear in `duplicate_exports` for `Foo`.
    
    `find_duplicate_exports` now accepts the resolved module list and
    extends `re_export_sources` with dynamic-import edges that act as
    re-exports. The classification mirrors the static `export from` shape
    exactly: a dynamic import counts as a re-export only when the wrapper
    module also exports the same name (`Named("X")` requires the wrapper
    to export "X"; `Default` requires the wrapper to have a default
    export). `Namespace` and `SideEffect` imports never count as
    re-exports.
    
    The dynamic-reexport collection lives in a separate
    `collect_dynamic_reexport_sources` helper to keep
    `find_duplicate_exports` focused on duplicate detection.
    
    Five unit tests:
    - happy paths for `.then(m => m.Foo)` and default-import variants
    - wrapper has Named dynamic import but exports a different name
    - wrapper has Default dynamic import but no default export
    - two unrelated modules with no dynamic link still flagged
    
    The two missing-export tests guard against false negatives where a
    module dynamically imports something but does not actually re-export
    it.
    
    All 16 existing call sites of `find_duplicate_exports` pass `&[]`
    for the new parameter, preserving their semantics. Pass `&[]` to opt
    out of dynamic-import re-export detection.
    fmguerreiro authored May 3, 2026
    Configuration menu
    Copy the full SHA
    1438939 View commit details
    Browse the repository at this point in the history
  5. feat(vitest): recognize __mocks__ specifiers as virtual (#265)

    Vitest's manual-mock convention places mock factories at
    `<package>/__mocks__/<module>.ts` and triggers them via
    `vi.mock('<module>')`. Some test setups also import directly from
    `@<scope>/__mocks__` paths via package.json `imports` aliases or
    workspace virtual paths:
    
        import { mockS3Send } from '@aws-sdk/__mocks__';
    
    Fallow flags these as `unlisted-dep`, and the auto-fix suggests
    'install this package' — but `@aws-sdk/__mocks__`,
    `@sentry/__mocks__`, `@supabase/__mocks__` etc. do not exist on
    npm.
    
    Adds a sibling abstraction to the existing `virtual_module_prefixes`
    (prefix-based matching used by Nuxt etc.):
    
    - New `Plugin` trait method `virtual_package_suffixes() -> &[&'static str]`
      with default `&[]`, propagated through `AggregatedPluginResult`.
    - `VitestPlugin` returns `&["/__mocks__"]`.
    - `find_unlisted_dependencies` skips any extracted `package_name`
      ending with a registered suffix.
    - `run_plugins` merges `virtual_package_suffixes` from per-workspace
      plugin runs into the root `AggregatedPluginResult`, mirroring the
      existing merge for `virtual_module_prefixes` and
      `generated_import_patterns`. Without this, when vitest is only in a
      workspace's `package.json` (not the root), the suffix gets registered
      locally and dropped before the analyzer reads it.
    
    Tests:
    - 5 unit tests in `vitest.rs` and `unused_deps_tests` covering
      scoped (`@aws-sdk/__mocks__`), unscoped (`some-pkg/__mocks__`),
      and non-mocks specifiers.
    - Registry test asserting vitest contributes the suffix.
    - 2 integration tests with fixtures: `vitest-mocks-virtual`
      (single-package, vitest at root) and `vitest-mocks-workspace`
      (monorepo, vitest only in a workspace's package.json) — the second
      fixture reproduces the workspace-aggregation case the unit tests
      alone cannot catch.
    fmguerreiro authored May 3, 2026
    Configuration menu
    Copy the full SHA
    49cf01e View commit details
    Browse the repository at this point in the history
  6. test(plugins): close coverage and macro gaps from #263 / #265 review

    Three follow-ups surfaced during pre-ship review of #263 and #265:
    
    1. #263 added five tests for the Next.js dynamic-import re-export fix.
       Four are regression-strength but `dynamic_import_named_without_matching_export_still_flagged`
       is coverage-adjacent: the wrapper exports `Bar` and the duplicate
       detection looks for `Foo` between two unrelated modules, so the
       wrapper's `matches_export` check is never on the path that decides
       the test outcome. Removing the Named branch of `matches_export`
       does not make this test fail. Add a true regression test
       (`dynamic_import_named_mismatched_with_wrapper_export_still_flagged`)
       where the wrapper exports `Foo` AND dynamically imports a different
       name `Bar` from source, so without the matches_export Named branch
       the wrapper would be registered as a re-export edge and the
       (source, wrapper) `Foo` duplicate would be silently suppressed.
       Empirically verified: removing the Named branch makes this test
       fail and leaves the other four passing.
    
    2. #265 added `Plugin::virtual_package_suffixes()` but only via an
       explicit `impl Plugin for VitestPlugin` block. The three
       `define_plugin!` macro variants in `plugins/mod.rs` still listed
       `virtual_module_prefixes` without a peer entry for the new field,
       so a future plugin author using the macro would have no path to
       declare suffixes. Extend all three variants to accept
       `virtual_package_suffixes:` and add a synthetic smoke test to guard
       future macro regressions.
    
    3. `.claude/rules/plugins.md` `## Plugin trait extensions` section
       listed `path_aliases` and `virtual_module_prefixes` but not the new
       `virtual_package_suffixes`. Add it with a one-line description and
       a Vitest example.
    BartWaardenburg committed May 3, 2026
    Configuration menu
    Copy the full SHA
    369054b View commit details
    Browse the repository at this point in the history
  7. fix(eslint): trace flat-config plugin imports through workspace-inter…

    …nal config packages (#266)
    
    In Turborepo/Nx monorepos that centralize ESLint config in a workspace
    package, every workspace gets false `unused-devdep` flags for the
    plugins that the shared config imports.
    
    Pattern:
    
        // apps/foo/eslint.config.mjs
        import next from '@scope/eslint-config/next';
        export default [...next];
    
        // packages/eslint-config/next.js
        import reactPlugin from 'eslint-plugin-react';
        export default [{ plugins: { react: reactPlugin } }];
    
        // apps/foo/package.json
        {
          "devDependencies": {
            "eslint": "^9",
            "eslint-plugin-react": "^7",
            "@scope/eslint-config": "*"
          }
        }
    
    `eslint-plugin-react` is flagged as unused-devdep in
    `apps/foo/package.json` even though the workspace package transitively
    imports it.
    
    Two changes work together:
    
    1. `crates/core/src/plugins/eslint.rs` — `extract_eslint_config` walks
       up the directory tree to find hoisted `node_modules` (Turborepo/pnpm
       do not co-locate workspace deps under the workspace's own
       `node_modules`), and `read_package_entry_for_specifier` resolves
       subpath imports like `@scope/eslint-config/next` via the package's
       `exports` map or by probing `.js`/`.mjs`/`.cjs` extensions on the
       bare subpath name.
    
    2. `crates/core/src/plugins/registry/mod.rs` — adds `"eslint"` to the
       `must_parse_workspace_config_when_root_active` allowlist. Without
       this, when ESLint is already active at the monorepo root, every
       workspace's `eslint.config.*` is silently skipped by
       `run_workspace_fast`, and the (1) resolver never gets called.
       Discovered via integration testing: the resolver was correct in
       isolation but never invoked at runtime.
    
    Tests:
    - Existing `eslint.rs` unit tests cover the resolver (38 passing).
    - New `run_workspace_fast_eslint_config_parsed_when_eslint_active_at_root`
      registry test reproduces the real-world Turborepo layout (hoisted
      `@scope/eslint-config` package + workspace eslint.config.mjs +
      skip_config_plugins containing 'eslint') and asserts
      `eslint-plugin-react` appears in `referenced_dependencies`.
    fmguerreiro authored May 3, 2026
    Configuration menu
    Copy the full SHA
    9726715 View commit details
    Browse the repository at this point in the history
  8. Configuration menu
    Copy the full SHA
    1bf581f View commit details
    Browse the repository at this point in the history
  9. ci(githooks): use rustup run nightly cargo miri instead of cargo +nig…

    …htly
    
    The pre-push hook's miri block called `cargo +nightly miri test`. The
    `+toolchain` shorthand is parsed only by rustup's cargo proxy
    (~/.cargo/bin/cargo); on machines where Homebrew's bare cargo wins the
    PATH lookup (`/opt/homebrew/bin/cargo` -> `/opt/homebrew/Cellar/rust/.../bin/cargo`)
    the bare cargo errors with `no such command: +nightly` even though
    rustup and the nightly toolchain are both installed.
    
    The detection block already used `rustup run nightly cargo miri --version`
    correctly, so the inconsistency between detection and execution was the
    bug. Switch the execution lines to the same `rustup run nightly` form.
    
    Verified locally: pushing a commit with Homebrew's cargo first in PATH
    now succeeds without needing `PATH="$HOME/.cargo/bin:$PATH" git push`.
    BartWaardenburg committed May 3, 2026
    Configuration menu
    Copy the full SHA
    18afe8f View commit details
    Browse the repository at this point in the history
  10. Configuration menu
    Copy the full SHA
    509b5fe View commit details
    Browse the repository at this point in the history
Loading