fix(compartment-mapper,bundle-source): async parser support#3186
Conversation
📚 Pull Request Stack
Managed by gh-stack |
🦋 Changeset detectedLatest commit: d5be9d8 The changes in this PR will be included in the next version bump. This PR includes changesets to release 2 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
f81a724 to
1463114
Compare
d46967c to
302f011
Compare
404d032 to
a423cb4
Compare
d47070d to
6e65a53
Compare
6e65a53 to
cef6137
Compare
There was a problem hiding this comment.
Pull request overview
This PR completes and formalizes async-parser support in @endo/compartment-mapper and @endo/bundle-source, splitting sync vs async parser implementations/types and updating the parser-mapping pipeline accordingly. It also adds a fixture-based test covering import() in ESM for compartment-mapper.
Changes:
- Introduces
AsyncParserImplementation(and related type updates) to properly model async parsers alongside sync parsers. - Refactors
map-parser.jsto generate distinct sync vs async extension parsers and adds type guards to select the correct pipeline. - Adds a new ESM dynamic
import()fixture and AVA test in@endo/compartment-mapper.
Reviewed changes
Copilot reviewed 8 out of 11 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/compartment-mapper/test/fixtures-dynamic-import-esm/node_modules/app/package.json | Adds ESM fixture package metadata for dynamic import test. |
| packages/compartment-mapper/test/fixtures-dynamic-import-esm/node_modules/app/index.js | Adds ESM entry that uses import() dynamically. |
| packages/compartment-mapper/test/fixtures-dynamic-import-esm/node_modules/app/foo.js | Adds simple default export for the dynamic import target. |
| packages/compartment-mapper/test/dynamic-import-esm.test.js | Adds AVA test exercising ESM dynamic import via scaffold. |
| packages/compartment-mapper/src/types/internal.ts | Narrows internal operator type export surface while preserving unions. |
| packages/compartment-mapper/src/types/external.ts | Defines AsyncParserImplementation, tightens sync parser typing, and adds SyncParserForLanguage. |
| packages/compartment-mapper/src/map-parser.js | Splits sync/async parser generation and adds shared language resolution + guards. |
| packages/compartment-mapper/src/link.js | Simplifies access to heuristicImports without unnecessary cast. |
| packages/compartment-mapper/src/import-hook.js | Adds a parse-fn type guard and uses it to enforce sync-only importNow paths. |
| packages/bundle-source/src/endo.js | Updates TypeScript-related parsers to be explicitly async parser implementations. |
| .changeset/fuzzy-hornets-shout.md | Declares patch releases for affected packages. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
8975d6a to
8101f56
Compare
There was a problem hiding this comment.
(should) reuse these from commons
| ) { | ||
| return languageForModuleSpecifier[specifier]; | ||
| } | ||
| return languageForExtension[extension] || extension; |
There was a problem hiding this comment.
(untrusted input) result of resolveLanguage is trusted later on as if it was an internal key, but it's input from untrusted source. index.toString or index.__proto__ are possible filenames
There was a problem hiding this comment.
index--the Record of transform functions--is also untrusted input, so I'm not sure what you're proposing we do.
There was a problem hiding this comment.
Totally different category. Extension is untrusted input for the user of Endo, it's coming from untrusted packages. We need to either use a fixed listy of what is available and throw early here, or very carefully handle the return value to not match prototype fields. The former would be my choice
There was a problem hiding this comment.
Where is it being used as if it is trusted? has omits unowned properties.
There was a problem hiding this comment.
I'm pretty sure this is not a problem in practice, but I've added some extra defense anyway:
const resolveLanguage = (
specifier,
location,
languageForExtension,
languageForModuleSpecifier,
) => {
const extension = parseExtension(location);
if (
!extensionImpliesLanguage(extension) &&
has(languageForModuleSpecifier, specifier)
) {
return languageForModuleSpecifier[specifier];
}
// new check
if (has(languageForExtension, extension)) {
return languageForExtension[extension];
}
return extension;
};So resolveLanguage cannot return anything untoward on languageForExtension. Further:
const validateLanguageForExtension = (
languageForExtension,
parserForLanguage,
) => {
const languageForExtensionEntries = [];
const problems = [];
for (const [extension, language] of entries(languageForExtension)) {
if (has(parserForLanguage, language)) {
languageForExtensionEntries.push([extension, language]);
} else {
problems.push(`${q(language)} for extension ${q(extension)}`);
}
}
if (problems.length > 0) {
throw Error(`No parser available for language: ${problems.join(', ')}`);
}
// null-proto object
return assign(create(null), fromEntries(languageForExtensionEntries));
};Now languageForExtension shouldn't even have such properties.
| * @param {*} options | ||
| * @returns {Generator<ReturnType<ModuleTransform> | ReturnType<SyncModuleTransform>, ParseResult | Promise<ParseResult>, any>} | ||
| */ | ||
| function* getParserGenerator( |
There was a problem hiding this comment.
why put it on a trampoline if it only exists in an async implementation's scope? I doubt it's more efficient that way. Wait, there's 2 of these. they're functionally identical but the other one is typed to optionally return a promise, and that seems wrong (either wrong types or missing yield to remove the promise involvement)
|
This is where I'd put things: 46f1fe4 |
8101f56 to
fe28002
Compare
Types are the problem here, though. The problem is typing These two factories could be combined into a single polymorphic factory function w/ overloads and inner conditionals, but that looks like adding even more complexity to me. So that type-fantasy matches reality, I threw in a guard or two to check we're actually working with sync parsers when we expect to be. |
|
(The alternative is just fudging the types and crossing fingers) |
|
Trampolines are in use here to make it possible for code to be reused between sync and async. Having two copies of the same exact function just to hang different types off of defeats the purpose. The types for the generator are only consumed by trampolines and don't matter for correctness of what we do here. If there is an unavoidable divergence in the two implementations, removed have to be removed |
I see what you're saying here. I'll take another look at it. |
a63baeb to
8ef1ce3
Compare
|
@naugtur OK, I've reverted most of my changes to /**
* @typedef GetSyncParserGeneratorFn
* @param {ParserGeneratorConfig} config
* @param {Uint8Array} bytes
* @param {string} specifier
* @param {string} location
* @param {string} packageLocation
* @param {*} options
* @returns {Generator<ReturnType<SyncModuleTransform>, ParseResult, any>}
*/
const result = syncTrampoline(
/** @type {GetSyncParserGeneratorFn} */(getParserGenerator),
config,
bytes,
specifier,
location,
packageLocation,
options,
);I don't view the above as particularly valuable, so I didn't do it. |
9ba12b8 to
5aa2a37
Compare
|
looks good, I need one more pass tomorrow, but no comments yet. |
This fixes the half-implemented type defs and support for async parsers by introducing `AsyncParserImplementation`. We needed sync-and-async-specific parse-mappers in `map-parser.js` and some type guards. A sync-parser-specific `SyncParserForLanguage` is introduced as well.
This adds missing tests to assert support of `import()` in ES module sources. As expected, it does not work within archives.
5aa2a37 to
d5be9d8
Compare
This fixes the half-implemented type defs and support for async parsers by introducing
AsyncParserImplementation.We needed sync-and-async-specific parse-mappers in
map-parser.jsand some type guards.A sync-parser-specific
SyncParserForLanguageis introduced as well.Bonus: added some tests for
import()in ESM to@endo/compartment-mapper.