@lotics/cli 0.142.0 → 0.143.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4563,7 +4563,7 @@ ${e.toString()}`);
4563
4563
  }
4564
4564
  };
4565
4565
  var type2Number = { "boolean": 3, "string": 2, "number": 1 };
4566
- var Infix = {
4566
+ var Infix2 = {
4567
4567
  compareOp: (value1, infix, value2, isArray1, isArray2) => {
4568
4568
  if (value1 == null) value1 = 0;
4569
4569
  if (value2 == null) value2 = 0;
@@ -4654,7 +4654,7 @@ ${e.toString()}`);
4654
4654
  module2.exports = {
4655
4655
  Prefix,
4656
4656
  Postfix,
4657
- Infix,
4657
+ Infix: Infix2,
4658
4658
  Operators: {
4659
4659
  compareOp: ["<", ">", "=", "<>", "<=", ">="],
4660
4660
  concatOp: ["&"],
@@ -4669,7 +4669,7 @@ ${e.toString()}`);
4669
4669
  "../../node_modules/fast-formula-parser/formulas/functions/math.js"(exports2, module2) {
4670
4670
  var FormulaError = require_error();
4671
4671
  var { FormulaHelpers, Types, Factorials, Criteria } = require_helpers();
4672
- var { Infix } = require_operators();
4672
+ var { Infix: Infix2 } = require_operators();
4673
4673
  var H = FormulaHelpers;
4674
4674
  var f = [];
4675
4675
  var fd2 = [];
@@ -5192,7 +5192,7 @@ ${e.toString()}`);
5192
5192
  if (criteria.match === criteria.value.test(value2)) {
5193
5193
  sum += valueToAdd;
5194
5194
  }
5195
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
5195
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
5196
5196
  sum += valueToAdd;
5197
5197
  }
5198
5198
  });
@@ -11472,7 +11472,7 @@ ${e.toString()}`);
11472
11472
  "../../node_modules/fast-formula-parser/formulas/functions/statistical.js"(exports2, module2) {
11473
11473
  var FormulaError = require_error();
11474
11474
  var { FormulaHelpers, Types, Criteria, Address } = require_helpers();
11475
- var { Infix } = require_operators();
11475
+ var { Infix: Infix2 } = require_operators();
11476
11476
  var H = FormulaHelpers;
11477
11477
  var { DistributionFunctions } = require_distribution();
11478
11478
  var StatisticalFunctions = {
@@ -11534,7 +11534,7 @@ ${e.toString()}`);
11534
11534
  sum += valueToAdd;
11535
11535
  cnt++;
11536
11536
  }
11537
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
11537
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
11538
11538
  sum += valueToAdd;
11539
11539
  cnt++;
11540
11540
  }
