Repository navigation
v3.30.0: flag retirement report, startup import weight, faster language server, Oxc 0.151
·
529 commits
to main
since this release
Immutable
release. Only release title and notes can be modified.
Features
fallow flags --retirementreports the flags that you can retire. Each row groups every site of one flag and lists the reasons to remove it. The reasons aresingle-read-site,test-only,literal-constant,identical-branches,empty-branch,guards-dead-codeanddefined-never-read. Theliteral-constantreason adds the new kindconstantfor a flag-prefixed module-levelconstwith a literal value, such asconst FEATURE_NEW_UI = true. A guard in the same module must test the flag. These flags are in the retirement report only, not infeature_flags[]. Thedefined-never-readreason also covers a Vercelflag()definition with an unused export, and an unused member of a flag registry enum. The reasons come from the code only, so they do not show the production state of the flag.- The retirement report works in every
fallow flagsformat and gives each flag an age from git. JSON adds a top-levelretirementobject, and human and markdown output add a "Retirement candidates" section. Compact addsflag-retire:lines, SARIF adds the rulefallow/flag-retirement-candidateat levelnote, and CodeClimate addsfallow/flag-retirementissues.--flag-age blame(the default),--flag-age pickaxeand--flag-age offset how Fallow measures the age.--reason,--min-age,--sort age|sites|nameand--topfilter and order the rows. Fallow removes no code. Every action hasauto_fixable: false.fallow explain flag-retirementdescribes the rule. fallow flags --retirement --flag-state <FILE>compares the code with a vendor flag export. The file is a local JSON file with one vendor-neutral schema, and Fallow reads it offline without credentials or network calls. The export adds the reasonsfully-rolled-out,archived-in-vendor,missing-in-vendorandvendor-only. Thevendor-onlyrows do not count insummary.distinct_flags. The newflags.vendorKeyPrefixconfig key removes a prefix from each vendor key before the match. An invalid file, or a file larger than 16 MiB, exits with code 2 and the error codeFALLOW_FLAG_STATE_INVALID.fallow flags --retirementcan gate CI on flag counts and flag age.--save-regression-baseline <PATH>writes a baseline with a newflagssection.--fail-on-regression --regression-baseline <PATH>exits with code 1 whendistinct_flagsgrows more than--tolerance.--max-flag-age <DAYS>exits with code 1 when a flag in scope is older than that limit. Each gate reports astatusofpass,exceededorskipped, and prints the result to stderr in every format. A run with--changed-sinceor--workspaceskips the regression gate and saves no baseline. Without--retirement, the regression options have no effect onfallow flags, and the command prints a warning.- The MCP
feature_flagstool and the Rust API can return the retirement report. The MCP tool adds theretirement,flag_stateandflag_ageparameters.FeatureFlagsOptionsadds aretirementfield. The CLI and the API build the report with the same engine function, so theretirementblock is the same on both. The regression gates stay CLI only. fallow flagsfinds more flag reads. The scan now reportsimport.meta.env.Xreads, and SDK calls with a registry member as the name, such asuseFlag(FLAGS.NewCheckout). It also finds flag reads inside a largerifor ternary test, such asif (process.env.FEATURE_X === 'true'). Flags in.jsfiles with JSX now match customsdkPatternsandenvPrefixes. More flag reads now have a guard, sodead_code_overlapcan find more unused exports.fallow list --entry-weightreports the startup import weight of each runtime entry point. The report counts the project modules and the source bytes that load before the entry runs. It splits the modules into eager, deferred and out of thread, and lists the packages on the startup path. The unit is source bytes on disk, so the value is not a bundle size. The output is human or JSON (entry_weightinfallow list --format json). The health score does not change.- An opt-in regression gate for the startup import weight.
fallow list --entry-weight --save-regression-baseline <PATH>writes the eager bytes, modules and packages of each entry into the baseline file. A later run with--regression-baseline <PATH>reports the change of each entry and the new eager packages. Add--fail-on-regressionto exit 1 when an entry grows more than--tolerance. A new entry never fails the gate.fallow dead-code --save-regression-baseline <PATH>now keeps the entry weights in the same file. fallow trace --pathandfallow vizshow dynamic imports. Each hop in the trace JSON has a newdynamicfield, and the human output tags such a hop[dynamic]. With--eager-only, the trace follows static value imports only. A new edge flag bit (2) marks a lazy edge. The focus view draws it with a short dash.- Built-in Waku plugin. The
wakuplugin activates from thewakudependency and follows the managed-mode file router. Every module under<srcDir>/pagesis an entry, except modules in_components,_hooksand_actionsfolders. The plugin readssrcDirfromwaku.config.*, withsrcas the default. Thanks @aheissenberger for the contribution (#2921). - The language server can parse the project before the first open. Set the initialization option
prewarmtotrue. Atinitialized, the server then loads the project session and parses the files, but analyzes and publishes nothing. The option is off by default and needs kept project sessions and a workspace root with apackage.json. --performancereports exact work counts, the time outside the pipeline and a span tree. The dead-code timings gain acountersobject with counts that do not change with the thread count or the machine. A standalonedead-coderun adds aprocessobject and aProcesssection with aWALLrow. The newspansarray gives the parent of each stage and marks concurrent spans. Health--performanceaddsgit_log_bytes, and the parse cache load gets its ownparse_cache_load_msfield.- Runtime hot paths show where speed work gives the largest gain. Each entry in
runtime_coverage.hot_pathsnow has anoptimization_targetblock. The block holdscost_score,cost_basis,inner_iterations_per_call,cognitive,cyclomaticandline_count. Comparecost_scoreonly between hot paths with the samecost_basis. The human output,--explainand the MCPget_hot_pathstool show the same fields.
Performance
- Import resolution, stylesheet scans and coverage remaps do less work. Each specifier in a file now resolves once, not once for each imported binding. A stylesheet is masked for comments one time, and the
health --csstoken scans count lines in one pass. Runtime coverage indexes each source-mapped script one time and finds each offset with a binary search. The findings do not change. - The language server keeps its project session between saves. A save now parses only the files that changed, and the other modules come from memory. A change to a config input, such as
package.json, a lockfile, atsconfigfile or a rule pack, loads the session again. On a platform without a file change time, such as Windows, each run reads the persisted parse cache as before. The server keeps sessions while their estimated memory is at most 512 MB in total. SetFALLOW_LSP_REUSE_SESSION=0to load a new session on each run. - The language server does less work for each analysis run. It publishes only the diagnostics that changed, and converts diagnostic columns in linear time. It reads an open file only when the buffer can differ from disk. Under autosave, a newer event cancels the run in flight, and the run after a cancelled one always finishes and publishes.
- Typed MCP tool calls share parsed modules. A later call on the same unchanged files takes the modules from memory. For the sequence
analyze,find_dupes,check_health,trace_file, run two times, the parse passes go from 6 to 1. The answers do not change. The store keeps at most 4 file lists and about 512 MiB of parsed modules. It does not keep a project with more than about 40 MiB of source. To stop the reuse, setFALLOW_MCP_WARM_SESSION=0. fallow flagsparses each file once with a customflagsconfig. Before,sdkPatterns,envPrefixesorconfigObjectHeuristicsmade the command parse every file a second time. The command also matches guarded flags to unused exports by file and line. Thedead_code_overlapoutput does not change.- Fallow starts fewer git processes. A run without
--diff-file,--diff-stdinorFALLOW_DIFF_FILEno longer callsgit rev-parsefor the diff base directories. The Impact project identity reads the git common directory and the toplevel with one call in a work tree. - The VS Code extension no longer searches the workspace for manifests at startup. It starts when the workspace root has a
package.jsonor a Fallow config file. It also starts when a JavaScript, TypeScript, Vue, Svelte, Astro or MDX file opens. A monorepo folder with no rootpackage.jsonnow starts the extension when the first source file opens. fallow vizpages load and respond faster. The page parses only the file list, the edges and the summary at start. On the Fallow repository, the data parsed at start drops from 2.49 MB to 64 KB. The HTML file drops from 2.8 MB to 1.9 MB. A hover on the treemap now makes 3 draw calls, not one call per tile. The page shows its frame before the script runs, so the layout does not shift.
Changed
fallow flagsgivesmediumconfidence to generic SDK names without a flag import. The namesisEnabled,getValueanduseFeaturealso occur in libraries that are not flag SDKs. A call to one of these names now hasconfidence: "medium"when its file imports no flag SDK and no flag module. Other SDK names, such asuseFlagandcheckGate, keephighconfidence.fallow flagsreportsconfig.features.xas one read. Withflags.configObjectHeuristicson, the scan reported the full access and also the objectconfig.features. Now it reports only the full access.- Oxc 0.151. The parser and AST crates move from Oxc 0.126 to 0.151, and
oxc_coverage_instrumentmoves to 0.13. The dead-code, duplication and health output of public projects is the same as with 3.29.0. fallow similar-code inspectandfallow audituse the shared test-code definition. Both now treat files under__test__/,spec/,specs/ande2e/, and files named*.e2e.*and*.e2e-spec.*, as tests.similar-code inspectalso adds*.cy.*files, and no longer lists files under__mocks__/. Infallow audit, a mock or a fixture that imports a changed file does not count as a test.
Bug fixes
- Health hotspots tag more test files. The
[test]tag and the JSON fieldis_test_pathnow use the shared test-path definition. Atest/,tests/,__tests__/or__mocks__/directory at the project root now matches. A.test.or.spec.marker now matches only in the file name. - Test-path checks now classify paths relative to the project root. Before, a project inside a test-like directory lost three results. The split line with test files and source files did not show under
test,testsorfixtures. Thestart with <file>hint of the barefallowcommand named no file undertestsorexamples. The CVA checks lost every finding undertestortests. The split line now saysM in test files, notM in test directories. .cy.files andspec/directories need more evidence to be tests. A.cy.file is now a test only in a Cypress context, so a locale file such assrc/i18n/strings.cy.tsis production code. Aspecorspecsdirectory now holds tests only at a test root.- A plain
fallow listignores the--tolerancevalue. Before, an invalid value exited 2 for a plain listing. Nowfallow listparses the value only for--entry-weight. - pnpm overrides in a two-document lockfile are no longer reported as unused. pnpm 12 writes
pnpm-lock.yamlas two YAML documents whenpackage.jsonsetspackageManager. Fallow now reads each project document of the lockfile. Thanks @sgaabdu4 for the report (#2909). - On Windows, plugin path rules skip the directories they exclude. A plugin can exclude files by a path segment, such as the TanStack Router
routeFileIgnorePattern. Before, the check did not split paths on the native Windows separator, so an excluded route file was still an entry point.
Upgrade notes
- Building fallow from source now needs Rust 1.96 or later (it was 1.92).
- The parse, extract, graph and duplication caches rebuild once, so the first run after the upgrade parses and resolves the project again.
isEnabled,getValueanduseFeaturecalls without a flag import change fromhightomediumconfidence. To keephighconfidence for your own function, add its name toflags.sdkPatterns.- With
flags.configObjectHeuristicson, object entries such asprocess.featuresno longer show in the output. - Waku projects: remove manual
src/pages/**entries. They credit every export, so a typo such asgetconfiggoes unreported underincludeEntryExports. --max-flag-ageneeds git history. Without full history the gate isskipped. To get full history, setfetch-depth: 0in GitHub Actions.- The human split line under unused files and unused exports now says
M in test files. Update scripts that match the old text. - Language server session reuse needs a client that registers watched files.
Full Changelog: v3.29.0...v3.30.0