Skip to content

Conversation

@arendjr
Copy link
Contributor

@arendjr arendjr commented Dec 3, 2025

Summary

Fixed #8292: Implement tracking of types of TypeScript constructor parameter properties.

This resolves certain false negatives in noFloatingPromises and other typed rules.

Example

class AsyncClass {
    async returnsPromise() {
        return 'value';
    }
}

class ShouldBeReported {
    constructor(public field: AsyncClass) { }
    //          ^^^^^^^^^^^^----------------- Parameter property declaration

    async shouldBeReported() {
        // `useFloatingPromise` will now report the following usage:
        this.field.returnsPromise();
    }
}

Test Plan

Test added.

Docs

N/A

@changeset-bot
Copy link

changeset-bot bot commented Dec 3, 2025

🦋 Changeset detected

Latest commit: 91c5cb6

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 13 packages
Name Type
@biomejs/biome Patch
@biomejs/cli-win32-x64 Patch
@biomejs/cli-win32-arm64 Patch
@biomejs/cli-darwin-x64 Patch
@biomejs/cli-darwin-arm64 Patch
@biomejs/cli-linux-x64 Patch
@biomejs/cli-linux-arm64 Patch
@biomejs/cli-linux-x64-musl Patch
@biomejs/cli-linux-arm64-musl Patch
@biomejs/wasm-web Patch
@biomejs/wasm-bundler Patch
@biomejs/wasm-nodejs Patch
@biomejs/backend-jsonrpc Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@github-actions github-actions bot added A-Linter Area: linter L-JavaScript Language: JavaScript and super languages A-Type-Inference Area: type inference labels Dec 3, 2025
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Dec 3, 2025

Walkthrough

This PR adds constructor-parameter handling to the type inference layer so TypeScript parameter properties are tracked as typed class members. It introduces a public ConstructorParameter wrapper, a TypeMemberAccessibility enum, updates FunctionParameter defaults, and changes class member collection to expand constructor parameters into TypeMembers. A test exercising noFloatingPromises verifies an unawaited async call on a constructor parameter property is now reported.

Possibly related PRs

Suggested reviewers

  • ematipico
  • dyc3

Pre-merge checks and finishing touches

✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely describes the main change: implementing constructor parameter property tracking in the core type system.
Description check ✅ Passed The description is well-related to the changeset, explaining the fix for issue #8292, providing a clear example, and documenting test coverage.
Linked Issues check ✅ Passed The PR fully addresses issue #8292 by implementing type tracking for TypeScript constructor parameter properties, enabling noFloatingPromises [#8292] to correctly detect promise-returning method calls on such properties.
Out of Scope Changes check ✅ Passed All changes are directly scoped to the objective: type inference enhancements for constructor parameters, a new test case, and a changeset entry documenting the fix.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

📜 Recent review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7df3788 and 91c5cb6.