@@ -11573,7 +11573,7 @@ ${e.toString()}`);
11573
11573
  if (criteria.op === "wc") {
11574
11574
  if (criteria.match === criteria.value.test(value2))
11575
11575
  cnt++;
11576
- } else if (Infix.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
11576
+ } else if (Infix2.compareOp(value2, criteria.op, criteria.value, Array.isArray(value2), isCriteriaArray)) {
11577
11577
  cnt++;
11578
11578
  }
11579
11579
  });
@@ -21126,7 +21126,7 @@ ${e.toString()}`);
21126
21126
  "../../node_modules/fast-formula-parser/grammar/utils.js"(exports2, module2) {
21127
21127
  var FormulaError = require_error();
21128
21128
  var { Address } = require_helpers();
21129
- var { Prefix, Postfix, Infix, Operators } = require_operators();
21129
+ var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
21130
21130
  var Collection = require_collection();
21131
21131
  var MAX_ROW = 1048576;
21132
21132
  var MAX_COLUMN = 16384;
@@ -21250,11 +21250,11 @@ ${e.toString()}`);
21250
21250
  if (this.isFormulaError(val2))
21251
21251
  return val2;
21252
21252
  if (Operators.compareOp.includes(infix))
21253
- return Infix.compareOp(val1, infix, val2, isArray1, isArray2);
21253
+ return Infix2.compareOp(val1, infix, val2, isArray1, isArray2);
21254
21254
  else if (Operators.concatOp.includes(infix))
21255
- return Infix.concatOp(val1, infix, val2, isArray1, isArray2);
21255
+ return Infix2.concatOp(val1, infix, val2, isArray1, isArray2);
21256
21256
  else if (Operators.mathOp.includes(infix))
21257
- return Infix.mathOp(val1, infix, val2, isArray1, isArray2);
21257
+ return Infix2.mathOp(val1, infix, val2, isArray1, isArray2);
21258
21258
  else
21259
21259
  throw new Error(`Unrecognized infix: ${infix}`);
21260
21260
  }
@@ -21806,7 +21806,7 @@ ${e.toString()}`);
21806
21806
  "../../node_modules/fast-formula-parser/grammar/dependency/utils.js"(exports2, module2) {
21807
21807
  var FormulaError = require_error();
21808
21808
  var { FormulaHelpers, Types, Address } = require_helpers();
21809
- var { Prefix, Postfix, Infix, Operators } = require_operators();
21809
+ var { Prefix, Postfix, Infix: Infix2, Operators } = require_operators();
21810
21810
  var Collection = require_collection();
21811
21811
  var MAX_ROW = 1048576;
21812
21812
  var MAX_COLUMN = 16384;
@@ -53990,14 +53990,14 @@ ${e.toString()}`);
53990
53990
  if (!range) return void 0;
53991
53991
  const header = [];
53992
53992
  for (let col = range.startCol; col <= range.endCol; col++) {
53993
- const cell = sheet.getCell(rowColToRef(range.startRow, col));
53993
+ const cell = sheet.getCellAt(range.startRow, col);
53994
53994
  header.push(formatHeader(cell?.value));
53995
53995
  }
53996
53996
  const records = [];
53997
53997
  for (let row = range.startRow + 1; row <= range.endRow; row++) {
53998
53998
  const rec = [];
53999
53999
  for (let col = range.startCol; col <= range.endCol; col++) {
54000
- const cell = sheet.getCell(rowColToRef(row, col));
54000
+ const cell = sheet.getCellAt(row, col);
54001
54001
  rec.push(coerceValue(cell?.value));
54002
54002
  }
54003
54003
  records.push(rec);
@@ -54041,23 +54041,70 @@ ${e.toString()}`);
54041
54041
  get size() {
54042
54042
  return this.store.size;
54043
54043
  }
54044
+ /**
54045
+ * The populated extent of this map, memoized until a mutation invalidates it.
54046
+ *
54047
+ * It lives HERE rather than on `SheetModel` because it is a pure function of
54048
+ * the cell set: a cache held next to the data cannot outlive it, so replacing
54049
+ * a sheet's map can never serve the previous map's extent. Every mutator
54050
+ * below drops it, including the packed pair — those bypass `set`/`delete`
54051
+ * entirely and are what the row/column commands use.
54052
+ *
54053
+ * Walking PACKED keys is the other half. `keys()` unpacks each key into an A1
54054
+ * string, which the caller then reparsed with a regex, so the old form packed,
54055
+ * unpacked and reparsed the whole sheet on every call — and it is called once
54056
+ * per oversized range during a recompute.
54057
+ */
54058
+ usedRange() {
54059
+ if (!this.usedRangeCache) {
54060
+ let minRow2 = Infinity;
54061
+ let maxRow2 = 0;
54062
+ let minCol2 = Infinity;
54063
+ let maxCol2 = 0;
54064
+ for (const packed of this.store.keys()) {
54065
+ const { row, col } = unpackRowCol(packed);
54066
+ if (row < minRow2) minRow2 = row;
54067
+ if (row > maxRow2) maxRow2 = row;
54068
+ if (col < minCol2) minCol2 = col;
54069
+ if (col > maxCol2) maxCol2 = col;
54070
+ }
54071
+ this.usedRangeCache = maxRow2 === 0 ? { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 } : { minRow: minRow2, maxRow: maxRow2, minCol: minCol2, maxCol: maxCol2 };
54072
+ }
54073
+ const { minRow, maxRow, minCol, maxCol } = this.usedRangeCache;
54074
+ return { minRow, maxRow, minCol, maxCol };
54075
+ }
54044
54076
  get(ref2) {
54045
54077
  return this.store.get(packRef(ref2));
54046
54078
  }
