@xtrek/ts-migrate-plugins
Version:
Set of codemods, which are doing transformation of js/jsx to ts/tsx
240 lines • 12.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.updateImports = void 0;
/* eslint-disable no-use-before-define, no-restricted-syntax */
const typescript_1 = __importDefault(require("typescript"));
const text_1 = require("./text");
function updateImports(sourceFile, toAdd, toRemove) {
const updates = [];
const printer = typescript_1.default.createPrinter();
const usedIdentifiers = getUsedIdentifiers(sourceFile);
const presentedImports = getPresentedImportIdentifiers(sourceFile);
const toAddActual = uniqAddImportUpdates(toAdd).filter((cur) => (isDefaultImport(cur) &&
usedIdentifiers.has(cur.defaultImport) &&
!presentedImports.has(cur.defaultImport)) ||
(isNamedImport(cur) &&
usedIdentifiers.has(cur.namedImport) &&
!presentedImports.has(cur.namedImport)));
const added = new Set();
const isNotAdded = (cur) => !added.has(cur);
const importDeclarations = sourceFile.statements.filter(typescript_1.default.isImportDeclaration);
importDeclarations.forEach((importDeclaration) => {
if (!importDeclaration.importClause)
return;
const moduleSpecifierText = importDeclaration.moduleSpecifier
.getText(sourceFile)
.replace(/['"]/g, '');
const isModuleSpecifier = (cur) => cur.moduleSpecifier === moduleSpecifierText;
let { importClause } = importDeclaration;
const shouldRemoveAllUnused = toRemove.filter(isModuleImport).some(isModuleSpecifier);
const shouldRemoveNameUnused = toRemove
.filter(isDefaultImport)
.some((cur) => cur.moduleSpecifier === moduleSpecifierText &&
importClause.name != null &&
cur.defaultImport != null &&
cur.defaultImport === importClause.name.text);
if ((shouldRemoveAllUnused || shouldRemoveNameUnused) &&
importClause.name &&
!usedIdentifiers.has(importClause.name.text)) {
importClause = typescript_1.default.factory.updateImportClause(importClause, importClause.isTypeOnly, undefined, importClause.namedBindings);
}
toAddActual
.filter(isDefaultImport)
.filter(isModuleSpecifier)
.filter(isNotAdded)
.filter((cur) => importClause.name && cur.defaultImport === importClause.name.text)
.forEach((cur) => added.add(cur));
const nameToAdd = toAddActual
.filter(isDefaultImport)
.filter(isModuleSpecifier)
.filter(isNotAdded);
if (nameToAdd.length > 0 && importClause.name == null) {
importClause = typescript_1.default.factory.updateImportClause(importClause, importClause.isTypeOnly, typescript_1.default.factory.createIdentifier(nameToAdd[0].defaultImport), importClause.namedBindings);
added.add(nameToAdd[0]);
}
if (shouldRemoveAllUnused &&
importClause.namedBindings &&
typescript_1.default.isNamespaceImport(importClause.namedBindings) &&
!usedIdentifiers.has(importClause.namedBindings.name.text)) {
importClause = typescript_1.default.factory.updateImportClause(importClause, importClause.isTypeOnly, importClause.name, undefined);
}
if (importClause.namedBindings && typescript_1.default.isNamedImports(importClause.namedBindings)) {
const elements = importClause.namedBindings.elements.filter((el) => {
const isUsed = usedIdentifiers.has(el.name.text);
if (isUsed)
return true;
const shouldRemove = shouldRemoveAllUnused ||
toRemove
.filter(isNamedImport)
.filter(isModuleSpecifier)
.some((cur) => cur.namedImport === el.name.text);
return !shouldRemove;
});
toAddActual
.filter(isNamedImport)
.filter(isModuleSpecifier)
.filter(isNotAdded)
.filter((cur) => elements.some((el) => el.name.text === cur.namedImport))
.forEach((cur) => added.add(cur));
if (elements.length !== importClause.namedBindings.elements.length) {
importClause = typescript_1.default.factory.updateImportClause(importClause, importClause.isTypeOnly, importClause.name, elements.length > 0
? typescript_1.default.factory.updateNamedImports(importClause.namedBindings, elements)
: undefined);
}
}
const namedToAdd = toAddActual
.filter(isNamedImport)
.filter(isModuleSpecifier)
.filter(isNotAdded);
if (namedToAdd.length > 0) {
importClause = typescript_1.default.factory.updateImportClause(importClause, importClause.isTypeOnly, importClause.name, typescript_1.default.factory.createNamedImports([
...(importClause.namedBindings && typescript_1.default.isNamedImports(importClause.namedBindings)
? importClause.namedBindings.elements
: []),
...namedToAdd.map((cur) => typescript_1.default.factory.createImportSpecifier(undefined, typescript_1.default.factory.createIdentifier(cur.namedImport))),
]));
namedToAdd.forEach((cur) => added.add(cur));
}
if (importClause !== importDeclaration.importClause) {
let numImports = 0;
if (importClause.name) {
numImports += 1;
}
if (importClause.namedBindings) {
if (typescript_1.default.isNamespaceImport(importClause.namedBindings)) {
numImports += 1;
}
if (typescript_1.default.isNamedImports(importClause.namedBindings)) {
numImports += importClause.namedBindings.elements.length;
}
}
if (numImports > 0) {
const upImpDec = typescript_1.default.factory.updateImportDeclaration(importDeclaration, importDeclaration.decorators, importDeclaration.modifiers, importClause, importDeclaration.moduleSpecifier);
const text = text_1.getTextPreservingWhitespace(importDeclaration, upImpDec, sourceFile);
updates.push({
kind: 'replace',
index: importDeclaration.pos,
length: importDeclaration.end - importDeclaration.pos,
text,
});
}
else {
const comments = typescript_1.default.getLeadingCommentRanges(sourceFile.getFullText(), importDeclaration.pos) || [];
const index = comments.length > 0 ? comments[comments.length - 1].end : importDeclaration.pos;
updates.push({
kind: 'delete',
index,
length: importDeclaration.end - index,
});
}
}
});
const toAddRemaining = toAddActual.filter(isNotAdded);
if (toAddRemaining.length > 0) {
const nodes = [];
const grouped = {};
toAddRemaining.forEach((cur) => {
grouped[cur.moduleSpecifier] = grouped[cur.moduleSpecifier] || [];
grouped[cur.moduleSpecifier].push(cur);
});
Object.keys(grouped).forEach((moduleSpecifier) => {
const nameToAdd = grouped[moduleSpecifier].filter(isDefaultImport);
const namedToAdd = grouped[moduleSpecifier].filter(isNamedImport);
const namedImports = namedToAdd.length > 0
? typescript_1.default.factory.createNamedImports(namedToAdd.map((cur) => typescript_1.default.factory.createImportSpecifier(undefined, typescript_1.default.factory.createIdentifier(cur.namedImport))))
: undefined;
if (nameToAdd.length <= 1) {
nodes.push(typescript_1.default.factory.createImportDeclaration(undefined, undefined, typescript_1.default.factory.createImportClause(false, nameToAdd.length === 1
? typescript_1.default.factory.createIdentifier(nameToAdd[0].defaultImport)
: undefined, namedImports), typescript_1.default.factory.createStringLiteral(moduleSpecifier)));
}
else {
nodes.push(typescript_1.default.factory.createImportDeclaration(undefined, undefined, typescript_1.default.factory.createImportClause(false, undefined, namedImports), typescript_1.default.factory.createStringLiteral(moduleSpecifier)));
nameToAdd.forEach((cur) => {
nodes.push(typescript_1.default.factory.createImportDeclaration(undefined, undefined, typescript_1.default.factory.createImportClause(false, typescript_1.default.factory.createIdentifier(cur.defaultImport), undefined), typescript_1.default.factory.createStringLiteral(moduleSpecifier)));
});
}
});
const pos = importDeclarations.length > 0 ? importDeclarations[importDeclarations.length - 1].end : 0;
nodes.forEach((node, i) => {
let text = printer.printNode(typescript_1.default.EmitHint.Unspecified, node, sourceFile);
if (pos > 0 || i > 0)
text = `\n${text}`;
updates.push({ kind: 'insert', index: pos, text });
});
}
return updates;
}
exports.updateImports = updateImports;
function getUsedIdentifiers(sourceFile) {
const usedIdentifiers = new Set();
const visitor = (node) => {
if (typescript_1.default.isIdentifier(node)) {
usedIdentifiers.add(node.text);
}
// Don't visit the import statements themselves.
if (!typescript_1.default.isImportDeclaration(node)) {
typescript_1.default.forEachChild(node, visitor);
}
};
typescript_1.default.forEachChild(sourceFile, visitor);
return usedIdentifiers;
}
function getPresentedImportIdentifiers(sourceFile) {
return sourceFile.statements.filter(typescript_1.default.isImportDeclaration).reduce((presentedImports, item) => {
if (item.importClause) {
if (item.importClause.namedBindings && typescript_1.default.isNamedImports(item.importClause.namedBindings)) {
item.importClause.namedBindings.elements.forEach((x) => x.name && presentedImports.add(x.name.escapedText.toString()));
}
else if (item.importClause.name && typescript_1.default.isIdentifier(item.importClause.name)) {
presentedImports.add(item.importClause.name.text);
}
}
return presentedImports;
}, new Set());
}
function isDefaultImport(update) {
return update.defaultImport != null;
}
function isNamedImport(update) {
return update.namedImport != null;
}
function isModuleImport(update) {
return (update.moduleSpecifier != null &&
update.defaultImport == null &&
update.namedImport == null);
}
function uniqAddImportUpdates(updates) {
const seen = {};
const initSeen = (moduleSpecifier) => {
if (!seen[moduleSpecifier]) {
seen[moduleSpecifier] = { name: new Set(), namedImport: new Set() };
}
};
const isSeen = (update) => {
initSeen(update.moduleSpecifier);
return ((isDefaultImport(update) && seen[update.moduleSpecifier].name.has(update.defaultImport)) ||
(isNamedImport(update) && seen[update.moduleSpecifier].namedImport.has(update.namedImport)));
};
const markAsSeen = (update) => {
initSeen(update.moduleSpecifier);
if (isDefaultImport(update)) {
seen[update.moduleSpecifier].name.add(update.defaultImport);
}
else if (isNamedImport(update)) {
seen[update.moduleSpecifier].namedImport.add(update.namedImport);
}
};
const newUpdates = [];
for (const update of updates) {
if (!isSeen(update)) {
newUpdates.push(update);
markAsSeen(update);
}
}
return newUpdates;
}
//# sourceMappingURL=imports.js.map