⛔ Files ignored due to path filters (1)
  • crates/biome_service/src/snapshots/biome_service__workspace__tests__debug_registered_types.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (1)
  • .changeset/constructed-carriers-corrolate.md (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • .changeset/constructed-carriers-corrolate.md
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: autofix
  • GitHub Check: End-to-end tests
  • GitHub Check: Documentation
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: Test Node.js API
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Bench (biome_js_formatter)

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (2)
crates/biome_js_type_info/src/local_inference.rs (2)

2243-2262: Verify the indexed loop pattern.

The loop iterates over 0..num_members while potentially pushing to members, but since you only read from indices below num_members, this is safe. However, consider using iterators with filter and flat_map for a more idiomatic Rust style, or alternatively collect constructor members separately before extending.

-        let num_members = members.len();
-        for i in 0..num_members {
-            let member = &members[i];
-            if member.is_constructor()
-                && let Some(member_ty) = resolver.resolve_and_get(&member.ty)
-                && let TypeData::Constructor(constructor) = member_ty.as_raw_data()
-            {
-                for param in &constructor.parameters {
-                    if let Some(_accessibility) = param.accessibility
-                        && let FunctionParameter::Named(named_param) = &param.parameter
-                    {
-                        // TODO: Assign accessibility to type members.
-                        members.push(Self {
-                            kind: TypeMemberKind::Named(named_param.name.clone()),
-                            ty: param.parameter.ty().clone(),
-                        });
-                    }
-                }
-            }
-        }
+        let extra_members: Vec<_> = members
+            .iter()
+            .filter(|member| member.is_constructor())
+            .filter_map(|member| resolver.resolve_and_get(&member.ty))
+            .filter_map(|member_ty| match member_ty.as_raw_data() {
+                TypeData::Constructor(constructor) => Some(constructor),
+                _ => None,
+            })
+            .flat_map(|constructor| {
+                constructor.parameters.iter().filter_map(|param| {
+                    if param.accessibility.is_some() {
+                        if let FunctionParameter::Named(named_param) = &param.parameter {
+                            // TODO: Assign accessibility to type members.
+                            return Some(Self {
+                                kind: TypeMemberKind::Named(named_param.name.clone()),
+                                ty: param.parameter.ty().clone(),
+                            });
+                        }
+                    }
+                    None
+                })
+            })
+            .collect();
+        members.extend(extra_members);

1510-1514: Consider logging or handling bogus parameters.

Returning Self::default() for JsBogusParameter silently discards potentially useful diagnostic information. This is fine for now, but you might want to track this for observability.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 343dc4d and 7df3788.

⛔ Files ignored due to path filters (1)
  • crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/issue8292.ts.snap is excluded by !**/*.snap and included by **
📒 Files selected for processing (4)
  • .changeset/constructed-carriers-corrolate.md (1 hunks)
  • crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/issue8292.ts (1 hunks)
  • crates/biome_js_type_info/src/local_inference.rs (10 hunks)
  • crates/biome_js_type_info/src/type_data.rs (3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
.changeset/**/*.md

📄 CodeRabbit inference engine (CONTRIBUTING.md)

.changeset/**/*.md: Create changesets for user-facing changes using just new-changeset; use headers with #### or ##### only; keep descriptions concise (1-3 sentences) and focus on user-facing changes
Use past tense when describing what was done ('Added new feature'), present tense when describing Biome behavior ('Biome now supports'); end sentences with a full stop
For new lint rules, show an example of an invalid case in an inline code snippet or code block; for rule changes, demonstrate what is now invalid or valid; for formatter changes, use a diff code block

Files:

  • .changeset/constructed-carriers-corrolate.md
crates/biome_js_type_info/**/*.rs

📄 CodeRabbit inference engine (crates/biome_js_type_info/CONTRIBUTING.md)

crates/biome_js_type_info/**/*.rs: No module may copy or clone data from another module in the module graph, not even behind an Arc
Use TypeReference instead of Arc for types that reference other types to avoid stale cache issues when modules are replaced
Store type data in linear vectors instead of using recursive data structures with Arc for improved data locality and performance
Use TypeReference variants (Qualifier, Resolved, Import, Unknown) to represent different phases of type resolution
Use TypeData::Unknown to indicate when type inference falls short or is not implemented
Distinguish between TypeData::Unknown and TypeData::UnknownKeyword to measure inference effectiveness versus explicit user-provided unknown types
When using ResolvedTypeData, track the ResolverId to ensure subsequent resolver calls use the correct context
Always apply the correct ResolverId when retrieving raw type data from ResolvedTypeData.as_raw_data() to prevent panics during subsequent resolution calls

Files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
**/*.rs

📄 CodeRabbit inference engine (CONTRIBUTING.md)

**/*.rs: Use the dbg!() macro for debugging output during testing, and pass the --show-output flag to cargo to view debug output
Use cargo t or cargo test to run tests; for a single test, pass the test name after the test command
Use snapshot testing with the insta crate; run cargo insta accept, cargo insta reject, or cargo insta review to manage snapshot changes
Write doctests as doc comments with code blocks; the code inside code blocks will be run during the testing phase
Use just f (alias for just format) to format Rust and TOML files before committing

Files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
**/*.ts

📄 CodeRabbit inference engine (CONTRIBUTING.md)

For Node.js package development, build WebAssembly bindings and JSON-RPC bindings; run tests against compiled files after implementing features or bug fixes

Files:

  • crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/issue8292.ts
crates/biome_js_type_info/**/local_inference.rs

📄 CodeRabbit inference engine (crates/biome_js_type_info/CONTRIBUTING.md)

Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Files:

  • crates/biome_js_type_info/src/local_inference.rs
🧠 Learnings (20)
📚 Learning: 2025-11-28T09:08:10.077Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-11-28T09:08:10.077Z
Learning: Applies to .changeset/**/*.md : Create changesets for user-facing changes using `just new-changeset`; use headers with `####` or `#####` only; keep descriptions concise (1-3 sentences) and focus on user-facing changes

Applied to files:

  • .changeset/constructed-carriers-corrolate.md
📚 Learning: 2025-11-28T09:08:10.077Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: CONTRIBUTING.md:0-0
Timestamp: 2025-11-28T09:08:10.077Z
Learning: Applies to .changeset/**/*.md : For new lint rules, show an example of an invalid case in an inline code snippet or code block; for rule changes, demonstrate what is now invalid or valid; for formatter changes, use a `diff` code block

Applied to files:

  • .changeset/constructed-carriers-corrolate.md
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` instead of `Arc` for types that reference other types to avoid stale cache issues when modules are replaced

Applied to files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeReference` variants (`Qualifier`, `Resolved`, `Import`, `Unknown`) to represent different phases of type resolution

Applied to files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Distinguish between `TypeData::Unknown` and `TypeData::UnknownKeyword` to measure inference effectiveness versus explicit user-provided unknown types

Applied to files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Use `TypeData::Unknown` to indicate when type inference falls short or is not implemented

Applied to files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-08-20T16:24:59.781Z
Learnt from: arendjr
Repo: biomejs/biome PR: 7266
File: crates/biome_js_type_info/src/type.rs:94-102
Timestamp: 2025-08-20T16:24:59.781Z
Learning: In crates/biome_js_type_info/src/type.rs, the flattened_union_variants() method returns TypeReference instances that already have the correct module IDs applied to them. These references should be used directly with resolver.resolve_reference() without applying additional module ID transformations, as variant references may originate from nested unions in different modules.

Applied to files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/scoped_resolver.rs : Implement full inference to resolve `TypeReference::Import` variants across the entire module graph

Applied to files:

  • crates/biome_js_type_info/src/type_data.rs
  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/tests/specs/**/*.{js,ts,tsx,jsx,json,css} : Test rules using snapshot tests via the `insta` library with test cases in `tests/specs/<group>/<rule_name>/` directories prefixed by `invalid` or `valid`

Applied to files:

  • crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/issue8292.ts
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/js_module_info/collector.rs : Implement module-level (thin) inference to resolve `TypeReference::Qualifier` variants by looking up declarations in module scopes and handling import statements

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/local_inference.rs : Implement local inference in dedicated modules to derive type definitions from expressions without context of surrounding scopes

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : When using `ResolvedTypeData`, track the `ResolverId` to ensure subsequent resolver calls use the correct context

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/*.rs : Always apply the correct `ResolverId` when retrieving raw type data from `ResolvedTypeData.as_raw_data()` to prevent panics during subsequent resolution calls

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:42.356Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_type_info/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:42.356Z
Learning: Applies to crates/biome_js_type_info/**/flattening.rs : Implement type flattening to simplify `TypeofExpression` variants once all component types are resolved

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Import the `FormatNode` trait and implement it for your Node when creating formatters in biome_js_formatter

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : When formatting AST nodes, use mandatory tokens from the AST instead of hardcoding token strings (e.g., use `node.l_paren_token().format()` instead of `token("(")`)

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:06:03.545Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_parser/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:06:03.545Z
Learning: Applies to crates/biome_parser/**/src/**/*.rs : Use `ConditionalParsedSyntax` for syntax that is only valid in specific contexts (e.g., strict mode, file types, language versions) and call `or_invalid_to_bogus()` to convert to a bogus node if not supported

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : For tokens that are not mandatory, use helper functions instead of hardcoding

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-24T18:05:27.810Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_js_formatter/CONTRIBUTING.md:0-0
Timestamp: 2025-11-24T18:05:27.810Z
Learning: Applies to crates/biome_js_formatter/**/*.rs : Do not attempt to 'fix' the code; if a token/node is known to be mandatory but is missing, return `None` instead

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
📚 Learning: 2025-11-27T23:04:02.022Z
Learnt from: CR
Repo: biomejs/biome PR: 0
File: crates/biome_analyze/CONTRIBUTING.md:0-0
Timestamp: 2025-11-27T23:04:02.022Z
Learning: Applies to crates/biome_analyze/**/*analyze/src/lint/**/*.rs : Use the `declare_node_union!` macro to query multiple node types at once by joining them into an enum with `Any*Like` naming convention

Applied to files:

  • crates/biome_js_type_info/src/local_inference.rs
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (12)
  • GitHub Check: Bench (biome_js_analyze)
  • GitHub Check: Bench (biome_js_formatter)
  • GitHub Check: Bench (biome_js_parser)
  • GitHub Check: Test Node.js API
  • GitHub Check: Lint project (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Check Dependencies
  • GitHub Check: Test (depot-windows-2022-16)
  • GitHub Check: Test (depot-ubuntu-24.04-arm-16)
  • GitHub Check: Documentation
  • GitHub Check: End-to-end tests
  • GitHub Check: Lint project (depot-windows-2022)
  • GitHub Check: autofix
🔇 Additional comments (7)
crates/biome_js_analyze/tests/specs/nursery/noFloatingPromises/issue8292.ts (1)

1-15: LGTM!

Test correctly exercises the scenario from issue #8292: a floating promise call via a constructor parameter property. This will validate that the type tracking now works for this.field.returnsPromise().

crates/biome_js_type_info/src/type_data.rs (3)

461-468: LGTM!

The ConstructorParameter struct cleanly wraps FunctionParameter with optional accessibility, enabling parameter property tracking without disrupting existing function parameter handling.


554-563: LGTM!

Sensible default implementation for FunctionParameter — returns a pattern parameter with unknown type and empty bindings. This supports fallback paths in constructor parameter conversion.


1511-1518: LGTM!

TypeMemberAccessibility enum with Public as the default aligns with TypeScript semantics where unspecified accessibility defaults to public.

crates/biome_js_type_info/src/local_inference.rs (3)

1817-1832: LGTM!

Clean handling of JsConstructorClassMember — the constructor is properly converted to a TypeMember with TypeMemberKind::Constructor.


2524-2538: LGTM!

The from_modifier_list method correctly extracts accessibility from TypeScript property parameter modifiers, defaulting to Public when no explicit modifier is found.


2540-2573: LGTM!

The helper functions constructor_params_from_js_constructor_params and constructor_params_from_js_params provide clean conversion paths for constructor parameters, properly wrapping FunctionParameter with accessibility metadata.

@codspeed-hq
Copy link

codspeed-hq bot commented Dec 3, 2025

CodSpeed Performance Report

Merging #8346 will not alter performance

Comparing arendjr:constructor-parameter-properties (91c5cb6) with main (343dc4d)

Summary

✅ 58 untouched
⏩ 95 skipped1

Footnotes

  1. 95 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

@github-actions github-actions bot added the A-Project Area: project label Dec 3, 2025
@arendjr arendjr merged commit f3aee1a into biomejs:main Dec 3, 2025
16 checks passed
@github-actions github-actions bot mentioned this pull request Dec 3, 2025
This was referenced Dec 15, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Linter Area: linter A-Project Area: project A-Type-Inference Area: type inference L-JavaScript Language: JavaScript and super languages

Projects

None yet

Development

Successfully merging this pull request may close these issues.

💅 noFloatingPromises fails on methods of parameter properties

1 participant