Skip to content

Commit 2f3ccdb

Browse files
authored
Add JavaScript/TypeScript dependency analyzer (#159)
* Add JavaScript/TypeScript dependency analyzer - New JavaScriptDependencyAnalyzer supporting ESM and CommonJS imports - Handles scoped packages (@org/pkg), Node.js built-ins, dynamic imports - Add generic get_dependencies() method to base class for consistent API - Update CLI to support javascript/typescript languages - Add comprehensive tests (19 new tests) and perf benchmarks - Update docs with Go and JavaScript analyzer documentation * Update roadmap * Bump version to 3.1.0 * Remove unnecessary export from __init__.py
1 parent 58d2e41 commit 2f3ccdb

12 files changed

Lines changed: 1756 additions & 31 deletions

docs/src/content/docs/api/dependency-analyzer.mdx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,125 @@ docs = terraform_analyzer.generate_resource_documentation(
354354

355355
* String containing the markdown documentation of resources
356356

357+
### GoDependencyAnalyzer
358+
359+
**Class: `GoDependencyAnalyzer`**
360+
*(defined in `kit/dependency_analyzer/go_dependency_analyzer.py`)*
361+
362+
The `GoDependencyAnalyzer` extends the base `DependencyAnalyzer` to analyze Go codebases, focusing on package import relationships.
363+
364+
#### Additional Methods
365+
366+
##### `get_package_dependencies`
367+
368+
**Method: `GoDependencyAnalyzer.get_package_dependencies`**
369+
*(defined in `kit/dependency_analyzer/go_dependency_analyzer.py`)*
370+
371+
Gets dependencies for a specific Go package.
372+
373+
```python
374+
# Get direct dependencies
375+
deps = go_analyzer.get_package_dependencies("github.com/myorg/myapp/pkg/server")
376+
377+
# Get all dependencies (including indirect)
378+
all_deps = go_analyzer.get_package_dependencies(
379+
"github.com/myorg/myapp/pkg/server",
380+
include_indirect=True
381+
)
382+
```
383+
384+
**Parameters:**
385+
386+
* **`package_path`** (`str`, required):
387+
Full import path of the package to check.
388+
* **`include_indirect`** (`bool`, optional):
389+
Whether to include indirect dependencies. Defaults to `False`.
390+
391+
**Returns:**
392+
393+
* List of package import paths this package depends on
394+
395+
### JavaScriptDependencyAnalyzer
396+
397+
**Class: `JavaScriptDependencyAnalyzer`**
398+
*(defined in `kit/dependency_analyzer/javascript_dependency_analyzer.py`)*
399+
400+
The `JavaScriptDependencyAnalyzer` extends the base `DependencyAnalyzer` to analyze JavaScript and TypeScript codebases, supporting both ESM (`import`) and CommonJS (`require`) module systems.
401+
402+
#### Additional Methods
403+
404+
##### `get_module_dependencies`
405+
406+
**Method: `JavaScriptDependencyAnalyzer.get_module_dependencies`**
407+
*(defined in `kit/dependency_analyzer/javascript_dependency_analyzer.py`)*
408+
409+
Gets dependencies for a specific JavaScript/TypeScript module.
410+
411+
```python
412+
# Get direct dependencies
413+
deps = js_analyzer.get_module_dependencies("src/index.js")
414+
415+
# Get all dependencies (including indirect)
416+
all_deps = js_analyzer.get_module_dependencies(
417+
"src/index.js",
418+
include_indirect=True
419+
)
420+
```
421+
422+
**Parameters:**
423+
424+
* **`module_path`** (`str`, required):
425+
Path to the module file.
426+
* **`include_indirect`** (`bool`, optional):
427+
Whether to include indirect dependencies. Defaults to `False`.
428+
429+
**Returns:**
430+
431+
* List of module paths/package names this module depends on
432+
433+
##### `generate_dependency_report`
434+
435+
**Method: `JavaScriptDependencyAnalyzer.generate_dependency_report`**
436+
*(defined in `kit/dependency_analyzer/javascript_dependency_analyzer.py`)*
437+
438+
Generates a comprehensive dependency report for the repository.
439+
440+
```python
441+
report = js_analyzer.generate_dependency_report(
442+
output_path="dependency_report.json"
443+
)
444+
```
445+
446+
**Parameters:**
447+
448+
* **`output_path`** (`str`, optional):
449+
Path to save the report JSON. If `None`, returns the report data without saving.
450+
451+
**Returns:**
452+
453+
* Dictionary with the complete dependency report including:
454+
* Package name (from package.json)
455+
* Internal module count
456+
* External package count
457+
* Node.js built-in usage
458+
* Detected cycles
459+
* High-dependency modules
460+
461+
#### Supported Import Styles
462+
463+
The JavaScript analyzer detects:
464+
- ESM imports: `import x from 'pkg'`, `import { x } from 'pkg'`
465+
- ESM re-exports: `export { x } from 'pkg'`, `export * from 'pkg'`
466+
- CommonJS: `require('pkg')`
467+
- Dynamic imports: `import('pkg')`
468+
469+
#### Dependency Classification
470+
471+
Dependencies are classified as:
472+
- **internal**: Relative imports (`./`, `../`)
473+
- **external**: npm packages (including scoped `@org/pkg`)
474+
- **node_builtin**: Node.js built-in modules (`fs`, `path`, `http`, etc.)
475+
357476
## Key Features and Notes
358477

359478
- All dependency analyzers store absolute file paths for resources, making it easy to locate components in complex codebases with files that might have the same name in different directories.
@@ -363,3 +482,5 @@ docs = terraform_analyzer.generate_resource_documentation(
363482
- Visualizations require the Graphviz software to be installed on your system.
364483

365484
- The dependency graph is built on first use and cached. If the codebase changes, you may need to call `build_dependency_graph()` again to refresh the analysis.
485+
486+
- **Supported languages**: Python, Terraform, Go, JavaScript/TypeScript

docs/src/content/docs/development/roadmap.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ This roadmap reflects our current priorities. Updated quarterly.
2020
## Now / Next
2121

2222
**Dependency Analysis Expansion**
23-
- JavaScript/TypeScript (package.json, ESM/CJS imports)
23+
- ~~JavaScript/TypeScript (package.json, ESM/CJS imports)~~ ✓ Shipped
2424
- Rust (Cargo.toml, mod system)
2525
- Java (Maven pom.xml, Gradle)
2626

@@ -42,4 +42,4 @@ These are ideas we're considering. No commitments.
4242

4343
## Feedback
4444

45-
Have ideas or priorities? [Open an issue](https://github.com/cased/kit/issues) or [start a discussion](https://github.com/cased/kit/discussions).
45+
Have ideas or priorities? [Open an issue](https://github.com/cased/kit/issues).

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "cased-kit"
3-
version = "3.0.0"
3+
version = "3.1.0"
44
description = "A modular toolkit for LLM-powered codebase understanding."
55
authors = [
66
{ name = "Cased", email = "ted@cased.com" }

src/kit/cli.py

Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,7 @@ def analyze_dependencies(
338338
):
339339
"""Analyze and visualize code dependencies within a repository.
340340
341-
Supports Python, Terraform, and Go dependency analysis with features including:
341+
Supports Python, Terraform, Go, and JavaScript/TypeScript dependency analysis with features including:
342342
• Dependency graph generation and export
343343
• Circular dependency detection
344344
• Module-specific analysis
@@ -348,7 +348,7 @@ def analyze_dependencies(
348348
Examples:
349349
kit dependencies . --language python --format dot --output deps.dot
350350
kit dependencies . --language terraform --cycles --visualize
351-
kit dependencies . --language python --module kit.repository --include-indirect
351+
kit dependencies . --language javascript --module src/index.js --include-indirect
352352
kit dependencies . --language python --llm-context --output context.md
353353
"""
354354
from kit import Repository
@@ -357,10 +357,10 @@ def analyze_dependencies(
357357
repo = Repository(path, ref=ref)
358358

359359
# Validate language
360-
supported_languages = ["python", "terraform", "go", "golang"]
360+
supported_languages = ["python", "terraform", "go", "golang", "javascript", "typescript", "js", "ts"]
361361
if language.lower() not in supported_languages:
362362
typer.secho(
363-
f"❌ Unsupported language: {language}. Supported: {', '.join(supported_languages)}",
363+
f"❌ Unsupported language: {language}. Supported: python, terraform, go, javascript, typescript",
364364
fg=typer.colors.RED,
365365
)
366366
raise typer.Exit(code=1)
@@ -407,23 +407,9 @@ def analyze_dependencies(
407407
typer.secho(f"❌ Module/resource '{module}' not found in dependency graph", fg=typer.colors.RED)
408408
raise typer.Exit(code=1)
409409

410-
# Get dependencies and dependents using the correct methods for each analyzer type
411-
if language.lower() == "python":
412-
# Use the Python-specific methods
413-
if hasattr(analyzer, "get_module_dependencies") and hasattr(analyzer, "get_dependents"):
414-
dependencies = analyzer.get_module_dependencies(module, include_indirect=include_indirect) # type: ignore
415-
dependents = analyzer.get_dependents(module, include_indirect=include_indirect) # type: ignore
416-
else:
417-
typer.secho("❌ Python dependency analyzer methods not available", fg=typer.colors.RED)
418-
raise typer.Exit(code=1)
419-
else: # terraform
420-
# Use the Terraform-specific methods
421-
if hasattr(analyzer, "get_resource_dependencies") and hasattr(analyzer, "get_dependents"):
422-
dependencies = analyzer.get_resource_dependencies(module, include_indirect=include_indirect) # type: ignore
423-
dependents = analyzer.get_dependents(module, include_indirect=include_indirect) # type: ignore
424-
else:
425-
typer.secho("❌ Terraform dependency analyzer methods not available", fg=typer.colors.RED)
426-
raise typer.Exit(code=1)
410+
# Get dependencies and dependents using the generic interface
411+
dependencies = analyzer.get_dependencies(module, include_indirect=include_indirect) # type: ignore
412+
dependents = analyzer.get_dependents(module, include_indirect=include_indirect) # type: ignore
427413

428414
dep_type = "All" if include_indirect else "Direct"
429415
typer.echo(f"📥 {dep_type} dependencies ({len(dependencies)}):")

src/kit/dependency_analyzer/dependency_analyzer.py

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,34 @@ def find_cycles(self) -> List[List[str]]:
6868
"""
6969
pass
7070

71+
@abstractmethod
72+
def get_dependencies(self, item: str, include_indirect: bool = False) -> List[str]:
73+
"""
74+
Get dependencies for a specific component.
75+
76+
Args:
77+
item: Identifier for the component (file path, module name, resource ID, etc.)
78+
include_indirect: Whether to include indirect/transitive dependencies
79+
80+
Returns:
81+
List of component identifiers this item depends on
82+
"""
83+
pass
84+
85+
@abstractmethod
86+
def get_dependents(self, item: str, include_indirect: bool = False) -> List[str]:
87+
"""
88+
Get components that depend on the specified item.
89+
90+
Args:
91+
item: Identifier for the component (file path, module name, resource ID, etc.)
92+
include_indirect: Whether to include indirect/transitive dependents
93+
94+
Returns:
95+
List of component identifiers that depend on this item
96+
"""
97+
pass
98+
7199
@abstractmethod
72100
def visualize_dependencies(self, output_path: str, format: str = "png") -> str:
73101
"""
@@ -208,7 +236,7 @@ def get_for_language(cls, repository: "Repository", language: str) -> "Dependenc
208236
209237
Args:
210238
repository: A kit.Repository instance
211-
language: Language identifier (e.g., 'python', 'terraform', 'go')
239+
language: Language identifier (e.g., 'python', 'terraform', 'go', 'javascript', 'typescript')
212240
213241
Returns:
214242
An appropriate DependencyAnalyzer instance for the language
@@ -217,6 +245,7 @@ def get_for_language(cls, repository: "Repository", language: str) -> "Dependenc
217245
ValueError: If the specified language is not supported
218246
"""
219247
from .go_dependency_analyzer import GoDependencyAnalyzer
248+
from .javascript_dependency_analyzer import JavaScriptDependencyAnalyzer
220249
from .python_dependency_analyzer import PythonDependencyAnalyzer
221250
from .terraform_dependency_analyzer import TerraformDependencyAnalyzer
222251

@@ -228,10 +257,12 @@ def get_for_language(cls, repository: "Repository", language: str) -> "Dependenc
228257
return TerraformDependencyAnalyzer(repository)
229258
elif language == "go" or language == "golang":
230259
return GoDependencyAnalyzer(repository)
260+
elif language in ("javascript", "typescript", "js", "ts"):
261+
return JavaScriptDependencyAnalyzer(repository)
231262
else:
232263
raise ValueError(
233264
f"Unsupported language for dependency analysis: {language}. "
234-
f"Currently supported languages: python, terraform, go"
265+
f"Currently supported languages: python, terraform, go, javascript, typescript"
235266
)
236267

237268
# ------------------------------------------------------------------

src/kit/dependency_analyzer/go_dependency_analyzer.py

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,19 @@ def dfs(pkg):
502502

503503
return cycles
504504

505+
def get_dependencies(self, item: str, include_indirect: bool = False) -> List[str]:
506+
"""
507+
Get dependencies for a specific component.
508+
509+
Args:
510+
item: Import path of the package
511+
include_indirect: Whether to include indirect dependencies
512+
513+
Returns:
514+
List of package import paths this package depends on
515+
"""
516+
return self.get_package_dependencies(item, include_indirect)
517+
505518
def get_package_dependencies(self, package_path: str, include_indirect: bool = False) -> List[str]:
506519
"""
507520
Get dependencies for a specific package.

0 commit comments

Comments
 (0)