Skip to content

Instantly share code, notes, and snippets.

@timostamm
Created April 18, 2023 11:24
Show Gist options
  • Save timostamm/3797825096a418b1f4c97d979e2d4ae7 to your computer and use it in GitHub Desktop.
Save timostamm/3797825096a418b1f4c97d979e2d4ae7 to your computer and use it in GitHub Desktop.
analyze a package in the local file system with @arethetypeswrong/core
import type {Analysis, FS, Host} from "@arethetypeswrong/core";
import {checkPackage, getProblems, summarizeProblems} from "@arethetypeswrong/core";
import {readdirSync, readFileSync} from "node:fs";
import path from "node:path";
import {z} from "zod";
export async function typeswrong(projectPath: string) {
const pkgPath = path.join(projectPath, "package.json");
const pkg = readJsonFile(pkgPath, PackageJson);
const host = createLocalFsHost(projectPath, pkg.name);
const analysis: Analysis = await checkPackage(pkg.name, undefined, host);
if (!analysis.containsTypes) {
throw new Error("no types found");
}
const problems = getProblems(analysis);
const summaries = summarizeProblems(problems, analysis);
if (problems.length > 0) {
console.log(JSON.stringify(summaries, null, 2));
console.log(JSON.stringify(problems, null, 2));
throw new Error("got problems " + problems.length);
}
return {problems, summaries};
}
function createLocalFsHost(projectPath: string, expectPackageName: string): Host {
return {
async createPackageFS(packageName: string, packageVersion?: string): Promise<FS> {
if (packageName !== expectPackageName) {
throw new Error(`expected package name "${expectPackageName}" but got "${packageName}"`);
}
if (packageVersion !== undefined) {
throw new Error("argument not supported");
}
const files = replacePathPrefix(readAllFiles(projectPath, ["node_modules"]), projectPath, "/node_modules/" + packageName );
return {
readFile: (path: string) => {
const contents = files.get(path);
if (!contents) {
throw new Error(`File not found: ${path}`);
}
return contents;
},
fileExists: (path: string) => files.has(path),
directoryExists: (path: string) => {
if (!path.endsWith("/") && !path.endsWith("\\")) {
path += "/";
}
for (const name of files.keys()) {
if (name.startsWith(path)) {
return true;
}
}
return false;
},
listFiles: () => Array.from(files.keys()),
};
},
createPackageFSFromTarball() {
throw new Error("not implemented");
}
};
}
function readAllFiles(dir: string, ignoreNames: string[]): Map<string, string> {
const all = new Map<string, string>();
readAllFilesRec(all, dir, ignoreNames);
return all;
}
function readAllFilesRec(all: Map<string, string>, curr: string, ignoreNames: string[]): void {
for (const ent of readdirSync(curr, {withFileTypes: true})) {
if (ignoreNames.includes(ent.name)) {
continue;
}
const name = path.join(curr, ent.name);
if (ent.isFile()) {
const content = readFileSync(name, "utf-8");
all.set(name, content);
} else if (ent.isDirectory()) {
readAllFilesRec(all, name, ignoreNames);
}
}
}
function replacePathPrefix(files: Map<string, string>, oldPrefix: string, newPrefix: string) {
const out = new Map<string, string>();
for (const [name, content] of files.entries()) {
if (name.startsWith(oldPrefix)) {
out.set(newPrefix + name.substring(oldPrefix.length), content);
} else {
out.set(name, content);
}
}
return out;
}
const PackageJson = z.object({
name: z.string(),
version: z.string().optional(),
type: z.union([z.literal("module"), z.literal("commonjs")]).optional(),
});
function readJsonFile<T extends z.ZodObject<any>>(path: string, schema: T): T["_output"] {
try {
const text = readFileSync(path, "utf-8");
const json = JSON.parse(text);
return schema.parse(json);
} catch (e) {
throw new Error(`failed to read ${path}: ${e instanceof Error ? e.message : String(e)}`);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment