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.64.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.65.0
Choose a head ref
  • 15 commits
  • 84 files changed
  • 5 contributors

Commits on May 4, 2026

  1. Configuration menu
    Copy the full SHA
    dfb8a5f View commit details
    Browse the repository at this point in the history
  2. Configuration menu
    Copy the full SHA
    564afbe View commit details
    Browse the repository at this point in the history

Commits on May 5, 2026

  1. test(extract): cover Vue generic="T extends X<Y>" parsing

    Lock in fallow's regex-based attrs scan so a Vue script-setup
    generic constraint with a type argument keeps the script body
    intact and oxc parses it normally. Asserts that both the value
    import and the type-only import survive the SFC boundary with
    correct `is_type_only` flags.
    BartWaardenburg committed May 5, 2026
    Configuration menu
    Copy the full SHA
    0d426c6 View commit details
    Browse the repository at this point in the history
  2. test(extract,core): cover Svelte type-only imports across the SFC bou…

    …ndary
    
    Lock in fallow's contract that type-only imports inside Svelte
    `<script lang="ts">` blocks survive extraction with
    `is_type_only=true` and are tracked as type-referenced when
    consumed only as type annotations. Coverage at both the
    extraction layer (unit test) and the analysis layer
    (svelte-project fixture extension), so an upstream `export type`
    whose only consumer is a type annotation stays reachable.
    BartWaardenburg committed May 5, 2026
    Configuration menu
    Copy the full SHA
    f1c1caf View commit details
    Browse the repository at this point in the history
  3. fix(extract): scan Vue generic / Svelte generics attributes for t…

    …ype references
    
    A type-only import whose only consumer is the script-tag
    `generic="T extends Test<boolean>"` (Vue) or
    `generics="T extends Item"` (Svelte) was falsely flagged as
    `unused_types` because the constraint lives on the tag, not in
    the script body. The body parse had no symbol references for the
    import, oxc_semantic classified the binding as unused, and the
    upstream `export type` got dead-code reported.
    
    Fix: when a script tag carries a generic / generics attribute,
    build an augmented source that appends a synthetic local type
    alias consuming the constraint, and run the binding-usage
    analyser on that augmented parse. The extractor still walks the
    original body, so synthetic declarations don't leak into module
    info. The non-generic code path is untouched.
    BartWaardenburg committed May 5, 2026
    Configuration menu
    Copy the full SHA
    bf6349a View commit details
    Browse the repository at this point in the history
  4. docs(rules): note generic/generics attribute scanning in extract-crat…

    …e.md
    
    The extract-crate rule file lists each module's capabilities as
    system context for future Claude sessions. Adding a brief mention
    of the new generic/generics attribute scanning keeps the rule
    file accurate.
    BartWaardenburg committed May 5, 2026
    Configuration menu
    Copy the full SHA
    36929fe View commit details
    Browse the repository at this point in the history
  5. chore(deps): bump crate-ci/typos from 1.45.1 to 1.45.2 (#277)

    Bumps [crate-ci/typos](https://github.com/crate-ci/typos) from 1.45.1 to 1.45.2.
    - [Release notes](https://github.com/crate-ci/typos/releases)
    - [Changelog](https://github.com/crate-ci/typos/blob/master/CHANGELOG.md)
    - [Commits](crate-ci/typos@cf5f1c2...7c57295)
    
    ---
    updated-dependencies:
    - dependency-name: crate-ci/typos
      dependency-version: 1.45.2
      dependency-type: direct:production
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored May 5, 2026
    Configuration menu
    Copy the full SHA
    b92dda2 View commit details
    Browse the repository at this point in the history
  6. chore(deps-dev): bump @tanstack/intent in /npm/fallow (#278)

    Bumps [@tanstack/intent](https://github.com/TanStack/intent) from 0.0.32 to 0.0.36.
    - [Release notes](https://github.com/TanStack/intent/releases)
    - [Commits](https://github.com/TanStack/intent/commits/v0.0.36)
    
    ---
    updated-dependencies:
    - dependency-name: "@tanstack/intent"
      dependency-version: 0.0.36
      dependency-type: direct:development
      update-type: version-update:semver-patch
    ...
    
    Signed-off-by: dependabot[bot] <support@github.com>
    Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
    dependabot[bot] authored May 5, 2026
    Configuration menu
    Copy the full SHA
    8d0d31d View commit details
    Browse the repository at this point in the history
  7. feat: detect Lit / Web Components registered classes (knip #1394)

    * feat(types): add is_side_effect_used flag to ExportInfo
    
    Threads a boolean through ExportInfo, scaffolding for marking exports
    that are alive only via runtime registration (Lit @CustomElement
    decorators, customElements.define calls). Defaults to false everywhere;
    no behavioral change yet.
    
    * feat(graph): plumb is_side_effect_used to ExportSymbol + analyzer
    
    Threads the flag from ExportInfo through ModuleGraph::build_module_node
    into ExportSymbol, then OR-checks it alongside !references.is_empty() in
    find_unused_exports. Reachable modules whose exports are flagged as
    side-effect-used are no longer flagged as unused-export.
    
    * feat(extract): detect Lit @CustomElement and customElements.define
    
    Adds two new helpers in visitor/helpers.rs and wires them into the AST
    walk:
    
    - has_lit_class_decorator(class): matches @CustomElement('tag') class
      decorators (bare-import and member-call forms).
    - extract_custom_elements_define(call): matches customElements.define(
      STRING, IDENT) call expressions.
    
    Both pattern matches push the class identifier into a new
    ModuleInfoExtractor.side_effect_registered_class_names set. A post-walk
    finalizer flips ExportInfo.is_side_effect_used = true for any export
    whose local binding name is in the set, so unused-export detection no
    longer reports Web Component classes as dead.
    
    Persisted across cache rounds via a new CachedExport.is_side_effect_used
    field; CACHE_VERSION bumps 65 -> 66.
    
    * feat(core): Lit framework plugin + side-effect class member detection
    
    - New crates/core/src/plugins/lit.rs with three heritage-scoped
      used_class_member rules (LitElement, ReactiveElement, HTMLElement) so
      Lit lifecycle and native Custom Elements lifecycle members are not
      reported as unused on framework-managed classes.
    - New Plugin trait method used_class_member_rules() for emitting
      ScopedUsedClassMemberRule entries; default empty Vec, only the new
      Lit plugin overrides it for now.
    - crates/core/src/analyze/unused_members.rs ORs is_side_effect_used
      alongside !references.is_empty() in the skip-whole-export-dead guard
      so member analysis still runs on @CustomElement / customElements.define
      registered classes.
    - Integration fixture tests/fixtures/lit-custom-element/ exercises the
      decorator form, the customElements.define form, the order-independent
      separate-then-define form, and the @decorators.customElement
      member-call decorator form. Two integration tests assert exports are
      credited and that lifecycle members (render, connectedCallback,
      observedAttributes) are allowlisted while genuinely unused helpers are
      still reported.
    
    * style: rustfmt drift on lit plugin and builtin registry
    
    * fix(extract): credit anonymous default-class @CustomElement
    
    rust-reviewer caught that 'export default @CustomElement("x") class
    extends LitElement {}' (no class id) slipped through the side-effect
    flag plumbing. The class has no identifier to key by name, and the
    Default export's local_name is unset, so the post-walk finalizer can
    not match.
    
    Fix: in visit_class, when @CustomElement is detected and class.id is
    None, flip is_side_effect_used directly on the most recent Default
    export (which was just pushed by visit_export_default_declaration
    before walk recursed into the class).
    
    Adds anonymous-default.ts fixture and an integration assertion to lock
    the behavior.
    
    * docs: lit / custom-element entry in detection.md + README
    
    Adds a new Analysis-level entry to .claude/rules/detection.md describing
    the Lit / Web Components registration credit (knip #1394) and lists Lit
    under the Frameworks plugin table in README.md. Documentation-only;
    no behavior change.
    
    * fix(extract): gate @CustomElement credit on Lit decorator import
    
    The previous implementation flipped is_side_effect_used whenever a class
    had a decorator whose callee was named 'customElement', regardless of
    where the binding came from. A user-defined function called
    'customElement' would silently suppress unused-export reporting on every
    class it decorated.
    
    Changes:
    
    - visitor/helpers.rs: lit_custom_element_decorator() now returns the
      local binding name(s) used at the decorator call site, in both bare
      identifier and namespace-member-call shapes. No import validation
      happens at the visitor's class node; that's deferred until after the
      full walk completes.
    - visitor/mod.rs: a new lit_custom_element_candidates Vec collects every
      @customElement-shaped decorator alongside its target (named class or
      anonymous-default export slot). After the walk, apply_lit_custom_element_
      candidates() resolves each candidate against the imports list, gating
      credit on a binding that came from lit/decorators.js or
      lit/decorators/custom-element.js. Named-import aliases (`import {
      customElement as ce }`) match because the validator compares the
      ImportInfo.local_name to the decorator's local name and the
      ImportedName::Named to the canonical 'customElement' string.
    - visitor/visit_impl.rs: visit_class records candidates instead of
      flipping the flag in place; anonymous-default exports record the
      export-vec index so post-walk validation flips the right slot.
    - analyze/unused_members.rs: native HTMLElement lifecycle members
      (connectedCallback, disconnectedCallback, observedAttributes, etc.) are
      now built-in heritage-scoped allowlists in the member analyzer. Plain
      Web Components projects with no Lit dependency still get correct
      lifecycle handling.
    - plugins/lit.rs: drops the HTMLElement-scoped rule (now covered by the
      built-in) and keeps the LitElement / ReactiveElement rules for Lit-
      specific lifecycle members (render, firstUpdated, etc.).
    - cache/types.rs: CACHE_VERSION bumps 66 -> 67.
    - New fixture tests/fixtures/web-components-native/ exercises (a) plain
      customElements.define with no Lit at all (positive: lifecycle
      allowlisted) and (b) a non-Lit decorator named 'customElement'
      (negative: the class export must still be reported as unused).
    - New fixture tests/fixtures/lit-custom-element/src/named-import-alias-
      decorator.ts covers `import { customElement as ce }`.
    - Two new integration tests in web_components.rs and one extra
      assertion in lit_custom_element.rs lock the import-aware contract.
    - detection.md note updated to match the new contract.
    BartWaardenburg authored May 5, 2026
    Configuration menu
    Copy the full SHA
    471d0f5 View commit details
    Browse the repository at this point in the history
  8. feat(npm): ship schema.json inside the fallow package

    * feat(npm): ship schema.json inside the fallow package (#275)
    
    Today the published `fallow` npm package omits `schema.json`, so the
    only stable way to point `$schema` at a matching schema is the
    `raw.githubusercontent.com/.../main/schema.json` URL — which:
    
      - drifts from the installed CLI version (URL tracks main, install
        is pinned to a tag),
      - requires network on every fresh editor open and trips up
        corporate proxies that block raw.githubusercontent.com,
      - has no clean offline / airgapped story.
    
    Two-line fix:
    
      - Add `schema.json` to `npm/fallow/package.json`'s files array.
      - In the release workflow, copy `schema.json` from the repo root
        to `npm/fallow/schema.json` immediately before the `npm publish
        ./npm/fallow` step. We can't rely on a prepublishOnly hook here
        because the existing publish call uses `--ignore-scripts`.
    
    After this, consumers can write:
    
      {
        "$schema": "./node_modules/fallow/schema.json",
        ...
      }
    
    …and get version-aligned validation with zero network calls. The
    remote URL keeps working as a fallback for users without a local
    install.
    
    The optional `fallow init` template change suggested in the issue
    isn't included here — that lives in the Rust CLI and is best done as
    a follow-up once this artifact is actually published.
    
    * ci(npm): assert schema package artifact
    
    ---------
    
    Co-authored-by: Chris (ChrisJr404) <11917633+ChrisJr404@users.noreply.github.com>
    Co-authored-by: Bart Waardenburg <bart@waardenburg.dev>
    3 people authored May 5, 2026
    Configuration menu
    Copy the full SHA
    547ac02 View commit details
    Browse the repository at this point in the history
  9. fix(migrate): accept trailing commas in jsonc input (#280)

    * fix(migrate): accept trailing commas in jsonc input
    
    Closes #276.
    
    real-world JSONC files (knip.jsonc, tsconfig.json, .vscode/settings.json,
    etc.) routinely use trailing commas. `load_json_or_jsonc` ran the input
    through `json_comments::StripComments` and then handed the result to
    serde_json, which rejects trailing commas with "trailing comma at line N
    column M". Add a final pass that drops `,` immediately before `}` or
    `]` while leaving commas inside string literals untouched. The pass runs
    only when the comment-stripped parse fails, so already-valid input has
    zero overhead.
    
    The new `strip_trailing_commas` helper is a small byte-level state
    machine (no extra deps) that tracks in-string + escaped state, so
    strings like "hello, world," survive verbatim.
    
    * fix(migrate): harden jsonc trailing comma cleanup
    
    ---------
    
    Co-authored-by: ChrisJr404 <chris@hacknow.com>
    Co-authored-by: Bart Waardenburg <bart@waardenburg.dev>
    3 people authored May 5, 2026
    Configuration menu
    Copy the full SHA
    47306d3 View commit details
    Browse the repository at this point in the history
  10. fix(unused-class-members): trace Angular signal queries and plural Qu…

    …eryList iteration (#283)
    
    * fix(extract): trace Angular signal queries and plural QueryList iteration
    
    The `unused-class-members` analyzer already credited methods called through
    `@ViewChild` / `@ContentChild` decorator queries because the property's TS
    type annotation was wired into the bound-member-access pipeline. Six other
    first-class Angular query patterns were missed:
    
    - `viewChild<T>(...)`, `contentChild<T>(...)` — singular signal factories
      whose initializer return type is `Signal<T>`. The property has no
      explicit type annotation, and the call site `this.vc()?.method()` puts a
      `CallExpression` between `this.vc` and `method`, which the static-member
      resolver did not descend into.
    - `viewChildren<T>(...)`, `contentChildren<T>(...)` — plural signal
      factories iterated as `this.vcs().forEach(c => c.method())`. Even with
      a known element type, the arrow's `c` parameter had no resolved type.
    - `@ViewChildren` / `@ContentChildren` — plural decorator queries typed
      `QueryList<T> | undefined`. `extract_type_annotation_name` flattens the
      annotation to the bare `"QueryList"` identifier, so the element type
      was never reachable.
    
    Approach (smallest change that closes the gap):
    
    1. Add `extract_angular_signal_query` to recognize the four signal-query
       factories and pull out `T` from either the explicit type argument or
       the first identifier argument (the locator-class form).
    
    2. Add `extract_query_list_element_type` to peel `QueryList<T>` (and the
       nullable `QueryList<T> | null|undefined` variants) out of a TS type
       annotation, and `has_angular_plural_query_decorator` to recognize the
       `@ViewChildren` / `@ContentChildren` decorator forms.
    
    3. In `visit_property_definition`, register the discovered element type
       on `ModuleInfoExtractor`:
       - Singular signal queries → `binding_target_names["this.<name>()"] = T`
       - Plural signal queries  → `iterable_element_types["this.<name>()"] = T`
       - Plural decorator queries → `iterable_element_types["this.<name>"] = T`
    
    4. Extend `static_member_object_name` to descend into a zero-argument
       `CallExpression` (yielding `"this.vc()"`) and into a `ChainExpression`
       so `this.vc()?.method` and `this.dvcs?.forEach(...)` resolve through
       the existing pipeline.
    
    5. In `visit_call_expression`, when the callee is `<receiver>.forEach`
       (or the optional-chained form) and `<receiver>` is a registered
       iterable, bind the arrow callback's first parameter to the element
       type. The flat `binding_target_names` map then drives `c.method()`
       resolution at end-of-visit, matching the existing scope-unaware
       convention noted in the visitor module.
    
    Adds the issue's full eight-pattern Angular reproducer as a single test
    asserting that all eight `ChildComponent` methods are traced through
    `MemberAccess { object: "ChildComponent", member: <name> }`.
    
    Closes #274
    
    * fix(extract): address Angular query review findings
    
    ---------
    
    Co-authored-by: ChrisJr404 <chris@hacknow.com>
    Co-authored-by: Bart Waardenburg <bart@waardenburg.dev>
    3 people authored May 5, 2026
    Configuration menu
    Copy the full SHA
    2ea51fc View commit details
    Browse the repository at this point in the history
  11. Configuration menu
    Copy the full SHA
    6e27b02 View commit details
    Browse the repository at this point in the history
  12. fix(plugins): treat vite config default export as framework-used (#285)

    Fixes #282. Mirrors the vitest fix in #271. With --include-entry-exports,
    fallow flagged vite.config.* default exports as unused even though Vite's
    CLI consumes that default export. The vite plugin now contributes
    used_exports for vite.config.{ts,js,mts,mjs} (default), matching the
    shape already used for vitest.config.* and vitest.workspace.*.
    
    Adds a regression fixture (vite-include-entry-exports-workspace) and an
    integration test that pins the new behavior. Existing entry-export
    validation tests continue to ensure --include-entry-exports still flags
    truly-unused named exports on non-config entries.
    
    Co-authored-by: Chris (ChrisJr404) <chris@hacknow.com>
    ChrisJr404 and ChrisJr404 authored May 5, 2026
    Configuration menu
    Copy the full SHA
    868d44d View commit details
    Browse the repository at this point in the history
  13. Configuration menu
    Copy the full SHA
    2b1b696 View commit details
    Browse the repository at this point in the history
Loading