Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion packages/compartment-mapper/NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ User-visible changes to the compartment mapper:

## Next release

* No changes yet.
* Reenables CommonJS support with a fast lexer and without a dependency on
Babel.

## 0.2.4 (2021-03-30)

Expand Down
3 changes: 0 additions & 3 deletions packages/compartment-mapper/README.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
# Compartment mapper

> :warning: CommonJS support is temporarily disabled, pending a solution for
> heuristic static analysis that does not entrain any Node.js built-ins.

The compartment mapper builds _compartment maps_ for Node.js style
applications, finding their dependencies and describing how to create
[Compartments][] for each package in the application.
Expand Down
1 change: 1 addition & 0 deletions packages/compartment-mapper/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"test": "ava"
},
"dependencies": {
"@endo/lexer": "^0.1.0",
"ses": "^0.12.7"
},
"devDependencies": {
Expand Down
10 changes: 5 additions & 5 deletions packages/compartment-mapper/src/node-modules.js
Original file line number Diff line number Diff line change
Expand Up @@ -135,9 +135,9 @@ const findPackage = async (readDescriptor, directory, name) => {
}
};

const languages = ['mjs', 'json'];
const uncontroversialParsers = { mjs: 'mjs', json: 'json' };
const commonParsers = uncontroversialParsers;
const languages = ['mjs', 'cjs', 'json'];
const uncontroversialParsers = { cjs: 'cjs', mjs: 'mjs', json: 'json' };
const commonParsers = { js: 'cjs', ...uncontroversialParsers };
const moduleParsers = { js: 'mjs', ...uncontroversialParsers };

/**
Expand All @@ -152,7 +152,7 @@ const inferParsers = (descriptor, location) => {
throw new Error(
`Cannot interpret parser map ${JSON.stringify(
parsers,
)} of package at ${location}, must be an object mapping file extensions to corresponding languages (mjs for ECMAScript modules or json for JSON modules`,
)} of package at ${location}, must be an object mapping file extensions to corresponding languages (mjs for ECMAScript modules, cjs for CommonJS modules, or json for JSON modules`,
);
}
const invalidLanguages = values(parsers).filter(
Expand All @@ -162,7 +162,7 @@ const inferParsers = (descriptor, location) => {
throw new Error(
`Cannot interpret parser map language values ${JSON.stringify(
invalidLanguages,
)} of package at ${location}, must be an object mapping file extensions to corresponding languages (mjs for ECMAScript modules or json for JSON modules`,
)} of package at ${location}, must be an object mapping file extensions to corresponding languages (mjs for ECMAScript modules, cjs for CommonJS modules, or json for JSON modules`,
);
}
return { ...uncontroversialParsers, ...parsers };
Expand Down
68 changes: 67 additions & 1 deletion packages/compartment-mapper/src/parse.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
// @ts-check
/// <reference types="ses" />

import { analyzeCommonJS } from '@endo/lexer';
import { parseExtension } from './extension.js';
import * as json from './json.js';

Expand Down Expand Up @@ -48,6 +49,7 @@ export const parseJson = async (
const source = textDecoder.decode(bytes);
/** @type {Readonly<Array<string>>} */
const imports = freeze([]);

/**
* @param {Object} exports
*/
Expand All @@ -57,13 +59,77 @@ export const parseJson = async (
return {
parser: 'json',
bytes,
record: freeze({ imports, execute }),
record: freeze({ imports, exports: freeze(['default']), execute }),
};
};

/** @type {ParseFn} */
export const parseCjs = async (
bytes,
_specifier,
location,
_packageLocation,
) => {
const source = textDecoder.decode(bytes);

if (typeof location !== 'string') {
throw new TypeError(
`Cannot create CommonJS static module record, module location must be a string, got ${location}`,
);
}

const { requires: imports, exports, reexports } = analyzeCommonJS(
source,
location,
);

/**
* @param {Object} moduleExports
* @param {Compartment} compartment
* @param {Record<string, string>} resolvedImports
*/
const execute = async (moduleExports, compartment, resolvedImports) => {
const functor = compartment.evaluate(
`(function (require, exports, module, __filename, __dirname) { ${source} //*/\n})\n//# sourceURL=${location}`,
);

const module = freeze({
get exports() {
return moduleExports;
},
set exports(value) {
moduleExports.default = value;
},
});

const require = freeze(importSpecifier => {
const namespace = compartment.importNow(resolvedImports[importSpecifier]);
if (namespace.default !== undefined) {
return namespace.default;
}
return namespace;
});

functor(
require,
moduleExports,
module,
location, // __filename
new URL('./', location).toString(), // __dirname
);
};

return {
parser: 'cjs',
bytes,
record: freeze({ imports, exports, reexports, execute }),
};
};

/** @type {Record<string, ParseFn>} */
export const parserForLanguage = {
mjs: parseMjs,
cjs: parseCjs,
json: parseJson,
};

Expand Down
Binary file modified packages/compartment-mapper/test/app.agar
Binary file not shown.
8 changes: 8 additions & 0 deletions packages/compartment-mapper/test/node_modules/app/main.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 23 additions & 1 deletion packages/compartment-mapper/test/test-main.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,14 +30,20 @@ const assertFixture = (t, namespace) => {
avery,
brooke,
clarke,
danny,
builtin,
receivedGlobalProperty,
receivedGlobalLexical,
typecommon,
typemodule,
typehybrid,
typeparsers,
} = namespace;

t.is(avery, 'Avery', 'exports avery');
t.is(brooke, 'Brooke', 'exports brooke');
t.is(clarke, 'Clarke', 'exports clarke');
t.is(danny, 'Danny', 'exports danny');

t.is(builtin, 'builtin', 'exports builtin');

Expand All @@ -47,9 +53,25 @@ const assertFixture = (t, namespace) => {
globalLexicals.globalLexical,
'exports global lexical',
);
t.deepEqual(
typecommon,
[42, 42, 42, 42],
'type=common package carries exports',
);
t.deepEqual(
typemodule,
[42, 42, 42, 42],
'type=module package carries exports',
);
t.deepEqual(
typeparsers,
[42, 42, 42, 42],
'parsers-specifying package carries exports',
);
t.is(typehybrid, 42, 'type=module and module= package carries exports');
};

const fixtureAssertionCount = 6;
const fixtureAssertionCount = 11;

// The "create builtin" test prepares a builtin module namespace object that
// gets threaded into all subsequent tests to satisfy the "builtin" module
Expand Down