diff --git a/dist/datamodel/usage/UsageIndex.d.ts b/dist/datamodel/usage/UsageIndex.d.ts
index 5b29db9..08b9a36 100644
--- a/dist/datamodel/usage/UsageIndex.d.ts
+++ b/dist/datamodel/usage/UsageIndex.d.ts
@@ -1,4 +1,6 @@
import type { MaskScope } from '../maskScope.js';
+import type { SlddScanResult } from '../parser/SlddScan.js';
+import type { MatScanResult } from '../parser/MatScan.js';
import type { ParsedSlx } from '../parser/SlxParser.js';
import type { NodeUsage } from '../../core/DataModel.js';
/**
@@ -155,6 +157,50 @@ export declare function summarizeFiles(files: UsageFile[]): FileSummaries;
* swallowing a throw would hand back an empty answer for a model that HAD been parsed.
*/
export declare function summarizeParsedModel(parsed: ParsedSlx, srcId: string, filename: string): FileSummaries;
+/**
+ * The dictionary branch of `summarizeFiles`, over a scan the caller has ALREADY run.
+ *
+ * Same trade as `summarizeParsedModel`, on the file kind a workspace holds most of. A host
+ * that scans a dictionary for its own purposes — data-explorer-vscode's cheap tier wants
+ * both the raw `refs` the scan carries and this summary out of one read — got the summary
+ * only by handing the bytes to `summarizeFiles`, which scanned them a second time. That
+ * second scan measured 46% of the tier's per-dictionary CPU: 14.6 ms of 31.5 ms on a
+ * 20,000-entry dictionary, spent deriving a `names` list the caller was already holding.
+ *
+ * The answer is a whole `FileSummaries` carrying just this dictionary, keyed by
+ * `refBasename(filename)` exactly as `summarizeFiles` keys it, so it is what
+ * `summarizeFiles([{ srcId, filename, bytes }])` returns for a `.sldd` and
+ * `mergeFileSummaries` folds it in beside the models and MAT-files. usageScanSummary.test.ts
+ * pins that equality over every committed dictionary — and, because `summarizeFiles` routes
+ * through here and so cannot disagree by construction, pins the summary itself against the
+ * FULL reader as well. Two ways to reach one summary is the shape a drift takes; two ways
+ * that share an implementation only move the risk into the implementation.
+ *
+ * `filename` is still required and is still not `srcId`: the map KEY comes off the
+ * basename, case-folded, because the lookups against it are references authored inside a
+ * model (see fileKinds.refBasename), and a srcId may be a path or a URI.
+ *
+ * WHAT THE CALLER MUST NOT DO: hand in a scan of bytes other than the ones `srcId` names.
+ * Nothing here can detect it — a scan is two string arrays — and the result is an index
+ * that credits one file's entries to another, which reads as a confidently wrong Usage
+ * cell rather than as an error.
+ *
+ * No per-file `try`, unlike `summarizeFiles`: there is no file here to be unreadable. A
+ * caller holding a scan has already been told the bytes were readable, and swallowing a
+ * throw would hand back an empty answer for a dictionary that HAD been read.
+ */
+export declare function summarizeSlddScan(scan: SlddScanResult, srcId: string, filename: string): FileSummaries;
+/**
+ * The MAT half of `summarizeSlddScan`, for a caller that already ran `scanMat` — the same
+ * saved read, on the file kind a model resolves last.
+ *
+ * `slddRefs` is empty and not a field this could fill: a MAT-file inherits nothing, which
+ * is what `DataSummary.slddRefs` says of it.
+ *
+ * The same caution applies — a scan of bytes `srcId` does not name cannot be detected here
+ * — and so does the reason there is no `try`: see `summarizeSlddScan`.
+ */
+export declare function summarizeMatScan(scan: MatScanResult, srcId: string, filename: string): FileSummaries;
/**
* Where the name `name` resolves for a block of `model` sitting at `systemPath`, or null
* if it does not.
diff --git a/dist/datamodel/usage/UsageIndex.d.ts.map b/dist/datamodel/usage/UsageIndex.d.ts.map
index f777eb7..c5c454c 100644
--- a/dist/datamodel/usage/UsageIndex.d.ts.map
+++ b/dist/datamodel/usage/UsageIndex.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"UsageIndex.d.ts","sourceRoot":"","sources":["../../../src/datamodel/usage/UsageIndex.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"UsageIndex.d.ts","sourceRoot":"","sources":["../../../src/datamodel/usage/UsageIndex.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/datamodel/usage/UsageIndex.js b/dist/datamodel/usage/UsageIndex.js
index 4ea7503..83a9068 100644
--- a/dist/datamodel/usage/UsageIndex.js
+++ b/dist/datamodel/usage/UsageIndex.js
@@ -85,48 +85,6 @@ function modelSummary(parsed, srcId, filename) {
})),
};
}
-/**
- * A usage summary reads ONE string per entry, so it goes through `scanSldd` rather than
- * `readSlddContent`: 3288 ms to 116 ms on the larger customer dictionary, and 63 MB of
- * retained heap to 2 MB, because the entry tree it used to build was discarded a line
- * later. `scanSldd` falls back to the full parse on any dictionary it cannot prove itself
- * equivalent on, so this is a substitution and not a second reader.
- *
- * Nothing is lost by not passing a `warnings` array: this never passed one either. A
- * usage index is built over a whole folder and drops unreadable files silently by design
- * (see `summarizeFiles`), so there was no channel for a per-part warning to reach.
- */
-function slddSummary(srcId, bytes) {
- const { names, refs } = scanSldd(bytes);
- return {
- srcId,
- // `filter(Boolean)` because `scanSldd` reports `''` for an entry with no readable
- // name, to keep positions aligned for callers that index by position. A Set keyed on
- // names has no use for that placeholder, and the previous code dropped it too.
- names: new Set(names.filter(Boolean)),
- slddRefs: refs.map(refBasename),
- };
-}
-/**
- * The MAT half of the same substitution `slddSummary` makes, for the same reason: this
- * reads one string per variable and `parseMat` was decoding every element of every matrix
- * to supply them — 1271 ms over the corpus's `.mat` files against 4.4 ms here.
- *
- * `scanMat` falls back to the full parse on any file it cannot prove itself equivalent
- * on, so this is a substitution and not a second reader. It discards `parseMat`'s
- * warnings, which this never collected either: a usage index is built over a folder and
- * drops unreadable files silently by design (see `summarizeFiles`).
- */
-function matSummary(srcId, bytes) {
- return {
- srcId,
- // `filter(Boolean)` because `scanMat` reports '' for a variable with no name, to keep
- // positions aligned for callers that index by position. A Set keyed on names has no
- // use for that placeholder, and the previous code dropped it too.
- names: new Set(scanMat(bytes).names.filter(Boolean)),
- slddRefs: [],
- };
-}
/**
* Parse each file into the summary its kind calls for, dispatching on the FILENAME through
* the shared kind tests.
@@ -149,10 +107,30 @@ export function summarizeFiles(files) {
models.push(...summarizeParsedModel(parseModel(file.bytes, file.filename), file.srcId, file.filename).models);
}
else if (isMatFile(file.filename)) {
- matByName.set(refBasename(file.filename), matSummary(file.srcId, file.bytes));
+ // `scanMat` and not `parseMat`, for the same reason the dictionary branch below
+ // scans: a summary reads one string per variable and the full parse was decoding
+ // every element of every matrix to supply it — 1271 ms over the corpus's `.mat`
+ // files against 4.4 ms. It falls back to `parseMat` on any file it cannot prove
+ // itself equivalent on, so this is a substitution and not a second reader, and it
+ // discards the warnings `parseMat` would collect, which this never collected
+ // either: unreadable files are dropped silently here by design.
+ //
+ // Then through `summarizeMatScan`, as the model branch goes through
+ // `summarizeParsedModel`. The part it returns is keyed the way this map is keyed,
+ // so folding its one entry in is a copy rather than a second keying rule.
+ for (const [name, summary] of summarizeMatScan(scanMat(file.bytes), file.srcId, file.filename).matByName) {
+ matByName.set(name, summary);
+ }
}
else if (isSlddFile(file.filename)) {
- slddByName.set(refBasename(file.filename), slddSummary(file.srcId, file.bytes));
+ // `scanSldd` and not `readSlddContent`: 3288 ms to 116 ms on the larger customer
+ // dictionary, and 63 MB of retained heap to 2 MB, because the entry tree the full
+ // read builds was discarded a line later. Same substitution discipline as above —
+ // it falls back to the full parse on anything it cannot prove itself equivalent
+ // on — and no `warnings` array for the same reason.
+ for (const [name, summary] of summarizeSlddScan(scanSldd(file.bytes), file.srcId, file.filename).slddByName) {
+ slddByName.set(name, summary);
+ }
}
}
catch {
@@ -186,6 +164,85 @@ export function summarizeFiles(files) {
export function summarizeParsedModel(parsed, srcId, filename) {
return { models: [modelSummary(parsed, srcId, filename)], slddByName: new Map(), matByName: new Map() };
}
+/**
+ * The dictionary branch of `summarizeFiles`, over a scan the caller has ALREADY run.
+ *
+ * Same trade as `summarizeParsedModel`, on the file kind a workspace holds most of. A host
+ * that scans a dictionary for its own purposes — data-explorer-vscode's cheap tier wants
+ * both the raw `refs` the scan carries and this summary out of one read — got the summary
+ * only by handing the bytes to `summarizeFiles`, which scanned them a second time. That
+ * second scan measured 46% of the tier's per-dictionary CPU: 14.6 ms of 31.5 ms on a
+ * 20,000-entry dictionary, spent deriving a `names` list the caller was already holding.
+ *
+ * The answer is a whole `FileSummaries` carrying just this dictionary, keyed by
+ * `refBasename(filename)` exactly as `summarizeFiles` keys it, so it is what
+ * `summarizeFiles([{ srcId, filename, bytes }])` returns for a `.sldd` and
+ * `mergeFileSummaries` folds it in beside the models and MAT-files. usageScanSummary.test.ts
+ * pins that equality over every committed dictionary — and, because `summarizeFiles` routes
+ * through here and so cannot disagree by construction, pins the summary itself against the
+ * FULL reader as well. Two ways to reach one summary is the shape a drift takes; two ways
+ * that share an implementation only move the risk into the implementation.
+ *
+ * `filename` is still required and is still not `srcId`: the map KEY comes off the
+ * basename, case-folded, because the lookups against it are references authored inside a
+ * model (see fileKinds.refBasename), and a srcId may be a path or a URI.
+ *
+ * WHAT THE CALLER MUST NOT DO: hand in a scan of bytes other than the ones `srcId` names.
+ * Nothing here can detect it — a scan is two string arrays — and the result is an index
+ * that credits one file's entries to another, which reads as a confidently wrong Usage
+ * cell rather than as an error.
+ *
+ * No per-file `try`, unlike `summarizeFiles`: there is no file here to be unreadable. A
+ * caller holding a scan has already been told the bytes were readable, and swallowing a
+ * throw would hand back an empty answer for a dictionary that HAD been read.
+ */
+export function summarizeSlddScan(scan, srcId, filename) {
+ return {
+ models: [],
+ slddByName: new Map([
+ [
+ refBasename(filename),
+ {
+ srcId,
+ // `filter(Boolean)` because `scanSldd` reports `''` for an entry with no readable
+ // name, to keep positions aligned for callers that index by position. A Set keyed
+ // on names has no use for that placeholder.
+ names: new Set(scan.names.filter(Boolean)),
+ slddRefs: scan.refs.map(refBasename),
+ },
+ ],
+ ]),
+ matByName: new Map(),
+ };
+}
+/**
+ * The MAT half of `summarizeSlddScan`, for a caller that already ran `scanMat` — the same
+ * saved read, on the file kind a model resolves last.
+ *
+ * `slddRefs` is empty and not a field this could fill: a MAT-file inherits nothing, which
+ * is what `DataSummary.slddRefs` says of it.
+ *
+ * The same caution applies — a scan of bytes `srcId` does not name cannot be detected here
+ * — and so does the reason there is no `try`: see `summarizeSlddScan`.
+ */
+export function summarizeMatScan(scan, srcId, filename) {
+ return {
+ models: [],
+ slddByName: new Map(),
+ matByName: new Map([
+ [
+ refBasename(filename),
+ {
+ srcId,
+ // `filter(Boolean)` because `scanMat` reports '' for a variable with no name, to
+ // keep positions aligned for callers that index by position.
+ names: new Set(scan.names.filter(Boolean)),
+ slddRefs: [],
+ },
+ ],
+ ]),
+ };
+}
// --- Resolving ---------------------------------------------------------------
/**
* Where the name `name` resolves for a block of `model` sitting at `systemPath`, or null
diff --git a/dist/datamodel/usage/UsageIndex.js.map b/dist/datamodel/usage/UsageIndex.js.map
index 459980a..044c49f 100644
--- a/dist/datamodel/usage/UsageIndex.js.map
+++ b/dist/datamodel/usage/UsageIndex.js.map
@@ -1 +1 @@
-{"version":3,"file":"UsageIndex.js","sourceRoot":"","sources":["../../../src/datamodel/usage/UsageIndex.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"UsageIndex.js","sourceRoot":"","sources":["../../../src/datamodel/usage/UsageIndex.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.d.ts b/dist/index.d.ts
index 3fe2bca..1fa89fd 100644
--- a/dist/index.d.ts
+++ b/dist/index.d.ts
@@ -40,7 +40,7 @@ export { scanModelStructure, type ModelStructure } from './datamodel/parser/Mode
export { DATA_PART, DATA_PART_XML, DATA_PART_KEY, TEXT_PARTS, TEXT_CONTENT } from './datamodel/parser/SlddParts.js';
export { SC_PART, SC_PART_XML, SC_TYPE_TO_CLASSIFICATION, applyScEdits, catalogFromDefinitions, classificationOf, scRenameEdits, scanScJsonText, scanScXml, } from './datamodel/parser/ScCatalog.js';
export type { ScDefinition, ScNameSite, ScTextEdit, SystemComposerCatalog } from './datamodel/parser/ScCatalog.js';
-export { buildUsageIndex, buildUsageIndexFromSummaries, mergeFileSummaries, summarizeFiles, summarizeParsedModel, resolveName, } from './datamodel/usage/UsageIndex.js';
+export { buildUsageIndex, buildUsageIndexFromSummaries, mergeFileSummaries, summarizeFiles, summarizeParsedModel, summarizeSlddScan, summarizeMatScan, resolveName, } from './datamodel/usage/UsageIndex.js';
export type { UsageIndex, UsageFile, FileSummaries, ModelSummary, DataSummary, ParamOrigin, OriginKind, } from './datamodel/usage/UsageIndex.js';
export { maskDefining } from './datamodel/maskScope.js';
export type { MaskScope } from './datamodel/maskScope.js';
diff --git a/dist/index.d.ts.map b/dist/index.d.ts.map
index 5b4aaea..c1e7a5b 100644
--- a/dist/index.d.ts.map
+++ b/dist/index.d.ts.map
@@ -1 +1 @@
-{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/dist/index.js b/dist/index.js
index 66af5bf..2934fcd 100644
--- a/dist/index.js
+++ b/dist/index.js
@@ -144,7 +144,13 @@ export { SC_PART, SC_PART_XML, SC_TYPE_TO_CLASSIFICATION, applyScEdits, catalogF
// `summarizeParsedModel` splits the summarising half the same way for the file a host has
// OPEN: it already parsed those bytes for the model's own rows, and `summarizeFiles` would
// parse them a second time to say which blocks use what. Same summary, one parse.
-export { buildUsageIndex, buildUsageIndexFromSummaries, mergeFileSummaries, summarizeFiles, summarizeParsedModel, resolveName, } from './datamodel/usage/UsageIndex.js';
+// `summarizeSlddScan` and `summarizeMatScan` are that split for the other two kinds, over a
+// `scanSldd`/`scanMat` result rather than a parse — a host that scans a dictionary for its
+// own indexes reads the same bytes twice otherwise, and the second scan measured 46% of one
+// such tier's per-dictionary CPU (14.6 ms of 31.5 ms on a 20,000-entry dictionary). All
+// three return a `FileSummaries` keyed as `summarizeFiles` keys it, so they fold together
+// through `mergeFileSummaries` whichever way each file was summarised.
+export { buildUsageIndex, buildUsageIndexFromSummaries, mergeFileSummaries, summarizeFiles, summarizeParsedModel, summarizeSlddScan, summarizeMatScan, resolveName, } from './datamodel/usage/UsageIndex.js';
// The innermost of those scopes, and the only one that is not a file: a masked subsystem's
// own parameters. On the barrel because `ParamOrigin.maskBlock` hands one out — a host
// rendering a mask-resolved parameter needs the block's path to name the source of the
diff --git a/dist/index.js.map b/dist/index.js.map
index 09f7450..71f2746 100644
--- a/dist/index.js.map
+++ b/dist/index.js.map
@@ -1 +1 @@
-{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
+{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
\ No newline at end of file
diff --git a/package.json b/package.json
index 9ce0614..ce6540d 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "data-explorer-core",
- "version": "1.20.0",
+ "version": "1.21.0",
"description": "Parser, data model, and node schema for Simulink data-dictionary, model, MAT-file, and project files. Presentation-independent; embeddable in-process.",
"license": "BSD-3-Clause",
"type": "module",
diff --git a/src/datamodel/usage/UsageIndex.ts b/src/datamodel/usage/UsageIndex.ts
index 5de7849..242e350 100644
--- a/src/datamodel/usage/UsageIndex.ts
+++ b/src/datamodel/usage/UsageIndex.ts
@@ -44,8 +44,10 @@ import { maskDefining } from '../maskScope.js';
import type { MaskScope } from '../maskScope.js';
import { basenameOf, isMatFile, isModelFile, isSlddFile, modelNameOf, refBasename } from '../fileKinds.js';
import { scanSldd } from '../parser/SlddScan.js';
+import type { SlddScanResult } from '../parser/SlddScan.js';
import { parseModel } from '../parser/ModelParser.js';
import { scanMat } from '../parser/MatScan.js';
+import type { MatScanResult } from '../parser/MatScan.js';
import type { ParsedSlx } from '../parser/SlxParser.js';
import type { NodeUsage } from '../../core/DataModel.js';
@@ -224,50 +226,6 @@ function modelSummary(parsed: ParsedSlx, srcId: string, filename: string): Model
};
}
-/**
- * A usage summary reads ONE string per entry, so it goes through `scanSldd` rather than
- * `readSlddContent`: 3288 ms to 116 ms on the larger customer dictionary, and 63 MB of
- * retained heap to 2 MB, because the entry tree it used to build was discarded a line
- * later. `scanSldd` falls back to the full parse on any dictionary it cannot prove itself
- * equivalent on, so this is a substitution and not a second reader.
- *
- * Nothing is lost by not passing a `warnings` array: this never passed one either. A
- * usage index is built over a whole folder and drops unreadable files silently by design
- * (see `summarizeFiles`), so there was no channel for a per-part warning to reach.
- */
-function slddSummary(srcId: string, bytes: ArrayBuffer): DataSummary {
- const { names, refs } = scanSldd(bytes);
- return {
- srcId,
- // `filter(Boolean)` because `scanSldd` reports `''` for an entry with no readable
- // name, to keep positions aligned for callers that index by position. A Set keyed on
- // names has no use for that placeholder, and the previous code dropped it too.
- names: new Set(names.filter(Boolean)),
- slddRefs: refs.map(refBasename),
- };
-}
-
-/**
- * The MAT half of the same substitution `slddSummary` makes, for the same reason: this
- * reads one string per variable and `parseMat` was decoding every element of every matrix
- * to supply them — 1271 ms over the corpus's `.mat` files against 4.4 ms here.
- *
- * `scanMat` falls back to the full parse on any file it cannot prove itself equivalent
- * on, so this is a substitution and not a second reader. It discards `parseMat`'s
- * warnings, which this never collected either: a usage index is built over a folder and
- * drops unreadable files silently by design (see `summarizeFiles`).
- */
-function matSummary(srcId: string, bytes: ArrayBuffer): DataSummary {
- return {
- srcId,
- // `filter(Boolean)` because `scanMat` reports '' for a variable with no name, to keep
- // positions aligned for callers that index by position. A Set keyed on names has no
- // use for that placeholder, and the previous code dropped it too.
- names: new Set(scanMat(bytes).names.filter(Boolean)),
- slddRefs: [],
- };
-}
-
/**
* Parse each file into the summary its kind calls for, dispatching on the FILENAME through
* the shared kind tests.
@@ -289,9 +247,29 @@ export function summarizeFiles(files: UsageFile[]): FileSummaries {
// already holds are then the same summary by construction, not by agreement.
models.push(...summarizeParsedModel(parseModel(file.bytes, file.filename), file.srcId, file.filename).models);
} else if (isMatFile(file.filename)) {
- matByName.set(refBasename(file.filename), matSummary(file.srcId, file.bytes));
+ // `scanMat` and not `parseMat`, for the same reason the dictionary branch below
+ // scans: a summary reads one string per variable and the full parse was decoding
+ // every element of every matrix to supply it — 1271 ms over the corpus's `.mat`
+ // files against 4.4 ms. It falls back to `parseMat` on any file it cannot prove
+ // itself equivalent on, so this is a substitution and not a second reader, and it
+ // discards the warnings `parseMat` would collect, which this never collected
+ // either: unreadable files are dropped silently here by design.
+ //
+ // Then through `summarizeMatScan`, as the model branch goes through
+ // `summarizeParsedModel`. The part it returns is keyed the way this map is keyed,
+ // so folding its one entry in is a copy rather than a second keying rule.
+ for (const [name, summary] of summarizeMatScan(scanMat(file.bytes), file.srcId, file.filename).matByName) {
+ matByName.set(name, summary);
+ }
} else if (isSlddFile(file.filename)) {
- slddByName.set(refBasename(file.filename), slddSummary(file.srcId, file.bytes));
+ // `scanSldd` and not `readSlddContent`: 3288 ms to 116 ms on the larger customer
+ // dictionary, and 63 MB of retained heap to 2 MB, because the entry tree the full
+ // read builds was discarded a line later. Same substitution discipline as above —
+ // it falls back to the full parse on anything it cannot prove itself equivalent
+ // on — and no `warnings` array for the same reason.
+ for (const [name, summary] of summarizeSlddScan(scanSldd(file.bytes), file.srcId, file.filename).slddByName) {
+ slddByName.set(name, summary);
+ }
}
} catch {
/* unreadable file contributes nothing */
@@ -326,6 +304,87 @@ export function summarizeParsedModel(parsed: ParsedSlx, srcId: string, filename:
return { models: [modelSummary(parsed, srcId, filename)], slddByName: new Map(), matByName: new Map() };
}
+/**
+ * The dictionary branch of `summarizeFiles`, over a scan the caller has ALREADY run.
+ *
+ * Same trade as `summarizeParsedModel`, on the file kind a workspace holds most of. A host
+ * that scans a dictionary for its own purposes — data-explorer-vscode's cheap tier wants
+ * both the raw `refs` the scan carries and this summary out of one read — got the summary
+ * only by handing the bytes to `summarizeFiles`, which scanned them a second time. That
+ * second scan measured 46% of the tier's per-dictionary CPU: 14.6 ms of 31.5 ms on a
+ * 20,000-entry dictionary, spent deriving a `names` list the caller was already holding.
+ *
+ * The answer is a whole `FileSummaries` carrying just this dictionary, keyed by
+ * `refBasename(filename)` exactly as `summarizeFiles` keys it, so it is what
+ * `summarizeFiles([{ srcId, filename, bytes }])` returns for a `.sldd` and
+ * `mergeFileSummaries` folds it in beside the models and MAT-files. usageScanSummary.test.ts
+ * pins that equality over every committed dictionary — and, because `summarizeFiles` routes
+ * through here and so cannot disagree by construction, pins the summary itself against the
+ * FULL reader as well. Two ways to reach one summary is the shape a drift takes; two ways
+ * that share an implementation only move the risk into the implementation.
+ *
+ * `filename` is still required and is still not `srcId`: the map KEY comes off the
+ * basename, case-folded, because the lookups against it are references authored inside a
+ * model (see fileKinds.refBasename), and a srcId may be a path or a URI.
+ *
+ * WHAT THE CALLER MUST NOT DO: hand in a scan of bytes other than the ones `srcId` names.
+ * Nothing here can detect it — a scan is two string arrays — and the result is an index
+ * that credits one file's entries to another, which reads as a confidently wrong Usage
+ * cell rather than as an error.
+ *
+ * No per-file `try`, unlike `summarizeFiles`: there is no file here to be unreadable. A
+ * caller holding a scan has already been told the bytes were readable, and swallowing a
+ * throw would hand back an empty answer for a dictionary that HAD been read.
+ */
+export function summarizeSlddScan(scan: SlddScanResult, srcId: string, filename: string): FileSummaries {
+ return {
+ models: [],
+ slddByName: new Map([
+ [
+ refBasename(filename),
+ {
+ srcId,
+ // `filter(Boolean)` because `scanSldd` reports `''` for an entry with no readable
+ // name, to keep positions aligned for callers that index by position. A Set keyed
+ // on names has no use for that placeholder.
+ names: new Set(scan.names.filter(Boolean)),
+ slddRefs: scan.refs.map(refBasename),
+ },
+ ],
+ ]),
+ matByName: new Map(),
+ };
+}
+
+/**
+ * The MAT half of `summarizeSlddScan`, for a caller that already ran `scanMat` — the same
+ * saved read, on the file kind a model resolves last.
+ *
+ * `slddRefs` is empty and not a field this could fill: a MAT-file inherits nothing, which
+ * is what `DataSummary.slddRefs` says of it.
+ *
+ * The same caution applies — a scan of bytes `srcId` does not name cannot be detected here
+ * — and so does the reason there is no `try`: see `summarizeSlddScan`.
+ */
+export function summarizeMatScan(scan: MatScanResult, srcId: string, filename: string): FileSummaries {
+ return {
+ models: [],
+ slddByName: new Map(),
+ matByName: new Map([
+ [
+ refBasename(filename),
+ {
+ srcId,
+ // `filter(Boolean)` because `scanMat` reports '' for a variable with no name, to
+ // keep positions aligned for callers that index by position.
+ names: new Set(scan.names.filter(Boolean)),
+ slddRefs: [],
+ },
+ ],
+ ]),
+ };
+}
+
// --- Resolving ---------------------------------------------------------------
/**
diff --git a/src/index.ts b/src/index.ts
index a4c182f..2b6bc28 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -219,12 +219,20 @@ export type { ScDefinition, ScNameSite, ScTextEdit, SystemComposerCatalog } from
// `summarizeParsedModel` splits the summarising half the same way for the file a host has
// OPEN: it already parsed those bytes for the model's own rows, and `summarizeFiles` would
// parse them a second time to say which blocks use what. Same summary, one parse.
+// `summarizeSlddScan` and `summarizeMatScan` are that split for the other two kinds, over a
+// `scanSldd`/`scanMat` result rather than a parse — a host that scans a dictionary for its
+// own indexes reads the same bytes twice otherwise, and the second scan measured 46% of one
+// such tier's per-dictionary CPU (14.6 ms of 31.5 ms on a 20,000-entry dictionary). All
+// three return a `FileSummaries` keyed as `summarizeFiles` keys it, so they fold together
+// through `mergeFileSummaries` whichever way each file was summarised.
export {
buildUsageIndex,
buildUsageIndexFromSummaries,
mergeFileSummaries,
summarizeFiles,
summarizeParsedModel,
+ summarizeSlddScan,
+ summarizeMatScan,
resolveName,
} from './datamodel/usage/UsageIndex.js';
export type {
diff --git a/test/usageScanSummary.test.ts b/test/usageScanSummary.test.ts
new file mode 100644
index 0000000..0b3d2a9
--- /dev/null
+++ b/test/usageScanSummary.test.ts
@@ -0,0 +1,469 @@
+// Copyright 2026 The MathWorks, Inc.
+//
+// Summarising a dictionary or a MAT-file from a SCAN the caller already ran, and the one
+// thing that has to be true of it: it is the same summary `summarizeFiles` builds from the
+// bytes.
+//
+// `summarizeSlddScan` and `summarizeMatScan` exist because a host reads these files for its
+// own indexes and then handed the bytes to `summarizeFiles`, which scanned them a second
+// time — 46% of one such tier's per-dictionary CPU. Which makes two ways to summarise one
+// file, and the tests that matter are the ones BETWEEN them. Not "does the new path answer
+// plausibly": a summariser reachable two ways that reads a different field on one of them
+// answers plausibly on both and is still wrong, and only a comparison catches it.
+//
+// So the shape here is an EQUALITY SWEEP: for every committed `.sldd` and `.mat` this suite
+// can reach, plus the constructed shapes no fixture has,
+//
+// summarizeFiles([{ srcId, filename, bytes }]) == summarize*Scan(scan(bytes), srcId, filename)
+//
+// compared whole — the `names` Sets, the map KEYS, and `slddRefs` in order. The fixtures are
+// globbed rather than listed, so a dictionary added for some unrelated reason is covered by
+// this the day it lands, and the globs are guarded by a count so an empty one cannot make
+// every case disappear silently.
+//
+// AND A SECOND ASSERTION BESIDE IT, because the first one alone is now weaker than it looks:
+// `summarizeFiles` was refactored to route THROUGH these functions, so the two sides share
+// the code that builds the summary and a wrong rule inside it would agree with itself. So
+// every swept file is also compared against a summary derived from the FULL readers —
+// `readSlddContent` and `parseMat`, the parsers the scanners substitute for. Verified by
+// mutation: without that, dropping `filter(Boolean)` from the summary left the whole sweep
+// green. See `expectMatchesFullReader`.
+//
+// The refusal cases are the ones worth reading. Both scanners fall back to the full reader
+// for bytes they have not been proven equivalent on, so the summary of a file that took the
+// fallback must ALSO match, and a case that merely happened to take the fast path would prove
+// nothing about that. For the dictionary the refusal is asserted directly, through
+// `scanDataSourceXml`, so it cannot quietly stop being one; for the MAT it is not assertable
+// from here and the case says so where it sits.
+import { describe, it, expect } from 'vitest';
+import { readFileSync, readdirSync } from 'node:fs';
+import { fileURLToPath } from 'node:url';
+import { strToU8, unzipSync, zipSync } from 'fflate';
+import { scanDataSourceXml } from '../src/datamodel/parser/SlddScan.js';
+import {
+ buildUsageIndex,
+ buildUsageIndexFromSummaries,
+ isJsonTextBytes,
+ mergeFileSummaries,
+ normalizeRefNames,
+ parseMat,
+ parseModel,
+ readSlddContent,
+ scanMat,
+ scanSldd,
+ slddChunkContent,
+ summarizeFiles,
+ summarizeMatScan,
+ summarizeParsedModel,
+ summarizeSlddScan,
+} from '../src/index.js';
+import type { UsageFile } from '../src/index.js';
+
+const FIXTURES = fileURLToPath(new URL('./fixtures/', import.meta.url));
+const MCOS = fileURLToPath(new URL('./fixtures/mcos/', import.meta.url));
+
+function bytesOf(path: string): ArrayBuffer {
+ const b = readFileSync(path);
+ return b.buffer.slice(b.byteOffset, b.byteOffset + b.byteLength) as ArrayBuffer;
+}
+
+const fixtureBytes = (rel: string): ArrayBuffer => bytesOf(fileURLToPath(new URL(`./fixtures/${rel}`, import.meta.url)));
+const artifact = (rel: string): ArrayBuffer =>
+ bytesOf(fileURLToPath(new URL(`./parity/artifacts/${rel}`, import.meta.url)));
+
+// A distinct srcId, never equal to the filename: the two are separate arguments and the
+// summary carries them differently — the srcId verbatim, the map key off the basename
+// case-folded — so a case where they coincided would let either stand in for the other.
+const file = (name: string, bytes: ArrayBuffer, srcId = `src:${name}`): UsageFile => ({
+ srcId,
+ filename: name,
+ bytes,
+});
+
+// ---- the two assertions -------------------------------------------------------------
+
+/**
+ * THE CONTRACT, for a dictionary: summarising the bytes and summarising a scan of those
+ * bytes produce the same `FileSummaries`, compared whole.
+ *
+ * `toEqual` is what makes this worth writing — it walks into the `Map` and the `Set`, so a
+ * wrong KEY (the basename rule) fails here just as a wrong name does, and `slddRefs` is an
+ * array so its ORDER is compared too. That order is contract: `resolveName` walks it and
+ * takes the first hit.
+ */
+function expectSlddPathsAgree(f: UsageFile): void {
+ expect(summarizeSlddScan(scanSldd(f.bytes), f.srcId, f.filename)).toEqual(summarizeFiles([f]));
+}
+
+function expectMatPathsAgree(f: UsageFile): void {
+ expect(summarizeMatScan(scanMat(f.bytes), f.srcId, f.filename)).toEqual(summarizeFiles([f]));
+}
+
+/**
+ * AND THE ORACLE, without which the equality above is by construction rather than by test.
+ *
+ * `summarizeFiles` now routes THROUGH these functions — that is the point of the refactor,
+ * one implementation and not two — so the two sides of the equality share the code that
+ * builds the summary and could only differ in which bytes were scanned and how the key was
+ * spelled. A wrong `names` rule would agree with itself perfectly. Verified by mutation:
+ * dropping `filter(Boolean)` and `refBasename` from the summary left the whole sweep green.
+ *
+ * So the sweep also compares against a summary derived from the FULL readers — the parsers
+ * the scanners substitute for — with the two rules spelled out here rather than imported:
+ * the `''` placeholder a scan keeps for position is not a name, and a reference is matched
+ * by basename case-folded. That is an independent derivation, and it is what makes the
+ * sweep able to fail.
+ */
+function expectMatchesFullReader(f: UsageFile, kind: 'sldd' | 'mat'): void {
+ const key = f.filename.toLowerCase(); // every swept file is named by its bare basename
+ if (kind === 'mat') {
+ expect(summarizeMatScan(scanMat(f.bytes), f.srcId, f.filename).matByName.get(key)).toEqual({
+ srcId: f.srcId,
+ names: new Set(parseMat(f.bytes).variables.map((v) => v.name).filter((n) => n !== '')),
+ slddRefs: [],
+ });
+ return;
+ }
+ const content = slddChunkContent(readSlddContent(f.bytes, []));
+ const entries = (content?.entries ?? []) as { name?: unknown }[];
+ expect(summarizeSlddScan(scanSldd(f.bytes), f.srcId, f.filename).slddByName.get(key)).toEqual({
+ srcId: f.srcId,
+ names: new Set(entries.map((e) => (typeof e?.name === 'string' ? e.name : '')).filter((n) => n !== '')),
+ slddRefs: (content ? normalizeRefNames(content['Dictionary References']) : []).map((r) =>
+ (r.split(/[\\/]/).pop() || r).toLowerCase(),
+ ),
+ });
+}
+
+// ---- fixture-wide sweep, dictionaries -------------------------------------------------
+
+const slddFixtures = readdirSync(FIXTURES).filter((n) => n.endsWith('.sldd')).sort();
+
+// The parity artifacts are the same two dictionaries MATLAB wrote in BOTH spellings, and
+// they are the largest committed ones — so they are the sweep's realistic input, where the
+// hand-built cases below are its sharp one.
+const slddArtifacts = ['binary/cases.sldd', 'binary/params.sldd', 'text/cases.sldd', 'text/params.sldd'];
+
+describe('summarizeSlddScan — the same summary summarizeFiles builds, on every committed dictionary', () => {
+ it('found the fixtures, in both spellings', () => {
+ // Guards the glob twice over: an empty list would make every case below disappear
+ // while the suite reported success, and a list of only one spelling would leave the
+ // zip-or-JSON fork — the one place the two readers differ most — untested.
+ expect(slddFixtures.length).toBeGreaterThan(15);
+ const zipped = slddFixtures.map((n) => !isJsonTextBytes(new Uint8Array(fixtureBytes(n))));
+ expect(zipped.filter(Boolean).length).toBeGreaterThan(0);
+ expect(zipped.filter((z) => !z).length).toBeGreaterThan(0);
+ });
+
+ it.each(slddFixtures)('agrees on fixtures/%s', (name) => {
+ const f = file(name, fixtureBytes(name));
+ expectSlddPathsAgree(f);
+ expectMatchesFullReader(f, 'sldd');
+ });
+
+ it.each(slddArtifacts)('agrees on artifacts/%s', (rel) => {
+ const f = file(rel.split('/')[1], artifact(rel), `src:${rel}`);
+ expectSlddPathsAgree(f);
+ expectMatchesFullReader(f, 'sldd');
+ });
+
+ it('is not comparing empty summaries', () => {
+ // The sweep is worthless if every dictionary summarises to nothing, so the two
+ // artifacts are pinned as carrying real names — in both spellings, since a reader that
+ // returned nothing for one of them would agree with itself perfectly.
+ for (const rel of ['binary/cases.sldd', 'text/cases.sldd']) {
+ const names = summarizeSlddScan(scanSldd(artifact(rel)), 'src', 'cases.sldd').slddByName.get('cases.sldd')?.names;
+ expect(names?.size, rel).toBeGreaterThan(3);
+ }
+ });
+});
+
+// ---- constructed dictionaries ---------------------------------------------------------
+
+/** A textual `.sldd` defining `names` and referencing `refs`, as usageIndex.test.ts builds one. */
+function slddBytes(names: string[], refs: unknown[] = []): ArrayBuffer {
+ const u8 = strToU8(
+ JSON.stringify({
+ __MW_TEXT_PARTS__: {
+ '__MW_TEXT_PART__/data/chunk0': {
+ __MW_TEXT_content: {
+ entries: names.map((name) => ({ name, class: 'Simulink.Parameter' })),
+ 'Dictionary References': refs,
+ AllowAccessBWS: false,
+ },
+ },
+ },
+ }),
+ );
+ return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;
+}
+
+const CHUNK_PART = 'data/chunk0.xml';
+const chunkXmlText = (body: string): string =>
+ '\n'
+ + `\n${body}\n`;
+
+/**
+ * A real compressed `.sldd`, unzipped, its data part replaced, rezipped — the technique
+ * usageIndex.test.ts and parseWarnings.test.ts use, so the bytes under test differ from a
+ * file MATLAB wrote in exactly the one way the case is about and the entries come back
+ * through the real binary reader.
+ */
+function binarySlddWithChunk(body: string): ArrayBuffer {
+ const entries = unzipSync(new Uint8Array(fixtureBytes('compressed.sldd')));
+ expect(Object.keys(entries)).toContain(CHUNK_PART); // the fixture really holds it
+ entries[CHUNK_PART] = strToU8(chunkXmlText(body));
+ const zipped = zipSync(entries);
+ return zipped.buffer.slice(zipped.byteOffset, zipped.byteOffset + zipped.byteLength) as ArrayBuffer;
+}
+
+const xmlEntry = (name: string): string => ``;
+const xmlRef = (name: string): string =>
+ ``;
+
+describe('summarizeSlddScan — the shapes no committed fixture has', () => {
+ // Every one of these is a shape the two paths could disagree on and no fixture exercises:
+ // references at all (no committed dictionary has one), an entry with no name, and the
+ // degenerate empty document.
+ const CASES: [what: string, load: () => UsageFile][] = [
+ [
+ // Both spellings of a reference and a mixed-case one, because the summary's `slddRefs`
+ // is `refs.map(refBasename)` — the fold that makes `Sub.SLDD` resolve — and an empty
+ // reference that `normalizeRefNames` drops, which is where the two paths could differ
+ // by one position.
+ 'a textual dictionary with references, in both spellings and mixed case',
+ () => file('top.sldd', slddBytes(['Kp', 'Ki'], ['LEAF.SLDD', { file: 'other/Mid.sldd' }, ''])),
+ ],
+ [
+ // Entries the scanner reports as `''`, which both paths must drop from the Set while
+ // the scan keeps them in position. A case with the unnamed entry FIRST, so a path that
+ // dropped it late would shift the rest.
+ 'a compressed dictionary with unnamed entries around a named one',
+ () =>
+ file(
+ 'holes.sldd',
+ binarySlddWithChunk(
+ ``
+ + xmlEntry('aParam')
+ + ``,
+ ),
+ ),
+ ],
+ [
+ 'a compressed dictionary with entries and references in one document',
+ () => file('both.sldd', binarySlddWithChunk(xmlEntry('A') + xmlRef('one.sldd') + xmlEntry('B') + xmlRef('two.sldd'))),
+ ],
+ ['a dictionary defining nothing at all', () => file('empty.sldd', slddBytes([]))],
+ [
+ // A JSON file short of the shape a dictionary has — a partial write really produces
+ // this, and both paths owe the empty summary rather than a throw on either.
+ 'valid JSON that is not a dictionary',
+ () => file('notadict.sldd', (() => {
+ const u8 = strToU8(JSON.stringify({ nothing: true }));
+ return u8.buffer.slice(u8.byteOffset, u8.byteOffset + u8.byteLength) as ArrayBuffer;
+ })()),
+ ],
+ ];
+
+ for (const [what, load] of CASES) {
+ it(`agrees on ${what}`, () => {
+ expectSlddPathsAgree(load());
+ });
+ }
+
+ it('really is comparing summaries with the awkward parts in them', () => {
+ // The equalities above would hold vacuously if the scanner returned nothing for these,
+ // so the two fields they were chosen for are pinned by value: references folded and in
+ // order, and the unnamed entries absent from a Set that still holds the named one.
+ const refs = CASES[0][1]();
+ expect(summarizeSlddScan(scanSldd(refs.bytes), refs.srcId, refs.filename).slddByName.get('top.sldd')).toEqual({
+ srcId: 'src:top.sldd',
+ names: new Set(['Kp', 'Ki']),
+ slddRefs: ['leaf.sldd', 'mid.sldd'],
+ });
+
+ const holes = CASES[1][1]();
+ const summary = summarizeSlddScan(scanSldd(holes.bytes), holes.srcId, holes.filename).slddByName.get('holes.sldd');
+ expect(scanSldd(holes.bytes).names).toEqual(['', 'aParam', '']); // the scan keeps the positions
+ expect(summary?.names).toEqual(new Set(['aParam'])); // the summary does not
+ });
+
+ it('agrees for a dictionary the fast path REFUSES and the full reader recovers', () => {
+ // The case the fallback exists for, and the reason it is asserted rather than assumed:
+ // `scanSldd` hands a document it has not been proven equivalent on to
+ // `readSlddContent`, so what `summarizeSlddScan` is given for such a file is the full
+ // reader's answer — and it still has to be the summary `summarizeFiles` builds. An
+ // entity in a name is the documented refusal (fast-xml-parser decodes `&`, reading
+ // the bytes plainly would not).
+ const body = xmlEntry('A&B') + xmlEntry('plain');
+ expect(() => scanDataSourceXml(strToU8(chunkXmlText(body)))).toThrow(/entity/i);
+ const refused = file('entity.sldd', binarySlddWithChunk(body));
+ // The recovery really happened — the decoded name, not the raw bytes.
+ expect(scanSldd(refused.bytes).names).toEqual(['A&B', 'plain']);
+ expectSlddPathsAgree(refused);
+ });
+
+ it('keys on the basename case-folded, and carries the srcId verbatim', () => {
+ // The key rule is the one thing a caller cannot pass in, so it is pinned directly: a
+ // reference authored as `Params.SLDD` inside a model has to find a file stored under a
+ // directory path, and the srcId — the host's own identifier, a URI as often as a path —
+ // has to come back out untouched.
+ const summaries = summarizeSlddScan(scanSldd(slddBytes(['Kp'])), 'vscode://x/Params.SLDD?v=2', '/work/dicts/Params.SLDD');
+ expect([...summaries.slddByName.keys()]).toEqual(['params.sldd']);
+ expect(summaries.slddByName.get('params.sldd')?.srcId).toBe('vscode://x/Params.SLDD?v=2');
+ // And the same file through `summarizeFiles`, so the key rule is not two rules.
+ expect(summaries).toEqual(
+ summarizeFiles([
+ { srcId: 'vscode://x/Params.SLDD?v=2', filename: '/work/dicts/Params.SLDD', bytes: slddBytes(['Kp']) },
+ ]),
+ );
+ });
+
+ it('holds nothing but the one dictionary', () => {
+ // The other two halves of the `FileSummaries` are empty, which is what lets a caller
+ // fold this in beside a model it parsed and a MAT it scanned.
+ const only = summarizeSlddScan(scanSldd(slddBytes(['Kp'])), 'src', 'a.sldd');
+ expect(only.models).toEqual([]);
+ expect(only.matByName.size).toBe(0);
+ expect(only.slddByName.size).toBe(1);
+ });
+});
+
+// ---- fixture-wide sweep, MAT-files ----------------------------------------------------
+
+const matFixtures = [
+ ...readdirSync(FIXTURES).filter((n) => n.endsWith('.mat')).map((n) => n),
+ ...readdirSync(MCOS).filter((n) => n.endsWith('.mat')).map((n) => `mcos/${n}`),
+].sort();
+
+describe('summarizeMatScan — the same summary summarizeFiles builds, on every committed MAT-file', () => {
+ it('found the fixtures at all', () => {
+ // `strings_v73.mat` is in this list on purpose and is the one file here neither reader
+ // can read: it is HDF5, both paths refuse it, and the case for that is at the bottom.
+ expect(matFixtures.length).toBeGreaterThan(10);
+ });
+
+ it.each(matFixtures.filter((n) => !n.endsWith('_v73.mat')))('agrees on fixtures/%s', (rel) => {
+ const f = file(rel.split('/').pop()!, fixtureBytes(rel), `src:${rel}`);
+ expectMatPathsAgree(f);
+ expectMatchesFullReader(f, 'mat');
+ });
+
+ it('agrees on artifacts/mat/cases.mat', () => {
+ const f = file('cases.mat', artifact('mat/cases.mat'));
+ expectMatPathsAgree(f);
+ expectMatchesFullReader(f, 'mat');
+ });
+
+ it('is not comparing empty summaries', () => {
+ // `strings.mat` holds eleven named variables and the unnamed MCOS element — so this
+ // pins both that the sweep has substance and that the placeholder is dropped.
+ const names = summarizeMatScan(scanMat(fixtureBytes('strings.mat')), 'src', 'strings.mat').matByName.get('strings.mat')?.names;
+ expect(names?.size).toBe(11);
+ expect(names?.has('')).toBe(false);
+ expect(scanMat(fixtureBytes('strings.mat')).names).toContain(''); // which the scan itself keeps
+ });
+
+ it('agrees for a truncated file, where the answer is the repaired read', () => {
+ // The MAT half of the recovery case: a file cut inside its last record. `parseMat`
+ // clamps every declared length against the buffer and hands back the names it could
+ // reach; `scanMat` will not reproduce a repair, so what comes out for these bytes is
+ // that clamped answer — six names on the intact file, five here — and the summary of it
+ // must still be the summary either path builds.
+ //
+ // That the fast path REFUSED rather than agreeing by luck is not assertable from here:
+ // the fallback returns `parseMat`'s answer, so the two are indistinguishable in the
+ // result. matScan.test.ts asserts it directly, by counting inflations through the
+ // native-inflate seam. What this case owes is the equality, on bytes that took that
+ // path.
+ const whole = fixtureBytes('sparse_cases.mat');
+ const cut = new Uint8Array(whole).subarray(0, whole.byteLength - 24).slice().buffer as ArrayBuffer;
+ expect(scanMat(whole).names).toHaveLength(6);
+ expect(parseMat(cut).variables.map((v) => v.name)).toEqual(scanMat(cut).names);
+ expect(scanMat(cut).names).toHaveLength(5); // a comparison with something in it
+ expectMatPathsAgree(file('sparse_cases.mat', cut));
+ });
+
+ it('holds nothing but the one MAT-file, and inherits nothing', () => {
+ const only = summarizeMatScan(scanMat(fixtureBytes('mcos/Numeric.mat')), 'src', 'Numeric.mat');
+ expect(only.models).toEqual([]);
+ expect(only.slddByName.size).toBe(0);
+ // A MAT-file references no dictionary — the field exists because `DataSummary` is one
+ // shape for both kinds, and for this kind it is always empty.
+ expect(only.matByName.get('numeric.mat')?.slddRefs).toEqual([]);
+ });
+});
+
+// ---- the two paths that cannot be compared, and why -----------------------------------
+
+describe('bytes no reader can read', () => {
+ it.each([
+ ['a dictionary that is neither a zip nor JSON', 'junk.sldd', () => scanSldd(strToU8('not a zip').buffer as ArrayBuffer)],
+ ['a MAT-file MATLAB wrote as HDF5', 'strings_v73.mat', () => scanMat(fixtureBytes('strings_v73.mat'))],
+ ])('has no summary to compare for %s, because there is no scan', (_what, name, scan) => {
+ // The one place the paths cannot be equal, and the same asymmetry `summarizeParsedModel`
+ // has: `summarizeFiles` drops an unreadable file silently (its per-file catch), while
+ // these functions are never reached for one — the caller's own scan threw first and it
+ // has nothing to hand over. So the summary they do not produce is the summary
+ // `summarizeFiles` does not produce either.
+ expect(scan).toThrow();
+ const bytes = name === 'junk.sldd' ? (strToU8('not a zip').buffer as ArrayBuffer) : fixtureBytes(name);
+ expect(summarizeFiles([file(name, bytes)])).toEqual({
+ models: [],
+ slddByName: new Map(),
+ matByName: new Map(),
+ });
+ });
+});
+
+// ---- composition ----------------------------------------------------------------------
+
+describe('a whole workspace summarised from what the caller already holds', () => {
+ // A real model MATLAB wrote, so the parse is not a construction of this file's: mdlcases.mdl
+ // links `mdlparams.sldd`, defines `inner`, `span` and `tau` in its own workspace, and its
+ // blocks read `Kp`, `Ki`, `inner`, `[tau 1]` and `span`. Which makes `Kp` resolve in the
+ // linked dictionary and `Ki` only through the reference that dictionary carries — so the
+ // chain the SCAN's own `refs` supplied is load-bearing in the answers below.
+ const model = file('mdlcases.mdl', artifact('mdl/mdlcases.mdl'));
+ const top = file('mdlparams.sldd', slddBytes(['Kp'], ['leaf.sldd']));
+ const leaf = file('leaf.sldd', slddBytes(['Ki']));
+ const mat = file('extra.mat', fixtureBytes('mcos/Numeric.mat'));
+ const parts = () => [
+ summarizeParsedModel(parseModel(model.bytes, model.filename), model.srcId, model.filename),
+ summarizeSlddScan(scanSldd(top.bytes), top.srcId, top.filename),
+ summarizeSlddScan(scanSldd(leaf.bytes), leaf.srcId, leaf.filename),
+ summarizeMatScan(scanMat(mat.bytes), mat.srcId, mat.filename),
+ ];
+
+ it('folds into the summaries summarizeFiles builds over the same four files', () => {
+ // The reason all three of these return a `FileSummaries` rather than a bare summary: the
+ // model is the file the host parsed for its own rows, the dictionaries and the MAT are
+ // files it scanned for its own indexes, and the parts fold together — models in file
+ // order, maps keyed alike — into what one pass over the four files produces.
+ expect(mergeFileSummaries(parts())).toEqual(summarizeFiles([model, top, leaf, mat]));
+ });
+
+ it('answers what buildUsageIndex answers from the bytes', () => {
+ const composed = buildUsageIndexFromSummaries(mergeFileSummaries(parts()));
+ const whole = buildUsageIndex([model, top, leaf, mat]);
+
+ // Not a vacuous comparison: one name resolves in the linked dictionary and one down its
+ // reference, and both directions of the index have entries to compare.
+ expect(whole.usagesOf(top.srcId, 'Kp')).toHaveLength(1);
+ expect(whole.usagesOf(leaf.srcId, 'Ki')).toHaveLength(1);
+ expect(whole.paramsOf(model.srcId, '1')).not.toEqual([]);
+
+ expect(composed.models).toEqual(whole.models);
+ for (const [srcId, name] of [
+ [top.srcId, 'Kp'],
+ [leaf.srcId, 'Ki'],
+ [model.srcId, 'tau'],
+ [mat.srcId, 'Numeric'],
+ ] as const) {
+ expect(composed.usagesOf(srcId, name)).toEqual(whole.usagesOf(srcId, name));
+ }
+ for (const sid of ['1', '2', '3', '6', '8']) {
+ expect(composed.paramsOf(model.srcId, sid)).toEqual(whole.paramsOf(model.srcId, sid));
+ }
+ });
+});