Repository navigation
chore(deps): update dependency fallow to v3.34.0 - #955
renovate[bot] wants to merge 1 commit into
Conversation
|
Important Review skippedBot user detected. To trigger a single review, invoke the ⚙️ Run configuration
You can disable this status message by setting the Use the checkbox below for a quick retry:
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
commit: |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #955 +/- ##
=======================================
Coverage 94.77% 94.77%
=======================================
Files 10 10
Lines 746 746
Branches 235 235
=======================================
Hits 707 707
Misses 36 36
Partials 3 3 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Note
This PR body was truncated due to platform limits.
This PR contains the following updates:
3.33.1→3.34.0Release Notes
fallow-rs/fallow (fallow)
v3.34.0Compare Source
Added
fallow mutationmeasures how well the tests detect code changes. Thecommand makes small changes (mutants) in the changed code and runs the
tests of the project against each one. A mutant that no test fails
on survives. The report gives the mutation score over the tested mutants,
the survivors by file with their covering tests, and the share of mutants
that a test reaches.
--planselects and ranks the mutants and runs no code.fallow mutation inspect <mutant_id>shows one mutant against the currentfile and runs no code. It reads the report that the last run saved in the
Fallow cache, or the file that
--reportnames. From a subdirectory, itfinds the report of a run at the project root. A changed file gives a
stalestatus.audit.--alland file paths widen or narrow it.host load alone does not make a mutant count as detected. The retry of
a timeout waits twice as long. A changed detection result is
flakyand leaves the score.
retried: trueon a mutant marks a status from both runs.--setup-timeoutsets how long each runner process can take to start,finish its dry run or reload (default
2m).--allow-dirtyis set. Mutantsnear file system, process, network or database calls are pruned unless
--allow-side-effectsis set. Fallow does not write to project files, andthe report lists any path that the tests changed.
-o <file>writes thereport after the run, and the worktree check leaves that file out.
--min-scoreormutation.minScoremakes alower score exit with 1.
--verdicts <file>marks equivalent mutants,which leave the score.
--max-mutants. The dry run decides their coverage: a pending mutant thatno test reaches in the dry run is
no_coverage. A capped run thereforereports the coverage of the full run.
*.test-d.ts,*.spec-d.ts, or a TypeScript file belowtest-d/) is not a test file of the plan. No runner executes it, so itno longer gives a false baseline failure or decides the runner.
baseline failure. The mutant runs with its other covering tests and is a
runtime error only when no covering test is left. A survivor of such a
run survives: the check of the test count uses the tests of the run,
so it no longer gives a runtime error such as
466 of 467 requested tests ran.dry-run time and the worker count. It is at least the runner timeout. A
loaded host no longer turns the mutants of a restarted companion into
runtime errors.
no_coveragemutant has atest_reachobject with the class of theaudittest reach:mocked-only,none,none-file,transitiveordirect. A mutant in an exported declaration gets the class of thatexport and its name. For
mocked-only, the object lists the tests andthe mocked modules. The human no-coverage lines count the mutants of
each file by class, and the plan prints one line with these counts.
Fallow cache. The key covers the mutant, its covering tests, the content
of the covering test files and the files that they import, the runner
setup files (also the Bun
preloadfiles), the lockfile, the runnerconfig and the files that it imports, the configs that the root
tsconfig.jsonextends, and the runner. A change to one of them runs themutant again. Dynamic imports that the module graph does not resolve, and
files that a test reads, are not in the key. Each cache entry and the
cacheobject of the report record this limit. A reused result hascached: true.--no-cacheruns each mutant.cache. A staging directory and a rename install it, so a concurrent run
on the same plan never reads a partial file. A run reuses a directory
with the expected content, and removes old run directories beyond the 5
most recently used ones.
mutationconfig section sets the runner, the operators, theminimum score and the concurrency.
--typecheckormutation.typechecktype-checks each survivor throughthe
--type-awarecompanion after the tests. A survivor that adds a typeerror to its file gets the status
compile_errorand leaves the score.The check reads only the mutated file and never writes a project file.
Without a companion, the run exits with 2 before the tests start.
The checker cannot read a Vue, Svelte or Astro component, so a survivor
in a component stays
survivedwith the skip reasonunsupported-file. (#3471)fallow health --mutation-report <file>reads a saved run report andadds the mutation score of each complexity finding next to its CRAP
score, as
mutationon the finding. A function whose file changed sincethe run is
staleand gets no score. The score never changes CRAP orthe health score. A plan or an invalid file exits with 2.
Vue
<script>and<script setup>blocks, the Svelte instance and modulescripts, and the Astro frontmatter. Svelte module files (
.svelte.js,.svelte.ts) get mutants too. The text outside the script blocks staysthe same, so the framework plugin of the project compiles the mutated
component.
the arguments and destructure defaults of
definePropsand the otherVue compiler macros, an expression that holds a Svelte rune call, and an exported declaration in
the Astro frontmatter. The arguments of a rune still get mutants.
such as
vite.config.tsor.eslintrc.js, with the reasonframework-config. No test loads these files, so their mutants wereno_coverageand lowered the coverage. Amodule.exports = ...configcounts as the default export. Only a file whose name marks a config
file counts, so
codegen-helpers.tsorapp/routes.tskeeps itsmutants. A mutant inside a function of the config,
for example an inline Vite plugin, stays in the plan. (#3483)
regex-literaloperator changes one token of a regular expressionliteral. It removes a
^or$anchor, negates a character class, orgives
\d,\wor\sthe opposite case.node:testof Node.js 22.18 or later, orbun testof Bun 1.3 or later.mutation.runnerselects one(
vitest,node-testorbun-test). The defaultautodetects itfrom the
vitestdependency, the test module that the test filesimport, a Bun file at the root, or a
node --testscript.node:testand
bun testrun each test file in its own process. (#3407)Neither runtime can load a Vue, Svelte or Astro component, so with
these runners the plan leaves component files out with the skip reason
runner-cannot-load, and their mutants do not lower the coverage.The human output prints one
Skipped files:line. (#3470)node:testrunner gives each test process the module loading flagsof the
node --testcommand in the project scripts:--experimental-transform-types, the strip-types flags,--import,--require,--experimental-loaderand--conditions. A projectwhose test script uses
--experimental-transform-typesfor aTypeScript
enumnow runs with noNODE_OPTIONS. The baseline failureof a test file that does not load now starts with the error line, for
example
SyntaxError [ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX]: ..., notwith a stack line. (#3487)
borpscript is anode:testrun. The files of its-p/--patternglob, or of its positional arguments, are test entrypoints. Without a file and a pattern, the default
borpglob**/*.test.{js,mjs,cjs,ts,mts,cts}applies.autoselectsnode-testfor a project with a
borpscript. Before, the plan of such a projecthad no test file, and each mutant was
no_coverage. Fallow does notread
.borp.yaml, so a bareborpcommand gets the default glob. (#3482)fallow auditcan skip sub-analyses.fallow auditnow takes--onlyand--skipwith the values and rules of barefallow --only/--skip:dead-code,architecture,dupesandhealth. The new configkeys
audit.deadCode,audit.healthandaudit.dupesset the defaultselection.
--skipremoves from it, and--onlyreplaces it. A skippedsub-analysis is skipped on the head and on the base snapshot, so it leaves
the verdict,
summaryandattribution. A warn-only gate for complexity andduplication is now
fallow audit --skip dead-codewith the complexity rulesat
warn.summary.dead_code_skipped,complexity_skippedorduplication_skipped. The human output and the GitHub step summary showthe row as skipped.
architecturenarrows the dead-code findings to, or away from, cycles,boundary violations and policy violations. Styling findings come from the
health sub-analysis.
for it is ignored.
--max-crap,--coverage,--coverage-rootand--runtime-coverageprint a note when the health sub-analysis is skipped.fallow --skip dead-code,dupes,healthnow exits 2 too, instead ofprinting an empty report.
--brief,fallow review) keeps every sub-analysis.only/skipinputs and the GitLabFALLOW_ONLY/FALLOW_SKIPvariables now apply toaudittoo. The MCPaudittooltakes
onlyandskip, and the programmaticrun_auditreads the threeconfig keys.
auditnow has theStylingrow in both ofits renderers.
Thanks @cr101 for the request. (#3381)
The review brief reports test reach for changed exports.
fallow audit --briefandfallow reviewadd atest_reachsection. For each valueexport that the diff changes, it states how tests reach it:
direct: a test file references the export.transitive: tests reach it only through other modules.mocked-only: only tests that mock a module on the import path referenceit, so the real code does not run in a test.
noneornone-file: no test references it, or no test loads the file.framework: a framework configuration export with a literal value, suchas the Next.js
dynamicoption.The JSON lists at most 25 exports that need attention, with the mocking
tests and the mocked module of a
mocked-onlyexport. The human brief showsthe counts and the first three rows. A project without test files gets one
no-testsline. The section is advisory and never changes the exit code.Built-in plugins for nine more tools. Before, fallow did not know these
tools. Thus it reported their config files, entry files and packages as
unused. Now each plugin activates on its package or config file:
misereadsmise.toml,.mise.toml,.config/mise/andconf.dconfigs. Task
runcommands credit the packages and local scripts thatthey run. A bare command gets credit only when the config puts
node_modules/.binonPATH.railwayreads eachservice()in.railway/railway.ts. The build,start and pre-deploy commands credit the packages and files that they run.
fluemakes the app, database, Cloudflare and agent modules of a Flueproject entry points. Values in
flue.config.*replace the defaultlocations.
textlintreads.textlintrcin each JSON, YAML and JS form, thetextlintkey inpackage.json, and atextlint --configfile. Eachenabled rule, filter and plugin credits its package, by short name or by
full name. A local rule path stays reachable.
jasminereadsspec/support/jasmine.*and makes thespec_filesandhelperstest entry points. Therequiresandloadermodules getcredit.
rstestreadsrstest.config.*. It followsinclude,exclude,setupFiles,globalSetup,resolve.aliasandprojects. It creditsthe test environment, browser mode and coverage provider packages.
serverless-frameworkandoslsreadserverless.ymlandserverless.ts. A rootserverless.*config also activatesserverless-framework, because the CLI is often installed globally.Each function
handlerbecomes an entry point. Eachpackage in
pluginsgets credit, and${file(...)}references to YAMLand JSON files resolve. The
slsbinary credits both packages.typescript-content-mapperruns theexecscript ofpackage.json#typescript.contentMapperas an entry point.fallow migraterecognizes more plugin sections and config keys. Aplugin section for
expo-router,varlock,turboor one of the newplugins above now gets the "auto-detected" message, not the "unknown key"
warning.
The root keys
cycles,ignoreGlobalBinaries,preprocessor,preprocessorOptionsandasyncCompilersnow get a specific message withthe closest fallow alternative.
Effect dead-code findings. Projects that declare
effectget three newrules, each at
warnby default:unused-effect-layerreports a layer that only unused layers compose.A layer with zero references stays an
unused-exportor anunused-class-member.unused-tagged-errorreports aData.TaggedErrororSchema.TaggedErrorclass that types orcatchTaghandlers reference,but that no code constructs, fails with, or decodes.
unproduced-catch-tagreports acatchTaghandler for a project tagthat no code produces, when no
unused-tagged-errorfinding covers it.Any value reference to an error class counts as a producer, so errors that
an API or RPC schema decodes are not reported. Tags that no project class
declares, and Effect built-in tags, are never reported.
Prop-drilling findings can enforce CI gates. Set
rules.prop-drillingtoerrorto enforce chains, orwarnfor advisory findings, including under--fail-on-issues. Source-hop overrides and suppressions control each chain.Baselines, audit attribution and changed-hop scopes support incremental
adoption, and CI reports show the source location. The rule stays off by
default, and editor diagnostics stay hints. Thanks
@miky-mfw for the request.
(Closes #3327)
fallow auditreports risky changes as rules. Five new rules compare thebase and head text of each changed file.
weakened-testsreports a new skipor focus marker, removed test cases, fewer assertions, or a less strict
coverage threshold.
added-suppressionreports a new lint, type-check,coverage or Fallow suppression comment.
test-only-branchreports a newprocess.env.NODE_ENV === 'test'style check in source code. A Vitestin-source test block (
if (import.meta.vitest)) is test code and is notreported.
diverged-clone-siblingreports an edit to one copy of duplicated code whenthe other copies did not change.
added-dependencyreports a newpackage.jsondependency. These two rules are off by default. The otherthree rules are
warnby default. Set a rule toerrorto fail the audit,or suppress one finding with
// fallow-ignore-next-line <rule>. Thefindings are in the new
diff_riskJSON section, in SARIF, in CodeClimate,in GitHub annotations, in the GitHub job summary and in the MCP
audittool. Compact output adds one record for each finding and markdown output
adds a table. The human status line, the issue count of the GitHub Action
and the GitLab template, and the VS Code audit status bar count the findings
too.
added-dependencyhas no suppression comment, becausepackage.jsoncannot hold one: set the rule to
offfor the path in anoverridesentry.fallow healthnow reports hidden change coupling and changedispersion. A hidden pair is two files that change together in most
commits while no import or re-export chain connects them. An agent that
follows imports does not find the second file. The new
change_couplingsection lists these pairs with the Code Maat defaults: 5 commits per file,
5 shared commits, a coupling degree of at least 30%, and commits with more
than 30 analyzed files skipped.
health.changeCouplingchanges thethresholds. Each hotspot also gets
median_files_per_commitandmedian_lines_per_commit. Without--since, these values read 12 monthsof history. A strong pair adds the
hidden_couplingfactor to therefactoring targets of its files. A file that no other target rule claims
becomes a
resolve_hidden_couplingrefactoring target, so existing targetskeep their category. This new category value moves
schema_versionto 12for health, 13 for audit, 14 for the combined report and 13 for the audit
brief.
fallow audit --briefshows an advisoryco_change_gapfact whena diff changes one file of a hidden pair and not the other. The health
score does not change. The churn cache moves to version 8 and is rebuilt
once. The
fallow-churn/v1import accepts an optionalcommitfield.Traces now tell you which consumers a name search does not find. A
grep -wfor an export name does not find every consumer: a default importunder another name, a barrel alias, or a whole-namespace use hides it. Each
direct_references[]entry offallow dead-code --trace FILE:NAMEnow hasline,search_visibility(found,renamed,missed),reason, andsearch_as.search_asis the name to search for instead. A barrel thatonly forwards the export is
foundwithreasonpass-throughand nevercounts as a gap, because it needs no edit when the export changes. A class
member trace has the same fields on
owner_direct_references[], checkedagainst the owner export name. Each
--impact-closurecoordination gaphas
search_visibility,reason, andsearch_as. The MCPtrace_exportand
impact_closuretools return them too.fallow audit --briefaddsimpact_closure.search_gap. It lists themissedandrenamedconsumersof each export whose declaration the diff changes, with at most 10
consumers per export and a total. A trace reads the consumer sources of the
traced export only. The audit brief reads the consumer sources of the
exports in the changed files.
Search gaps name their symbol and read SFC declarations. An
--impact-closurecoordination gap now hassearch_symbol. It names theentry of
consumed_symbolsthatsearch_visibility,reason, andsearch_asdescribe. The human output names the symbol when the consumeruses more than one symbol. The MCP
impact_closuretool returns the fieldtoo. For a changed
.vue,.svelte,.astro, or.mdxfile, the auditbrief now reads the declaration range from the script block, so a change
inside a function body gives an
impact_closure.search_gapentry.(#3371,
#3372)
Alchemy projects no longer report their stacks and deployed Workers as
unused. A new built-in
alchemyplugin activates on thealchemydependency. Each
alchemy.run.{ts,mts,cts,js,mjs,cjs}file is an entrypoint, so the Worker modules that a stack imports stay reachable. The file
that a resource names in
mainstays reachable from the file that declaresit. Fallow reads
new URL("./x.ts", import.meta.url),${import.meta.dirname}/x.ts,path.join(import.meta.dirname, ...), astring next to such a
rootDir, and a string in a stack file. Whenmainnames its own file, the
defaultexport and the exported Durable Object,Workflow and Container classes count as used. The CLI loads
@effect/platform-nodeand@effect/platform-bunat runtime, so thesepackages are credited. The extraction cache version changes, so the first
run after the upgrade parses each file again.
OpenTelemetry SDK callbacks are no longer reported as unused class
members. A new built-in
opentelemetryplugin activates on any@opentelemetry/*dependency. The SDK calls the methods of a class thatimplements
Sampler,SpanProcessor,SpanExporter,PushMetricExporter,LogRecordProcessor,LogRecordExporter,ContextManager,TextMapPropagator,TextMapGetterorTextMapSetter.The plugin credits only the methods of that interface. A class that extends
MetricReadergets credit foronInitialized,onForceFlushandonShutdown. Other members of the class stay reportable.Changed
A single
*inentryanddynamicallyLoadedglobs no longer matches/.The pattern
src/plugins/*/index.jsalso matched nested files such assrc/plugins/alpha/components/button/index.js. Each nested match became anentry point, so fallow did not report it as an unused file. A single
*nowstays within one path segment and only
**crosses directories, as inpicomatch. If your config used a
single
*to match nested files, change it to**. Thanks@jleifeld for the report.
(Closes #3490)
A single
*now stays within one path segment in every config pathglob. If your config uses a single
*to match nested files, change itto
**. For example, changesrc/generated/*tosrc/generated/**, and*.test.tsto**/*.test.ts. For each pattern that now matches fewerfiles, fallow prints a warning on stderr. The warning names the config
field, the pattern, the count of files that only the old rule matched, and
the rewrite.
--quiethides the warning, and the JSON output does notchange. The
entryanddynamicallyLoadedfields already use this rule.It now also applies to
ignorePatterns,ignoreFindings,overrides[].files,ignoreExports[].file,ignoreCatalogReferences[].consumer,duplicates.ignore,similarCode.ignore,health.ignore,health.thresholdOverrides[].files,the
boundarieszone and coverage globs, the rule-pack file globs, and theframework[]fieldsusedExports[].pattern,manifestEntries[].manifestsand
detection.pattern. Only**crosses directories, as in picomatch.Before this change,
ignorePatterns: ["src/generated/*"]also ignored thefiles in the subfolders of
src/generated, and"files": ["*.test.ts"]inoverridesmatched test files at any depth. A narrower glob can show newfindings. A line that you copy from
.gitignorecan match fewer files inignorePatterns. Fallow matches the glob against each file path, notagainst each directory. Thus
src/generated/*in.gitignoreignores thesubfolders of
src/generated, but inignorePatternsit does not. Globsthat match package names or import specifiers, such as
ignoreDependenciesandignoreUnresolvedImports, do not change.Runs with a restored cache are faster. A fresh checkout moves the
timestamps of every source file, so each run of a CI job that restores its
cache rewrote every cache entry. A cache entry whose content did not change
now gets only its new timestamps. On a large monorepo, such a run is about a
fifth faster. The dead-code run also frees its parsed modules on a helper
thread, so the report starts sooner.
Barrels that
export *many packages no longer slow the graph down.Each import through such a barrel was checked against every package that
the barrel re-exports. Now it is checked only against the packages that
export its name, so the cost grows with the number of imports, not with
imports times packages. Findings do not change.
One oxc version in the build.
oxc_coverage_instrumentmoves to0.14, which uses oxc 0.152 like the rest of Fallow. The parser, AST and
semantic crates no longer compile twice.
cargo denynow fails when twoversions of the core oxc crates enter the dependency graph.
The MCP
audittool has afallow://tools/auditguide. The guidenames the five diff-risk rules and holds the styling, coverage, baseline,
grouping, runtime-coverage and
FALLOW_DIFF_FILEdetail. Theaudittooldescription is now a short summary that points to the guide. (#3369)
Workspace dependency declaration guidance is clearer. Root imports of npm
workspace packages need a dependency declaration; npm links resolve imports
without declaring them. Guidance covers private/tooling ancestor exceptions,
self-references and explicit opt-outs. The declaration policy is unchanged.
Thanks @VariableVince for the question.
(Closes #3325)
Existing
prop-drilling: "error"settings now enforce CI. Change the ruleto
warnto retain advisory analysis after upgrading. Archived JSON reportswith
effective_severity: "error"also render as errors; changing the currentconfiguration does not rewrite those reports.
Complexity findings name more callback bodies. A function without a
declared name now takes a name from the code around it, where it got
<anonymous>or<arrow>before:Effect.fn("Users.create")(function* ...)givesUsers.create.Command.make("setup"),group.handle("github")orit("renders").const load = () => Effect.gen(function* ...), givesload.Names that fallow gave before do not change, and scores do not change.
Coverage matching and runtime function identities still use the placeholder.
A renamed finding gets a new CodeClimate fingerprint one time. An identity
baseline (
--baseline-mode identity) gives the unused allowance of an<anonymous>or<arrow>bucket to new names in the same file, so a savedbaseline stays valid. Re-save the baseline to record the new names. A file
move no longer moves an
<arrow>bucket, the same as an<anonymous>bucket.
Storybook, Vitest, Rstest, Jasmine and Playwright globs now accept every
extglob group. One shared rewrite now reads the groups
@(..),?(..),+(..),*(..)and!(..)for these plugins and forimport.meta.glob.Before, each plugin had its own rewrite with its own gaps. The changes per
plugin are:
@(..)matched. Astoriesglob such as../src/**/!(*.dev).stories.@(js|ts)matched no file. Now every groupmatches, and
!(*.dev)excludes the files that match*.dev.testMatchglob with a group made every scriptbelow
testDiran entry point. Now only the files that the glob matchesare entry points. The match still ignores case.
!(..)and*(..)now match. A Vitest plain(a|b)group still matches one of its alternatives.test.includeglob with any group made no file anentry point, so Fallow reported every test file as unused. Now the
test.includeglobs of the config and of each project use the sharedrewrite.
A
+(..)group in an include glob now matches one or more repetitions.The glob matcher cannot repeat a group, so
+(ts|js)becomes{ts,js}*.This can make a few more files entry points, for example
a.test.jsxfor*.test.+(ts|js). A+(..)group at the end of a glob also matches alonger extension, so
*.test.+(ts|js)also matchesa.test.tsx.Before, Vitest, Rstest and Jasmine matched the group
once only, which missed names that repeat the group. An exclude glob still
matches a
+(..)group once only, so an exclude never removes a file thatthe tool keeps.
Fixed
The MCP routes now apply the same safety rules.
complete evidence (
--type-aware-require complete, ortypeAware.require: completein the config). A direct tool call alreadyreturned an error for this run, but Code Mode returned the partial result
as a success.
result over the cap:
isError: trueand codeFALLOW_MCP_SUBPROCESS_OUTPUT_LIMIT, withcontext: "typed".tool call does. Before, it shared the MCP server's own stdin.
.or..) is now refused. Thevalue becomes one URL path segment, so
..addressed a different route.fallow coverage analyze --cloud, the cloud read commands and the cloudMCP tools refuse it, with the rule that already applies to commit SHAs.
Two Fallow processes can save the cache at the same time. The
extraction cache and the graph cache wrote through one fixed temp file
(
cache.bin.tmp,graph-cache.bin.tmp). An editor server and a CLI run, ora pre-commit hook beside an editor, that shared
.fallow/wrote into thesame file. A save could then fail, or the cache could hold bytes from two
runs and be rebuilt from cold on the next run. Each save now writes its own
temp file and renames it into place.
Cache eviction keeps the files that every run reads. At the size cap
(
cache.maxSizeMb), the extraction cache evicted the entries that it hadparsed longest ago. A file that never changes was parsed once and was then
the first entry to go, so the next run parsed it again. Eviction now removes
first the entries that recent runs did not discover, for example test
files after
--productionruns.fallow audit --briefno longer reports every export as new publicAPI after a run without
--brief. Only a brief run computes thepublic exports of the base, but both runs shared one cached base
snapshot. A brief run after a plain run on the same base read an empty
base set, so
public_api_addedlisted every export of the changed publicfiles. The cache key now includes
--brief, and the cache version movesto 13, so old snapshots are not used.
The audit base-snapshot cache no longer grows without a limit. Each
new base commit or changed-file set wrote one more snapshot file of up to
16 MiB below
<cache>/cache/audit-base-v*/, and nothing removed them.Now each save removes the files that are older than
audit.cacheMaxAgeDays(counted from the last use) and keeps only the newest 32 files.
fallow audit-cache prunereports and removes these files too, as entrieswith
pass: "snapshot". A file over the cap has the reasonover-capacity.An edit to
package.json,deno.jsonortsconfig.jsonnow rebuildsthe cached module graph. The graph cache compared source files only. A
change to a
package.jsonimportsmap, a workspace package name, a Denoimport map, or a
tsconfig.jsonthat the resolver finds next to a sourcefile (with its
extendsandreferenceschain) did not change the cachekey. The next run then used the old import targets and could report a live
file as unused. Now the cache key includes the content of these files, the
workspace names and roots, and the framework static directory mounts. The
graph cache version changes, so the first run after an upgrade rebuilds
the graph.
The worktree check of
fallow mutationsees more stray writes. Before,the check left out the whole cache directory, so a
cache.dirof.orsrchid each write of the tests. Now it leaves out only<cache>/mutationand the output files. A run refuses a cache directorythat is the project root or a directory above it, or that holds a planned
file, with
FALLOW_MUTATION_UNSAFE_CACHE_DIRand exit code 2. The checkalso compares the ignored files that git lists at the start, such as
.env, so a write to one of them is aworktree-changederror. The docsnow say that side-effect pruning covers only a function whose own body
calls a sink.
Mutation companions no longer outlive Fallow. A companion that loses
its stdin during a request now kills its process group and exits. Before,
it waited for the request, and its test workers kept running after Fallow
ended. Fallow now also ends the companions on SIGHUP, SIGQUIT and a panic.
When Fallow starts with SIGHUP ignored, as under
nohup, a hangup stilldoes not end the run.
A direct MCP tool call now stops when the client cancels it or when it
times out.
notifications/cancellednow reaches the work of the call.Before, the server kept the analysis running until it finished or until
the response deadline.
on a timeout. The analysis then stops at its next stage boundary. Before,
a timed-out typed call continued to run to the end.
specification requires. A timeout still returns a structured error:
FALLOW_MCP_SUBPROCESS_TIMEOUTorFALLOW_MCP_API_TIMEOUT.that still run after their caller stopped. While that count is full, a
Code Mode host call uses the subprocess.
A
code_executecall now stops when the client cancels it. Before, acancel did not reach Code Mode, and only
timeout_msended the snippet.nothing.
a timeout.
The typed and CLI routes of an MCP tool now read the same settings.
production: falsenow gives--no-productionon the CLI route. Before,only the typed route turned production mode off, and the CLI route used
the config value.
feature_flagson the CLI route now ignores an emptyroot,configorworkspace, as the typed route does.Health recommendations use valid config keys.
fallow recommendandthe taste-choice catalog use
health.maxCyclomatic,health.maxCognitiveand
health.maxCrap, matching the config reader.GitHub audits keep blocking findings visible. Summary sections sort
errors before warnings and prioritize introduced findings under
new-onlybefore truncation. Rule severity is separate from the complexity band.
Styling details appear in summaries and annotations; missing historical
attribution remains unknown.
Saved reports retain failure and rule identity. Failed-run envelopes
are rejected with their original message, code, help and failure exit
code. Legacy SARIF and CodeClimate keep canonical rule IDs, and CodeClimate
distinguishes styling values and subkinds on the same line without
changing other fingerprints. A styling finding rendered from a saved or
legacy report gets a new SARIF rule ID and CodeClimate fingerprint one
time, so code scanning and the merge request widget show it as new once.
Named exports returned through higher-order import loaders stay in use.
Unknown wrappers preserve the imported module's exports instead of assuming
that only
defaultis read. Default-only credit requires a proven componentloader import with no local shadowing. The extraction and graph caches rebuild
so cached results use the corrected import facts.
An electron-vite renderer without an
inputkeeps its files.electron-vite builds
src/renderer/index.htmlwhen therenderersectionof
electron.vite.config.*declares nobuild.rollupOptions.input. Fallowdid not use this default entry, so it reported every renderer file as
unused. Now the default HTML entry and the files that it loads are
reachable.
The review brief labels an edited export
modified. Thetest_reachsection labelled an export
newwhen every line of its declaration was anadded line. A one-line edit replaces the line, so an edited one-line export
read as
new. Now an export isnewonly when the file did not export thename at the base of the diff: a new export, a renamed export, or an export
of a new file. An edited, rewritten or moved declaration is
modified.The JSON
test_reachobject also addse2e_runner: truewhen the projectuses Playwright or Cypress. The human brief already said that these tests
do not import files.
A namespace that holds only types is now a type export. TypeScript
emits no JavaScript for a namespace whose body has only interfaces, type
aliases,
declaredeclarations,import typealiases and nestednamespaces of the same kind. Before, Fallow treated such a namespace as a
value export when it had no
declareorexport type. Examples areexport namespace JSX { export interface Element {} }and the same shapewith
moduleor a dotted name such asA.B.C. Now:unused-type, not asunused-export. The SARIFrule id, the CodeClimate check name and fingerprint, the LSP diagnostic
and the MCP and audit output change with it.
health --coverage-gaps, and itleaves the
test_reachsection of the review brief.fallow-ignore-next-line unused-exportcomment on such anamespace no longer matches. The finding then reports as
unused-typeand the comment as a stale suppression. Change the comment to
unused-type.A body with any other statement, exported or not, stays a value. A
const enumand a non-typeimport X = A.Balias also keep the namespacea value. A namespace that merges with a function, class, enum or variable
of the same name stays a value too, so a value import of the merged name
keeps crediting it. The extraction and graph caches are versioned again, so
a warm cache does not keep the old classification.
A dynamic
import()whose value escapes no longer reports the targetexports as unused. An
import()without a binding credited no export,also when its value left the expression. Examples are
return import('./x')in a nested block,register(import('./x')),[import('./x')]andp.then(cb)afterconst p = import('./x'). Suchan
import()now credits every export of the target module. Only animport()whose value is discarded still loads the module without acredit. Examples are
import('./x');,await import('./x');,import('./x').catch(onError);andimport('./x').then(() => start()). A local loader function now also recognizes areturn import('./x')insideswitch,if,tryand loop blocks. Thusconst m = await load(name); m.createAdapter()creditscreateAdapteronly. The extraction and graph cache versions are bumped, so the first run
after the update re-parses every file.
The MCP
trace_exporttool runs the CLI when type-aware analysis ison. With
typeAware.enabledin config orFALLOW_TYPE_AWAREset,fallow dead-code --traceaddssemanticevidence to the trace, and withtypeAware.require: completeit exits non-zero when that evidence isincomplete. The typed route of
trace_exportand the Code ModetraceExportroute ignored these settings and returned the syntactictrace only. They now run the CLI in this case.
fallow_api::run_trace_exportreturns the error code
FALLOW_TYPE_AWARE_CONFIG_UNSUPPORTEDwhen type-awareanalysis is on, also for
type_aware.enabled: Some(true). SetSome(false)to get the syntactic trace. WhentypeAware.require: completecomes from the config, an incomplete semantic trace or symbolimpact is now an MCP error, as the CLI exit 1 says. Before, only the
type_aware_requireparameter made it an error.Search gaps no longer count files that only forward an export. A file
that imports an export and only exports it again needs no edit when the
export changes. Examples are
import { a } from './x'; export { a }andimport { a as c } from './x'; export { c }. Such a file is nowfoundwith
reasonpass-throughin--tracereferences, in--impact-closurecoordination gaps and in the audit brief
impact_closure.search_gap, thesame as a pure barrel. A file that also uses the binding stays a normal
consumer. A file that forwards the binding with
export default borexport = balso stays a consumer, because its importers are not listedunder the original export. A namespace import with member access (
ns.a) staysfound,and a whole-object use (
Object.keys(ns), a spread,nsas a value) staysmissed.Coverage gaps and the CRAP estimate count only value references from
tests. Before, a test reference that uses a value export only as a type
counted as test reach. This covers an
import type, and also a plainimportthat the test uses only in type positions (for exampletypeof fn). Nowfallow health --coverage-gapsreports such an exportunder
untested_exports. The static CRAP estimate for that function uses40% coverage (test-reachable file), not 85% (directly tested export). The
static test signal of runtime coverage changes the same way. File-level
reach does not change. A test with only type references to a module still
makes that file test-reachable.
The coverage-gap note names mocked exports.
fallow health --coverage-gapsnow says "only tests that mock this module reference it"when the only test references to an untested export go through a closed
factory mock. Before, the note said that no test-reachable module references
the export.
The MCP
audittool andfallow_api::run_auditread theauditconfigsection. The typed route ignored
audit.gate,audit.cssandaudit.cssDeep. A project with"gate": "all"got thenew-onlyverdictfrom the MCP tool, and the CLI gave
all. The typed route now uses thesekeys when the tool parameter is not set, the same as
fallow audit. Thetyped route cannot apply
audit.deadCodeBaseline,audit.healthBaselineor
audit.dupesBaseline. When the config sets one of them, the MCP toolnow runs the CLI, which applies the baseline.
fallow_api::run_auditreturns the error code
FALLOW_AUDIT_BASELINE_UNSUPPORTEDfor these keys.It does not ignore them.
type_aware: falsekeeps a run syntactic on every programmatic surface.The MCP
analyze,check_health,auditandinspect_targettoolsignored
type_aware: false: when the config turned type-aware analysison, the run was type-aware. The tools now pass
--no-type-awareto the CLIand keep the typed route syntactic, as
--no-type-awaredoes. Infallow_api,TypeAwareOptions.enabledis now anOption<bool>.Some(false)runs syntactic without theFALLOW_TYPE_AWARE_CONFIG_UNSUPPORTEDerror, also whenFALLOW_TYPE_AWAREor the config turns type-aware analysis on. In theNode bindings,
typeAware: { enabled: false }does the same. Before, itwas the same as an unset option. An explicit choice now also reaches
detection, as the CLI flags do: a syntactic run credits the framework
class members that the type-aware config holds back for verification, and
a type-aware run turns the opt-in
private-type-leaksrule on unless theconfig sets it. Before,
fallow_apiand the Node bindings reported noprivate-type leaks for a type-aware run where
fallow dead-code --type-awarereported them.The MCP tools run type-aware analysis when the config turns it on.
fallowruns type-aware analysis whentypeAware.enabledistrue, andfallow auditalso whenaudit.typeAwareistrue. The typed route of theMCP
analyze,check_changed,check_healthandaudittools and of theCode Mode
combinedroute ignored these keys and theFALLOW_TYPE_AWAREvariable. It returned syntactic results where the CLI returned type-aware
results. These routes now run the CLI when one of these settings turns on
type-aware analysis.
fallow_api::run_dead_code,run_circular_dependencies,run_boundary_violations,run_audit,run_combinedandrun_healthnow return the error codeFALLOW_TYPE_AWARE_CONFIG_UNSUPPORTEDin this case. They do not returnsyntactic results. The Node bindings return the same error. To run
type-aware analysis through the API, set
typeAware.enabledin the calloptions. To keep a call syntactic, set
FALLOW_TYPE_AWARE=0.Clone families have one order on every surface.
fallow audit,fallow dupes --changed-sinceandfallow dupes --baselinenow orderclone_families[]by file set.fallow dupes, the MCP tools andfallow_apialready used this order. Before, these three commands orderedthe families by duplicated lines. The
suggestions[]of a family now followthe order of its
groups[]on every surface. Twomirrored_directories[]entries with the same
total_linesare now ordered by directory pair. Thefamily set and the values do not change.
The review brief and the audit report the same weakened guardrails.
The
weakeningsection offallow audit --briefandfallow reviewnowreads its test, threshold and suppression signals from the
diff_riskfindings of the same run. Before, a second detector could report a moved
test as removed, a lower
maxCrapas a lowered threshold, or asuppression token in a Markdown file as an added suppression. The JSON keys
do not change. The
evidencetext now follows the diff-risk evidence, forexample
test cases: 2 -> 1. (#3368)weakened-testsreads JavaScript-family test files only. A test filein another language, such as a Rust integration test under
tests/, canhold
it.skiporit(text in a string. The rule no longer reports a skipmarker or removed tests from such a file, and such a file no longer changes
the test-case totals of the change.
The GitLab template counts styling findings like the GitHub Action.
Under
gate: all, the issue count now includes every styling finding.Under
gate: new-only, it includes the introduced styling findings. Before,FALLOW_FAIL_ON_ISSUESdid not see them. (#3370)A test run with a directory or without file arguments now credits its
test files. A script or a CI
runline such asbun test src/,deno test,deno test testsornode --test test/made no file a testentry point. So the code that these tests import got coverage tier
nonein
fallow healthandfallow auditCRAP scores. Now a directoryargument gives the default test file patterns of the runner inside that
directory. A
bun testordeno testrun with no file argument gives thedefault patterns of the working directory. Fallow tells a directory from a
file by its syntax: the argument ends with
/, or its last segment has noextension and no glob character. Other files in the directory do not
become entry points, except below a directory named
test, wherenode --testruns every source file. Thenode --test <dir>form followsNode 20. Node 21 and later report an error for a directory argument. A Bun
name filter such as
bun test parseradds no pattern. The test files thatthese runs find, and the files that they import, are no longer reported as
unused files in
fallow dead-code.The config schema now describes
warncorrectly. The descriptions ofSeverityand of the rules that default towarnsaid thatwarndoes notfail CI. A
warnfinding does not change the CLI exit code unless--fail-on-issuesis set. The dead-code count gate of the GitHub Actioncounts
warnfindings, becausefail-on-issuesdefaults totrueand thegate reads
total_issues.The diff-risk rules of
fallow auditreport less noise. A check overmerged pull requests of five public projects found these causes, and the
rules now handle them:
weakened-testsdoes not report removed tests or assertions when thelost test lines name code that the same change removed, such as a deleted
function, error code or fixture directory. The tests went with that code.
A name of a deleted file that a changed file still holds, a name that a
deleted file only imports, a directory that still exists, and a reserved
word such as
importdo not count.weakened-testsreports an assertion drop only when assertions are atleast half of the code lines that the test file lost. A test rewrite that
moves setup checks into a shared helper does not report.
added-suppressiondoes not report files below a fixture or generateddirectory, such as a vendored
fixture/generated/schema.tswith a// @ts-nocheckheader.test-only-branchdoes not report a check that only keepsconsoleoutput quiet in tests, or a check that picks a
.env.testfile.diverged-clone-siblingdoes not report an edit to a story copy(
*.stories.*,*.story.*), the same as a test copy.diverged-clone-siblingis nowoffby default. Most of its findingswere copies that differ on purpose, for example an input and an output
type, a legacy copy, or one component that gets a new feature. Set the
rule to
warnorerrorto review every diverged copy.(Closes #3367)
A static member read through a namespace is no longer an unused class
member. Code such as
Layer.provide(Discord.ApiClient.layer)did not countas a use of
ApiClient.layer. Now a read ofNs.Class.membercredits themember when
Nsis a namespace import or a named import ofexport * as Ns from. Re-export barrels between the namespace and the classare followed. A local binding that shadows the namespace does not credit the
member.
Members that a package or the runtime calls are no longer unused class
members. Two cases are now credited:
overridemember of a class whose base comes from a package or is aglobal constructor, for example
override onStop()on a subclass ofContainerfrom@cloudflare/containers, oroverride get message()ona
Schema.TaggedErrororData.TaggedErrorclass from Effect.message,stack,causeandtoStringon a subclass of a nativeError, also withoutoverride. The runtime reads them to print anerror. Before, only
namegot this credit.An
overridegets no new credit when the base is a project class, a mixincall that takes a class (
extends Mixin(LocalBase)), an unresolved import,or a local class with the name of a global.
Files that
node --test,bun testordeno testruns are now testentry points. A package.json script or a CI
runline such asnode --test scripts/*.test.mjsmade these files entry points with thesupport role only. So the code that they import got coverage tier
noneinfallow healthandfallow auditCRAP scores. Now the file and globarguments of these runners have the test role, and the imported code gets
the static coverage estimate. With no file arguments, the files that match
the default
node --testpatterns are test entry points too.bun testina script named
testworks the same way. A flag value is not a test file:a reporter, a loader, a global setup module and a
--test-coverage-includeglob keep no test role. A setup file such as--import ./setup.mjsruns in the test process, so it is test code too.A
--test-coverage-includeor--test-coverage-excludeglob also nolonger makes the files that it matches entry points.
The config JSON Schema now accepts
extendsas a single string. Theloader already accepted
"extends": "./base.json"and the array form. Theschema allowed only the array, so editors flagged a valid config. The
schema now allows both forms.
Thanks @Smrtnyk for the report.
(Closes #3351)
CSS health now credits a keyframe name in an
animationshorthand thatcontains
var(). Avar()orenv()in the value made the CSS parser keepthe declaration as raw tokens, so
animation: pulse var(--duration) easedidnot count as a use of
@keyframes pulse. Now each literal name in theshorthand and in
animation-namecounts, also in a comma-separated list. Avar()in the name position stays unresolved. Thanks@Smrtnyk for the report.
(Closes #3349)
CSS health now recognizes the classes of Vue
<Transition>and<TransitionGroup>. A literalname="fade"creditsfade-enter-from,fade-enter-active,fade-enter-to,fade-leave-from,fade-leave-activeand
fade-leave-to. A<TransitionGroup>also creditsfade-move. A tagwithout
namecredits the same classes with thevprefix. Literal customclass props such as
enter-active-classalso count. A bound:namecreditsno generated class, because the name is not known before run time. Scoped
styles get the credit only from the same component. Global styles get it
from any Vue component. Thanks @Smrtnyk for the
report. (Closes #3350)
unused-store-memberno longer reports live Pinia setup-store members intwo cases. A helper can read a member through a parameter typed
ReturnType<typeof useCounterStore>, or through a type alias of that form that the same file declares beforethe helper.
Fallow now credits that read to the store, also when the i
Configuration
📅 Schedule: (UTC)
🚦 Automerge: Disabled by config. Please merge this manually once you are satisfied.
♻ Rebasing: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox.
🔕 Ignore: Close this PR and you won't be reminded about this update again.
This PR was generated by Mend Renovate. View the repository job log.