Commit 1967fcd
authored
feat(resolver): resolve prototype-based method calls, spread/iteration callbacks, func-prop this-dispatch, and object-rest param dispatch (JS) (#1331)
* feat(resolver): resolve prototype-based method calls (Foo.prototype.bar = fn)
Teach the JS extractor and call resolver about pre-ES6 prototype OOP patterns:
Extractor (javascript.ts):
- `Foo.prototype.bar = function(){}` → emits `Foo.bar` definition (kind: method)
- `Foo.prototype.bar = f` → seeds typeMap['Foo.bar'] = { type: 'f', confidence: 0.9 }
- `Foo.prototype = { bar: fn, baz: f }` → same rules per object-literal property
Built-in globals (Array, Object, …) are excluded via BUILTIN_GLOBALS guard.
Call resolver (call-resolver.ts):
- After a symbol-DB miss on a typed receiver, checks typeMap['Type.method'] for
prototype aliases (covers `A.prototype.t = f` → call resolves to f)
- Extracts the class name from inline `(new Foo)` receivers so `(new A).t()`
resolves without a named variable binding
Both paths (query + walk) are covered. Adds 7 unit tests.
Closes #1317
* test(cha): add 3-level hierarchy fixture for transitive CHA closure (#1313)
Extends the TypeScript resolution benchmark hierarchy fixture with
Ellipse extends Circle (Shape → Circle → Ellipse) and makeEllipse to
provide RTA evidence. Adds the transitive Shape.describe → Ellipse.area
expected edge, validating the BFS-based runChaPostPass expansion.
Closes #1313
* fix: remove duplicate prototype extractor functions and fix format
* fix: address review feedback — shorthand prototype props, inline-new doc, and tests
* feat(resolver): track array spread and Array.from/concat/flat callbacks (JS)
Phase 8.3e — array-element points-to analysis for JS/TS.
Closes #1321
## What's resolved
`f(...arr)`, `for (x of arr)`, `Array.from(arr, cb)`, and
`new Set(arr)` patterns now produce call edges where function
references flow through array operations:
const arr = [a, b];
f(...arr); // f→a, f→b via spread
for (x of arr) x() // outer→a, outer→b via iteration
Recall on Jelly micro-test fixtures: spread 0→100%, more1 0→100%.
## Implementation
- **types.ts** — 4 new interfaces: `ArrayElemBinding`,
`SpreadArgBinding`, `ForOfBinding`, `ArrayCallbackBinding`
- **extractors/javascript.ts** — `extractArrayElemBindingsWalk` +
`extractSpreadForOfWalk` hooked into both query and walk paths
- **points-to.ts** — array-element seeding, wildcard constraints,
per-index spread constraints, for-of and callback constraints
- **build-edges.ts** — passes new bindings to pts map builder;
`buildParamFlowPtsPostPass` extended to handle all pts binding types
- **wasm-worker-{protocol,entry,pool}.ts** — serializes/deserializes
new bindings across the WASM Worker thread boundary
- **tests/** — pts unit tests + jelly-micro fixtures for spread/more1
* fix: add native orchestrator post-pass for prototype method resolution
The Rust engine does not recognise Foo.prototype.bar = function(){} as a
method definition, so prototype-based method nodes were absent from the DB
when the native orchestrator ran. This causes the integration tests to fail
on all platforms where the native addon is available.
Fix two issues:
1. Remove duplicate extractPrototypeMethodsWalk call in extractSymbolsQuery
that was inflating the definitions array (identified by Greptile)
2. Add runPostNativePrototypeMethods post-pass to native-orchestrator.ts:
- Re-parses JS/TS files via WASM after native build
- Inserts any method nodes missing from the DB (prototype patterns)
- Resolves and inserts call edges to those new nodes using the WASM
typeMap and the call-resolver
* style: format test fixtures and pts test call sites
* fix: scan all files for prototype call edges, not just definition files (#1331)
The newDefFiles guard restricted call scanning to only files that define
new prototype methods, silently dropping call edges from files that only
call those methods. A foo.speak() call in app.js to Foo.speak defined in
lib.js would never produce an edge. Remove the guard — the newNodeIds
check inside the loop already prevents duplicate edges. Also hoist
db.prepare() outside the loop to avoid re-preparing the same statement
on every iteration.
* perf: pre-filter prototype files and remove dead seenByPair DB load (#1331)
* feat(resolver): resolve this-dispatch on function-as-object property methods (JS)
Extract `fn.method = function() {}` assignments as `method` definitions in both
the query-based and walk-based JS extraction paths, enabling `this.other()` calls
inside such methods to resolve via the existing callerName-based this-dispatch
logic in `resolveByMethodOrGlobal`.
Extend the native-engine prototype backfill post-pass to also trigger on files
containing `fn.prop = function` patterns so the same resolution applies when
the Rust orchestrator runs.
Closes #1334
* feat(resolver): resolve property calls on object destructuring rest parameters (JS)
Adds Phase 8.3f: when a function parameter uses object destructuring with a
rest element (`function f3({ e1: eee1, ...eerest })`), and the rest object's
property is called (`eerest.e4()`), resolve the callee via a three-hop chain:
ObjectRestParamBinding (eerest ← f3 param 0)
+ ParamBinding (f3(obj) → obj at index 0)
+ ObjectPropBinding (obj = { e4 } → obj.e4 = e4)
→ pts["eerest.e4"] = {"e4"} → calls edge f3 → e4
Changes:
- types.ts: add ObjectRestParamBinding and ObjectPropBinding interfaces
- javascript.ts: extractObjectRestParamBindingsWalk (finds rest params in
object-destructured function params) and extractObjectPropBindingsWalk
(finds shorthand/identifier properties in object literals); wired into
both extractSymbolsQuery and extractSymbolsWalk paths
- wasm-worker-{protocol,entry,pool}.ts: serialize new binding arrays
- points-to.ts: seed pts["rest.propName"] = {"fn"} from the three-hop chain
- build-edges.ts: new Phase 8.3f receiver-pts fallback — when a receiver call
is unresolved, check pts["receiver.name"] for rest-dispatch targets; also
include new bindings in buildPointsToMapForFile null-check guard
Jelly micro-test benchmark (rest fixture): recall=100% TP=1 FN=0 FP=0
Closes #1336
* fix(lint): apply biome auto-fixes across extractors and domain files
useOptionalChain rewrites and formatting fixes flagged by Biome in CI.
* fix(pts): resolve module-level for-of and class-method for-of PTS keys
Two bugs in the forOfBindings points-to resolution path:
1. <module> sentinel never consumed: extractSpreadForOfWalk emits
ForOfBinding with enclosingFunc='<module>' for top-level for-of loops,
but build-edges.ts only looked up scopedPtsKey (null at module level).
Add a modulePtsKey fallback that checks '<module>::call.name' so
`for (const f of arr) { f(); }` at module scope resolves correctly.
2. method_definition pushes unqualified name: funcStack.push('bar') but
findCaller returns callerName='Foo.bar' from the definitions array.
Add a classStack to extractSpreadForOfWalk so method_definition nodes
push the qualified name 'Foo.bar', matching the PTS key the lookup uses.
* fix(bench): sync JS fixture names and use super.count() in DoubleCounter (#1331)
Two bugs introduced by the fix(lint) commit (4ed709e):
1. define-property.js functions were renamed defProp/defProps/create → _defProp/
_defProps/_create (to suppress biome noUnusedVariables), but expected-edges.json
was not updated. This caused 5 false positives and 5 false negatives in the
benchmark (precision 84.4%, recall 81.8%).
2. DoubleCounter.count was changed from super.count() to Counter.count() by the
same lint fix commit. The fixture is meant to test static class-inheritance
resolution via super.count(); reverting to Counter.count() made the edge a
plain same-file call, causing the class-inheritance recall to drop to 2/3.
Fix: update expected-edges.json names to match renamed functions; restore
super.count() in inheritance.js with a biome-ignore suppression explaining
the intent.
* fix(native): add prototype method extraction to Rust engine (#1327) (#1339)
* fix(native): add prototype method extraction to Rust engine (#1327)
Implement parity with the WASM JS extractor for pre-ES6 prototype OOP patterns.
Extractor (crates/codegraph-core/src/extractors/javascript.rs):
- `Foo.prototype.bar = function(){}` → emits `Foo.bar` definition (kind: method)
- `Foo.prototype.bar = identifier` → seeds typeMap['Foo.bar'] = identifier (confidence 0.9)
- `Foo.prototype = { bar: fn, ... }` → same rules per property (pair, method_definition,
shorthand_property_identifier)
Built-in globals (Array, Object, …) are excluded via `is_js_builtin_global` guard.
Adds 6 unit tests covering all three patterns plus edge cases.
Edge builder (crates/codegraph-core/src/edge_builder.rs):
- After a typeMap-resolved type lookup, check typeMap['TypeName.method'] for prototype
aliases (`Foo.prototype.bar = identifierAlias`), mirroring the protoAlias fallback
added to call-resolver.ts in the WASM path.
- Inline new-expression receiver: extract class name from `(new Foo).bar()` receivers
using string parsing (mirrors the `^\(?\s*new\s+[A-Z...]` regex in call-resolver.ts),
enabling resolution without a named variable binding.
Verified against the integration test in
tests/integration/prototype-method-resolution.test.ts (all 3 tests pass with native engine).
docs check acknowledged
Closes #1327
* fix(native): fix parity divergence in extract_inline_new_type
Use strip_prefix('(').unwrap_or(receiver) instead of trim_start_matches('(')
to strip at most one leading paren, matching the JS regex ^\(?. Also update
the doc comment to reflect that _ and $ prefixes are also accepted.
* fix(native): strip one surrounding quote pair in prototype object-literal key
`trim_matches` was stripping ALL quote chars (e.g. `"it's"` became `its`).
Replace with strip_prefix + strip_suffix to remove exactly the outermost
matching quote pair.
* fix(extractor): remove duplicate extractPrototypeMethodsWalk calls
Both extractSymbolsQuery and extractSymbolsWalk had a second call to
extractPrototypeMethodsWalk appended at the bottom, causing prototype
methods to be extracted twice. Remove the duplicate from each path.
The duplication caused a ~44% WASM benchmark regression on the query
path (used by wasm-worker-entry.js in benchmarks).
* style: fix biome format violations inherited from base branch merge
Long lines in wasm-worker-entry.ts, wasm-worker-pool.ts and two fixture
files were not wrapped per the 100-char line width rule.
* perf(native): remove .prototype. files from WASM post-pass filter
The Rust engine now extracts `Foo.prototype.bar = fn` definitions
natively (PR #1327). Remove the `.prototype.` text filter from the
`runPostNativePrototypeMethods` pre-filter so those files are no longer
WASM-reparsed on every native build.
The function-as-object-property pattern (`fn.method = function(){}`)
is still not handled by Rust and continues to use the WASM post-pass.
This eliminates the 422% Build ms/file regression seen on CI.
* fix(native): exclude prototype patterns from WASM post-pass pre-filter
The regex /\b\w+\.\w+\s*=\s*function/ matched the substring
'prototype.bar = function' inside 'Foo.prototype.bar = function(){}',
causing prototype files to be queued for WASM re-processing even though
the Rust engine now handles those patterns natively. Added a negative
lookahead to exclude the prototype shape, matching only
function-as-object-property patterns like 'fn.method = function'.
Fixes the duplicate-node risk flagged in Greptile review of #1339.
* test(native): add unit tests for extract_inline_new_type edge cases
Cover the string-parsing logic in extract_inline_new_type:
(new Foo), (new Foo('arg')), no-parens form, _ and $ prefixes,
lowercase rejection, plain identifier, and the newFoo-not-a-keyword case.
* fix(bench): sync JS fixture names and exclude benchmark fixtures from biome lint (#1339)
Commit 4ed709e's biome auto-fix renamed defProp/defProps/create to
_defProp/_defProps/_create (unused-variable prefix), but the
expected-edges.json manifest still referenced the old names.
This caused 5 false positives and 5 false negatives in the JS benchmark,
dropping precision to 84.4% (below the 100% threshold) and recall to
81.8% (below 90%).
Also fixes the class-inheritance DoubleCounter fixture: the code used
Counter.count() (a direct static call) but the manifest expected a
class-inheritance edge via super.count(). Changed to super.count() so
the fixture tests what the manifest documents.
Prevent recurrence by adding a biome.json override that disables lint
for tests/benchmarks/resolution/fixtures/** — fixture files are
hand-written sample code that must use specific patterns (including
apparently-unused functions and super calls) to exercise resolution.
* fix: extend native post-pass pre-filter to include arrow-function property assignments (#1331)
The pre-filter regex only matched `fn.method = function(){}` patterns,
silently skipping files where all func-prop assignments use arrow functions
(`fn.method = () => {}`). Such files were never WASM-reparsed and their
method definitions were not inserted by the post-pass.
Extend the regex to match both traditional function expressions and arrow
function expressions (both `() => {}` and `param => {}` forms).
* test(extractor): verify exported arrow function funcStack tracking in extractSpreadForOfWalk (#1359)
* test(extractor): verify exported arrow func funcStack tracking in extractSpreadForOfWalk (#1354)
Add regression tests confirming that `export const f = (arr) => { for (const x of arr) x(); }`
correctly pushes `f` onto the funcStack so for-of bindings record the right enclosing caller.
The recursive walk visits `variable_declarator` regardless of whether it is nested under a plain
`lexical_declaration` or an `export_statement`, so the gap reported in the PR #1331 review was
already closed by commit a6c5d2d. These tests document and gate that behavior.
Closes #1354
* fix: remove duplicate paramBindings in SerializedExtractorOutput, rename process identifier
The merge at 3c164f2 introduced a second `paramBindings` field (using the
top-level ParamBinding import) alongside the existing inline-import form at
line 68, causing TS2300 duplicate-identifier errors that broke every CI job.
Removed the duplicate and the now-unused ParamBinding top-level import.
Also renamed the `process` arrow-function identifier in the Phase 8.3e test
to `handleItems` — `process` is a Node.js global and its presence in the test
obscures that the test is solely about the export-wrapping code path.
* fix(wasm-worker): restore paramBindings in SerializedExtractorOutput, remove dup in pool (#1331)
The previous commit removed paramBindings from SerializedExtractorOutput
to fix a TS2300 duplicate-identifier error, but left two references to
ser.paramBindings in wasm-worker-pool.ts (lines 110 and 125), causing
TS2339 errors that broke every CI job.
Restore paramBindings as an inline import in the protocol interface
(matching the style of the other binding fields), and remove the
duplicate line 125 copy in pool.ts.
* fix(extractor): resolve arrow-function rest-param bindings via enclosing variable_declarator (#1331)
extractObjectRestParamBindingsWalk checked childForFieldName('name') on
every function node, but arrow_function and function_expression nodes have
no name field in the tree-sitter grammar — so const f = ({ ...rest }) => {}
always produced an undefined funcName and silently skipped the entire
parameter scan.
Fix: when childForFieldName('name') returns null and the parent node is a
variable_declarator, fall back to the declarator's own name field. This
mirrors the same pattern used in extractSpreadForOfWalk for arrow functions.
Closes the gap reported in the Greptile review (comment 3367102930).
* fix(wasm-worker): remove duplicate paramBindings serialization in wasm-worker-entry (#1331)
paramBindings was serialized twice (lines 808 and 826 in the original code).
The second spread was a no-op since it overwrites the first with the same
value, but the duplicate caused confusion and was flagged in the Greptile
review as evidence of an incomplete field migration.1 parent 81ef6fa commit 1967fcd
54 files changed
Lines changed: 2059 additions & 89 deletions
File tree
- crates/codegraph-core/src
- extractors
- src
- domain
- graph
- builder
- stages
- resolver
- extractors
- tests
- benchmarks/resolution
- fixtures
- javascript
- jelly-micro
- more1
- rest
- spread
- this
- typescript
- tracer
- engines
- integration
- parsers
- unit
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
25 | 25 | | |
26 | 26 | | |
27 | 27 | | |
28 | | - | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
29 | 37 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
407 | 407 | | |
408 | 408 | | |
409 | 409 | | |
410 | | - | |
| 410 | + | |
| 411 | + | |
| 412 | + | |
| 413 | + | |
| 414 | + | |
| 415 | + | |
| 416 | + | |
| 417 | + | |
| 418 | + | |
| 419 | + | |
| 420 | + | |
411 | 421 | | |
412 | 422 | | |
413 | 423 | | |
414 | 424 | | |
415 | 425 | | |
416 | 426 | | |
| 427 | + | |
| 428 | + | |
| 429 | + | |
| 430 | + | |
| 431 | + | |
| 432 | + | |
| 433 | + | |
| 434 | + | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
417 | 440 | | |
418 | 441 | | |
419 | 442 | | |
| |||
489 | 512 | | |
490 | 513 | | |
491 | 514 | | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
492 | 540 | | |
493 | 541 | | |
494 | 542 | | |
| |||
1370 | 1418 | | |
1371 | 1419 | | |
1372 | 1420 | | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
| 1425 | + | |
| 1426 | + | |
| 1427 | + | |
| 1428 | + | |
| 1429 | + | |
| 1430 | + | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
| 1435 | + | |
| 1436 | + | |
| 1437 | + | |
| 1438 | + | |
| 1439 | + | |
| 1440 | + | |
| 1441 | + | |
| 1442 | + | |
| 1443 | + | |
| 1444 | + | |
| 1445 | + | |
| 1446 | + | |
| 1447 | + | |
| 1448 | + | |
| 1449 | + | |
| 1450 | + | |
| 1451 | + | |
| 1452 | + | |
| 1453 | + | |
| 1454 | + | |
| 1455 | + | |
| 1456 | + | |
| 1457 | + | |
| 1458 | + | |
| 1459 | + | |
| 1460 | + | |
| 1461 | + | |
| 1462 | + | |
| 1463 | + | |
| 1464 | + | |
| 1465 | + | |
| 1466 | + | |
| 1467 | + | |
| 1468 | + | |
| 1469 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
29 | 29 | | |
30 | 30 | | |
31 | 31 | | |
| 32 | + | |
| 33 | + | |
32 | 34 | | |
33 | 35 | | |
34 | 36 | | |
| |||
445 | 447 | | |
446 | 448 | | |
447 | 449 | | |
| 450 | + | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
| 463 | + | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
| 468 | + | |
| 469 | + | |
| 470 | + | |
| 471 | + | |
| 472 | + | |
| 473 | + | |
| 474 | + | |
| 475 | + | |
| 476 | + | |
| 477 | + | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
| 487 | + | |
| 488 | + | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
| 497 | + | |
| 498 | + | |
| 499 | + | |
| 500 | + | |
| 501 | + | |
| 502 | + | |
| 503 | + | |
| 504 | + | |
| 505 | + | |
| 506 | + | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
| 514 | + | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
| 527 | + | |
| 528 | + | |
| 529 | + | |
| 530 | + | |
| 531 | + | |
| 532 | + | |
| 533 | + | |
| 534 | + | |
| 535 | + | |
| 536 | + | |
| 537 | + | |
| 538 | + | |
| 539 | + | |
| 540 | + | |
| 541 | + | |
| 542 | + | |
| 543 | + | |
| 544 | + | |
| 545 | + | |
| 546 | + | |
| 547 | + | |
| 548 | + | |
| 549 | + | |
| 550 | + | |
| 551 | + | |
| 552 | + | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
448 | 593 | | |
449 | 594 | | |
450 | 595 | | |
| |||
2446 | 2591 | | |
2447 | 2592 | | |
2448 | 2593 | | |
| 2594 | + | |
| 2595 | + | |
| 2596 | + | |
| 2597 | + | |
| 2598 | + | |
| 2599 | + | |
| 2600 | + | |
| 2601 | + | |
| 2602 | + | |
| 2603 | + | |
| 2604 | + | |
| 2605 | + | |
| 2606 | + | |
| 2607 | + | |
| 2608 | + | |
| 2609 | + | |
| 2610 | + | |
| 2611 | + | |
| 2612 | + | |
| 2613 | + | |
| 2614 | + | |
| 2615 | + | |
| 2616 | + | |
| 2617 | + | |
| 2618 | + | |
| 2619 | + | |
| 2620 | + | |
| 2621 | + | |
| 2622 | + | |
| 2623 | + | |
| 2624 | + | |
| 2625 | + | |
| 2626 | + | |
| 2627 | + | |
| 2628 | + | |
| 2629 | + | |
| 2630 | + | |
| 2631 | + | |
| 2632 | + | |
| 2633 | + | |
| 2634 | + | |
| 2635 | + | |
| 2636 | + | |
| 2637 | + | |
| 2638 | + | |
| 2639 | + | |
| 2640 | + | |
| 2641 | + | |
| 2642 | + | |
| 2643 | + | |
| 2644 | + | |
| 2645 | + | |
| 2646 | + | |
| 2647 | + | |
| 2648 | + | |
| 2649 | + | |
| 2650 | + | |
| 2651 | + | |
| 2652 | + | |
| 2653 | + | |
| 2654 | + | |
| 2655 | + | |
| 2656 | + | |
| 2657 | + | |
| 2658 | + | |
| 2659 | + | |
| 2660 | + | |
| 2661 | + | |
| 2662 | + | |
| 2663 | + | |
| 2664 | + | |
| 2665 | + | |
| 2666 | + | |
2449 | 2667 | | |
2450 | 2668 | | |
2451 | 2669 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
82 | 82 | | |
83 | 83 | | |
84 | 84 | | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
85 | 88 | | |
86 | 89 | | |
87 | 90 | | |
| |||
0 commit comments