54079
+ /**
54080
+ * Read by coordinate, skipping the A1 round trip.
54081
+ *
54082
+ * `get(ref)` costs a number→string→regex→number conversion per call, which
54083
+ * is invisible until a caller reads a range cell by cell: on one real
54084
+ * workbook `refToRowCol` alone was 39% of a full recompute. A caller already
54085
+ * holding (row, col) — the range resolver, any grid walk — should use this.
54086
+ */
54087
+ getAt(row, col) {
54088
+ return this.store.get(packRowCol(row, col));
54089
+ }
54047
54090
  set(ref2, cell) {
54048
54091
  const key = packRef(ref2);
54049
54092
  if (key < 0) return this;
54050
54093
  this.store.set(key, cell);
54094
+ this.usedRangeCache = void 0;
54051
54095
  return this;
54052
54096
  }
54053
54097
  has(ref2) {
54054
54098
  return this.store.has(packRef(ref2));
54055
54099
  }
54056
54100
  delete(ref2) {
54057
- return this.store.delete(packRef(ref2));
54101
+ const deleted = this.store.delete(packRef(ref2));
54102
+ if (deleted) this.usedRangeCache = void 0;
54103
+ return deleted;
54058
54104
  }
54059
54105
  clear() {
54060
54106
  this.store.clear();
54107
+ this.usedRangeCache = void 0;
54061
54108
  }
54062
54109
  *keys() {
54063
54110
  for (const k of this.store.keys()) yield unpackRef(k);
@@ -54085,10 +54132,13 @@ ${e.toString()}`);
54085
54132
  /** Direct packed-key set, for the same hot loops. */
54086
54133
  setPacked(packed, cell) {
54087
54134
  this.store.set(packed, cell);
54135
+ this.usedRangeCache = void 0;
54088
54136
  }
54089
54137
  /** Direct packed-key delete. */
54090
54138
  deletePacked(packed) {
54091
- return this.store.delete(packed);
54139
+ const deleted = this.store.delete(packed);
54140
+ if (deleted) this.usedRangeCache = void 0;
54141
+ return deleted;
54092
54142
  }
54093
54143
  };
54094
54144
  var StyleRegistry = class {
@@ -54271,6 +54321,10 @@ ${e.toString()}`);
54271
54321
  getCell(ref2) {
54272
54322
  return this.cells.get(ref2);
54273
54323
  }
54324
+ /** Read by coordinate, skipping the A1 round trip — see `CellMap.getAt`. */
54325
+ getCellAt(row, col) {
54326
+ return this.cells.getAt(row, col);
54327
+ }
54274
54328
  /**
54275
54329
  * Set cell value with optional inline style. Interns style via the
54276
54330
  * StyleRegistry when available. When `style` is supplied and matches the
@@ -54365,22 +54419,12 @@ ${e.toString()}`);
54365
54419
  this.set(ref2, label, style);
54366
54420
  });
54367
54421
  }
54368
- /** Compute the bounding box of all non-empty cells. */
54422
+ /**
54423
+ * The populated extent of the sheet. Derived from — and cached by — the cell
54424
+ * map, so it cannot outlive the data it describes.
54425
+ */
54369
54426
  getUsedRange() {
54370
- let minRow = Infinity;
54371
- let maxRow = 0;
54372
- let minCol = Infinity;
54373
- let maxCol = 0;
54374
- for (const ref2 of this.cells.keys()) {
54375
- const rc = refToRowCol(ref2);
54376
- if (!rc) continue;
54377
- if (rc.row < minRow) minRow = rc.row;
54378
- if (rc.row > maxRow) maxRow = rc.row;
54379
- if (rc.col < minCol) minCol = rc.col;
54380
- if (rc.col > maxCol) maxCol = rc.col;
54381
- }
54382
- if (maxRow === 0) return { minRow: 1, maxRow: 1, minCol: 1, maxCol: 1 };
54383
- return { minRow, maxRow, minCol, maxCol };
54427
+ return this.cells.usedRange();
54384
54428
  }
54385
54429
  };
