Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions README.es.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ backend adapta el análisis según el lenguaje, y agregar uno nuevo toma unas po
líneas. No existe una herramienta libre que haga esto bien para varios lenguajes
a la vez.

> **Estado:** MVP. Soporta **Python**, **JavaScript/TypeScript**, **Go** y **Rust**. Tipo
> de diagrama: call graph (funciones como nodos, llamadas como flechas).
> **Estado:** MVP. Soporta **Python**, **JavaScript/TypeScript**, **Go**, **Rust** y **SQL**.
> Tipos de diagrama: call graph (funciones como nodos, llamadas como flechas) y,
> para SQL, entidad-relación (tablas como nodos, foreign keys como aristas).

---

Expand Down Expand Up @@ -155,6 +156,7 @@ Las tareas para empezar están etiquetadas como [`good first issue`](https://git
**Hecho**

- [x] Call graph para Python, JavaScript, TypeScript, Go y Rust
- [x] Diagramas de esquema SQL (ER / UML): tablas, columnas, PK/FK, relaciones
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- [x] Arquitectura pluggable por lenguaje (añadir uno son pocas líneas)
- [x] UI bilingüe (en/es) con docs in-app

Expand All @@ -170,7 +172,6 @@ Las tareas para empezar están etiquetadas como [`good first issue`](https://git
- [ ] Modo proyecto: analizar una carpeta entera como un solo grafo
- [ ] Interacción en nodos: click para resaltar llamadores/llamados, panel de detalle
- [ ] Más tipos de diagrama: control-flow y dependencias entre módulos
- [ ] **Soporte SQL**: diagramas ER / UML de esquema (tablas, columnas, relaciones) a partir de DDL SQL ([#19](https://github.com/DataDave-Dev/weftmap/issues/19))
- [ ] Exportar el grafo (PNG/SVG) y permalinks para compartir
- [ ] Más métodos de entrada: subir ZIP y URL de repo de GitHub
- [ ] Rendimiento para bases de código grandes
Expand Down
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ which. The differentiator is a **pluggable, multi-language architecture**: the
backend adapts the analysis per language, and adding a new one takes only a few
lines. No existing free tool does this well across several languages at once.

> **Status:** MVP. Supports **Python**, **JavaScript/TypeScript**, **Go** and **Rust**.
> Diagram type: call graph (functions as nodes, calls as arrows).
> **Status:** MVP. Supports **Python**, **JavaScript/TypeScript**, **Go**, **Rust** and **SQL**.
> Diagram types: call graph (functions as nodes, calls as arrows) and, for SQL,
> entity-relationship (tables as nodes, foreign keys as edges).

---

Expand Down Expand Up @@ -153,6 +154,7 @@ Newcomer-friendly tasks are tagged [`good first issue`](https://github.com/DataD
**Shipped**

- [x] Call graph for Python, JavaScript, TypeScript, Go and Rust
- [x] SQL schema diagrams (ER / UML): tables, columns, PK/FK, relationships
Comment thread
coderabbitai[bot] marked this conversation as resolved.
- [x] Pluggable per-language architecture (add a language in a few lines)
- [x] Bilingual UI (en/es) with in-app docs

Expand All @@ -168,7 +170,6 @@ Newcomer-friendly tasks are tagged [`good first issue`](https://github.com/DataD
- [ ] Project mode: analyze a whole folder as a single graph
- [ ] Node interaction: click to highlight callers/callees, function detail panel
- [ ] More diagram types: control-flow and module-dependency graphs
- [ ] **SQL support**: ER / UML schema diagrams (tables, columns, relationships) from SQL DDL ([#19](https://github.com/DataDave-Dev/weftmap/issues/19))
- [ ] Export the graph (PNG/SVG) and shareable permalinks
- [ ] More input methods: ZIP upload and GitHub repo URL
- [ ] Performance for large codebases
Expand Down
Binary file added public/wasm/tree-sitter-sql.wasm
Binary file not shown.
1 change: 1 addition & 0 deletions src/app/[lang]/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ export default async function AppPage({
inputPlaceholder={t.inputPlaceholder}
diagramPlaceholder={t.diagramPlaceholder}
noFunctions={t.noFunctions}
noTables={t.noTables}
snippetTab={t.snippetTab}
projectTab={t.projectTab}
uploadFolder={t.uploadFolder}
Expand Down
1 change: 1 addition & 0 deletions src/app/api/analyze/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const EXT: Record<string, string> = {
typescript: "ts",
go: "go",
rust: "rs",
sql: "sql",
};

function normalizePath(p: string): string {
Expand Down
17 changes: 13 additions & 4 deletions src/components/docs/pages/Languages.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,10 @@ export default function Languages({ lang }: { lang: Locale }) {
<>
<H1>Lenguajes</H1>
<Lead>
Weftmap soporta cinco lenguajes hoy. La arquitectura es ampliable: cada
Weftmap soporta seis lenguajes hoy. La arquitectura es ampliable: cada
lenguaje es una gramática de tree-sitter más un conjunto de consultas.
Cinco generan un <strong>call graph</strong>; SQL genera un diagrama
<strong> entidad-relación</strong>.
</Lead>

<Matrix headers={["Lenguaje", "Funciones", "Clases", "Imports", "Herencia"]} />
Comment on lines +61 to 67

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the language matrix consistent with the “six languages” claim.

The lead now says six supported languages, but the matrix still renders from ROWS with only five entries. This creates conflicting documentation on the same page.

Possible fix
 const ROWS: Row[] = [
   { lang: "Python", fns: true, classes: true, imports: true, extends: true },
   { lang: "JavaScript", fns: true, classes: true, imports: true, extends: true },
   { lang: "TypeScript", fns: true, classes: true, imports: true, extends: true },
   { lang: "Go", fns: true, classes: false, imports: false, extends: false },
   { lang: "Rust", fns: true, classes: false, imports: false, extends: false },
+  { lang: "SQL", fns: false, classes: false, imports: false, extends: false },
 ];

Also applies to: 93-100

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/docs/pages/Languages.tsx` around lines 61 - 67, The lead text
claims that "Weftmap soporta seis lenguajes" (Weftmap supports six languages),
but the Matrix component is populated from the ROWS variable which currently
contains only five language entries. Add a sixth language entry to the ROWS
variable to match the "six languages" claim in the text. Ensure the new entry
follows the same structure as the existing rows with columns for Lenguaje,
Funciones, Clases, Imports, and Herencia.

Expand All @@ -71,12 +73,14 @@ export default function Languages({ lang }: { lang: Locale }) {
<LI><strong>TypeScript</strong> — igual que JavaScript pero con la gramática dedicada, así las anotaciones de tipos se parsean sin problemas.</LI>
<LI><strong>Go</strong> — funciones y métodos. Go no tiene clases, así que no hay nodos de clase ni herencia.</LI>
<LI><strong>Rust</strong> — funciones y llamadas. La resolución entre archivos usa definiciones únicas.</LI>
<LI><strong>SQL</strong> — diagrama ER a partir de DDL (<Code>CREATE TABLE</Code>, <Code>ALTER TABLE</Code>): tablas con columnas y tipos, claves PK/FK, relaciones por foreign key con cardinalidad (1:1, 1:N y N:M vía tablas puente). Orientado a PostgreSQL.</LI>
</UL>

<Callout kind="warn">
En Go y Rust los imports son por paquete o módulo (no por archivo), así que no se dibujan
aristas de import. Las llamadas entre archivos del mismo paquete sí se
resuelven por nombre.
resuelven por nombre. SQL es un tipo de diagrama distinto: en lugar de
funciones y llamadas, dibuja tablas y relaciones.
</Callout>
</>
);
Expand All @@ -86,8 +90,10 @@ export default function Languages({ lang }: { lang: Locale }) {
<>
<H1>Languages</H1>
<Lead>
Weftmap supports five languages today. The architecture is extensible: each
language is a tree-sitter grammar plus a set of queries.
Weftmap supports six languages today. The architecture is extensible: each
language is a tree-sitter grammar plus a set of queries. Five produce a
<strong> call graph</strong>; SQL produces an{" "}
<strong>entity-relationship</strong> diagram.
</Lead>

<Matrix headers={["Language", "Functions", "Classes", "Imports", "Inheritance"]} />
Expand All @@ -99,11 +105,14 @@ export default function Languages({ lang }: { lang: Locale }) {
<LI><strong>TypeScript</strong> — same as JavaScript but with the dedicated grammar, so type annotations parse cleanly.</LI>
<LI><strong>Go</strong> — functions and methods. Go has no classes, so there are no class nodes or inheritance.</LI>
<LI><strong>Rust</strong> — functions and calls. Cross-file resolution uses unique definitions.</LI>
<LI><strong>SQL</strong> — ER diagram from DDL (<Code>CREATE TABLE</Code>, <Code>ALTER TABLE</Code>): tables with columns and types, PK/FK keys, and foreign-key relationships with cardinality (1:1, 1:N, and N:M via junction tables). PostgreSQL-oriented.</LI>
</UL>

<Callout kind="warn">
In Go and Rust, imports are package- or module-based (not file-based), so import edges are not
drawn. Calls between files in the same package are still resolved by name.
SQL is a different diagram type: instead of functions and calls, it draws
tables and relationships.
</Callout>
</>
);
Expand Down
2 changes: 1 addition & 1 deletion src/components/sections/Features.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ export default function Features({
{big && (
<div className="mt-auto pt-7">
<div className="flex flex-wrap gap-2">
{["Python", "JavaScript", "TypeScript", "Go", "Rust"].map((l) => (
{["Python", "JavaScript", "TypeScript", "Go", "Rust", "SQL"].map((l) => (
<span
key={l}
className="px-2.5 py-1 rounded-md border border-white/15 bg-white/[0.05] font-mono text-xs text-[#cbd5e1]"
Expand Down
2 changes: 1 addition & 1 deletion src/components/sections/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import HeroPreview from "./HeroPreview";

gsap.registerPlugin(useGSAP);

const LANGUAGES = ["Python", "JavaScript", "TypeScript", "Go", "Rust"];
const LANGUAGES = ["Python", "JavaScript", "TypeScript", "Go", "Rust", "SQL"];

type Props = {
badge: string;
Expand Down
34 changes: 31 additions & 3 deletions src/components/ui/CodeWorkspace.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@ import { useEffect, useMemo, useRef, useState } from "react";
import Diagram from "./Diagram";
import type { Graph } from "@/lib/analysis/types";

const LANGUAGES = ["python", "javascript", "typescript", "go", "rust"];
const LANGUAGES = ["python", "javascript", "typescript", "go", "rust", "sql"];

const EXT: Record<string, string> = {
python: "py",
javascript: "js",
typescript: "ts",
go: "go",
rust: "rs",
sql: "sql",
};

const PROJECT_EXTS: Record<string, string[]> = {
Expand All @@ -20,6 +21,7 @@ const PROJECT_EXTS: Record<string, string[]> = {
typescript: [".ts", ".tsx"],
go: [".go"],
rust: [".rs"],
sql: [".sql"],
};

const IGNORE_DIR =
Expand Down Expand Up @@ -135,6 +137,30 @@ fn clean(data: Vec<i32>) -> Vec<i32> {
fn save(x: Vec<i32>) {
write(x);
}
`,
sql: `CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE
);

CREATE TABLE posts (
id SERIAL PRIMARY KEY,
author_id INTEGER NOT NULL REFERENCES users (id),
title TEXT NOT NULL
);

CREATE TABLE tags (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE
);

CREATE TABLE post_tags (
post_id INTEGER NOT NULL,
tag_id INTEGER NOT NULL,
PRIMARY KEY (post_id, tag_id),
FOREIGN KEY (post_id) REFERENCES posts (id),
FOREIGN KEY (tag_id) REFERENCES tags (id)
);
`,
};

Expand All @@ -149,6 +175,7 @@ type Props = {
inputPlaceholder: string;
diagramPlaceholder: string;
noFunctions: string;
noTables: string;
snippetTab: string;
projectTab: string;
uploadFolder: string;
Expand All @@ -162,6 +189,7 @@ export default function CodeWorkspace({
inputPlaceholder,
diagramPlaceholder,
noFunctions,
noTables,
snippetTab,
projectTab,
uploadFolder,
Expand Down Expand Up @@ -450,7 +478,7 @@ export default function CodeWorkspace({
<span className="w-3 h-3 rounded-full bg-white/15" />
<span className="w-3 h-3 rounded-full bg-white/15" />
<span className="ml-3 font-mono text-[11px] uppercase tracking-wide text-muted/70">
call graph
{language === "sql" ? "schema diagram" : "call graph"}
</span>
</div>
{graph && (
Expand All @@ -472,7 +500,7 @@ export default function CodeWorkspace({
{error}
</div>
) : graph ? (
<Diagram graph={graph} emptyLabel={noFunctions} />
<Diagram graph={graph} emptyLabel={language === "sql" ? noTables : noFunctions} />
) : (
<div className="grid place-items-center h-full px-6 text-center text-sm text-muted">
{diagramPlaceholder}
Expand Down
78 changes: 73 additions & 5 deletions src/components/ui/Diagram.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,16 +14,24 @@ import ReactFlow, {
} from "reactflow";
import dagre from "@dagrejs/dagre";
import "reactflow/dist/style.css";
import type { Graph, GraphNode } from "@/lib/analysis/types";
import type { Graph, GraphNode, TableColumn } from "@/lib/analysis/types";

const NODE_W = 156;
const NODE_H = 40;
const HEADER = 30;
const PAD = 16;

// Table (ER) node dimensions.
const TABLE_W = 232;
const TABLE_HEADER = 34;
const TABLE_ROW = 24;

const CALL_COLOR = "rgba(255,255,255,0.32)";
const IMPORT_COLOR = "#5eead4";
const EXTENDS_COLOR = "#c4b5fd";
const REF_COLOR = "#fdba74";

const tableHeight = (cols: number) => TABLE_HEADER + cols * TABLE_ROW + 4;

// Container node for a module or class: header label + invisible handles so
// module/class-level edges (imports, top-level calls, extends) can attach.
Expand All @@ -50,7 +58,36 @@ function ContainerNode({ data }: NodeProps<{ label: string; kind: "module" | "cl
);
}

const nodeTypes = { container: ContainerNode };
// Table node for ER / schema diagrams: header + a row per column with PK/FK badges.
function TableNode({ data }: NodeProps<{ label: string; columns: TableColumn[] }>) {
return (
<div className="overflow-hidden rounded-xl border border-white/15 bg-[#13151b]" style={{ width: TABLE_W }}>
<div className="border-b border-white/10 bg-white/[0.05] px-3 py-2 font-mono text-[12px] font-semibold text-fg">
{data.label}
</div>
<div>
{data.columns.map((c) => (
<div
key={c.name}
className="flex items-center gap-2 border-b border-white/[0.05] px-3 text-[11px] last:border-0"
style={{ height: TABLE_ROW }}
>
<span className="flex w-9 shrink-0 gap-1 font-mono text-[9px] font-semibold">
{c.pk && <span className="text-amber-300" title="Primary key">PK</span>}
{c.fk && <span className="text-orange-300" title="Foreign key">FK</span>}
</span>
<span className="flex-1 truncate font-mono text-fg">{c.name}</span>
<span className="shrink-0 truncate font-mono text-[10px] text-muted/70">{c.type}</span>
</div>
))}
</div>
<Handle type="target" position={Position.Left} className="!opacity-0" />
<Handle type="source" position={Position.Right} className="!opacity-0" />
</div>
);
}

const nodeTypes = { container: ContainerNode, table: TableNode };

const fnStyle = {
width: NODE_W,
Expand Down Expand Up @@ -136,7 +173,14 @@ function layout(graph: Graph): Layout {
return { w: maxX + PAD, h: maxY + PAD };
}

for (const m of roots) sizeById.set(m.id, sizeOf(m.id));
for (const m of roots) {
sizeById.set(
m.id,
m.type === "table"
? { w: TABLE_W, h: tableHeight(m.columns?.length ?? 0) }
: sizeOf(m.id),
);
}

// Top-level layout of modules (imports + cross-module calls/extends).
const topAncestor = (nodeId: string): string | null => {
Expand Down Expand Up @@ -203,6 +247,15 @@ function layout(graph: Graph): Layout {
draggable: false,
};
}
if (n.type === "table") {
return {
id: n.id,
type: "table",
data: { label: n.label, columns: n.columns ?? [] },
position,
style: { width: size.w },
};
}
return {
id: n.id,
data: { label: n.label },
Expand All @@ -215,14 +268,23 @@ function layout(graph: Graph): Layout {
});

const edgeColor = (kind: string) =>
kind === "imports" ? IMPORT_COLOR : kind === "extends" ? EXTENDS_COLOR : CALL_COLOR;
kind === "imports"
? IMPORT_COLOR
: kind === "extends"
? EXTENDS_COLOR
: kind === "references"
? REF_COLOR
: CALL_COLOR;

const edges: Edge[] = graph.edges.map((e, i) => ({
id: `e${i}`,
source: e.source,
target: e.target,
data: { kind: e.kind },
animated: e.kind === "calls",
label: e.kind === "references" ? e.cardinality : undefined,
labelStyle: { fill: REF_COLOR, fontSize: 10, fontFamily: "ui-monospace, monospace" },
labelBgStyle: { fill: "#13151b" },
markerEnd: { type: MarkerType.ArrowClosed, color: edgeColor(e.kind) },
style: {
stroke: edgeColor(e.kind),
Expand All @@ -237,6 +299,7 @@ const LEGEND: { color: string; label: string; dashed: boolean }[] = [
{ color: CALL_COLOR, label: "calls", dashed: false },
{ color: IMPORT_COLOR, label: "imports", dashed: true },
{ color: EXTENDS_COLOR, label: "extends", dashed: true },
{ color: REF_COLOR, label: "references", dashed: true },
];

export default function Diagram({
Expand All @@ -249,6 +312,11 @@ export default function Diagram({
const { nodes, edges } = useMemo(() => layout(graph), [graph]);
const [hidden, setHidden] = useState<Set<string>>(new Set());

const legend = useMemo(() => {
const present = new Set(graph.edges.map((e) => e.kind));
return LEGEND.filter((l) => present.has(l.label as Graph["edges"][number]["kind"]));
}, [graph]);

const shownEdges = useMemo(
() => edges.filter((e) => !hidden.has((e.data as { kind: string }).kind)),
[edges, hidden],
Expand Down Expand Up @@ -285,7 +353,7 @@ export default function Diagram({
position="top-left"
className="flex gap-2 rounded-lg border border-white/10 bg-black/70 px-2 py-1.5 text-[11px] backdrop-blur"
>
{LEGEND.map((l) => {
{legend.map((l) => {
const off = hidden.has(l.label);
return (
<button
Expand Down
1 change: 1 addition & 0 deletions src/i18n/dictionaries/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
"inputPlaceholder": "Paste your code here…",
"diagramPlaceholder": "The diagram will appear here.",
"noFunctions": "No functions found in the code.",
"noTables": "No tables found in the SQL.",
"comingSoon": "New interface coming soon.",
"snippetTab": "Snippet",
"projectTab": "Project",
Expand Down
Loading
Loading