Return (start, end_exclusive) ranges of consecutive 'prop' logical lines (length >= 2) that are safe to reorder. A run is collected only when the surrounding block is a QML object declaration (`Foo { ... }` or `Rectangle { ... }`). JavaScript bodies (handler functions like `onClick
(lines: list[LogicalLine])
| 489 | |
| 490 | |
| 491 | def find_property_runs(lines: list[LogicalLine]) -> list[tuple[int, int]]: |
| 492 | """Return (start, end_exclusive) ranges of consecutive 'prop' logical |
| 493 | lines (length >= 2) that are safe to reorder. |
| 494 | |
| 495 | A run is collected only when the surrounding block is a QML object |
| 496 | declaration (`Foo { ... }` or `Rectangle { ... }`). JavaScript bodies |
| 497 | (handler functions like `onClicked: { ... }`, `function foo() { ... }`, |
| 498 | arrow-body `=> { ... }`) are skipped entirely: the `identifier: value` |
| 499 | pattern inside a JS object literal has dangling-comma semantics, and |
| 500 | statements inside a JS statement block are not order-independent. |
| 501 | |
| 502 | The walk tracks a stack of (is_js, depth) entries so nested `{ ... }` |
| 503 | inside a JS body stay in JS mode until brackets rebalance. |
| 504 | """ |
| 505 | runs: list[tuple[int, int]] = [] |
| 506 | n = len(lines) |
| 507 | |
| 508 | # Stack entries: True for JS body, False for QML body. An open that |
| 509 | # occurs while the top of the stack is already JS inherits JS mode. |
| 510 | js_stack: list[bool] = [] |
| 511 | |
| 512 | def in_js() -> bool: |
| 513 | return any(js_stack) |
| 514 | |
| 515 | i = 0 |
| 516 | while i < n: |
| 517 | line = lines[i] |
| 518 | |
| 519 | if line.kind == "open": |
| 520 | # Classify the new block: an open line inside an already-JS block |
| 521 | # inherits JS mode (nested `{ ... }` inside a function body). |
| 522 | is_js = opens_js_body(line.raws[0]) or in_js() |
| 523 | |
| 524 | # A single physical open line typically contributes one `{`, but |
| 525 | # pathological sources may have several. Push one frame per `{`; |
| 526 | # all nested frames share the same JS classification. |
| 527 | net = _brace_delta_raw(line.raws[0]) |
| 528 | frames_to_push = max(1, net) |
| 529 | for _ in range(frames_to_push): |
| 530 | js_stack.append(is_js) |
| 531 | i += 1 |
| 532 | continue |
| 533 | |
| 534 | if line.kind == "close": |
| 535 | net = _brace_delta_raw(line.raws[0]) |
| 536 | pops = max(1, -net) if net < 0 else 1 |
| 537 | for _ in range(pops): |
| 538 | if js_stack: |
| 539 | js_stack.pop() |
| 540 | i += 1 |
| 541 | continue |
| 542 | |
| 543 | if in_js(): |
| 544 | i += 1 |
| 545 | continue |
| 546 | |
| 547 | if line.kind == "prop": |
| 548 | j = i |
no test coverage detected