54386
54430
  var WorkbookModel = class {
@@ -54627,7 +54671,7 @@ ${e.toString()}`);
54627
54671
  let visible = true;
54628
54672
  for (const filterCol of activeFilters) {
54629
54673
  const col = startCol + filterCol.colIndex;
54630
- const cell = sheet.getCell(rowColToRef(r, col));
54674
+ const cell = sheet.getCellAt(r, col);
54631
54675
  const cellText = cell?.displayValue ?? "";
54632
54676
  if (filterCol.filterValues && filterCol.filterValues.length > 0) {
54633
54677
  if (!filterCol.filterValues.includes(cellText)) {
@@ -61857,6 +61901,28 @@ ${e.toString()}`);
61857
61901
  const bang = key.indexOf("!");
61858
61902
  return { sheet: key.slice(0, bang), ref: key.slice(bang + 1) };
61859
61903
  }
61904
+ var COARSE_COL_SUFFIX = ":*";
61905
+ var COARSE_ROW_PREFIX = "*:";
61906
+ function columnKey(sheet, column) {
61907
+ return `${sheet}!${column}${COARSE_COL_SUFFIX}`;
61908
+ }
61909
+ function rowKey(sheet, row) {
61910
+ return `${sheet}!${COARSE_ROW_PREFIX}${row}`;
61911
+ }
61912
+ function isCoarseKey(key) {
61913
+ return key.endsWith(COARSE_COL_SUFFIX) || key.includes(`!${COARSE_ROW_PREFIX}`);
61914
+ }
61915
+ var A1 = /^([A-Z]+)(\d+)$/;
61916
+ function coarseKeysFor(cellKeyStr) {
61917
+ const bang = cellKeyStr.indexOf("!");
61918
+ if (bang < 0) return [];
61919
+ const sheet = cellKeyStr.slice(0, bang);
61920
+ const ref2 = cellKeyStr.slice(bang + 1);
61921
+ const m = A1.exec(ref2);
61922
+ if (!m) return [];
61923
+ return [columnKey(sheet, m[1]), rowKey(sheet, Number(m[2]))];
61924
+ }
61925
+ var EMPTY = /* @__PURE__ */ new Set();
61860
61926
  var DependencyGraph = class {
61861
61927
  constructor() {
61862
61928
  // Forward edges: cell → cells it references (precedents)
@@ -61891,19 +61957,35 @@ ${e.toString()}`);
61891
61957
  }
61892
61958
  /** Get all cells that directly depend on the given cell. */
61893
61959
  getDirectDependents(cell) {
61894
- return Array.from(this.dependents.get(cell) ?? []);
61960
+ return Array.from(this.dependentsOf(cell));
61961
+ }
61962
+ /**
61963
+ * Everything that references this cell — directly, or through the column or
61964
+ * row node that contains it. The single place `dependents` is read, so a
61965
+ * whole-column reference cannot be forgotten by one traversal and honoured by
61966
+ * another.
61967
+ */
61968
+ dependentsOf(cell) {
61969
+ const direct = this.dependents.get(cell);
61970
+ if (isCoarseKey(cell)) return direct ?? EMPTY;
61971
+ const coarse = coarseKeysFor(cell);
61972
+ let widened;
61973
+ for (const key of coarse) {
61974
+ const via = this.dependents.get(key);
61975
+ if (!via || via.size === 0) continue;
61976
+ widened ?? (widened = new Set(direct));
61977
+ for (const d of via) widened.add(d);
61978
+ }
61979
+ return widened ?? direct ?? EMPTY;
61895
61980
  }
61896
61981
  /** Get all cells that need recalculation when the given cells change, in topological order. */
61897
61982
  getRecalcOrder(changed) {
61898
- const changedSet = new Set(changed);
61899
61983
  const affected = /* @__PURE__ */ new Set();
61900
61984
  const queue = [...changed];
61901
61985
  let head = 0;
61902
61986
  while (head < queue.length) {
61903
61987
  const cell = queue[head++];
61904
- const deps = this.dependents.get(cell);
61905
- if (!deps) continue;
61906
- for (const dep of deps) {
61988
+ for (const dep of this.dependentsOf(cell)) {
61907
61989
  if (!affected.has(dep)) {
61908
61990
  affected.add(dep);
61909
61991
  queue.push(dep);
@@ -61912,15 +61994,11 @@ ${e.toString()}`);
61912
61994
  }
