js-slang
Version:
Javascript-based implementations of Source, written in Typescript
81 lines • 3.96 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
const lodash_1 = require("lodash");
const create = require("../../../utils/ast/astCreator");
const helpers_1 = require("../../../utils/ast/helpers");
const typeGuards_1 = require("../../../utils/ast/typeGuards");
const dict_1 = require("../../../utils/dict");
const utils_1 = require("../../utils");
/**
* Collates import declarations from each module and creates corresponding combined
* import declarations. Will also filter out non-Source module imports
*/
function hoistAndMergeImports(program) {
const [importDeclarations, nonImportDeclarations] = (0, lodash_1.partition)(program.body, typeGuards_1.isImportDeclaration);
const importRecords = new dict_1.default();
importDeclarations.forEach(decl => {
const source = (0, helpers_1.getModuleDeclarationSource)(decl);
if (!(0, utils_1.isSourceModule)(source))
return;
// Non-Source module imports should have already been dealt with at this point
// so we only need to be concerned with Source module imports
const { namespaces, regularSpecifiers, defaultSpecifiers } = importRecords.setdefault(source, {
regularSpecifiers: new dict_1.default(),
defaultSpecifiers: new Set(),
namespaces: new Set()
});
decl.specifiers.forEach(spec => {
const declaredName = spec.local.name;
switch (spec.type) {
case 'ImportNamespaceSpecifier': {
namespaces.add(declaredName);
break;
}
case 'ImportDefaultSpecifier': {
defaultSpecifiers.add(declaredName);
break;
}
case 'ImportSpecifier': {
const importedName = spec.imported.name;
regularSpecifiers.setdefault(importedName, new Set()).add(declaredName);
break;
}
}
});
});
const combinedImports = importRecords.flatMap((source, { regularSpecifiers, defaultSpecifiers, namespaces }) => {
const declarations = [];
namespaces.forEach(name => {
declarations.push(create.importDeclaration(source, [create.importNamespaceSpecifier(name)]));
});
const specifiers = [];
regularSpecifiers.forEach((importedName, localNames) => {
localNames.forEach(name => {
specifiers.push(create.importSpecifier(importedName, name));
});
});
if (defaultSpecifiers.size > 0) {
const [first, ...others] = defaultSpecifiers;
// We can combine only one default specifier with regular import specifiers
// Insert it at the front of the array because when acorn parses the AST
// its usually the first specifier. When we compare ASTs in tests
// the specifier will then be in the right place
specifiers.unshift(create.importDefaultSpecifier(first));
// If there is more than 1 default specifier,
// then we need to create a separate declaration for each
// since one ImportDeclaration cannot have multiple ImportDefaultSpecifiers
others.forEach(localName => {
declarations.push(create.importDeclaration(source, [create.importDefaultSpecifier(localName)]));
});
}
// Ensures that every module will at least have one ImportDeclaration
// associated with it, preserving side effect imports
if (specifiers.length > 0 || declarations.length === 0) {
declarations.push(create.importDeclaration(source, specifiers));
}
return declarations;
});
program.body = [...combinedImports, ...nonImportDeclarations];
}
exports.default = hoistAndMergeImports;
//# sourceMappingURL=hoistAndMergeImports.js.map
;