Text-Vergleich, &-Verkettung und benannte Bereiche

- Vergleichsoperatoren (= <> > < >= <=) unterscheiden jetzt Text- von
  Zahlenvergleich statt jede Seite zwangsweise durch toNumber() zu
  schicken (=A1="Nord" gab vorher #VALUE! statt 1); ermöglicht
  Text-Kriterien in selbstgebauten COUNTIF/SUMIF/AVERAGEIF-Funktionen
  (neue README-Sektion "SUMIF/COUNTIF/AVERAGEIF nachbauen").
- Neuer &-Operator (Excel-Präzedenz zwischen Vergleich und +/-) plus
  CONCAT/CONCATENATE als Funktions-Äquivalent.
- Benannte Bereiche: neuer Toolbar-Button "🏷" verwaltet dokumentweite
  Namen für eine feste Zelle/einen festen Bereich (z. B. Umsatz_Q1statt
  Umsatz!B2:B6), unqualifiziert in jeder Top-Level-Formel nutzbar.
  Live gegen die Tabellen-ID aufgelöst (übersteht Tabellen-Umbenennung),
  bewusst nicht innerhalb einer benutzerdefinierten Funktion selbst
  nutzbar (Aufrufer können sie aber als Argument übergeben).
- Nebenbei: COUNT2s Autovervollständigungs-Hint korrigiert (zeigte
  "COUNT(...)" statt "COUNT2(...)").

374 Tests, vue-tsc/eslint/build sauber, Playwright-live-verifiziert.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
wkh
2026-09-08 21:59:47 +02:00
co-authored by Claude Sonnet 5
parent 9015a991aa
commit 2c54537427
22 changed files with 1460 additions and 86 deletions
+12 -4
View File
@@ -93,6 +93,7 @@ function expand(
case 'string':
case 'cell':
case 'range':
case 'namedref':
case 'param':
case 'omitted':
return node; // leaves -- nothing to expand
@@ -180,6 +181,7 @@ export function substitute(node: Node, bound: ReadonlyMap<string, Node>): Node {
case 'string':
case 'cell':
case 'range':
case 'namedref':
case 'omitted':
return node;
case 'unary':
@@ -293,16 +295,22 @@ export function declaredBlockNames(block: BlockNode, out: string[] = []): string
/** The first FOR loop, anywhere in the block (including nested inside
* another FOR/IF), whose source isn't (syntactically) a parameter, a
* single cell, or a range -- i.e. can never resolve to a concrete list of
* cells no matter what the caller passes. Checked at save time so this is
* caught immediately, not just on the first real call; the evaluator's
* single cell, a range, or a named-range reference -- i.e. can never
* resolve to a concrete list of cells no matter what the caller passes.
* Checked at save time so this is caught immediately, not just on the
* first real call; the evaluator's
* interpreter re-checks the same thing on the *substituted* source at call
* time regardless (needed either way, since a parameter's shape isn't
* known until then) as the actual enforcement. */
export function findInvalidForSource(block: BlockNode): ForNode | null {
for (const stmt of block.statements) {
if (stmt.kind === 'for') {
if (stmt.source.kind !== 'param' && stmt.source.kind !== 'cell' && stmt.source.kind !== 'range') {
if (
stmt.source.kind !== 'param' &&
stmt.source.kind !== 'cell' &&
stmt.source.kind !== 'range' &&
stmt.source.kind !== 'namedref'
) {
return stmt;
}
const nested = findInvalidForSource(stmt.body);