61913
61995
  if (affected.size === 0) return [];
61914
61996
  const inDegree = /* @__PURE__ */ new Map();
61997
+ for (const cell of affected) inDegree.set(cell, 0);
61915
61998
  for (const cell of affected) {
61916
- let count = 0;
61917
- const prec = this.precedents.get(cell);
61918
- if (prec) {
61919
- for (const p of prec) {
61920
- if (affected.has(p)) count++;
61921
- }
61999
+ for (const dep of this.dependentsOf(cell)) {
62000
+ if (affected.has(dep)) inDegree.set(dep, (inDegree.get(dep) ?? 0) + 1);
61922
62001
  }
61923
- inDegree.set(cell, count);
61924
62002
  }
61925
62003
  const result = [];
61926
62004
  const ready = [];
@@ -61931,9 +62009,7 @@ ${e.toString()}`);
61931
62009
  while (readyHead < ready.length) {
61932
62010
  const cell = ready[readyHead++];
61933
62011
  result.push(cell);
61934
- const deps = this.dependents.get(cell);
61935
- if (!deps) continue;
61936
- for (const dep of deps) {
62012
+ for (const dep of this.dependentsOf(cell)) {
61937
62013
  if (!affected.has(dep)) continue;
61938
62014
  const newDeg = (inDegree.get(dep) ?? 1) - 1;
61939
62015
  inDegree.set(dep, newDeg);
@@ -61954,12 +62030,13 @@ ${e.toString()}`);
61954
62030
  const stack = new Array();
61955
62031
  const prec = this.precedents.get(cell);
61956
62032
  if (!prec) return null;
62033
+ const originCoarse = new Set(coarseKeysFor(cell));
61957
62034
  for (const p of prec) {
61958
62035
  stack.push({ cell: p, path: [cell, p] });
61959
62036
  }
61960
62037
  while (stack.length > 0) {
61961
62038
  const { cell: current, path } = stack.pop();
61962
- if (current === cell) return path;
62039
+ if (current === cell || originCoarse.has(current)) return path;
61963
62040
  if (visited.has(current)) continue;
61964
62041
  visited.add(current);
61965
62042
  const nextPrec = this.precedents.get(current);
@@ -62072,6 +62149,20 @@ ${e.toString()}`);
62072
62149
  };
62073
62150
  }
62074
62151
 
62152
+ // ../xlsx/src/blank_comparison.ts
62153
+ var import_operators = __toESM(require_operators(), 1);
62154
+ var patched = false;
62155
+ function applyExcelBlankSemantics() {
62156
+ if (patched) return;
62157
+ patched = true;
62158
+ const original = import_operators.Infix.compareOp;
62159
+ import_operators.Infix.compareOp = (value1, infix, value2, isArray1, isArray2) => {
62160
+ if (value1 == null && typeof value2 === "string") value1 = "";
62161
+ if (value2 == null && typeof value1 === "string") value2 = "";
62162
+ return original(value1, infix, value2, isArray1, isArray2);
62163
+ };
62164
+ }
62165
+
62075
62166
  // ../xlsx/src/formula_engine.ts
62076
62167
  var unwrapArg = (a) => a !== null && typeof a === "object" && "value" in a ? a.value : a;
62077
62168
  function adaptFormulajs(fn) {
@@ -62114,9 +62205,24 @@ ${e.toString()}`);
62114
62205
  bridgeCtxCache = withCtx;
62115
62206
  return withCtx;
62116
62207
  }
