Skip to content

Instantly share code, notes, and snippets.

@fersilva16
Last active December 30, 2021 01:48
Show Gist options
  • Save fersilva16/d585a4a032e9724639e62ef2a9ec4fcd to your computer and use it in GitHub Desktop.
Save fersilva16/d585a4a032e9724639e62ef2a9ec4fcd to your computer and use it in GitHub Desktop.
Transform imports to namespace import
import { load } from '@workspace/shared';
import Foo from '@workspace/shared';
import * as Bar from '@workspace/shared';
load();
Foo.doSomething();
Bar.default.doSomething();
function test() {
const load = {};
const Foo = {};
Bar.load();
Bar.default.doSomething();
console.log(load);
console.log(Foo);
}
import * as Test from '@workspace/shared';
Test.load();
Test.default.doSomething();
Test.default.doSomething();
function test() {
const load = {};
const Foo = {};
Test.load();
Test.default.doSomething();
console.log(load);
console.log(Foo);
}
import type {
API,
FileInfo,
ASTNode,
BlockStatement,
ASTPath,
Identifier,
} from 'jscodeshift';
/**
* Import source: package or the path to a file.
* Examples:
* - fs
* - ./filename
* - ../path/to/file.ts
*/
// const importSource = './path/to/file';
const importSource = '@workspace/shared';
/**
* Name of the namespace.
*/
const namespace = 'Test';
type ASTPathx<T> = Omit<ASTPath<T>, 'parent'> & { parent: ASTPath<ASTNode> };
export default function transformToNamespaceImport(
fileInfo: FileInfo,
{ jscodeshift }: API,
) {
const root = jscodeshift(fileInfo.source);
const imports = new Map<string, ASTNode>();
const scopesWithDeclaration = new Map<BlockStatement, Set<string>>();
function getBlockStatement({
node,
parent,
}: ASTPathx<ASTNode>): BlockStatement | undefined {
if (node.type === 'Program') return undefined;
if (node.type === 'BlockStatement') return node;
return getBlockStatement(parent);
}
const importDeclarations = root.find(jscodeshift.ImportDeclaration, {
source: {
value: importSource,
},
});
if (!importDeclarations.length) {
console.log(`Import declaration with source "${importSource}" not found!`);
return;
}
importDeclarations
.forEach(({ node }) => {
node.specifiers!.forEach((specifier) => {
switch (specifier.type) {
case 'ImportSpecifier':
imports.set(
specifier.local!.name || specifier.imported.name,
jscodeshift.memberExpression(
jscodeshift.identifier(namespace),
jscodeshift.identifier(specifier.imported.name),
),
);
break;
case 'ImportDefaultSpecifier':
imports.set(
specifier.local!.name,
jscodeshift.memberExpression(
jscodeshift.identifier(namespace),
jscodeshift.identifier('default'),
),
);
break;
case 'ImportNamespaceSpecifier':
imports.set(
specifier.local!.name,
jscodeshift.identifier(namespace),
);
break;
}
});
})
.replaceWith((_, index) => {
if (index === 0) {
return jscodeshift.importDeclaration(
[
jscodeshift.importNamespaceSpecifier(
jscodeshift.identifier(namespace),
),
],
jscodeshift.stringLiteral(importSource),
);
}
return undefined;
});
root
.find(jscodeshift.Identifier)
.filter(({ node, parent }: ASTPathx<Identifier>) => {
if (
[
'ImportSpecifier',
'ImportDefaultSpecifier',
'ImportNamespaceSpecifier',
].includes(parent.node.type)
) {
return false;
}
if (parent.node.type === 'MemberExpression') {
return parent.node.object === node;
}
if (!imports.has(node.name)) return false;
if (
['VariableDeclarator', 'FunctionDeclaration'].includes(parent.node.type)
) {
const blockStatement = getBlockStatement(parent);
if (blockStatement) {
const ignoreds = scopesWithDeclaration.get(blockStatement);
if (!ignoreds) {
scopesWithDeclaration.set(blockStatement, new Set([node.name]));
} else {
ignoreds.add(node.name);
}
}
return false;
}
return true;
})
.filter(({ node, parent }) => {
if (!imports.has(node.name)) return false;
const blockStatement = getBlockStatement(parent);
if (blockStatement) {
const variables = scopesWithDeclaration.get(blockStatement);
if (!variables) return false;
return !variables.has(node.name);
}
return true;
})
.replaceWith(({ node }) => imports.get(node.name));
return root.toSource();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment