mirror of
https://github.com/bendtherules/ask262.git
synced 2026-08-18 13:21:55 +00:00
feat(build-graph): add comments and pass options
This commit is contained in:
+32
-6
@@ -4,13 +4,26 @@ import * as cheerio from "cheerio";
|
|||||||
import { glob } from "glob";
|
import { glob } from "glob";
|
||||||
import Graph from "graphology";
|
import Graph from "graphology";
|
||||||
|
|
||||||
|
// Directory containing built ECMAScript specification HTML files (ecmarkup output)
|
||||||
const SPEC_DIR = "./spec-built/multipage";
|
const SPEC_DIR = "./spec-built/multipage";
|
||||||
|
// Directory containing the JavaScript engine implementation source code
|
||||||
const CODE_DIR = "./engine262/src";
|
const CODE_DIR = "./engine262/src";
|
||||||
|
|
||||||
import { GRAPH_FILE } from "../constants";
|
import { GRAPH_FILE } from "../constants";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds a knowledge graph mapping ECMAScript specification sections
|
||||||
|
* to their implementation functions in the JavaScript engine.
|
||||||
|
*
|
||||||
|
* The graph contains:
|
||||||
|
* - Nodes: Spec sections and JS functions
|
||||||
|
* - Edges: LINKS_TO (spec section references) and IMPLEMENTS (code->spec)
|
||||||
|
*/
|
||||||
async function buildGraph() {
|
async function buildGraph() {
|
||||||
const graph = new Graph({ multi: true });
|
// Initialize a multi-graph (allows multiple edges between same nodes)
|
||||||
|
const graph = new Graph({ multi: true, type: "directed", allowSelfLoops: false });
|
||||||
|
|
||||||
|
// Phase 1: Discover and parse specification HTML files
|
||||||
const htmlFiles = await glob(path.join(SPEC_DIR, "*.html"));
|
const htmlFiles = await glob(path.join(SPEC_DIR, "*.html"));
|
||||||
console.log(
|
console.log(
|
||||||
`Found ${htmlFiles.length} specification HTML file(s) in ${SPEC_DIR}`,
|
`Found ${htmlFiles.length} specification HTML file(s) in ${SPEC_DIR}`,
|
||||||
@@ -19,22 +32,27 @@ async function buildGraph() {
|
|||||||
console.warn(`Warning: No specification HTML files found in ${SPEC_DIR}`);
|
console.warn(`Warning: No specification HTML files found in ${SPEC_DIR}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 2: Extract spec sections and create nodes
|
||||||
console.log("Processing specification for graph...");
|
console.log("Processing specification for graph...");
|
||||||
for (const file of htmlFiles) {
|
for (const file of htmlFiles) {
|
||||||
const fileName = path.basename(file);
|
const fileName = path.basename(file);
|
||||||
const content = fs.readFileSync(file, "utf-8");
|
const content = fs.readFileSync(file, "utf-8");
|
||||||
const $ = cheerio.load(content);
|
const $ = cheerio.load(content);
|
||||||
|
// ecmarkup generates content within #spec-container
|
||||||
const $container = $("#spec-container");
|
const $container = $("#spec-container");
|
||||||
|
|
||||||
|
// Each specification section is an <emu-clause> with an ID
|
||||||
$container.find("emu-clause").each((_i, elem) => {
|
$container.find("emu-clause").each((_i, elem) => {
|
||||||
const id = $(elem).attr("id");
|
const id = $(elem).attr("id");
|
||||||
const title = $(elem).find("h1").first().text().trim();
|
const title = $(elem).find("h1").first().text().trim();
|
||||||
|
|
||||||
if (id) {
|
if (id) {
|
||||||
|
// Create node for this spec section if not already exists
|
||||||
if (!graph.hasNode(id)) {
|
if (!graph.hasNode(id)) {
|
||||||
graph.addNode(id, { title, type: "SpecSection", file: fileName });
|
graph.addNode(id, { title, type: "SpecSection", file: fileName });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Extract internal links
|
// Extract internal links (placeholder for potential future use)
|
||||||
$(elem)
|
$(elem)
|
||||||
.find("a[href]")
|
.find("a[href]")
|
||||||
.each((_j, link) => {
|
.each((_j, link) => {
|
||||||
@@ -51,7 +69,8 @@ async function buildGraph() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add edges for internal links
|
// Phase 3: Create edges between spec sections based on internal links
|
||||||
|
// This pass runs after all nodes are created to ensure target nodes exist
|
||||||
for (const file of htmlFiles) {
|
for (const file of htmlFiles) {
|
||||||
const content = fs.readFileSync(file, "utf-8");
|
const content = fs.readFileSync(file, "utf-8");
|
||||||
const $ = cheerio.load(content);
|
const $ = cheerio.load(content);
|
||||||
@@ -65,6 +84,7 @@ async function buildGraph() {
|
|||||||
const href = $(link).attr("href");
|
const href = $(link).attr("href");
|
||||||
if (href?.includes("#")) {
|
if (href?.includes("#")) {
|
||||||
const [, targetId] = href.split("#");
|
const [, targetId] = href.split("#");
|
||||||
|
// Only create edge if target node exists and is different from source
|
||||||
if (
|
if (
|
||||||
targetId &&
|
targetId &&
|
||||||
graph.hasNode(targetId) &&
|
graph.hasNode(targetId) &&
|
||||||
@@ -80,6 +100,7 @@ async function buildGraph() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 4: Discover and parse JavaScript implementation files
|
||||||
console.log("Processing code for graph...");
|
console.log("Processing code for graph...");
|
||||||
const jsFiles = await glob(path.join(CODE_DIR, "**/*.mts"));
|
const jsFiles = await glob(path.join(CODE_DIR, "**/*.mts"));
|
||||||
console.log(`Found ${jsFiles.length} code file(s) in ${CODE_DIR}`);
|
console.log(`Found ${jsFiles.length} code file(s) in ${CODE_DIR}`);
|
||||||
@@ -87,11 +108,13 @@ async function buildGraph() {
|
|||||||
console.warn(`Warning: No code files found in ${CODE_DIR}`);
|
console.warn(`Warning: No code files found in ${CODE_DIR}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 5: Extract evaluation functions and link to spec sections
|
||||||
for (const file of jsFiles) {
|
for (const file of jsFiles) {
|
||||||
const fileName = path.basename(file);
|
const fileName = path.basename(file);
|
||||||
const content = fs.readFileSync(file, "utf-8");
|
const content = fs.readFileSync(file, "utf-8");
|
||||||
|
|
||||||
// Simple heuristic: look for function names starting with Evaluate_
|
// Pattern matches: export function*? Evaluate_<Name>
|
||||||
|
// The engine262 project uses this naming convention for spec implementations
|
||||||
const functionMatches = content.matchAll(
|
const functionMatches = content.matchAll(
|
||||||
/export function\*? (Evaluate_([a-zA-Z0-9_]+))/g,
|
/export function\*? (Evaluate_([a-zA-Z0-9_]+))/g,
|
||||||
);
|
);
|
||||||
@@ -100,6 +123,7 @@ async function buildGraph() {
|
|||||||
const shortName = match[2];
|
const shortName = match[2];
|
||||||
const funcNodeId = `func-${fullFuncName}`;
|
const funcNodeId = `func-${fullFuncName}`;
|
||||||
|
|
||||||
|
// Create node for this function if not already exists
|
||||||
if (!graph.hasNode(funcNodeId)) {
|
if (!graph.hasNode(funcNodeId)) {
|
||||||
graph.addNode(funcNodeId, {
|
graph.addNode(funcNodeId, {
|
||||||
name: fullFuncName,
|
name: fullFuncName,
|
||||||
@@ -108,8 +132,9 @@ async function buildGraph() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to link to a spec section
|
// Link function to spec section using naming convention heuristic
|
||||||
// Heuristic: Evaluate_IfStatement -> sec-if-statement
|
// Example: Evaluate_IfStatement -> sec-if-statement
|
||||||
|
// Converts CamelCase to kebab-case: IfStatement -> if-statement
|
||||||
const specId = `sec-${shortName.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`;
|
const specId = `sec-${shortName.replace(/([a-z0-9])([A-Z])/g, "$1-$2").toLowerCase()}`;
|
||||||
if (graph.hasNode(specId)) {
|
if (graph.hasNode(specId)) {
|
||||||
graph.addEdge(funcNodeId, specId, { type: "IMPLEMENTS" });
|
graph.addEdge(funcNodeId, specId, { type: "IMPLEMENTS" });
|
||||||
@@ -117,6 +142,7 @@ async function buildGraph() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Phase 6: Export the completed graph to JSON
|
||||||
console.log(`Graph built with ${graph.order} nodes and ${graph.size} edges.`);
|
console.log(`Graph built with ${graph.order} nodes and ${graph.size} edges.`);
|
||||||
fs.writeFileSync(GRAPH_FILE, JSON.stringify(graph.export(), null, 2));
|
fs.writeFileSync(GRAPH_FILE, JSON.stringify(graph.export(), null, 2));
|
||||||
console.log(`Graph saved to ${GRAPH_FILE}`);
|
console.log(`Graph saved to ${GRAPH_FILE}`);
|
||||||
|
|||||||
Reference in New Issue
Block a user