Skip to content

Commit

Permalink
Improve assertion validation and errors, add tests
Browse files Browse the repository at this point in the history
  • Loading branch information
GeoffreyBooth committed Oct 14, 2021
1 parent bd7c391 commit 10ba639
Show file tree
Hide file tree
Showing 8 changed files with 166 additions and 67 deletions.
14 changes: 7 additions & 7 deletions lib/internal/errors.js
Original file line number Diff line number Diff line change
Expand Up @@ -1083,13 +1083,13 @@ E('ERR_HTTP_SOCKET_ENCODING',
E('ERR_HTTP_TRAILER_INVALID',
'Trailers are invalid with this transfer encoding', Error);
E('ERR_ILLEGAL_CONSTRUCTOR', 'Illegal constructor', TypeError);
E('ERR_IMPORT_ASSERTION_DISALLOWED',
'Import assertions are not allowed for modules of format "%s"',
SyntaxError);
E('ERR_IMPORT_ASSERTION_INVALID',
'Modules of format "%s" need an import assertion %s of "%s"', TypeError);
E('ERR_IMPORT_ASSERTION_MISSING',
'Modules of format "%s" need an import assertion %s of "%s"', SyntaxError);
E('ERR_IMPORT_ASSERTION_TYPE_FAILED',
'Modules of format "%s" need an import assertion type "%s"', TypeError);
E('ERR_IMPORT_ASSERTION_TYPE_MISSING',
'Modules of format "%s" need an import assertion type "%s"', SyntaxError);
E('ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED',
'Modules of format "%s" do not support import assertion type "%s"',
TypeError);
E('ERR_INCOMPATIBLE_OPTION_PAIR',
'Option "%s" cannot be used in combination with option "%s"', TypeError);
E('ERR_INPUT_TYPE_NOT_ALLOWED', '--input-type can only be used with string ' +
Expand Down
31 changes: 17 additions & 14 deletions lib/internal/modules/esm/assert.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,17 @@ const {
} = primordials;

const {
ERR_IMPORT_ASSERTION_DISALLOWED,
ERR_IMPORT_ASSERTION_INVALID,
ERR_IMPORT_ASSERTION_MISSING,
ERR_UNKNOWN_MODULE_FORMAT,
ERR_IMPORT_ASSERTION_TYPE_FAILED,
ERR_IMPORT_ASSERTION_TYPE_MISSING,
ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED,
} = require('internal/errors').codes;

/**
* Define a map of module formats to import assertion types (the value of `type` in `assert { type: 'json' }`).
* A value of `false` means that the presence of an import assertion type for this format should throw an error.
* This matches browser behavior, where `import('data:text/javascript,export{}', { assert: { type: 'javascript' } })` throws.
* @type {Map<string, string | false}
*/
const formatTypeMap = new SafeMap([
['builtin', false],
['commonjs', false],
Expand All @@ -22,26 +27,24 @@ const formatTypeMap = new SafeMap([


function validateAssertions(format, importAssertions) {
let validType;
try {
validType = formatTypeMap.get(format);
} catch {
throw new ERR_UNKNOWN_MODULE_FORMAT(format);
}
const validType = formatTypeMap.get(format);

if (validType === false) {
if (validType === undefined) {
// Ignore assertions for module types we don’t recognize, to allow new formats in the future
return true;
} else if (validType === false) {
if (!importAssertions ||
!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
return true;
}
throw new ERR_IMPORT_ASSERTION_DISALLOWED(format);
throw new ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED(format, validType);
} else {
if (validType === importAssertions?.type) {
return true;
} else if (!ObjectPrototypeHasOwnProperty(importAssertions, 'type')) {
throw new ERR_IMPORT_ASSERTION_MISSING(format, 'type', validType);
throw new ERR_IMPORT_ASSERTION_TYPE_MISSING(format, validType);
} else {
throw new ERR_IMPORT_ASSERTION_INVALID(format, 'type', validType);
throw new ERR_IMPORT_ASSERTION_TYPE_FAILED(format, validType);
}
}
}
Expand Down
44 changes: 21 additions & 23 deletions test/es-module/test-esm-dynamic-import-assertion.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,9 @@
// Flags: --experimental-json-modules
'use strict';
const common = require('../common');
const { rejects, strictEqual } = require('assert');

const jsModuleDataUrl = 'data:text/javascript,export{}';
const { strictEqual } = require('assert');

async function test() {
await rejects(
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'json' } }),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

await rejects(
import('data:text/javascript,', { assert: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

await rejects(
import('data:text/javascript,', { assert: { type: 'undefined' } }),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

{
const results = await Promise.allSettled([
import('../fixtures/empty.js', { assert: { type: 'json' } }),
Expand All @@ -45,6 +23,26 @@ async function test() {
strictEqual(results[0].status, 'fulfilled');
strictEqual(results[1].status, 'rejected');
}

{
const results = await Promise.allSettled([
import('../fixtures/empty.json', { assert: { type: 'json' } }),
import('../fixtures/empty.json'),
]);

strictEqual(results[0].status, 'fulfilled');
strictEqual(results[1].status, 'rejected');
}

{
const results = await Promise.allSettled([
import('../fixtures/empty.json'),
import('../fixtures/empty.json', { assert: { type: 'json' } }),
]);

strictEqual(results[0].status, 'rejected');
strictEqual(results[1].status, 'fulfilled');
}
}

test().then(common.mustCall());
44 changes: 21 additions & 23 deletions test/es-module/test-esm-dynamic-import-assertion.mjs
Original file line number Diff line number Diff line change
@@ -1,28 +1,6 @@
// Flags: --experimental-json-modules
import '../common/index.mjs';
import { rejects, strictEqual } from 'assert';

const jsModuleDataUrl = 'data:text/javascript,export{}';

await rejects(
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'json' } }),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

await rejects(
import(import.meta.url, { assert: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);

await rejects(
import(import.meta.url, { assert: { type: 'undefined' } }),
{ code: 'ERR_IMPORT_ASSERTION_DISALLOWED' }
);
import { strictEqual } from 'assert';

{
const results = await Promise.allSettled([
Expand All @@ -43,3 +21,23 @@ await rejects(
strictEqual(results[0].status, 'fulfilled');
strictEqual(results[1].status, 'rejected');
}

{
const results = await Promise.allSettled([
import('../fixtures/empty.json', { assert: { type: 'json' } }),
import('../fixtures/empty.json'),
]);

strictEqual(results[0].status, 'fulfilled');
strictEqual(results[1].status, 'rejected');
}

{
const results = await Promise.allSettled([
import('../fixtures/empty.json'),
import('../fixtures/empty.json', { assert: { type: 'json' } }),
]);

strictEqual(results[0].status, 'rejected');
strictEqual(results[1].status, 'fulfilled');
}
52 changes: 52 additions & 0 deletions test/es-module/test-esm-import-assertion-errors.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// Flags: --experimental-json-modules
'use strict';
const common = require('../common');
const { rejects, strictEqual } = require('assert');

const jsModuleDataUrl = 'data:text/javascript,export{}';
const jsonModuleDataUrl = 'data:application/json,""';

async function test() {
await rejects(
// This rejects because of the unsupported MIME type, not because of the unsupported assertion
import(`data:text/css,`, { assert: { type: 'css' } }),
{ code: 'ERR_INVALID_MODULE_SPECIFIER' }
)

await rejects(
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'json' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

await rejects(
import('data:text/javascript,', { assert: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

await rejects(
import(jsonModuleDataUrl),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: {} }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
);
}

test().then(common.mustCall());
47 changes: 47 additions & 0 deletions test/es-module/test-esm-import-assertion-errors.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Flags: --experimental-json-modules
import '../common/index.mjs';
import { rejects } from 'assert';

const jsModuleDataUrl = 'data:text/javascript,export{}';
const jsonModuleDataUrl = 'data:application/json,""';

await rejects(
// This rejects because of the unsupported MIME type, not because of the unsupported assertion
import(`data:text/css,`, { assert: { type: 'css' } }),
{ code: 'ERR_INVALID_MODULE_SPECIFIER' }
)

await rejects(
import(`data:text/javascript,import${JSON.stringify(jsModuleDataUrl)}assert{type:"json"}`),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

await rejects(
import(jsModuleDataUrl, { assert: { type: 'json' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

await rejects(
import(import.meta.url, { assert: { type: 'unsupported' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED' }
);

await rejects(
import(jsonModuleDataUrl),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: {} }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { foo: 'bar' } }),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_MISSING' }
);

await rejects(
import(jsonModuleDataUrl, { assert: { type: 'unsupported' }}),
{ code: 'ERR_IMPORT_ASSERTION_TYPE_FAILED' }
);
1 change: 1 addition & 0 deletions test/fixtures/empty.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}

0 comments on commit 10ba639

Please sign in to comment.