-
Notifications
You must be signed in to change notification settings - Fork 53
Comparing changes
Open a pull request
base repository: fallow-rs/fallow
base: v2.62.0
head repository: fallow-rs/fallow
compare: v2.63.0
- 11 commits
- 82 files changed
- 2 contributors
Commits on May 2, 2026
-
Configuration menu - View commit details
-
Copy full SHA for 331b141 - Browse repository at this point
Copy the full SHA 331b141View commit details
Commits on May 3, 2026
-
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.
Configuration menu - View commit details
-
Copy full SHA for db70a40 - Browse repository at this point
Copy the full SHA db70a40View commit details -
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.
Configuration menu - View commit details
-
Copy full SHA for 371fc4b - Browse repository at this point
Copy the full SHA 371fc4bView commit details -
Configuration menu - View commit details
-
Copy full SHA for 3b35832 - Browse repository at this point
Copy the full SHA 3b35832View commit details -
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.Configuration menu - View commit details
-
Copy full SHA for 1438939 - Browse repository at this point
Copy the full SHA 1438939View commit details -
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.Configuration menu - View commit details
-
Copy full SHA for 49cf01e - Browse repository at this point
Copy the full SHA 49cf01eView commit details -
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.
Configuration menu - View commit details
-
Copy full SHA for 369054b - Browse repository at this point
Copy the full SHA 369054bView commit details -
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`.
Configuration menu - View commit details
-
Copy full SHA for 9726715 - Browse repository at this point
Copy the full SHA 9726715View commit details -
Configuration menu - View commit details
-
Copy full SHA for 1bf581f - Browse repository at this point
Copy the full SHA 1bf581fView commit details -
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`.
Configuration menu - View commit details
-
Copy full SHA for 18afe8f - Browse repository at this point
Copy the full SHA 18afe8fView commit details -
Configuration menu - View commit details
-
Copy full SHA for 509b5fe - Browse repository at this point
Copy the full SHA 509b5feView commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff v2.62.0...v2.63.0