UNPKG

bmocha

Version:

Alternative implementation of mocha

463 lines (350 loc) 9 kB
/*! * esm.js - package.json type checker for bmocha * Copyright (c) 2018-2019, Christopher Jeffrey (MIT License). * https://github.com/bcoin-org/bmocha */ 'use strict'; const assert = require('assert'); const fs = require('fs'); const Module = require('module'); const path = require('path'); const { basename, dirname, extname, join, resolve, sep } = path; /* * Caches */ const rootCache = Object.create(null); const importCache = Object.create(null); /* * Flags */ const parts = process.version.split(/[^\d]/); const execArgv = process.execArgv || []; const NODE_VERSION = (0 + (parts[1] & 0xff) * 0x10000 + (parts[2] & 0xff) * 0x00100 + (parts[3] & 0xff) * 0x00001); const DEFAULT_TYPE = (() => { if (execArgv.includes('--experimental-default-type=commonjs')) return 'commonjs'; if (execArgv.includes('--experimental-default-type=module')) return 'module'; const index = execArgv.indexOf('--experimental-default-type'); if (index + 1 >= execArgv.length) return null; const arg = execArgv[index + 1]; if (arg === 'commonjs' || arg === 'module') return arg; return null; })(); // Module detection is default in 22.7.0. // XXX Maybe always set to true. const DETECT_MODULE = NODE_VERSION >= 0x160700 ? !execArgv.includes('--no-experimental-detect-module') : execArgv.includes('--experimental-detect-module'); const STRIP_TYPES = execArgv.includes('--experimental-strip-types') || execArgv.includes('--experimental-transform-types'); const TRANSFORM_TYPES = execArgv.includes('--experimental-transform-types'); const WASM_MODULES = execArgv.includes('--experimental-wasm-modules'); /* * ESM */ function stat(file) { assert(typeof file === 'string'); let st; try { st = fs.statSync(file); } catch (e) { if ((e.errno | 0) < 0) return e.errno | 0; return -1; } if (st.isFile()) return 0; if (st.isDirectory()) return 1; return -1; } function findRoot(path) { assert(typeof path === 'string'); path = resolve(path); if (stat(path) === 0) path = dirname(path); let dir = path; for (;;) { if (basename(dir) === 'node_modules') return null; const loc = join(dir, 'package.json'); if (stat(loc) === 0) return dir; const next = dirname(dir); if (next === dir) return null; dir = next; } } function readJSON(file) { assert(typeof file === 'string'); try { return JSON.parse(fs.readFileSync(file, 'utf8')); } catch (e) { return null; } } function readPackageType(path) { assert(typeof path === 'string'); const root = findRoot(path); if (root != null) { const cache = rootCache[root]; if (cache !== undefined) return cache; const json = readJSON(join(root, 'package.json')); if (json != null && (json.type === 'commonjs' || json.type === 'module')) { rootCache[root] = json.type; return json.type; } rootCache[root] = null; } return null; } function isModule(path) { return readPackageType(path) === 'module'; } function isImport(path) { assert(typeof path === 'string'); path = resolve(path); const cache = importCache[path]; if (cache != null) return cache; const result = _isImport(path); importCache[path] = result; return result; } function _isImport(path) { switch (stat(path)) { case 0: break; case 1: return isModule(path); default: return false; } const ext = extname(path); switch (ext) { case '.mjs': case '.mts': case '.wasm': return true; case '.cjs': case '.cts': case '.json': case '.node': return false; } const packageType = readPackageType(dirname(path)); if (ext === '.js' || ext === '.ts') { if (packageType != null) return packageType === 'module'; // Note: UNDOCUMENTED. if (DEFAULT_TYPE != null) return DEFAULT_TYPE === 'module' && !underNodeModules(path); if (DETECT_MODULE && detectModuleSyntax(path)) return true; return false; } if (ext === '') { if (packageType != null) return packageType === 'module'; // Note: UNDOCUMENTED. if (DEFAULT_TYPE != null) return DEFAULT_TYPE === 'module' && !underNodeModules(path); // Note: UNDOCUMENTED. // https://github.com/nodejs/node/blob/v23.1.0/lib/internal/modules/esm/get_format.js#L203 if (WASM_MODULES && detectWasmBinary(path)) return true; if (DETECT_MODULE && detectModuleSyntax(path)) return true; return false; } return false; // ERR_UNKNOWN_MODULE_FORMAT } /* * Syntax Detection */ const CJS_SCOPE = [ 'exports', 'require', 'module', '__filename', '__dirname' ]; // https://github.com/nodejs/node/blob/ed5cb37/src/node_contextify.cc#L1406 // Node 12.11.0 and up const MODULE_ERRORS = [ 'Cannot use import statement outside a module', // `import` statements 'Unexpected token \'export\'', // `export` statements 'Cannot use \'import.meta\' outside a module' // `import.meta` references ]; // https://github.com/nodejs/node/blob/63d04d4/src/node_contextify.cc#L1422 const RETRY_ERRORS = [ 'Identifier \'module\' has already been declared', 'Identifier \'exports\' has already been declared', 'Identifier \'require\' has already been declared', 'Identifier \'__filename\' has already been declared', 'Identifier \'__dirname\' has already been declared', // Node 15.1.0 and up 'await is only valid in async functions and the top level bodies of modules', // Node 8.0.0 and up 'await is only valid in async function' ]; let amaro = null; function tsCompile(file, code) { if (Module.stripTypeScriptTypes) { // https://github.com/nodejs/node/pull/55282 try { return Module.stripTypeScriptTypes(code, { mode: TRANSFORM_TYPES ? 'transform' : 'strip', sourceMap: false, sourceUrl: file }); } catch (e) { return null; } } if (!amaro) { try { amaro = require('bpkg/vendor/amaro'); } catch (e) { try { amaro = require('amaro'); } catch (e) { return null; } } } try { return amaro.transformSync(code, { mode: TRANSFORM_TYPES ? 'transform' : 'strip-only', sourceMap: false, filename: file }).code; } catch (e) { return null; } } function hasError(err, messages) { for (const msg of messages) { if (err.message.includes(msg)) return true; } return false; } function stripBOM(text) { assert(typeof text === 'string'); // UTF-16 BOM (also slices UTF-8 BOM). if (text.charCodeAt(0) === 0xfeff) text = text.substring(1); return text; } function stripHashbang(code) { assert(typeof code === 'string'); if (code.length < 2 || code.charCodeAt(0) !== 0x23 || code.charCodeAt(1) !== 0x21) { return code; } let i = 2; let j = 1; for (; i < code.length; i++) { const ch = code.charCodeAt(i); // LF if (ch === 0x0a) break; // CR if (ch === 0x0d) { // CRLF if (i + 1 < code.length) { if (code.charCodeAt(i + 1) === 0x0a) j = 2; } break; } // LS if (ch === 0x2028) break; // PS if (ch === 0x2029) break; } if (i === code.length) return ''; return code.substring(i + j); } function detectModuleSyntax(path) { assert(typeof path === 'string'); let code; try { code = fs.readFileSync(path, 'utf8'); } catch (e) { if (e.code === 'ENOENT' || e.code === 'EISDIR') return false; throw e; } code = stripHashbang(stripBOM(code)); if (extname(path) === '.ts') { if (code.startsWith('#!')) return false; code = tsCompile(path, code); if (code == null) return false; } const args = [...CJS_SCOPE, code]; try { new Function(...args); } catch (e) { if (hasError(e, MODULE_ERRORS)) return true; if (hasError(e, RETRY_ERRORS)) { const wrapped = `(async function() {${code}})();`; const args = [...CJS_SCOPE, wrapped]; try { new Function(...args); } catch (e) { return hasError(e, MODULE_ERRORS); } return true; } } return false; } function detectWasmBinary(path) { assert(typeof path === 'string'); let data; try { data = fs.readFileSync(path); } catch (e) { if (e.code === 'ENOENT' || e.code === 'EISDIR') return false; throw e; } return data.readUInt32BE(0) === 0x0061736d; } function underNodeModules(path) { return path.includes(`${sep}node_modules${sep}`); } /* * Expose */ exports.isModule = isModule; exports.isImport = isImport; exports.DEFAULT_TYPE = DEFAULT_TYPE; exports.DETECT_MODULE = DETECT_MODULE; exports.STRIP_TYPES = STRIP_TYPES; exports.TRANSFORM_TYPES = TRANSFORM_TYPES; exports.WASM_MODULES = WASM_MODULES;