Skip to content

v3.41.0: mutation tasks and recheck, nested pnpm workspaces, fewer false positives

Choose a tag to compare

@BartWaardenburg BartWaardenburg released this 07 Nov 16:46
· 175 commits to main since this release
Immutable release. Only release title and notes can be modified.
v3.41.0
3426924

Features

  • fallow mutation tasks lists the open tasks of a mutation report as one batch. A task is a survived or no_coverage mutant. Each task has its file, line, function, operator, the source lines before and after the change, and the covering test files. The human and --format markdown output is a numbered list that a person or a coding agent can use to write the missing tests. --format json gives the mutation-tasks JSON output. Without --report, the command reads the report of the last run. It runs no code. The new MCP tool mutation_tasks returns the same batch. (#4269)

  • fallow mutation --recheck reruns the open tasks of a report against the current tests. Write tests for the batch of fallow mutation tasks, then run fallow mutation --recheck. Fallow runs each survived and no_coverage mutant of the saved report again. With --mutant, only the given ids run. In the new recheck object, each mutant has the outcome killed, survived, no_coverage, tests_fail_on_original or unsettled. A selected file that changed after the report stops the recheck with FALLOW_MUTATION_SOURCE_CHANGED. An uncommitted change to another non-test file stops it with FALLOW_MUTATION_NON_TEST_CHANGE. A recheck without --mutant replaces the saved report, so fallow mutation tasks then lists the mutants that are still open. (#4277)

  • fallow mutation --compare <file> lists the mutants that a test change no longer detects. Save a complete report, change the tests, then run fallow mutation --compare <file>. The new compare object lists a mutant as regressed when it was killed or timeout before and is survived or no_coverage now. Each regressed mutant has its earlier previous_killing_tests, and the human output names the test that killed it before. A flaky mutant in either run is inconclusive. A mutant id in one run only is added or removed. When the saved report or the run is not complete, Fallow exits with code 2 and FALLOW_MUTATION_COMPARE_INCOMPLETE. (#4306)

  • Fallow analyzes the members of a nested pnpm workspace. A directory with its own package.json and pnpm-workspace.yaml, which no root workspace pattern declares, is an independent pnpm project. Its members are now workspaces, and fallow list --workspaces shows them under the nested project root. A catalog: reference in the nested project resolves against the nested pnpm-workspace.yaml. The catalog findings and fallow fix use the nested file. A missing member directory gives a workspace-pattern-dir-missing diagnostic, and Fallow then reports no unused entries of the nested catalog. A member that a root workspace pattern declares keeps the root catalog, and the root catalog checks do not change. Fallow does not read nested npm and yarn workspaces. (#4307)

  • fallow list --entry-points and --trace-file name the package.json script of a script entry point. The human output shows the script key, for example scripts/build.js (package.json script `build`). The JSON output adds the optional field script to each entry_points[] item and entry_script to the file trace. (#4300)

Changed

  • fallow mutation --kill-matrix-for gives each target its own evidence status. Each entry of kill_matrix.targets now has a status (complete or incomplete) and its reasons. A mutant with incomplete evidence now removes only the candidates of the targets that cover it. The run-wide reasons partial-mutation-run, baseline-failure and missing-test-file-metadata still remove all candidates. The top-level status keeps its meaning, so candidates can be nonempty while status is incomplete. (#4317)

  • An audit review comment of an inherited complexity finding names the base values. This applies to --format review-github, review-gitlab and codeclimate. The comment names each exceeded metric with its base and head values. For example: (inherited from the base: cyclomatic 23 -> 19, cognitive 22 -> 18). A reviewer can see if the function stayed the same or became simpler. The JSON output does not change, so fallow report --from on a saved audit shows the comment without the base values. The CodeClimate fingerprints do not change. (#4304)

  • Audit review comments state the attribution of dead-code, stale-suppression, duplication and styling findings. review-github and review-gitlab put introduced or inherited after the severity label, as they do for complexity findings. Under --gate new-only, an inherited finding shows that it does not gate the audit. The CodeClimate output and the fingerprints do not change. (#4319)

  • Unused internal Rust items are gone. Workspace crates lost the pub items that no other crate uses. Only fallow-api, fallow-types, fallow-output, fallow-config, fallow-report-protocol, fallow-license and fallow-v8-coverage keep their exported items. The removed items include the deprecated fallow_core::analyze::feature_flags functions. Use fallow_api::run_feature_flags. The CLI, the JSON output, the npm packages and the protocols do not change. (#4260)

Bug fixes

Dead code and resolution

  • Workspace imports resolve without an install or a build. A tsconfig paths entry of one workspace no longer makes the package imports of other workspaces unresolved. With emitDeclarationOnly: true, an import of build output such as dist/cli.js now maps to src/cli.ts when that file exists. Fallow tries each active exports or imports condition in manifest order until one target resolves. Thus a types condition that points to an unbuilt file no longer hides a source condition. (#4310)

  • A framework path alias of a workspace package stays in that package. In a monorepo with two SvelteKit apps, $lib/foo in one app could resolve into the other app. Fallow then reported false boundary-violation and unused-file findings. A plugin alias from a workspace package (SvelteKit $lib, Nuxt ~/ and the others) now applies only to the files of that package. When the package has no target for the import, Fallow now reports unresolved-import. SvelteKit $lib also follows kit.files.lib. Thanks @MikeSpock for the report. (#4287)

  • ESLint, Jest and package.json imports entries credit more packages and files. An ESLint extends entry with a subpath, such as eslint-config-shared/back/typescript, now credits its package. For a workspace package without an install, Fallow now finds the package by name and uses the file that the entry loads. A workspace eslint.config.mjs no longer hides the root .eslintrc.json. Fallow reads a relative Jest preset as Jest config, and a package in setupFiles, setupFilesAfterEnv, globalSetup or globalTeardown gets credit. Manual mocks in __mocks__ stay used when a config sets testMatch or testRegex. Each condition target of a package.json imports entry is now used. (#4289, #4334)

  • Package scripts give more entry points. Fallow reads the body of sh -c, bash -c, zsh -c and dash -c as a command list. Fallow then finds the entry point of each chained command (#4273). A Yarn run -T jest call counts as a jest call. A Jest moduleNameMapper target that names one file with <rootDir> is an entry point. Fallow reads the version and publish inputs of changesets/action as commands (#4323). When a script runs the binary of an npm alias from a pnpm or Bun catalog, Fallow credits the declared package, for example @typescript/native. Thanks @cloud-walker for the report. (#4321)

  • The Nuxt plugin reads the dir object of nuxt.config. Custom pages, layouts, middleware, plugins and modules directories add their convention entry points. Fallow no longer reports their files as unused. (#4272)

  • fallow dead-code follows more dynamic imports. A pattern with a literal suffix and no extension, such as import(`./icons/__${name}__`), now loads the matching module files. A dynamic import of a const string now credits the package. (#4314)

  • fallow dead-code credits class members more accurately. A method chain through methods that return this now credits each member of the chain (#4314). Members of an inherited this.find() result get credit when the base class is not exported. A local of a call with arguments, such as const table = this.findBy(1), now credits the members of the return type (#4320). A shadowing callback parameter, loop head, catch parameter or block declaration no longer credits the return type of a this method result (#4303).

Mutation testing

  • More mutation runs complete with correct results. Fallow no longer counts bench files and helper modules below __tests__ as test files (#4265). A test name with a lone UTF-16 surrogate no longer stops the dry run (#4264). The runner strips the types of a TypeScript mutant behind a project loader that compiles TypeScript (#4290). The result cache now keys on the bunfig.toml preload files also when no package enables the Bun plugin (#4263).

  • Mutation runs no longer leave the temp files of killed test runs. Each test runner process gets its own temp directory, and Fallow deletes it when the process ends. This is also true after Ctrl+C (#4284, #4301, #4309, #4315). A Ctrl+C during the dry run no longer prints a runner error (#4327).

Health, duplication and policy

  • fallow health scores the functions in Astro <script> blocks. Each inline <script> block, including is:inline and define:vars scripts, now gets a score, as a Vue or Svelte <script> block does. Existing Astro projects can get new complexity and CRAP findings. Thanks @Djkde01 for the report. (#4286)

  • fallow health --css counts @keyframes in SCSS and Less sources. An animation: spin 1s next to its @keyframes spin no longer gives a false undefined-keyframes finding. When Fallow cannot resolve a Sass or Less animation value, the unreferenced check skips the Sass and Less @keyframes. The new summary field keyframes_unchecked gives their count. Thanks @Smrtnyk for the report. (#4262)

  • A thresholdOverrides entry that sets a lower limit reads active while it applies. Before, such an entry read stale. A stale row tells you to remove the entry, but this entry produced the finding. (#4295)

  • fallow dupes reports clone ranges that match the cloned tokens. Each token now has the span of its own text. A clone no longer covers all the lines of a block, so duplicated_lines and duplication_percentage can go down. fallow dupes now reports some clone groups that the long ranges hid (#4322). Identical Vue, Svelte and Astro files give a clone group for each section. Thanks @levi-broeksma for the report. (#4288)

  • banned-call, banned-effect and boundaries.calls.forbidden report each call site. Before, Fallow reported only the first call of a callee path in a file, and one suppression comment hid every later call. Thanks @Smrtnyk for the report. (#4257)

  • A banned-effect rule follows an aliased destructured require(). const { exec: run } = require("child_process"); run(cmd) now gives a shell effect. (#4308)

  • --save-baseline warns when the path is in the Fallow cache directory. Git ignores that directory, so Git does not commit the baseline. Use a committed path such as fallow-baselines/dead-code.json. (#4322)

Security analysis

  • fallow security matches more sink shapes. Raw SQL methods such as whereRaw and $queryRawUnsafe now match on a call result or a this field, for example db("t").whereRaw(x) (#4276). A default or namespace import of child_process or vm now resolves to the node: path, as a require() binding does. The callee in evidence and candidate.sink.callee changes to match, for example node:child_process.exec(). finding_id does not change. After import { promises as fsp } from "node:fs", the call fsp.readFile(path) now resolves to node:fs.readFile and can give a path-traversal candidate. Code with a promises as fs alias now gives fewer candidates. A default or namespace import of fs keeps its written callee. (#4308)

Type-aware analysis and caches

  • --type-aware-require complete no longer fails on a member with two or more exact uses. A symbol-use result now keeps one evidence item for each file that references the symbol, up to 40 items. A confirmed-used result with truncated evidence is complete. Thanks @BarrMan for the report. (#4256)

  • The graph cache misses in three more cases. A warm run now rebuilds the graph when a file appears at a path that import resolution tried before (#4255). It also rebuilds after a Bun version change of a hoisted package and after a Deno install (#4305).

Command output

  • rule-pack, hooks install, ci-template and the output schema give correct text. fallow rule-pack prints 3 rules again. fallow hooks install --target agent reports the real .gitignore change and says would create in a dry run. fallow ci-template gitlab | head exits quietly, and the --vendor help names only the files that the flag writes. The output schema describes is_internal_dependency of fallow workspaces correctly. (#4279)

Security

  • The VS Code extension requires an Ed25519 signature for every binary that it downloads. Before, the extension accepted a release asset without a .sig file when the asset matched the digest of the GitHub API. Every release since v2.45.1 signs each binary, so a normal install does not change. On the next start, the extension deletes an installed binary that has only a .sha256 marker and downloads it again. When a release asset has no signature, set fallow.lspPath to a fallow-lsp binary that you trust, then reload the window. (#4253)

  • agent install, hooks install, init and their uninstall commands no longer write through a symlink that leaves the project. A cloned repository could commit a file such as .mcp.json or .husky/pre-commit as a symlink to a file outside the project. Fallow now refuses such a path, and also a dangling link. --user targets and .git/hooks still follow symlinks. agent install reports the row as skipped with the reason symlinked_path. hooks install and init exit with code 2. A link that stays inside the project still works. (#4254)

Upgrade notes

  • The extraction, graph and duplication caches change, so the first run after the upgrade parses each file again.
  • banned-call, banned-effect and boundaries.calls.forbidden now report each call site. A baseline from before the upgrade covers only the first site of each path, so the other sites show as new findings.
  • fallow dupes clone line ranges change. A duplication baseline without fingerprints can need a new save. A fallow-ignore comment that only matched a line outside the real clone no longer hides the clone.
  • Astro projects can get new complexity and CRAP findings from <script> blocks.
  • fallow security evidence names the node: path for a default or namespace import of child_process or vm.

Full Changelog: v3.40.0...v3.41.0