Files
pi/scripts/check-ts-relative-imports.mjs
2026-05-20 13:06:28 +02:00

75 lines
2.3 KiB
JavaScript

import { readdirSync, readFileSync } from "node:fs";
import { join } from "node:path";
import ts from "typescript";
const ignoredDirectories = new Set([".git", "coverage", "dist", "node_modules"]);
const files = [];
function collectTypescriptFiles(directory) {
for (const entry of readdirSync(directory, { withFileTypes: true })) {
if (entry.isDirectory()) {
if (!ignoredDirectories.has(entry.name)) {
collectTypescriptFiles(join(directory, entry.name));
}
continue;
}
if (entry.isFile() && entry.name.endsWith(".ts") && !entry.name.endsWith(".d.ts")) {
files.push(join(directory, entry.name));
}
}
}
function isRelativeJavaScriptSpecifier(specifier) {
return /^\.\.?\//.test(specifier) && /\.js(?:[?#].*)?$/.test(specifier);
}
function getImportTypeSpecifier(node) {
if (!ts.isLiteralTypeNode(node.argument)) return undefined;
if (!ts.isStringLiteralLike(node.argument.literal)) return undefined;
return node.argument.literal;
}
const failures = [];
collectTypescriptFiles(".");
for (const file of files.sort()) {
const sourceText = readFileSync(file, "utf8");
const sourceFile = ts.createSourceFile(file, sourceText, ts.ScriptTarget.Latest, true);
function checkSpecifier(node) {
if (!isRelativeJavaScriptSpecifier(node.text)) return;
const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile));
failures.push(`${file}:${line + 1}:${character + 1}: ${node.text}`);
}
function visit(node) {
if (ts.isImportDeclaration(node) && ts.isStringLiteralLike(node.moduleSpecifier)) {
checkSpecifier(node.moduleSpecifier);
} else if (ts.isExportDeclaration(node) && node.moduleSpecifier && ts.isStringLiteralLike(node.moduleSpecifier)) {
checkSpecifier(node.moduleSpecifier);
} else if (
ts.isCallExpression(node) &&
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
node.arguments[0] &&
ts.isStringLiteralLike(node.arguments[0])
) {
checkSpecifier(node.arguments[0]);
} else if (ts.isImportTypeNode(node)) {
const specifier = getImportTypeSpecifier(node);
if (specifier) checkSpecifier(specifier);
}
ts.forEachChild(node, visit);
}
visit(sourceFile);
}
if (failures.length > 0) {
console.error("Relative .js imports are not allowed in non-declaration .ts files:");
for (const failure of failures) console.error(` ${failure}`);
process.exit(1);
}