1'use strict';
2
3const {
4  ArrayPrototypeFilter,
5  ArrayPrototypeIncludes,
6  ObjectKeys,
7  ObjectValues,
8  ObjectPrototypeHasOwnProperty,
9} = primordials;
10const { validateString } = require('internal/validators');
11
12const {
13  ERR_IMPORT_ASSERTION_TYPE_FAILED,
14  ERR_IMPORT_ASSERTION_TYPE_MISSING,
15  ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED,
16  ERR_IMPORT_ATTRIBUTE_UNSUPPORTED,
17} = require('internal/errors').codes;
18
19// The HTML spec has an implied default type of `'javascript'`.
20const kImplicitAssertType = 'javascript';
21
22/**
23 * Define a map of module formats to import attributes types (the value of
24 * `type` in `with { type: 'json' }`).
25 * @type {Map<string, string>}
26 */
27const formatTypeMap = {
28  '__proto__': null,
29  'builtin': kImplicitAssertType,
30  'commonjs': kImplicitAssertType,
31  'json': 'json',
32  'module': kImplicitAssertType,
33  'wasm': kImplicitAssertType, // It's unclear whether the HTML spec will require an attribute type or not for Wasm; see https://github.com/WebAssembly/esm-integration/issues/42
34};
35
36/**
37 * The HTML spec disallows the default type to be explicitly specified
38 * (for now); so `import './file.js'` is okay but
39 * `import './file.js' with { type: 'javascript' }` throws.
40 * @type {Array<string, string>}
41 */
42const supportedAssertionTypes = ArrayPrototypeFilter(
43  ObjectValues(formatTypeMap),
44  (type) => type !== kImplicitAssertType);
45
46
47/**
48 * Test a module's import attributes.
49 * @param {string} url The URL of the imported module, for error reporting.
50 * @param {string} format One of Node's supported translators
51 * @param {Record<string, string>} importAttributes Validations for the
52 *                                                  module import.
53 * @returns {true}
54 * @throws {TypeError} If the format and assertion type are incompatible.
55 */
56function validateAttributes(url, format,
57                            importAttributes = { __proto__: null }) {
58  const keys = ObjectKeys(importAttributes);
59  for (let i = 0; i < keys.length; i++) {
60    if (keys[i] !== 'type') {
61      throw new ERR_IMPORT_ATTRIBUTE_UNSUPPORTED(keys[i], importAttributes[keys[i]]);
62    }
63  }
64  const validType = formatTypeMap[format];
65
66  switch (validType) {
67    case undefined:
68      // Ignore attributes for module formats we don't recognize, to allow new
69      // formats in the future.
70      return true;
71
72    case kImplicitAssertType:
73      // This format doesn't allow an import assertion type, so the property
74      // must not be set on the import attributes object.
75      if (!ObjectPrototypeHasOwnProperty(importAttributes, 'type')) {
76        return true;
77      }
78      return handleInvalidType(url, importAttributes.type);
79
80    case importAttributes.type:
81      // The asserted type is the valid type for this format.
82      return true;
83
84    default:
85      // There is an expected type for this format, but the value of
86      // `importAttributes.type` might not have been it.
87      if (!ObjectPrototypeHasOwnProperty(importAttributes, 'type')) {
88        // `type` wasn't specified at all.
89        throw new ERR_IMPORT_ASSERTION_TYPE_MISSING(url, validType);
90      }
91      return handleInvalidType(url, importAttributes.type);
92  }
93}
94
95/**
96 * Throw the correct error depending on what's wrong with the type assertion.
97 * @param {string} url The resolved URL for the module to be imported
98 * @param {string} type The value of the import assertion `type` property
99 */
100function handleInvalidType(url, type) {
101  // `type` might have not been a string.
102  validateString(type, 'type');
103
104  // `type` might not have been one of the types we understand.
105  if (!ArrayPrototypeIncludes(supportedAssertionTypes, type)) {
106    throw new ERR_IMPORT_ASSERTION_TYPE_UNSUPPORTED(type);
107  }
108
109  // `type` was the wrong value for this format.
110  throw new ERR_IMPORT_ASSERTION_TYPE_FAILED(url, type);
111}
112
113
114module.exports = {
115  kImplicitAssertType,
116  validateAttributes,
117};
118