UNPKG

js-slang

Version:

Javascript-based implementations of Source, written in Typescript

158 lines 6.62 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.defaultLinkerOptions = void 0; const parser_1 = require("../../parser/parser"); const errors_1 = require("../errors"); const helpers_1 = require("../../utils/ast/helpers"); const misc_1 = require("../../utils/misc"); const utils_1 = require("../../parser/utils"); const typeGuards_1 = require("../../utils/ast/typeGuards"); const directedGraph_1 = require("./directedGraph"); const resolver_1 = require("./resolver"); /** * Helper error type. Thrown to cause any Promise.all calls * to reject immediately instead of just returning undefined, * which would still require all promises to be resolved */ class LinkerError extends Error { } exports.defaultLinkerOptions = { resolverOptions: resolver_1.defaultResolutionOptions }; /** * Starting from the entrypoint file, parse all imported local modules and create * a dependency graph. * * @param fileGetter A function that, when given a file path, either returns the contents * of that file as a string, or if it doesn't exist, `undefined` */ async function parseProgramsAndConstructImportGraph(fileGetter, entrypointFilePath, context, options = exports.defaultLinkerOptions, shouldAddFileName) { const importGraph = new directedGraph_1.DirectedGraph(); const programs = {}; const files = {}; const sourceModulesToImport = new Set(); // Wrapper around resolve file to make calling it more convenient async function resolveDependency(fromPath, node) { const toPath = (0, helpers_1.getModuleDeclarationSource)(node); const resolveResult = await (0, resolver_1.default)(fromPath, toPath, fileGetter, options.resolverOptions); if (!resolveResult) { throw new errors_1.ModuleNotFoundError(toPath, node); } if (resolveResult.type === 'source') { // We can assume two things: // 1. Source modules do not depend on one another // 2. They will always be loaded first before any local modules // Thus it is not necessary to track them in the import graph sourceModulesToImport.add(toPath); return; } const { absPath, contents } = resolveResult; // Special case of circular import: the module specifier // refers to the current file if (absPath === fromPath) { throw new errors_1.CircularImportError([absPath, absPath]); } node.source.value = absPath; importGraph.addEdge(absPath, fromPath); // No need to parse programs we've already parsed before if (absPath in programs) return; await parseAndEnumerateModuleDeclarations(absPath, contents); } async function parseAndEnumerateModuleDeclarations(fromModule, fileText) { const parseOptions = shouldAddFileName ? { sourceFile: fromModule } : {}; const program = (0, parser_1.parse)(fileText, context, parseOptions); if (!program) { // The program has syntax errors or something, // exit early throw new LinkerError(); } programs[fromModule] = program; files[fromModule] = fileText; await Promise.all((0, misc_1.mapAndFilter)(program.body, node => { switch (node.type) { case 'ExportNamedDeclaration': { if (!node.source) return undefined; // case falls through! } case 'ImportDeclaration': case 'ExportAllDeclaration': // We still have to visit each node to update // each node's source value return resolveDependency(fromModule, node); default: return undefined; } })); } let entrypointFileText = undefined; function hasVerboseErrors() { // Always try to infer if verbose errors should be enabled let statement = null; if (!programs[entrypointFilePath]) { if (entrypointFileText === undefined) { // non-existent entrypoint return false; } // There are syntax errors in the entrypoint file // we use parseAt to try parse the first line statement = (0, utils_1.parseAt)(entrypointFileText, 0); } else { // Otherwise we can use the entrypoint program as it has been passed const entrypointProgram = programs[entrypointFilePath]; // Check if the program had any code at all if (entrypointProgram.body.length === 0) return false; [statement] = entrypointProgram.body; } if (statement === null) return false; // The two different parsers end up with two different ASTs // These are the two cases where 'enable verbose' appears // as a directive if ((0, typeGuards_1.isDirective)(statement)) { return statement.directive === 'enable verbose'; } return statement.type === 'Literal' && statement.value === 'enable verbose'; } try { entrypointFileText = await fileGetter(entrypointFilePath); // Not using boolean test here, empty strings are valid programs // but are falsy if (entrypointFileText === undefined) { throw new errors_1.ModuleNotFoundError(entrypointFilePath); } await parseAndEnumerateModuleDeclarations(entrypointFilePath, entrypointFileText); const topologicalOrderResult = importGraph.getTopologicalOrder(); if (topologicalOrderResult.isValidTopologicalOrderFound) { return { ok: true, topoOrder: topologicalOrderResult.topologicalOrder, programs, sourceModulesToImport, files, verboseErrors: hasVerboseErrors() }; } context.errors.push(new errors_1.CircularImportError(topologicalOrderResult.firstCycleFound)); } catch (error) { if (!(error instanceof LinkerError)) { // Any other error that occurs is just appended to the context // and we return undefined context.errors.push(error); } } return { ok: false, verboseErrors: hasVerboseErrors() }; } exports.default = parseProgramsAndConstructImportGraph; //# sourceMappingURL=linker.js.map