Skip to content

Instantly share code, notes, and snippets.

@AlexGeb
Created April 9, 2026 10:50
Show Gist options
  • Select an option

  • Save AlexGeb/c1b0427b2f0b924f755071ae9385f8b8 to your computer and use it in GitHub Desktop.

Select an option

Save AlexGeb/c1b0427b2f0b924f755071ae9385f8b8 to your computer and use it in GitHub Desktop.
import path from "path";
import { Project, ts } from "ts-morph";
const packageName = "assistant";
const packageDir = `packages/${packageName}`;
const project = new Project({
tsConfigFilePath: path.resolve(`${packageDir}/tsconfig.json`),
});
const assistantFiles = new Set(
project
.getSourceFiles(`${packageDir}/src/**/*{.ts,.tsx}`)
.map((f) => f.getFilePath()),
);
console.log(
`Found ${assistantFiles.size} source files in ${packageDir}/src...`,
);
const MAX_ITERATIONS = 5;
let iteration = 0;
let totalFixed = 0;
let totalUnfixed = 0;
while (iteration < MAX_ITERATIONS) {
iteration++;
console.log(`\n--- Pass ${iteration} ---`);
// Phase 1: Fix TS1484 errors (type-only imports missing `type` keyword)
let fixedThisPass = 0;
for (const sourceFile of project.getSourceFiles()) {
if (!assistantFiles.has(sourceFile.getFilePath())) continue;
const ts1484Diagnostics = sourceFile
.getPreEmitDiagnostics()
.filter((d) => d.getCode() === 1484);
if (ts1484Diagnostics.length === 0) continue;
const relPath = path.relative(process.cwd(), sourceFile.getFilePath());
for (const diagnostic of ts1484Diagnostics) {
const start = diagnostic.getStart();
if (start === undefined) continue;
const node = sourceFile.getDescendantAtPos(start);
if (!node) continue;
const importSpecifier =
node.getFirstAncestorByKind(ts.SyntaxKind.ImportSpecifier) ??
(node.getKind() === ts.SyntaxKind.ImportSpecifier ? node : undefined);
if (importSpecifier && "setIsTypeOnly" in importSpecifier) {
(importSpecifier as any).setIsTypeOnly(true);
fixedThisPass++;
}
}
if (ts1484Diagnostics.length > 0) {
sourceFile.saveSync();
console.log(
` [TS1484] Fixed ${ts1484Diagnostics.length} import(s) in ${relPath}`,
);
}
}
totalFixed += fixedThisPass;
if (fixedThisPass === 0 && iteration > 1) {
console.log(" No more TS1484 errors to fix.");
break;
}
// Phase 2: Undo TS1361 errors (imports wrongly marked as type-only that are used as values)
let unfixedThisPass = 0;
for (const sourceFile of project.getSourceFiles()) {
if (!assistantFiles.has(sourceFile.getFilePath())) continue;
const ts1361Diagnostics = sourceFile
.getPreEmitDiagnostics()
.filter((d) => d.getCode() === 1361);
if (ts1361Diagnostics.length === 0) continue;
const relPath = path.relative(process.cwd(), sourceFile.getFilePath());
const seen = new Set<number>();
for (const diagnostic of ts1361Diagnostics) {
const messageText = diagnostic.getMessageText();
const message =
typeof messageText === "string"
? messageText
: messageText.getMessageText();
// Extract the identifier name from the error message
// Pattern: "'Foo' cannot be used as a value because it was imported using 'import type'."
const match = message.match(/^'(\w+)' cannot be used as a value/);
if (!match) continue;
const identifierName = match[1]!;
// Find the import specifier for this identifier in the file's import declarations
for (const importDecl of sourceFile.getImportDeclarations()) {
for (const specifier of importDecl.getNamedImports()) {
const name =
specifier.getAliasNode()?.getText() ?? specifier.getName();
if (name === identifierName && specifier.isTypeOnly()) {
const pos = specifier.getStart();
if (seen.has(pos)) continue;
seen.add(pos);
specifier.setIsTypeOnly(false);
unfixedThisPass++;
console.log(
` [TS1361] Reverted type-only on '${identifierName}' in ${relPath}`,
);
}
}
}
}
if (unfixedThisPass > 0) {
sourceFile.saveSync();
}
}
totalUnfixed += unfixedThisPass;
if (fixedThisPass === 0 && unfixedThisPass === 0) {
console.log(" No changes needed. Converged.");
break;
}
}
console.log(`\n=== Summary ===`);
console.log(` Passes: ${iteration}`);
console.log(` Imports marked as type-only: ${totalFixed}`);
console.log(` Imports reverted (used as value): ${totalUnfixed}`);
console.log(` Net fixes: ${totalFixed - totalUnfixed}`);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment