diff --git a/dist/node/index.d.ts.map b/dist/node/index.d.ts.map index 0dad06d..7d7be0d 100644 --- a/dist/node/index.d.ts.map +++ b/dist/node/index.d.ts.map @@ -1 +1 @@ -{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAYA,OAAO,wCAAwC,CAAC;AAEhD,OAAO,EAAE,aAAa,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,qCAAqC,CAAC;AAwBlF,eAAO,MAAM,aAAa,uBAAiB,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAOxE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,WAAW,EAAE,CAiBpG"} \ No newline at end of file +{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAYA,OAAO,wCAAwC,CAAC;AAEhD,OAAO,EAAE,aAAa,IAAI,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEvE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,2BAA2B,CAAC;AAC7D,OAAO,EAAY,KAAK,YAAY,EAAE,MAAM,qCAAqC,CAAC;AA4ClF,eAAO,MAAM,aAAa,uBAAiB,CAAC;AAE5C,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,GAAG,WAAW,CAOxE;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,YAAY,EAAE,GAAG,WAAW,EAAE,CAiBpG"} \ No newline at end of file diff --git a/dist/node/index.js b/dist/node/index.js index f72a27a..7f5b3fd 100644 --- a/dist/node/index.js +++ b/dist/node/index.js @@ -14,6 +14,7 @@ import { setNativeInflate } from '../datamodel/parser/Inflate.js'; import { createSession as _createSession } from '../core/DataModel.js'; import { ingest } from '../core/ingest.js'; import { reasonOf } from '../datamodel/parser/ParseWarning.js'; +import { isTomlProjectFile } from '../datamodel/fileKinds.js'; // Arm the fast inflate engine explicitly. `Inflate.ts` can find `node:zlib` on its // own through `process.getBuiltinModule`, but that method only exists from Node 22.3, // and this package supports consumers on older hosts — the VS Code extension declares @@ -33,6 +34,24 @@ setNativeInflate({ zlibHead: (prefix) => inflateSync(prefix, { finishFlush: zlibConstants.Z_SYNC_FLUSH }), }); const SUPPORTED = new Set(['.sldd', '.slx', '.mdl', '.mat', '.prj']); +/** + * Whether a directory entry is something `loadFromPath` can open. + * + * An extension set AND a name test, because one supported file is identified by its + * NAME: a TOML-format project's entire definition is a file called `matlab.toml`, with + * no `.prj` marker and no `resources/` left to find it by. Matching `.toml` instead + * would call every `Cargo.toml` and `pyproject.toml` in a folder a MATLAB project — + * see `isTomlProjectFile`, which is why that predicate exists. + * + * It is that shared predicate and deliberately not a literal here, because this is the + * second path over the same rule: `ingest` dispatches a project on `isProjectFile`, so + * a scan that disagrees either hides a file that opens fine or lists one nothing can + * open. Keeping the extensions as a set is still right — they are this module's own + * business, and `ingest` sniffs CONTENT rather than extension for all of them. + */ +function isSupportedName(name) { + return SUPPORTED.has(extname(name).toLowerCase()) || isTomlProjectFile(name); +} // Re-export createSession so Node consumers can import everything from one place. export const createSession = _createSession; export function loadFromPath(session, path) { @@ -65,7 +84,7 @@ export function loadFromPath(session, path) { * minus the stderr write. Same shape as SlddNode.parse's sink. */ export function loadDirectory(session, dir, skipped) { - const names = readdirSync(dir).filter((n) => SUPPORTED.has(extname(n).toLowerCase())).sort(); + const names = readdirSync(dir).filter(isSupportedName).sort(); const out = []; for (const name of names) { try { diff --git a/dist/node/index.js.map b/dist/node/index.js.map index 96b4c40..c72980c 100644 --- a/dist/node/index.js.map +++ b/dist/node/index.js.map @@ -1 +1 @@ -{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file +{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"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"} \ No newline at end of file diff --git a/src/node/index.ts b/src/node/index.ts index b0ddce8..6b8c64c 100644 --- a/src/node/index.ts +++ b/src/node/index.ts @@ -17,6 +17,7 @@ import { ingest } from '../core/ingest.js'; import type { Session } from '../core/DataModel.js'; import type { ISourceNode } from '../core/NodeInterfaces.js'; import { reasonOf, type ParseWarning } from '../datamodel/parser/ParseWarning.js'; +import { isTomlProjectFile } from '../datamodel/fileKinds.js'; // Arm the fast inflate engine explicitly. `Inflate.ts` can find `node:zlib` on its // own through `process.getBuiltinModule`, but that method only exists from Node 22.3, @@ -39,6 +40,25 @@ setNativeInflate({ const SUPPORTED = new Set(['.sldd', '.slx', '.mdl', '.mat', '.prj']); +/** + * Whether a directory entry is something `loadFromPath` can open. + * + * An extension set AND a name test, because one supported file is identified by its + * NAME: a TOML-format project's entire definition is a file called `matlab.toml`, with + * no `.prj` marker and no `resources/` left to find it by. Matching `.toml` instead + * would call every `Cargo.toml` and `pyproject.toml` in a folder a MATLAB project — + * see `isTomlProjectFile`, which is why that predicate exists. + * + * It is that shared predicate and deliberately not a literal here, because this is the + * second path over the same rule: `ingest` dispatches a project on `isProjectFile`, so + * a scan that disagrees either hides a file that opens fine or lists one nothing can + * open. Keeping the extensions as a set is still right — they are this module's own + * business, and `ingest` sniffs CONTENT rather than extension for all of them. + */ +function isSupportedName(name: string): boolean { + return SUPPORTED.has(extname(name).toLowerCase()) || isTomlProjectFile(name); +} + // Re-export createSession so Node consumers can import everything from one place. export const createSession = _createSession; @@ -73,7 +93,7 @@ export function loadFromPath(session: Session, path: string): ISourceNode { * minus the stderr write. Same shape as SlddNode.parse's sink. */ export function loadDirectory(session: Session, dir: string, skipped?: ParseWarning[]): ISourceNode[] { - const names = readdirSync(dir).filter((n) => SUPPORTED.has(extname(n).toLowerCase())).sort(); + const names = readdirSync(dir).filter(isSupportedName).sort(); const out: ISourceNode[] = []; for (const name of names) { try { diff --git a/test/node-loader.test.ts b/test/node-loader.test.ts index b48e3b0..5e99f8a 100644 --- a/test/node-loader.test.ts +++ b/test/node-loader.test.ts @@ -147,4 +147,31 @@ describe('node loader', () => { expect(loaded.map((n) => n.name)).toEqual(['legacy.MDL', 'upper.SLDD']); expect(errors).toEqual([]); }); + + it('loadDirectory finds a TOML project by NAME, and no other .toml', () => { + // The one supported file an extension set cannot express. A project converted with + // `matlab.project.DefinitionFiles.Toml` has no `.prj` and no `resources/` — its whole + // definition is a file called `matlab.toml` — so a scan filtering on extensions alone + // reads a real project directory as holding nothing, which is how this started. + // + // The marker is MATLAB-WRITTEN, copied from the parity artifact rather than typed + // here: a hand-authored TOML standing in for what MATLAB writes is how a reader comes + // to depend on a spelling MATLAB never uses. + const dir = mkdtempSync(join(tmpdir(), 'dex-toml-dir-')); + copyFileSync( + fileURLToPath(new URL('./parity/artifacts/project/Toml/parityProject/matlab.toml', import.meta.url)), + join(dir, 'matlab.toml'), + ); + // The hazard the NAME test exists for, written so its absence below is the filter + // rejecting it and not an empty directory: `.toml` is the config format of half the + // tooling a MATLAB repository sits beside. + writeFileSync(join(dir, 'Cargo.toml'), '[package]\nname = "not-a-matlab-project"\n'); + writeFileSync(join(dir, 'pyproject.toml'), '[project]\nname = "also-not"\n'); + + const skipped: ParseWarning[] = []; + const loaded = loadDirectory(createSession(), dir, skipped); + + expect(loaded.map((n) => n.name)).toEqual(['matlab.toml']); + expect(skipped).toEqual([]); // listed AND opened — a listing that then fails is worse + }); });