feat: typescript types

This commit is contained in:
TrèsAbhi committed 2026-03-20 14:43:12 -05:00
1 parent 4e586253c0
commit 80cc74a945
2 files changed
+112 -40

No files matched your search

@@ -65,9 +65,6 @@ for (const line of bodyOriginal.split("\n")) {
<div class="content"> <div class="content">
<MarkdownCustom {body} /> <MarkdownCustom {body} />
<!-- {"see" in macros && <ApiMacroSee {locale} value={macros.see} />}
{"type" in macros && <ApiMacroType {locale} value={macros.type} />} -->
{ {
Object.entries(macroComponents).map(([macro, Component]) => { Object.entries(macroComponents).map(([macro, Component]) => {
if (!(macro in macros)) return null; if (!(macro in macros)) return null;
@@ -1,4 +1,5 @@
--- ---
import { resolve } from "path";
import { existsSync } from "fs"; import { existsSync } from "fs";
import { readFile } from "fs/promises"; import { readFile } from "fs/promises";
import { Link } from "./Link"; import { Link } from "./Link";
@@ -7,6 +8,22 @@ import HighlightedCodeBlock from "./HighlightedCodeBlock.astro";
import { Text } from "./Text"; import { Text } from "./Text";
import { ArrowBottomRightIcon } from "@radix-ui/react-icons"; import { ArrowBottomRightIcon } from "@radix-ui/react-icons";
import { Code } from "./Code"; import { Code } from "./Code";
import {
createProgram,
createSourceFile,
forEachChild,
isEnumDeclaration,
isInterfaceDeclaration,
isMethodSignature,
isPropertySignature,
isTypeAliasDeclaration,
isTypeReferenceNode,
ScriptTarget,
TypeFormatFlags,
type InterfaceDeclaration,
type Node,
type TypeNode,
} from "typescript";
interface Props { interface Props {
value: string; value: string;
@@ -18,60 +35,118 @@ const strings = await api.strings(locale);
const roots = ["src/protos", "../closed/protos"]; const roots = ["src/protos", "../closed/protos"];
const lang = "proto"; const lang = "ts";
const importPattern = /import "(.+)";/gm;
const messagePattern = /message .+ {\s*\n(.+\s*\n)+}/gm;
const explored = new Set<string>(); const name = "Avatar";
let messages: string[] = []; const path = resolve(`${roots[0]}/${value}.ts`);
const buffer = await readFile(path);
await pullMessages(`${roots[0]}/${value}.proto`); const program = createProgram([path], {});
const source = program.getSourceFile(path);
async function pullMessages(path: string) { if (!source) {
if (explored.has(path)) return; throw new Error(`Could not find ${path}`);
}
explored.add(path); const checker = program.getTypeChecker();
const file = await readFile(path); const collected = new Map<string, string>();
const content = file.toString();
const imports = content.matchAll(importPattern);
for (const _import of imports) { let declaration: InterfaceDeclaration | undefined;
for (const root of roots) {
const path = `${root}/${_import[1]}`;
if (existsSync(path)) { forEachChild(source, (node) => {
await pullMessages(path); if (!isInterfaceDeclaration(node) || node.name.text !== name) return;
break; declaration = node;
} });
if (declaration === undefined) {
throw new Error(`Could not find ${name} in ${path}`);
}
function printNode(node: Node) {
if (isInterfaceDeclaration(node)) {
const symbol = checker.getSymbolAtLocation(node.name);
if (symbol) {
const text = node.getText();
collected.set(node.name.text, text);
node.members.forEach((member) => {
if (isPropertySignature(member) && member.type) {
collectTypeDependencies(member.type);
}
if (isMethodSignature(member)) {
member.parameters.forEach((param) => {
if (param.type) {
collectTypeDependencies(param.type);
}
});
if (member.type) {
collectTypeDependencies(member.type);
}
}
});
} }
} }
const matchedMessages = content.matchAll(messagePattern); forEachChild(node, printNode);
}
for (const message of matchedMessages) { function isRelevantTypeDecl(node: Node) {
messages = [message[0], ...messages]; return (
isInterfaceDeclaration(node) ||
isTypeAliasDeclaration(node) ||
isEnumDeclaration(node)
);
}
function collectTypeDependencies(node: TypeNode) {
const type = checker.getTypeAtLocation(node);
const symbol = type.getSymbol();
if (!symbol) return;
const name = symbol.getName();
if (!collected.has(name)) {
const declarations = symbol.getDeclarations();
if (declarations) {
declarations.forEach((decl) => {
if (!isRelevantTypeDecl(decl)) return;
const name = (decl as any).name?.text;
if (!name || collected.has(name)) return;
collected.set(name, decl.getText());
forEachChild(decl, collectTypeDependenciesFromNode);
});
}
} }
} }
const content = messages.join("\n\n"); function collectTypeDependenciesFromNode(node: Node) {
if (isTypeReferenceNode(node)) {
const indexPattern = /^( +).+\.(.+ = \d+;)$/m; collectTypeDependencies(node);
let stripped = "";
for (const line of content.split("\n")) {
const matches = line.match(indexPattern);
if (matches === null) {
stripped += `${line}\n`;
continue;
} }
stripped += `${matches[1]}${matches[2]}\n`; forEachChild(node, collectTypeDependenciesFromNode);
} }
stripped = stripped.replaceAll(" ", " "); printNode(declaration);
let content = "";
for (const [, definition] of collected) {
content += `${definition}\n\n`;
}
content = content.replaceAll(/^export\s*/gm, "");
content = content.trim();
--- ---
<style> <style>
@@ -136,7 +211,7 @@ stripped = stripped.replaceAll(" ", " ");
</div> </div>
<div class="types"> <div class="types">
<HighlightedCodeBlock {lang} content={stripped} /> <HighlightedCodeBlock {lang} content={content} />
<div class="overlay"> <div class="overlay">
<Link underline="always" href="#" className="view-full"> <Link underline="always" href="#" className="view-full">