Skip to content

Conversation

@phillipc
Copy link
Member

@phillipc phillipc commented Jan 13, 2026

Knip finds and fixes unused dependencies, exports and files in your JavaScript and TypeScript projects.

In this PR we configured Knip and fixed all findings.

Summary by CodeRabbit

  • Chores

    • Added automated CI workflow and knip config to validate package dependencies/imports; new Makefile targets for dependency checks.
    • Added dev tooling and devDependencies for dependency analysis; removed many transitive runtime deps to streamline installs.
  • Refactor

    • Tightened and consolidated TypeScript types; added new public type exports.
    • Reduced public surface: converted several former exports to internal and switched some default exports to named exports.
  • Build

    • Updated TypeScript compiler options to preserve modern module syntax.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 13, 2026

📝 Walkthrough

Walkthrough

Adds a knip-based dependency/import CI workflow and Makefile targets, removes many transitive tslib entries, introduces typed interfaces in packages/utils, converts many imports to type-only, adjusts exports/visibility, and tweaks TypeScript compiler options.

Changes

Cohort / File(s) Summary
CI & Build Targets
\.github/workflows/dependency-cycle.yml, Makefile
New GitHub Actions workflow to run dependency/import checks on PRs and manual dispatch; adds knip and lerna-check make targets and integrates them into build steps.
Knip config & dev deps
knip.json, package.json
Adds knip.json workspace config; adds knip and @types/node to root devDependencies.
TypeScript compiler settings
tsconfig.json, tsconfig.dts.json
importHelpers set to false and verbatimModuleSyntax enabled; declaration emit option adjusted.
New public interfaces
packages/utils/src/interfaces.ts, packages/utils/src/index.ts
Introduces and re-exports types/interfaces (IProvider, BindingAccessors, KnockoutUtils, ArrayAndObjectUtils) defining provider/utility contracts.
Type refactors & usages
packages/utils/src/options.ts, packages/provider/src/Provider.ts, packages/provider/src/BindingHandlerObject.ts, packages/builder/src/Builder.ts, packages/bind/src/applyBindings.ts, packages/bind/src/bindingContext.ts
Replace concrete types with new interfaces, add type-only imports, update class implements and several method/type signatures.
Public API removals / export changes
packages/computed/src/computed.ts, packages/observable/src/observableArray.ts, packages/observable/src/extenders.ts, packages/provider.mustache/src/mustacheParser.ts, packages/utils.jsx/src/JsxObserver.ts, packages/utils.jsx/src/index.ts
Remove or make internal several previously exported types/functions; convert JsxObserver from default to named export.
Added internal helpers
packages/computed/src/proxy.ts
Exposes internal helpers (getObservable, peek, isProxied) from proxy module.
Numerous package.json edits (tslib removal / dependency reshuffle)
builds/..., packages/*/package.json (many files)
Removes tslib across many packages; adds/replaces dependencies (@tko/bind, @tko/observable, @tko/provider.native, etc.) reshaping transitive graph.
Type-only import conversions
packages/binding.template/..., packages/provider.component/spec/customElementBehaviors.ts, packages/binding.template/src/nativeTemplateEngine.ts, ...
Convert value imports to type-only imports (e.g., type Observable, type TemplateOptions, type Computed) across modules to reduce runtime imports.

Sequence Diagram(s)

sequenceDiagram
  participant PR as Pull Request
  participant GH as GitHub Actions
  participant Runner as CI Runner (Node/NPM)
  participant Make as Makefile
  participant Knip as knip
  participant Lerna as lerna

  PR->>GH: trigger workflow (pull_request or workflow_dispatch)
  GH->>Runner: start runner, checkout repo
  Runner->>Runner: setup Node.js 24 and cache npm
  Runner->>Make: run `make` (build)
  Runner->>Make: run `make knip`
  Make->>Knip: invoke knip via npx to analyze imports/exports
  Knip-->>Make: report dependency/import issues
  Runner->>Make: run `make lerna-check`
  Make->>Lerna: run lerna across packages to validate packages
  Lerna-->>Make: report results
  Make-->>Runner: exit status
  Runner-->>GH: post workflow status
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • brianmhunt

Poem

🐰
I hopped through types with nimble paws,
Cleared out tslib and tended laws.
Knip sniffed cycles in the night,
CI pranced, the checks took flight —
A rabbit's hop, the repo's bright!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 44.44% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title directly and accurately summarizes the main changes: adding knip linter configuration and fixing all unused dependencies and exports found by the tool.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6da7b30 and 75ffdf6.

📒 Files selected for processing (1)
  • packages/utils/src/index.ts
⏰ 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). (1)
  • GitHub Check: testheadless (24, test-headless-ff)
🔇 Additional comments (1)
packages/utils/src/index.ts (1)

32-32: LGTM!

Using export type for type-only re-exports is the correct TypeScript idiom—it ensures these interfaces are erased at compile time while consolidating the public type surface through the package's main entry point.


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.

❤️ Share

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: 2

🤖 Fix all issues with AI agents
In @knip.json:
- Line 11: The ignoreDependencies pattern "karma-[.]*" is wrong and won't match
plugins like "karma-chrome-launcher"; update the value in the
"ignoreDependencies" array by replacing the token "karma-[.]*" with a regex that
matches any karma plugin prefix such as "karma-.+" so that entries like
"karma-mocha" and "karma-chrome-launcher" are ignored as intended.

In @packages/utils.jsx/src/JsxObserver.ts:
- Line 467: Remove the stray comment line that reads "//Duplicated export
default JsxObserver" which sits next to the redundant default export for the
JsxObserver symbol; simply delete that comment line (and any adjacent redundant
default export if present) so only the existing named export of JsxObserver
remains and no cluttering comment is left in the file.
🧹 Nitpick comments (9)
packages/computed/src/computed.ts (1)

70-70: Consider either removing entirely or keeping the export.

Commenting out code with a TODO is generally discouraged. The type MaybeComputed appears nowhere else in the codebase, confirming it is truly unused. Either remove it entirely, or—if this utility type provides value for library consumers (accepting plain values or computed values)—restore the export and remove the TODO comment.

packages/observable/src/observableArray.ts (1)

15-15: Prefer removing unused code over commenting it out.

Commented-out code adds noise and tends to stay indefinitely. If MaybeObservableArray is truly unused, remove it entirely. If it might be needed by external consumers or for future use, consider:

  1. Keeping the export and adding a Knip ignore directive if it's part of the public API surface
  2. Creating a tracking issue if restoration is planned
Option 1: Remove entirely
-//TODO Currently unused --> export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
Option 2: Keep as public API with Knip ignore
-//TODO Currently unused --> export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
+// knip-ignore-next - Public API for consumers
+export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
packages/utils.component/package.json (1)

16-22: Consider updating stale peerDependencies versions.

The peerDependencies reference alpha versions (^4.0.0-alpha8.0, ^4.0.0-alpha9.0) while the regular dependencies are at ^4.0.0-beta1.3. This version gap may cause resolution warnings or compatibility confusion for consumers.

If not addressed in this PR, consider tracking this for a follow-up update.

packages/utils/src/interfaces.ts (1)

42-45: Potential circular type reference between IKnockoutUtilsInstance and Options.

IKnockoutUtilsInstance references Options, and Options (in options.ts) references IKnockoutUtilsInstance. While this works with type-only imports, consider documenting this bidirectional relationship or using a more minimal options type here to reduce coupling.

packages/provider/src/BindingHandlerObject.ts (1)

26-29: Consider adding type annotation to get parameter.

The nameOrDotted parameter lacks a type annotation. For consistency with the interface and the typed set method, consider adding : string.

Suggested fix
-  get(nameOrDotted) {
+  get(nameOrDotted: string) {
     const [name] = nameOrDotted.split('.')
     return this[name]
   }
packages/provider/src/Provider.ts (1)

58-59: Address or remove the TODO comment.

The TODO comment flags postProcess as a potentially unused hook. Consider either removing the commented-out code if it's confirmed unused, or implementing the hook if it's needed for extensibility.

Would you like me to help search the codebase to confirm whether postProcess is used anywhere, or open an issue to track this cleanup?

packages/builder/src/Builder.ts (2)

109-128: Minor redundancy: addOrRemoveItem is declared twice.

addOrRemoveItem is already part of IArrayAndObjectUtils (see packages/utils/src/interfaces.ts lines 23-39), so the explicit declaration on line 110 is redundant. Consider removing it for clarity.

♻️ Suggested fix
 export type Utils = IArrayAndObjectUtils & {
-  addOrRemoveItem: typeof addOrRemoveItem
   cloneNodes: typeof cloneNodes
   compareArrays: typeof compareArrays

163-171: Clarify the type-merging comment.

The comment on line 171 is unclear. Consider rephrasing for better readability.

♻️ Suggested fix
 export type KnockoutInstance = IKnockoutUtilsInstance & {
   // --- Utilities ---
   cleanNode: typeof cleanNode
   dependencyDetection: typeof dependencyDetection
   computedContext: typeof dependencyDetection
   filters: typeof options.filters
   ignoreDependencies: typeof dependencyDetection.ignore
   memoization: typeof memoization
-  //Type merging from IKnockoutInstance options: typeof options
+  // Note: `options` property is inherited from IKnockoutUtilsInstance
   removeNode: typeof removeNode
.github/workflows/dependency-cycle.yml (1)

3-7: Consider adding push trigger for the main branch.

The workflow only runs on pull_request and workflow_dispatch. Consider also triggering on pushes to the main branch to catch any issues that slip through or are introduced via direct commits.

Suggested enhancement
 on:
   # Runs for every pull-requests created
   pull_request:
+  push:
+    branches: [main]
   # Allows you to run this workflow manually from the Actions tab
   workflow_dispatch:
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 40ac9a6 and 9c6f5b5.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (51)
  • .github/workflows/dependency-cycle.yml
  • Makefile
  • builds/knockout/package.json
  • builds/reference/package.json
  • knip.json
  • package.json
  • packages/bind/package.json
  • packages/bind/src/applyBindings.ts
  • packages/bind/src/bindingContext.ts
  • packages/binding.component/package.json
  • packages/binding.core/package.json
  • packages/binding.foreach/package.json
  • packages/binding.if/package.json
  • packages/binding.template/package.json
  • packages/binding.template/spec/nativeTemplateEngineBehaviors.ts
  • packages/binding.template/src/nativeTemplateEngine.ts
  • packages/builder/package.json
  • packages/builder/src/Builder.ts
  • packages/computed/package.json
  • packages/computed/src/computed.ts
  • packages/computed/src/proxy.ts
  • packages/filter.punches/package.json
  • packages/lifecycle/package.json
  • packages/observable/package.json
  • packages/observable/src/extenders.ts
  • packages/observable/src/observableArray.ts
  • packages/provider.attr/package.json
  • packages/provider.bindingstring/package.json
  • packages/provider.component/package.json
  • packages/provider.component/spec/customElementBehaviors.ts
  • packages/provider.databind/package.json
  • packages/provider.multi/package.json
  • packages/provider.mustache/package.json
  • packages/provider.mustache/src/mustacheParser.ts
  • packages/provider.native/package.json
  • packages/provider.virtual/package.json
  • packages/provider/package.json
  • packages/provider/src/BindingHandlerObject.ts
  • packages/provider/src/Provider.ts
  • packages/utils.component/package.json
  • packages/utils.functionrewrite/package.json
  • packages/utils.jsx/package.json
  • packages/utils.jsx/src/JsxObserver.ts
  • packages/utils.jsx/src/index.ts
  • packages/utils.parser/package.json
  • packages/utils/package.json
  • packages/utils/src/index.ts
  • packages/utils/src/interfaces.ts
  • packages/utils/src/options.ts
  • tsconfig.dts.json
  • tsconfig.json
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-10T12:14:25.781Z
Learnt from: phillipc
Repo: knockout/tko PR: 233
File: COVERAGE.md:4-6
Timestamp: 2026-01-10T12:14:25.781Z
Learning: In the knockout/tko repository, COVERAGE.md is an auto-generated file (by the test coverage tool) and should be excluded from markdown linting to avoid flagging formatting issues that cannot be manually addressed.

Applied to files:

  • knip.json
🧬 Code graph analysis (6)
packages/provider/src/BindingHandlerObject.ts (1)
packages/utils/src/index.ts (1)
  • IBindingHandlerObject (33-33)
packages/utils/src/options.ts (2)
packages/utils/src/index.ts (2)
  • IKnockoutUtilsInstance (36-36)
  • IProvider (34-34)
packages/utils/src/interfaces.ts (2)
  • IKnockoutUtilsInstance (42-45)
  • IProvider (55-107)
packages/bind/src/bindingContext.ts (2)
packages/utils/src/index.ts (1)
  • IKnockoutUtilsInstance (36-36)
packages/utils/src/interfaces.ts (1)
  • IKnockoutUtilsInstance (42-45)
packages/utils/src/interfaces.ts (5)
packages/utils/src/index.ts (4)
  • IBindingHandlerObject (33-33)
  • IArrayAndObjectUtils (37-37)
  • IKnockoutUtilsInstance (36-36)
  • IProvider (34-34)
packages/utils/src/array.ts (11)
  • addOrRemoveItem (80-91)
  • arrayFilter (58-67)
  • arrayFirst (26-32)
  • arrayForEach (9-20)
  • arrayGetDistinctValues (50-56)
  • arrayIndexOf (22-24)
  • arrayMap (34-39)
  • arrayPushAll (69-78)
  • arrayRemoveItem (41-48)
  • compareArrays (149-165)
  • range (98-106)
packages/utils/src/symbol.ts (1)
  • createSymbolOrString (7-9)
packages/utils/src/object.ts (2)
  • objectForEach (32-38)
  • objectMap (40-54)
packages/utils/src/options.ts (1)
  • Options (14-133)
packages/bind/src/applyBindings.ts (2)
packages/utils/src/index.ts (1)
  • IProvider (34-34)
packages/utils/src/interfaces.ts (1)
  • IProvider (55-107)
packages/builder/src/Builder.ts (1)
packages/utils/src/interfaces.ts (2)
  • IArrayAndObjectUtils (24-40)
  • IKnockoutUtilsInstance (42-45)
⏰ 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). (1)
  • GitHub Check: testheadless (24, test-headless-ff)
🔇 Additional comments (65)
packages/binding.template/src/nativeTemplateEngine.ts (1)

2-2: LGTM!

Correctly using inline type modifier for TemplateOptions since it's only used as a type annotation. This aligns with the verbatimModuleSyntax requirement and improves clarity about which imports are types versus runtime values.

packages/provider.mustache/src/mustacheParser.ts (1)

72-92: LGTM! Proper reduction of public API surface.

Removing exports from innerParse and parseOuterMatch is appropriate since both are internal implementation details only consumed within this module. The public API is correctly limited to parseInterpolation.

packages/observable/package.json (1)

33-35: LGTM!

The removal of tslib as part of the Knip cleanup is appropriate. Modern TypeScript configurations typically inline helpers or target ES versions that don't require the runtime library.

packages/provider.attr/package.json (1)

11-13: LGTM!

Consistent tslib removal aligned with the repository-wide dependency cleanup.

tsconfig.dts.json (1)

3-8: LGTM!

Setting verbatimModuleSyntax to null in the declaration-only config is necessary when using outFile for bundled .d.ts generation. The verbatimModuleSyntax option conflicts with module concatenation required by outFile.

packages/utils.jsx/package.json (1)

27-34: LGTM!

The tslib removal is consistent with the repository-wide cleanup. All required @tko/* dependencies are retained.

packages/utils.parser/package.json (1)

11-14: LGTM!

The removal of tslib as part of the Knip cleanup is appropriate. The remaining dependencies (@tko/observable and @tko/utils) are retained correctly.

Ensure that the TypeScript compilation settings (e.g., verbatimModuleSyntax) are configured such that tslib helpers are no longer emitted or required at runtime.

packages/utils/package.json (1)

30-30: LGTM!

Having an empty dependencies object is valid and appropriate for a foundational utility package. This keeps @tko/utils lightweight with no external runtime dependencies, which is ideal for a package that many others depend on.

packages/lifecycle/package.json (1)

4-7: LGTM!

The tslib removal is consistent with the monorepo-wide cleanup. The remaining dependencies (@tko/computed and @tko/utils) are appropriate for the lifecycle package functionality.

builds/knockout/package.json (1)

48-64: LGTM!

The tslib removal is consistent with the monorepo-wide Knip cleanup. The build package correctly retains all necessary @tko/* dependencies for the knockout distribution.

packages/binding.component/package.json (1)

7-15: Verify compatibility of @tko/provider to @tko/provider.native switch.

The dependency updates appear intentional per the PR scope, but require confirmation that:

  • No remaining imports of @tko/provider exist in this package that would break with the switch to @tko/provider.native
  • @tko/provider.native is a compatible replacement and exports the same public API
  • Consumers of this package won't experience breaking changes from this switch

Check the binding.component source code for any @tko/provider imports and verify API compatibility.

packages/provider.component/package.json (1)

11-18: LGTM!

The dependency list is clean after the tslib removal. Version constraints are consistent with the monorepo convention.

packages/binding.template/package.json (1)

25-30: LGTM!

Clean removal of tslib dependency with consistent versioning across @tko/* packages.

builds/reference/package.json (1)

17-35: LGTM!

The tslib removal is consistent with the broader cleanup effort across the monorepo.

packages/filter.punches/package.json (1)

25-27: LGTM!

Clean tslib removal leaving only the required @tko/observable dependency.

packages/computed/package.json (1)

10-13: LGTM - tslib removal aligns with dependency cleanup.

Removing the tslib runtime dependency is appropriate when using modern TypeScript compilation settings that inline helpers.

packages/binding.core/package.json (1)

25-30: LGTM - Consistent dependency cleanup.

The tslib removal is consistent with the broader dependency hygiene effort across the monorepo. All @tko/* dependencies remain aligned at ^4.0.0-beta1.3.

packages/utils.component/package.json (1)

11-15: LGTM - tslib removal is appropriate.

The dependency cleanup aligns with the PR objectives.

packages/provider.mustache/package.json (1)

25-29: Removed dependencies are not imported in the source files.

Verification confirms that @tko/binding.template, @tko/utils, and tslib are not imported anywhere in packages/provider.mustache/src/. The removal is safe.

tsconfig.json (2)

10-10: LGTM! Aligns with tslib removal across packages.

Setting importHelpers: false causes TypeScript to inline helper functions rather than importing them from tslib. This is consistent with the removal of tslib as a dependency across the monorepo packages.


26-26: Setting enforces explicit type-only imports as described.

The verbatimModuleSyntax option in tsconfig.json is a valid TypeScript 4.9+ setting that enforces explicit import type syntax for type-only imports. This prevents accidental runtime imports of types and supports proper tree-shaking.

Note: TypeScript is not available in the sandbox environment, so the suggested compilation verification (npx tsc --noEmit) could not be completed. This verification should be performed locally or in the project's CI pipeline to confirm all existing imports are compatible with the stricter enforcement.

packages/binding.if/package.json (1)

25-29: LGTM! Dependency cleanup aligns with Knip findings.

The removal of @tko/computed and tslib (as indicated by the AI summary) is consistent with the PR's objective to fix unused dependency warnings reported by Knip.

packages/binding.template/spec/nativeTemplateEngineBehaviors.ts (1)

4-4: LGTM! Correct use of type-only import.

ObservableArray is used only as a type annotation (at line 147), while observable and observableArray are used as runtime values. The inline type modifier correctly distinguishes this, satisfying the verbatimModuleSyntax requirement.

packages/provider.component/spec/customElementBehaviors.ts (1)

3-3: LGTM! Correct use of type-only import.

Observable is used only as a type (in the interface extension at line 276: interface myObs extends Observable), while observable, isWritableObservable, and isObservable are used as runtime values. The inline type modifier is appropriate here.

package.json (1)

22-22: LGTM! New dev dependencies for tooling support.

Adding @types/node for Node.js type definitions and knip for unused code detection aligns with the PR objectives. Both package versions are valid and have no security vulnerabilities.

packages/binding.foreach/package.json (1)

25-29: LGTM!

The dependency cleanup removing @tko/provider and tslib aligns with the Knip findings. The remaining dependencies (@tko/bind, @tko/observable, @tko/utils) represent the actual runtime requirements for the foreach binding.

packages/utils.functionrewrite/package.json (1)

11-11: LGTM!

Removing the tslib runtime dependency is appropriate if the package doesn't use TypeScript helpers that require it, or if helpers are now inlined. This aligns with the broader PR effort to clean up unused dependencies identified by Knip.

packages/computed/src/proxy.ts (2)

91-102: LGTM — internal helper utilities are cleanly structured.

The @internal JSDoc annotations appropriately signal that these are not part of the public API. The implementations are straightforward:

  • getObservable retrieves the mirror observable
  • peek delegates to the observable's peek method
  • isProxied checks for the proxy symbol

Since these are internal utilities, they reasonably assume valid inputs from internal callers.


104-104: Good pattern for exposing internal utilities.

Attaching the helpers to the proxy function object provides a convenient namespace-style API (proxy.getObservable(...)) while the named exports enable tree-shaking for consumers who only need specific utilities.

packages/observable/src/extenders.ts (4)

39-44: Visibility reduction: notify is no longer directly exportable.

The function is now internal but remains accessible via the extenders object. This is appropriate API hygiene — consumers should use the extender system rather than importing these functions directly.


46-53: Visibility reduction: deferred is no longer directly exportable.

Same pattern as notify — the function remains accessible through the extenders object while reducing the public API surface.


55-73: Visibility reduction: rateLimit is no longer directly exportable.

Consistent with the other extender functions. The implementation logic remains unchanged.


83-83: No breaking changes from visibility modifications.

Direct imports of notify, deferred, and rateLimit are not used in the codebase. All internal consumers import these functions via the extenders object, which continues to expose them unchanged. The visibility change is backward compatible.

packages/provider.databind/package.json (1)

11-14: Dependency changes are justified.

The addition of @tko/bind is confirmed—it's imported in packages/provider.databind/src/DataBindProvider.ts (import type { BindingContext } from '@tko/bind'). The removal of tslib is also correct, as it was not being used in the source code. These changes align with the cleanup objectives.

packages/utils/src/interfaces.ts (4)

1-17: LGTM on imports structure.

Clean separation of runtime imports for typeof usage and type-only import for Options. This correctly avoids runtime circular dependencies while establishing type contracts.


19-22: Interface aligns with BindingHandlerObject implementation.

The IBindingHandlerObject interface correctly captures the set and get method signatures. Verified against packages/provider/src/BindingHandlerObject.ts which implements this interface.


24-40: IArrayAndObjectUtils correctly aggregates utility function types.

Using typeof to reference the actual function implementations ensures the interface stays synchronized with the implementations in ./array, ./object, and ./symbol.


49-107: IProvider interface is well-documented with proper implementation coverage.

The interface provides comprehensive JSDoc documentation for each member. Verification confirms the Provider class properly implements all required members:

  • FOR_NODE_TYPES: Implemented as a getter in the abstract class, overridden in all subclasses
  • setGlobals(), nodeHasBindings(), getBindingAccessors(), preprocessNode(), makeBindingAccessors(): All implemented
  • bindingHandlers, instance, preemptive: All present with correct behavior

Note on the type narrowing suggestion: While FOR_NODE_TYPES uses number[], narrowing it to (1 | 3 | 8)[] is not practical. The MultiProvider class stores node types dynamically in a variable, and various providers legitimately use different combinations of values. The current type width appropriately supports the abstract provider pattern and allows flexibility for extensions.

The instance property correctly implements legacy compatibility through a getter/setter that wraps the assigned provider in a LegacyProvider when needed.

packages/provider/src/BindingHandlerObject.ts (1)

2-4: Clean implementation of the new interface.

The type-only import and implements IBindingHandlerObject clause correctly enforce the type contract without affecting runtime behavior.

packages/utils/src/options.ts (3)

1-1: Type-only imports correctly break cyclic dependencies.

Importing interfaces instead of concrete types from ./interfaces eliminates the cycle that would occur if importing from Provider or other packages.


19-19: Type change to interface preserves API while enabling decoupling.

Changing from a concrete type to IKnockoutUtilsInstance maintains compatibility while allowing the options module to be independent of the full knockout instance implementation.


40-41: This review comment is based on an incorrect assumption about TypeScript configuration.

strictPropertyInitialization is explicitly set to false in tsconfig.json, so TypeScript will not flag this uninitialized property. The property is intentionally left uninitialized and is expected to be set after construction.

Likely an incorrect or invalid review comment.

packages/utils/src/index.ts (1)

32-38: Type re-exports correctly establish the public API surface.

Using export type { ... } aligns with the PR's enablement of verbatimModuleSyntax and ensures these interfaces are available to consuming packages like @tko/provider and @tko/builder.

packages/utils.jsx/src/index.ts (1)

3-3: Breaking change: JsxObserver is now a named export.

This changes the import pattern from:

import JsxObserver from '@tko/utils.jsx'

to:

import { JsxObserver } from '@tko/utils.jsx'

All internal consumers in the monorepo have been updated to use the named import pattern.

packages/provider/src/Provider.ts (3)

9-10: LGTM!

Good refactor to import BindingAccessors and IProvider from the centralized @tko/utils package, aligning with the PR's goal to consolidate type definitions.


19-19: LGTM!

Implementing IProvider interface ensures the Provider class adheres to the expected contract and improves type safety across the codebase.


93-93: LGTM!

The explicit type annotation on makeBindingAccessors aligns with the IProvider interface contract.

packages/builder/src/Builder.ts (1)

40-42: LGTM!

Good use of type-only imports for IKnockoutUtilsInstance and IArrayAndObjectUtils, aligning with verbatimModuleSyntax requirements.

packages/bind/src/applyBindings.ts (3)

19-19: LGTM!

Correct use of type-only import syntax for Computed, consistent with TypeScript's verbatimModuleSyntax setting.


29-29: LGTM!

Switching from concrete Provider class to IProvider interface follows the dependency inversion principle. This decouples the binding logic from the specific provider implementation.


60-67: LGTM!

The return type and parameter type updates to IProvider are consistent with the interface-based refactor.

packages/bind/src/bindingContext.ts (3)

1-1: LGTM!

Clean import combining value imports with type-only import for IKnockoutUtilsInstance.


11-11: LGTM!

Correct use of type-only import for BindingContextExtendCallback.


24-26: The type narrowing of ko to IKnockoutUtilsInstance doesn't break any existing usages in the codebase.

The ko property type is indeed narrowed from the full KnockoutInstance to IKnockoutUtilsInstance, which only exposes utils and options. At runtime, the full instance is still assigned (line 82), but TypeScript will only allow access to the narrower interface. However, a comprehensive search of the codebase reveals no binding code or templates that access ko properties (e.g., ko.observable, ko.computed) via $context.ko or context.ko, so this type narrowing does not introduce any type errors in practice.

packages/provider.bindingstring/package.json (1)

11-14: LGTM on removing tslib.

With importHelpers: false in the root tsconfig.json, TypeScript does not emit helper imports from tslib, making it unnecessary as a runtime dependency. Removing it from packages/provider.bindingstring/package.json is correct.

Makefile (2)

54-56: LGTM!

The knip target is well-configured with --no-config-hints to suppress noisy suggestions in CI output.


69-71: LGTM!

The lerna-check target provides a lightweight validation that lerna can resolve all package dependencies, which effectively catches circular dependency issues and broken package references.

knip.json (1)

1-20: LGTM overall!

The knip configuration is well-structured for a monorepo setup. The settings appropriately:

  • Ignore type/interface exports that are used within the same file
  • Exclude common dev/test dependencies from unused dependency warnings
  • Configure workspace-specific entry points and project patterns
packages/bind/package.json (1)

6-11: The @tko/provider dependency removal from packages/bind/package.json is safe. No source files in this package import from @tko/provider.

packages/provider.multi/package.json (1)

11-14: The @tko/bind dependency is actually imported in packages/provider.multi/src/MultiProvider.ts (line 5: import type { BindingContext } from '@tko/bind'), so this is a valid and necessary dependency addition.

.github/workflows/dependency-cycle.yml (1)

17-21: No action required. The workflow configuration is correct.

actions/setup-node@v6 is the current latest version (v6.1.0 as of December 2025), and Node.js 24 is available on GitHub-hosted runners and will become the default in March 2026. Both versions are appropriate for this workflow.

Likely an incorrect or invalid review comment.

packages/provider.virtual/package.json (1)

12-16: Dependency updates look correct.

The addition of @tko/bind and removal of tslib aligns with the Knip cleanup objectives. Version specifications are consistent with other packages in the monorepo.

packages/provider.native/package.json (1)

11-15: Dependency restructuring looks appropriate.

The package now explicitly declares its direct dependencies (@tko/bind, @tko/observable, @tko/provider) rather than relying on transitive dependencies through tslib. Version specifications are consistent at ^4.0.0-beta1.3.

packages/provider/package.json (1)

12-16: Dependency updates align with the refactoring goals.

The base provider package now explicitly declares @tko/bind, @tko/observable, and @tko/utils as direct dependencies, removing the tslib transitive dependency. Version specifications are consistent across the monorepo.

packages/builder/package.json (1)

11-20: Dependency updates look correct for the builder package.

The addition of @tko/provider and @tko/binding.template while removing unused dependencies aligns with the Knip cleanup objectives.

Note: Most @tko/* dependencies use ^4.0.0-beta1.3, while @tko/utils.parser uses ^4.0.0-beta1.6. This is correct—@tko/utils.parser is published at 4.0.0-beta1.6 in the monorepo, making this version pinning accurate.

@phillipc phillipc changed the title Add knip Add knip linter and fix all unused dependencies and exports Jan 13, 2026
Copy link
Member

@brianmhunt brianmhunt left a comment

Choose a reason for hiding this comment

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

The Knip part looks great, but I don't understand the point or benefit of the I* interfaces, and it will almost certainly break existing things. Is there a reason to add it? If there is a benefit let's avoid the I* convention and just go with the type naming styles we already have.

}
postProcess(/* node */) {}

//TODO unused hook? postProcess(/* node */) {}
Copy link
Member

Choose a reason for hiding this comment

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

I think leave postProcess there, it is meant to be overloaded, and the no-op serves for typing Provider generally. Unless it's never used, in which case we can probably take it out (but I feel like it was used, so we should be sure)

Copy link
Member Author

Choose a reason for hiding this comment

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

Okay, I will remove it. This hook is never called.

Copy link
Member Author

Choose a reason for hiding this comment

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

No call, no override, only definited as noop:
grafik

import type { Observable, ObservableFunctions } from './observable'

export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
//TODO Currently unused --> export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
Copy link
Member

Choose a reason for hiding this comment

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

this is definitely used as an ambient declaration like KnockoutObservable, but perhaps not this version here.

import type { Observable, ObservableFunctions } from './observable'

export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
//TODO Currently unused --> export type MaybeObservableArray<T = any> = T[] | ObservableArray<T>
Copy link
Member

Choose a reason for hiding this comment

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

this is definitely used as an ambient declaration like KnockoutObservable, but perhaps not this version here.

Which is to say, I don't know if we can delete it, but lets not since it seems way out of scope for Knip

Copy link
Member Author

@phillipc phillipc Jan 13, 2026

Choose a reason for hiding this comment

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

Good to discuss. That's why I only commented on it. I can change it to @lintignore

