|
// SourceCodeParser.test.ts |
|
|
|
import { SourceCodeParser } from "./SourceCodeParser"; |
|
import * as fs from "fs"; |
|
import * as path from "path"; |
|
import * as os from "os"; |
|
|
|
describe("SourceCodeParser", () => { |
|
const tempDir = path.join(os.tmpdir(), "source-code-parser-test"); |
|
|
|
beforeAll(() => { |
|
// Create temporary directory |
|
fs.mkdirSync(tempDir, { recursive: true }); |
|
|
|
// Create sample Markdown modules |
|
/* |
|
Entry Point: main.md |
|
main.md -> [User Interface](./ui.md), [Backend](./backend.md), [External Link](https://example.com/external.md) |
|
ui.md -> [Components](./components.md) |
|
backend.md -> [Database](./database.md) |
|
components.md -> no dependencies |
|
database.md -> no dependencies |
|
*/ |
|
|
|
const files = { |
|
"main.md": ` |
|
# Main Module |
|
|
|
This is the main module. |
|
|
|
- [User Interface](./ui.md) |
|
- [Backend](./backend.md) |
|
- [External Link](https://example.com/external.md) |
|
`, |
|
"ui.md": ` |
|
# User Interface |
|
|
|
UI components. |
|
|
|
- [Components](./components.md) |
|
`, |
|
"backend.md": ` |
|
# Backend |
|
|
|
Backend services. |
|
|
|
- [Database](./database.md) |
|
`, |
|
"components.md": ` |
|
# Components |
|
|
|
Reusable UI components. |
|
`, |
|
"database.md": ` |
|
# Database |
|
|
|
Database schema and migrations. |
|
`, |
|
}; |
|
|
|
for (const [filename, content] of Object.entries(files)) { |
|
const filePath = path.join(tempDir, filename); |
|
fs.writeFileSync(filePath, content.trim(), "utf-8"); |
|
} |
|
}); |
|
|
|
afterAll(() => { |
|
// Clean up temporary directory |
|
fs.rmSync(tempDir, { recursive: true, force: true }); |
|
}); |
|
|
|
it("should correctly parse the source tree and build the adjacency list", async () => { |
|
const entryPoint = path.join(tempDir, "main.md"); |
|
const parser = new SourceCodeParser(entryPoint); |
|
const adjacencyList = await parser.parse(); |
|
|
|
// Resolve all paths |
|
const mainPath = path.resolve(entryPoint); |
|
const uiPath = path.resolve(path.join(tempDir, "ui.md")); |
|
const backendPath = path.resolve(path.join(tempDir, "backend.md")); |
|
const componentsPath = path.resolve(path.join(tempDir, "components.md")); |
|
const databasePath = path.resolve(path.join(tempDir, "database.md")); |
|
|
|
// Define expected adjacency list |
|
const expectedAdjacencyList = { |
|
[mainPath]: { |
|
source: `# Main Module |
|
|
|
This is the main module. |
|
|
|
- [User Interface](./ui.md) |
|
- [Backend](./backend.md) |
|
- [External Link](https://example.com/external.md)`, |
|
dependencies: { |
|
[uiPath]: "User Interface", |
|
[backendPath]: "Backend", |
|
// External link should be ignored, so not included |
|
}, |
|
}, |
|
[uiPath]: { |
|
source: `# User Interface |
|
|
|
UI components. |
|
|
|
- [Components](./components.md)`, |
|
dependencies: { |
|
[componentsPath]: "Components", |
|
}, |
|
}, |
|
[backendPath]: { |
|
source: `# Backend |
|
|
|
Backend services. |
|
|
|
- [Database](./database.md)`, |
|
dependencies: { |
|
[databasePath]: "Database", |
|
}, |
|
}, |
|
[componentsPath]: { |
|
source: `# Components |
|
|
|
Reusable UI components.`, |
|
dependencies: {}, |
|
}, |
|
[databasePath]: { |
|
source: `# Database |
|
|
|
Database schema and migrations.`, |
|
dependencies: {}, |
|
}, |
|
}; |
|
|
|
expect(adjacencyList).toEqual(expectedAdjacencyList); |
|
}); |
|
|
|
it("should throw an error for missing modules", async () => { |
|
// Create a module with a missing dependency |
|
const missingDepPath = path.join(tempDir, "missing_dep.md"); |
|
fs.writeFileSync( |
|
missingDepPath, |
|
` |
|
# Missing Dependency Module |
|
|
|
This module has a missing dependency. |
|
|
|
- [Nonexistent](./nonexistent.md) |
|
`.trim(), |
|
"utf-8" |
|
); |
|
|
|
const parser = new SourceCodeParser(missingDepPath); |
|
|
|
await expect(parser.parse()).rejects.toThrow(/Module not found/); |
|
}); |
|
|
|
it("should handle circular dependencies gracefully", async () => { |
|
// Create circular dependencies |
|
const aPath = path.join(tempDir, "a.md"); |
|
const bPath = path.join(tempDir, "b.md"); |
|
|
|
fs.writeFileSync( |
|
aPath, |
|
` |
|
# Module A |
|
|
|
- [Module B](./b.md) |
|
`.trim(), |
|
"utf-8" |
|
); |
|
|
|
fs.writeFileSync( |
|
bPath, |
|
` |
|
# Module B |
|
|
|
- [Module A](./a.md) |
|
`.trim(), |
|
"utf-8" |
|
); |
|
|
|
const parser = new SourceCodeParser(aPath); |
|
const adjacencyList = await parser.parse(); |
|
|
|
const resolvedAPath = path.resolve(aPath); |
|
const resolvedBPath = path.resolve(bPath); |
|
|
|
const expectedAdjacencyList = { |
|
[resolvedAPath]: { |
|
source: `# Module A |
|
|
|
- [Module B](./b.md)`, |
|
dependencies: { |
|
[resolvedBPath]: "Module B", |
|
}, |
|
}, |
|
[resolvedBPath]: { |
|
source: `# Module B |
|
|
|
- [Module A](./a.md)`, |
|
dependencies: { |
|
[resolvedAPath]: "Module A", |
|
}, |
|
}, |
|
}; |
|
|
|
expect(adjacencyList).toEqual(expectedAdjacencyList); |
|
}); |
|
|
|
it("should ignore external links and not include them as dependencies", async () => { |
|
const entryPoint = path.join(tempDir, "main.md"); |
|
const parser = new SourceCodeParser(entryPoint); |
|
const adjacencyList = await parser.parse(); |
|
|
|
// Resolve all paths |
|
const mainPath = path.resolve(entryPoint); |
|
const externalLink = "https://example.com/external.md"; // Should be ignored |
|
|
|
// Check that external link is not included in dependencies |
|
expect(adjacencyList[mainPath].dependencies).not.toHaveProperty( |
|
externalLink |
|
); |
|
}); |
|
}); |