62208
+ var MAX_EXPANDED_CELLS = 1e4;
62117
62209
  var _FormulaEngine = class _FormulaEngine {
62118
62210
  constructor(workbook) {
62119
62211
  this.graph = new DependencyGraph();
62212
+ /**
62213
+ * Formula cells (by key) whose text the parser could not read AT ALL, found
62214
+ * while building the graph from a loaded file.
62215
+ *
62216
+ * They are held apart from every other failure because the honest response
62217
+ * differs: a formula that evaluates TO an error has been understood, so
62218
+ * writing `#DIV/0!` reports a real answer, while one we cannot parse has not
62219
+ * been — and overwriting the value its author computed would replace a
62220
+ * correct number with a guess. These keep their cached value and are named in
62221
+ * the report instead. The engine's own rule, one level down: degrade what you
62222
+ * cannot draw, refuse what you cannot read.
62223
+ */
62224
+ this.unreadableFormulas = /* @__PURE__ */ new Set();
62225
+ applyExcelBlankSemantics();
62120
62226
  this.workbook = workbook;
62121
62227
  this.parser = new import_fast_formula_parser.default({
62122
62228
  onCell: (ref2) => this.resolveCell(ref2),
@@ -62137,11 +62243,13 @@ ${e.toString()}`);
62137
62243
  /** Build dependency graph from all formula cells in the workbook. */
62138
62244
  buildGraph() {
62139
62245
  this.graph.clear();
62246
+ this.unreadableFormulas.clear();
62140
62247
  for (const sheet of this.workbook.sheets) {
62141
62248
  for (const [ref2, cell] of sheet.cells) {
62142
62249
  if (!cell.formula) continue;
62143
62250
  const key = cellKey({ sheet: sheet.name, ref: ref2 });
62144
- const deps = this.extractDependencies(cell.formula, sheet.name);
62251
+ const { deps, readable } = this.analyzeFormula(cell.formula, sheet.name);
62252
+ if (!readable) this.unreadableFormulas.add(key);
62145
62253
  this.graph.setDependencies(key, deps);
62146
62254
  }
62147
62255
  }
@@ -62197,8 +62305,13 @@ ${e.toString()}`);
62197
62305
  onCellChanged(sheetName, ref2, formula) {
62198
62306
  const key = cellKey({ sheet: sheetName, ref: ref2 });
62199
62307
  if (formula) {
62200
- const deps = this.extractDependencies(formula, sheetName);
62308
+ const { deps, readable } = this.analyzeFormula(formula, sheetName);
62201
62309
  this.graph.setDependencies(key, deps);
62310
+ this.unreadableFormulas.delete(key);
62311
+ if (!readable) {
62312
+ this.setCellError(key, "#ERROR!");
62313
+ return;
62314
+ }
62202
62315
  const cycle = this.graph.detectCycle(key);
62203
62316
  if (cycle) {
62204
62317
  this.setCellError(key, "#CIRC!");
@@ -62207,6 +62320,7 @@ ${e.toString()}`);
62207
62320
  this.evaluateCell(key);
62208
62321
  } else {
62209
62322
  this.graph.removeCell(key);
62323
+ this.unreadableFormulas.delete(key);
62210
62324
  }
62211
62325
  const order = this.graph.getRecalcOrder([key]);
62212
62326
  for (const depKey of order) {
@@ -62223,6 +62337,7 @@ ${e.toString()}`);
62223
62337
  // ===========================================================================
62224
62338
  /** Evaluate a single formula cell and update its value. */
62225
62339
  evaluateCell(key) {
62340
+ if (this.unreadableFormulas.has(key)) return;
62226
62341
  const { sheet: sheetName, ref: ref2 } = parseCellKey(key);
62227
62342
  const sheetIndex = this.workbook.sheets.findIndex((s) => s.name === sheetName);
62228
62343
  if (sheetIndex < 0) return;
@@ -62271,26 +62386,60 @@ ${e.toString()}`);
62271
62386
  // ===========================================================================
62272
62387
  /** Extract cell/range references from a formula string. Returns empty array for unparseable formulas. */
62273
62388
  extractDependencies(formula, currentSheet) {
62274
- const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
62275
- const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
62276
- const deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
62389
+ return this.analyzeFormula(formula, currentSheet).deps;
62390
+ }
62391
+ /**
62392
+ * Dependencies plus whether the parser could read the formula at all.
62393
+ *
62394
+ * `fast-formula-parser` reports a formula it cannot LEX by throwing, the same
62395
+ * channel it uses for `#VALUE!`, so the lexer failure of a single cell used to
62396
+ * escape `buildGraph` and abort the recompute for the entire workbook —
62397
+ * observed on a real workbook whose `VLOOKUP` pointed at another file
62398
+ * (`'[1]DATA'!B:C`, an external-workbook reference the lexer has no rule for).
62399
+ * Anything the grammar does not cover lands here the same way, so this catches
62400
+ * the class rather than that one syntax.
62401
+ */
62402
+ analyzeFormula(formula, currentSheet) {
62403
+ let deps;
62404
+ try {
62405
+ const sheet = this.workbook.sheets.find((s) => s.name === currentSheet);
62406
+ const expanded = sheet ? expandStructuredRefs(formula, sheet.tables, 1) : formula;
62407
+ deps = this.depParser.parse(expanded, { sheet: currentSheet, row: 1, col: 1 });
62408
+ } catch {
62409
+ return { deps: [], readable: false };
62410
+ }
62277
62411
  const refs = [];
62278
62412
  for (const dep of deps) {
62279
62413
  const depSheet = dep.sheet ?? currentSheet;
62280
62414
  if (dep.from && dep.to) {
62281
- const maxCells = 1e4;
62282
- let count = 0;
62283
- for (let r = dep.from.row; r <= dep.to.row && count < maxCells; r++) {
62284
- for (let c = dep.from.col; c <= dep.to.col && count < maxCells; c++) {
62415
+ const rows = dep.to.row - dep.from.row + 1;
62416
+ const cols = dep.to.col - dep.from.col + 1;
62417
+ if (rows * cols > MAX_EXPANDED_CELLS) {
62418
+ if (cols <= rows) {
62419
+ for (let c = dep.from.col; c <= dep.to.col; c++) {
62420
+ refs.push(columnKey(depSheet, colNumToLetters(c)));
62421
+ }
62422
+ } else {
62423
+ for (let r = dep.from.row; r <= dep.to.row; r++) {
62424
+ refs.push(rowKey(depSheet, r));
62425
+ }
62426
+ }
62427
+ continue;
62428
+ }
62429
+ for (let r = dep.from.row; r <= dep.to.row; r++) {
62430
+ for (let c = dep.from.col; c <= dep.to.col; c++) {
62285
62431
  refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(r, c) }));
62286
- count++;
62287
62432
  }
62288
62433
  }
62289
62434
  } else if (dep.row != null && dep.col != null) {
62290
62435
  refs.push(cellKey({ sheet: depSheet, ref: rowColToRef(dep.row, dep.col) }));
62291
62436
  }
62292
62437
  }
62293
- return refs;
62438
+ return { deps: refs, readable: true };
62439
+ }
62440
+ /** Formula cells the parser could not read, by key. Read by the recompute report. */
62441
+ unreadable() {
62442
+ return this.unreadableFormulas;
62294
62443
  }
62295
62444
  // ===========================================================================
62296
62445
  // Cell value resolution callbacks
@@ -62320,8 +62469,7 @@ ${e.toString()}`);
62320
62469
  for (let r = ref2.from.row; r <= lastRow; r++) {
62321
62470
  const row = [];
62322
62471
  for (let c = ref2.from.col; c <= lastCol; c++) {
62323
- const cell = sheet.getCell(rowColToRef(r, c));
62324
- row.push(cell?.value ?? null);
62472
+ row.push(sheet.getCellAt(r, c)?.value ?? null);
62325
62473
  }
62326
62474
  result.push(row);
62327
62475
  }
@@ -62411,15 +62559,19 @@ ${e.toString()}`);
62411
62559
  const engine = new FormulaEngine(workbook);
62412
62560
  engine.buildGraph();
62413
62561
  engine.evaluateAll();
62414
- return collectRecomputeReport(workbook);
62562
+ return collectRecomputeReport(workbook, engine.unreadable());
62415
62563
  }
62416
- function collectRecomputeReport(workbook) {
62564
+ function collectRecomputeReport(workbook, unreadable) {
62417
62565
  let total = 0;
62418
62566
  const unevaluated = [];
62419
62567
  for (const sheet of workbook.sheets) {
62420
62568
  for (const [ref2, cell] of sheet.cells) {
62421
62569
  if (!cell.formula) continue;
62422
62570
  total++;
62571
+ if (unreadable.has(cellKey({ sheet: sheet.name, ref: ref2 }))) {
62572
+ unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: "#UNREADABLE!" });
62573
+ continue;
62574
+ }
62423
62575
  if (cell.value === null) {
62424
62576
  unevaluated.push({ sheet: sheet.name, ref: ref2, formula: cell.formula, error: cell.error ?? "#ERROR!" });
62425
62577
  }