Currently, Knip detected that this type def is unused

Copy link
Member Author

Choose a reason for hiding this comment

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

We have ported this type from the official knockout.d.ts to tko, but never used it.

return proxy
}

/** @internal */
Copy link
Member

Choose a reason for hiding this comment

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

I don't know what this is or why we'd add it, so lets skip it unless there's a clear need.

If something is internal it should be private or #private. Also seems out of scope for Knip

Copy link
Member Author

Choose a reason for hiding this comment

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

The reason for this is the export. In my opinion, the export only happens for testability in the same package. @internal is typescript sugar for such cases. https://www.typescriptlang.org/tsconfig/#stripInternal

Knip recognize this, too. And understand in this way that this is not an unnecessary export.

* Providers are responsible for detecting bindings on DOM nodes and
* producing binding-accessor objects that the binding system consumes.
*/
export interface IProvider {
Copy link
Member

Choose a reason for hiding this comment

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

the authority and single source of truth is Provider. We dont want a redundant arbitrary interface that duplicates the class definition that we already have. No IAnything.

Copy link
Member Author

Choose a reason for hiding this comment

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

I'm with you. I proceeded this way to break the cyclical packages dependency. Utils <-> Provider and Bind. Any other ideas?

Copy link
Member Author

Choose a reason for hiding this comment

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

Probably an abstract base class? ProviderBase? (I wouldn't want to change the package structure.)

get(nameOrDotted: string): any
}

export interface IArrayAndObjectUtils {
Copy link
Member

Choose a reason for hiding this comment

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

this is a hard-coded subset of:

import * as array from 'array'
typeof array

but its not clear what benefit it adds, or what it has to do with Knip.

Copy link
Member Author

Choose a reason for hiding this comment

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

It also breaks the cyclic reference between Builder.ts and Options.ts...

Copy link
Member Author

@phillipc phillipc Jan 14, 2026

Choose a reason for hiding this comment

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

We need a type structure like this part for defining the return type of builder.create in builder.ts. This is a subset of some utils classes.


// Reference to the own knockout instance
knockoutInstance: KnockoutInstance | null = null
knockoutInstance: IKnockoutUtilsInstance | null = null
Copy link
Member

Choose a reason for hiding this comment

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

I feel like this is both misleading and incorrect and will break a lot. Let's take out all the I* things, it seems unrelated/unnecessary.

Copy link
Member Author

Choose a reason for hiding this comment

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

Done, changed to KnockoutUtils

import type { IBindingHandlerObject } from '@tko/utils'

export default class BindingHandlerObject {
export default class BindingHandlerObject implements IBindingHandlerObject {
Copy link
Member

Choose a reason for hiding this comment

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

we're never going to have anything else that implements this, and now we have two sources of truth, making this fragile, redundant, and confusing.

Copy link
Member Author

Choose a reason for hiding this comment

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

I've removed IBindingHandlerObject for now and replaced it with "any" in IProvider. This way, we're only dealing with the minimal problem with IProvider. Any idea?

Here again is the initial situation:

grafik

If this is corrected – then type cycle. In this case, everything still works because there are type imports. However, that's not correct.

}

export type KnockoutInstance = {
export type KnockoutInstance = IKnockoutUtilsInstance & {
Copy link
Member

Choose a reason for hiding this comment

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

I'm unclear why we would do this. Also we don't want a novel naming convention I* that's inconsistent with everything else in the project. We can just call it KnockoutUtils. It can also be a class.

Copy link
Member Author

@phillipc phillipc Jan 13, 2026

Choose a reason for hiding this comment

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

I* is removed. We need another solution for the IProvider.

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

🤖 Fix all issues with AI agents
In @packages/provider.mustache/src/AttributeMustacheProvider.ts:
- Around line 81-84: In getPossibleDirectBinding (AttributeMustacheProvider),
you’re computing bindingName from ATTRIBUTES_BINDING_MAP but then incorrectly
passing attrName to this.bindingHandlers.get; change the lookup to use
bindingName instead (i.e., return bindingName &&
this.bindingHandlers.get(bindingName)) so mapped names like 'class' → 'css'
resolve the correct handler; ensure no other logic relies on the original
attrName being passed.
🧹 Nitpick comments (2)
packages/builder/src/Builder.ts (1)

162-170: Clean up the comment artifact on line 170.

The comment //Type merging from IKnockoutInstance options: typeof options appears to be an artifact from a previous implementation attempt. Consider removing it or reformatting it as a proper explanatory comment.

Since KnockoutUtils is defined as { utils: ArrayAndObjectUtils; options: Options }, the options property is correctly provided via the intersection type - the comment is redundant.

Suggested fix
 export type KnockoutInstance = KnockoutUtils & {
   // --- Utilities ---
   cleanNode: typeof cleanNode
   dependencyDetection: typeof dependencyDetection
   computedContext: typeof dependencyDetection
   filters: typeof options.filters
   ignoreDependencies: typeof dependencyDetection.ignore
   memoization: typeof memoization
-  //Type merging from IKnockoutInstance options: typeof options
+  // Note: `options` property is provided via KnockoutUtils intersection
   removeNode: typeof removeNode
packages/utils/src/interfaces.ts (1)

39-39: Consider stricter typing for BindingAccessors.

Using Function is a loose type. Consider a more specific signature if the accessor pattern is consistent:

export type BindingAccessors = { [name: string]: () => unknown }

However, if accessors have varying signatures, the current definition is acceptable.

📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9c6f5b5 and a9b86e9.

📒 Files selected for processing (10)
  • .github/workflows/dependency-cycle.yml
  • knip.json
  • packages/bind/src/bindingContext.ts
  • packages/builder/src/Builder.ts
  • packages/provider.mustache/src/AttributeMustacheProvider.ts
  • packages/provider/src/BindingHandlerObject.ts
  • packages/utils.jsx/src/JsxObserver.ts
  • packages/utils/src/index.ts
  • packages/utils/src/interfaces.ts
  • packages/utils/src/options.ts
💤 Files with no reviewable changes (1)
  • packages/utils.jsx/src/JsxObserver.ts
🚧 Files skipped from review as they are similar to previous changes (4)
  • .github/workflows/dependency-cycle.yml
  • packages/bind/src/bindingContext.ts
  • knip.json
  • packages/utils/src/index.ts
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2026-01-03T18:06:10.239Z
Learnt from: phillipc
Repo: knockout/tko PR: 233
File: packages/binding.core/src/value.ts:64-72
Timestamp: 2026-01-03T18:06:10.239Z
Learning: In packages/binding.core/src/value.ts, the ieAutoCompleteHackNeeded getter and related propertyChange/focus/blur event handlers for IE10/11 autocomplete workarounds should be kept, as IE10/11 support is intentionally retained even though IE9 support has been removed.

Applied to files:

  • packages/provider.mustache/src/AttributeMustacheProvider.ts
🧬 Code graph analysis (3)
packages/provider/src/BindingHandlerObject.ts (2)
packages/utils/src/index.ts (1)
  • IBindingHandlerObject (33-33)
packages/utils/src/interfaces.ts (1)
  • IBindingHandlerObject (41-44)
packages/builder/src/Builder.ts (2)
packages/utils/src/index.ts (3)
  • ArrayAndObjectUtils (37-37)
  • KnockoutUtils (36-36)
  • memoization (39-39)
packages/utils/src/interfaces.ts (2)
  • ArrayAndObjectUtils (19-35)
  • KnockoutUtils (37-37)
packages/utils/src/options.ts (2)
packages/utils/src/index.ts (2)
  • KnockoutUtils (36-36)
  • IProvider (34-34)
packages/utils/src/interfaces.ts (2)
  • KnockoutUtils (37-37)
  • IProvider (52-104)
⏰ 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). (1)
  • GitHub Check: testheadless (24, test-headless-ff)
🔇 Additional comments (11)
packages/builder/src/Builder.ts (2)

40-43: LGTM! Clean type imports.

The addition of virtualElements as a value import and the type-only imports for KnockoutUtils and ArrayAndObjectUtils properly support the type composition changes below.


109-127: LGTM! Cleaner type composition using intersection.

Using ArrayAndObjectUtils & intersection instead of duplicating all array utility members directly in the Utils type is a good DRY improvement. The type now derives array/object utilities from the shared definition in @tko/utils.

packages/provider/src/BindingHandlerObject.ts (2)

1-4: LGTM! Good interface extraction.

The class now properly implements IBindingHandlerObject, centralizing the contract in @tko/utils. This helps break cyclic dependencies and establishes a clear public API surface.


26-29: LGTM! Explicit parameter typing.

Adding the explicit string type to the nameOrDotted parameter aligns with the IBindingHandlerObject interface contract.

packages/utils/src/options.ts (3)

1-1: LGTM! Type-only imports from local interfaces.

Using type-only imports from ./interfaces properly supports the verbatimModuleSyntax TypeScript option enabled in this PR.


19-19: LGTM! Narrower type for knockoutInstance.

Changing from the full KnockoutInstance type to KnockoutUtils helps break cyclic type dependencies while still providing the essential utilities and options needed at this level.


41-41: No issue. strictPropertyInitialization is explicitly set to false in tsconfig.json, which allows the uninitialized property declaration. This is a valid and intentional configuration choice.

packages/utils/src/interfaces.ts (4)

1-17: LGTM! Well-organized imports.

The imports correctly bring in the necessary functions and types from local modules to construct the centralized type definitions.


19-37: LGTM! Clean utility type definitions.

ArrayAndObjectUtils properly captures the signatures of all array/object utility functions using typeof, ensuring type safety. KnockoutUtils provides a minimal interface for breaking cyclic dependencies.


46-104: LGTM! Well-documented IProvider interface.

The interface is comprehensive with good JSDoc documentation explaining each member's purpose. This provides a clear contract for binding providers and helps decouple the provider implementation from consumers.


41-44: Update the value parameter type in the set method to reflect actual usage.

The type annotation value?: string | object is too restrictive. Usage throughout the codebase passes functions and objects with function properties (e.g., binding handler definitions with init and update methods). The implementation at line 7 stores values via this[nameOrObject] = value, which allows any type. The parameter type should be any or a more specific binding handler type to match the actual implementation behavior and real-world usages.

Comment on lines +81 to 84
getPossibleDirectBinding(attrName: string) {
const bindingName = this.ATTRIBUTES_BINDING_MAP[attrName]
return bindingName && this.bindingHandlers.get(attrName)
return bindingName && this.bindingHandlers.get(attrName) //FIXME this.bindingHandlers.get(bindingName) ?
}
Copy link
Contributor

@coderabbitai coderabbitai bot Jan 13, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bug: Using attrName instead of bindingName for handler lookup.

The method retrieves bindingName from ATTRIBUTES_BINDING_MAP (e.g., 'class''css') but then passes attrName to bindingHandlers.get(). This means for attributes like class, the code looks up a class handler instead of the intended css handler.

The FIXME comment correctly identifies the issue—this should be fixed rather than deferred.

🐛 Proposed fix
  getPossibleDirectBinding(attrName: string) {
    const bindingName = this.ATTRIBUTES_BINDING_MAP[attrName]
-   return bindingName && this.bindingHandlers.get(attrName) //FIXME this.bindingHandlers.get(bindingName) ?
+   return bindingName && this.bindingHandlers.get(bindingName)
  }

Note: If this is intentional behavior, please clarify the purpose of ATTRIBUTES_BINDING_MAP since the mapped value is currently unused.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
getPossibleDirectBinding(attrName: string) {
const bindingName = this.ATTRIBUTES_BINDING_MAP[attrName]
return bindingName && this.bindingHandlers.get(attrName)
return bindingName && this.bindingHandlers.get(attrName) //FIXME this.bindingHandlers.get(bindingName) ?
}
getPossibleDirectBinding(attrName: string) {
const bindingName = this.ATTRIBUTES_BINDING_MAP[attrName]
return bindingName && this.bindingHandlers.get(bindingName)
}
🤖 Prompt for AI Agents
In @packages/provider.mustache/src/AttributeMustacheProvider.ts around lines 81
- 84, In getPossibleDirectBinding (AttributeMustacheProvider), you’re computing
bindingName from ATTRIBUTES_BINDING_MAP but then incorrectly passing attrName to
this.bindingHandlers.get; change the lookup to use bindingName instead (i.e.,
return bindingName && this.bindingHandlers.get(bindingName)) so mapped names
like 'class' → 'css' resolve the correct handler; ensure no other logic relies
on the original attrName being passed.

Copy link
Member Author

Choose a reason for hiding this comment

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

@coderabbitai Please create an issue for that. We need to fix it separately and also require a test case.

Copy link
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Create a GitHub issue for the bug in AttributeMustacheProvider.getPossibleDirectBinding

gh issue create \
  --title "Bug in AttributeMustacheProvider.getPossibleDirectBinding: using attrName instead of bindingName" \
  --body "## Description

In \`packages/provider.mustache/src/AttributeMustacheProvider.ts\`, the \`getPossibleDirectBinding\` method has a bug where it retrieves \`bindingName\` from \`ATTRIBUTES_BINDING_MAP\` but then incorrectly passes \`attrName\` to \`bindingHandlers.get()\`.

This means for attributes like \`class\`, the code looks up a \`class\` handler instead of the intended \`css\` handler (since \`DEFAULT_ATTRIBUTE_BINDING_MAP\` maps \`'class'\` → \`'css'\`).

The method currently has a FIXME comment acknowledging this issue.

## Current Behavior
\`\`\`typescript
getPossibleDirectBinding(attrName: string) {
  const bindingName = this.ATTRIBUTES_BINDING_MAP[attrName]
  return bindingName && this.bindingHandlers.get(attrName) //FIXME this.bindingHandlers.get(bindingName) ?
}
\`\`\`

The mapped \`bindingName\` value is computed but never used in the lookup.

## Expected Behavior
The method should use \`bindingName\` for the handler lookup so that attribute mappings work correctly.

## Requirements
- Fix the handler lookup to use the correct binding name
- Add test case to verify the fix works correctly
- Ensure attribute mappings like 'class' → 'css' resolve the correct handler

## Context
Reported by: @phillipc
Related PR: #234 
Review comment: https://github.com/knockout/tko/pull/234#discussion_r2688341614" \
  --assignee phillipc

Length of output: 42


@phillipc, I've created issue #235 to track this bug. The issue includes:

  • A detailed description of the bug in getPossibleDirectBinding
  • The current vs. expected behavior
  • Requirements for the fix (including the need for a test case)
  • Backlinks to this PR and review comment

The issue has been assigned to you for follow-up.


🧬 Code Graph Analysis Results

packages/provider.src/index.ts

ProviderParamsInput

packages/provider/src/Provider.ts

export default class Provider implements IProvider {
  constructor(params?: ProviderParamsInput | null) {
    if (this.constructor === Provider) {
      throw new Error('Provider is an abstract base class.')
    }
    if (!('FOR_NODE_TYPES' in this)) {
      // FOR_NODE_TYPES must return a list of integers corresponding to the
      // node.nodeType's that the provider handles.
      throw new Error('Providers must have FOR_NODE_TYPES property')
    }
    this.bindingHandlers = params?.bindingHandlers || new BindingHandlerObject()
    this.globals = params?.globals || {}
  }

  get FOR_NODE_TYPES(): number[] {
    throw new Error('Providers must override FOR_NODE_TYPES property')
  }

  setGlobals(globals) {
    this.globals = globals
  }
  get preemptive() {
    return false
  }
  nodeHasBindings(node: Node, context?: BindingContext): boolean {
    return false
  }
  getBindingAccessors(node: Node, context?: BindingContext): BindingAccessors {
    return Object.create(null)
  }

  /**
   * Preprocess a given node.
   * @param {Node} node
   * @returns {[Node]|null}
   */
  preprocessNode(node: Node): Node[] | null {
    return [node]
  }

  //TODO unused hook? postProcess(/* node */) {}

  bindingHandlers: BindingHandlerObject
  globals: any | undefined
  _overloadInstance: any | undefined

  /** For legacy binding provider assignments to
   *  ko.bindingProvider.instance = ... */
  get instance() {
    return this._overloadInstance || this
  }
  set instance(provider) {
    if (!provider || provider === this) {
      this._overloadInstance = undefined
    } else {
      this._overloadInstance = new LegacyProvider(provider, this)
    }
  }

  // Given a function that returns bindings, create and return a new object that contains
  // binding value-accessors functions. Each accessor function calls the original function
  // so that it always gets the latest value and all dependencies are captured. This is used
  // by ko.applyBindingsToNode and getBindingsAndMakeAccessors.
  makeAccessorsFromFunction(callback) {
    return objectMap(dependencyDetection.ignore(callback), (value, key) => () => callback()[key])
  }

  // Returns the valueAccessor function for a binding value
  makeValueAccessor(value): Function {
    return () => value
  }

  // Given a bindings function or object, create and return a new object that contains
  // binding value-accessors functions. This is used by ko.applyBindingsToNode.
  makeBindingAccessors(bindings: any, context: any, node: Node): BindingAccessors {
    if (typeof bindings === 'function') {
      return this.makeAccessorsFromFunction(bindings.bind(null, context, node))
    } else {
      return objectMap(bindings, this.makeValueAccessor)
    }
  }
}

packages/provider.src/index.ts

ProviderParamsInput

packages/provider.mustache/src/mustacheParser.ts

export function* parseInterpolation(text: string | null) {
  for (const textOrExpr of parseOuterMatch(text?.match(OUTER_EXPRESSION))) {
    if (textOrExpr.text) {
      yield textOrExpr
    }
  }
}

packages/binding.core/src/attr.ts

export const attr = {
  update: function (element, valueAccessor, _allBindings: AllBindings) {
    const value = unwrap(valueAccessor()) || {}
    objectForEach(value, function (attrName, attrValue) {
      attrValue = unwrap(attrValue)

      // Find the namespace of this attribute, if any.
      const prefixLen = attrName.indexOf(':')
      const namespace = prefixLen > 0 && element.lookupNamespaceURI(attrName.substr(0, prefixLen))

      // To cover cases like "attr: { checked:someProp }", we want to remove the attribute entirely
      // when someProp is a "no value"-like value (strictly null, false, or undefined)
      // (because the absence of the "checked" attr is how to mark an element as not checked, etc.)
      const toRemove = attrValue === false || attrValue === null || attrValue === undefined

      if (toRemove) {
        if (namespace) {
          element.removeAttributeNS(namespace, attrName)
        } else {
          element.removeAttribute(attrName)
        }
      } else {
        attrValue = attrValue.toString()
        if (namespace) {
          element.setAttributeNS(namespace, attrName, attrValue)
        } else {
          element.setAttribute(attrName, attrValue)
        }
      }

      // Deliberately being case-sensitive here because XHTML would regard "Name" as a different thing
      // entirely, and there's no strong reason to allow for such casing in HTML.
      if (attrName === 'name') {
        element.name = toRemove ? '' : attrValue
      }
    })
  }
}

packages/provider/src/Provider.ts

export default class Provider implements IProvider {
  ...
}

🧠 Learnings used
Learnt from: phillipc
Repo: knockout/tko PR: 233
File: packages/binding.core/src/value.ts:64-72
Timestamp: 2026-01-03T18:06:10.239Z
Learning: In packages/binding.core/src/value.ts, the ieAutoCompleteHackNeeded getter and related propertyChange/focus/blur event handlers for IE10/11 autocomplete workarounds should be kept, as IE10/11 support is intentionally retained even though IE9 support has been removed.

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

🤖 Fix all issues with AI agents
In @packages/utils/src/index.ts:
- Around line 32-37: Remove the trailing whitespace after the opening brace in
the export type statement so the line "export type { IProvider,
BindingAccessors, KnockoutUtils, ArrayAndObjectUtils } from './interfaces'" has
no extra spaces between "{" and "IProvider"; update the export line (export type
{ ... } from './interfaces') to eliminate the stray spaces to satisfy Prettier
formatting.
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9b86e9 and 6da7b30.

📒 Files selected for processing (3)
  • packages/provider/src/BindingHandlerObject.ts
  • packages/utils/src/index.ts
  • packages/utils/src/interfaces.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/provider/src/BindingHandlerObject.ts
  • packages/utils/src/interfaces.ts
🧰 Additional context used
🪛 GitHub Actions: Check code formatting with Prettier
packages/utils/src/index.ts

[warning] 1-1: Code style issues found by Prettier. Run 'npx prettier --write' to fix formatting.

⏰ 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). (1)
  • GitHub Check: testheadless (24, test-headless-ff)

@phillipc
Copy link
Member Author

phillipc commented Jan 16, 2026

@brianmhunt: Summary, Knip detected some unused exports and missing dependencies at package.json level between the mono-repo-projects. While I add the missing project references, some cyclical references were uncovered.

To resolve that I have add some interfaces in utils. Now, I have refactor this PR to one last "IProvider" and "KnockoutUtils". I* notations are not typical typescript naming style, we can change it perhaps to an abstract base class like ProviderBase, but this is only an easy stylistic aspect.

You are welcome with a better idea to solve this problem. To put this in perspective, all of this is about the path to well-defined TKO types.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants