Skip to content

Commit 08a0557

Browse files
committed
added typescript support with regression tracking
1 parent d8bda40 commit 08a0557

41 files changed

Lines changed: 2724 additions & 86 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
name: Tests
12+
runs-on: ubuntu-latest
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Install Rust
17+
uses: dtolnay/rust-toolchain@stable
18+
19+
- name: Cache cargo
20+
uses: actions/cache@v4
21+
with:
22+
path: |
23+
~/.cargo/registry
24+
~/.cargo/git
25+
target
26+
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
27+
28+
- name: Unit tests
29+
run: cargo test
30+
31+
- name: Regression tests
32+
run: |
33+
cargo build --quiet
34+
SMARTGREP="./target/debug/smartgrep" bash tests/regression/run.sh

AGENTS_README.md

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ You have access to `smartgrep`, a structural code navigation tool. Read this bef
1010

1111
It is **not a text search tool**. It is a **structural search tool**. It understands code, not bytes.
1212

13-
**Supported languages: Rust, Java, Go.** smartgrep only indexes `.rs`, `.go`, and `.java` files. It has no knowledge of `.md`, `.yaml`, `.toml`, `.json`, `.py`, `.ts`, or any other file type. For those, handle them as you normally would without smartgrep — using smartgrep for code does not mean ignoring everything else.
13+
**Supported languages: Rust, Java, Go, and TypeScript.** smartgrep only indexes `.rs`, `.go`, `.java`, `.ts`, and `.tsx` files. It has no knowledge of `.md`, `.yaml`, `.toml`, `.json`, `.py`, or any other file type. For those, handle them as you normally would without smartgrep — using smartgrep for code does not mean ignoring everything else.
1414

1515
---
1616

@@ -69,14 +69,19 @@ Symbols use language-native kind strings, not a shared enum. The `kind` field on
6969
| **Rust** | fn, method, struct, enum, trait, impl, const, type, mod |
7070
| **Java** | class, interface, enum, method, record |
7171
| **Go** | func, method, struct, interface, const, type |
72+
| **TypeScript** | function, class, interface, enum, type, method, const, namespace |
7273

7374
**Dependency kinds:** Call, TypeRef, Implements (renamed from FunctionCall, TypeReference, TraitImpl)
7475

7576
**`interfaces` vs `traits`:**
76-
- `interfaces` = kind="interface" (Java and Go only)
77+
- `interfaces` = kind="interface" (Java, Go, and TypeScript)
7778
- `traits` = kind="trait" (Rust only)
7879
- These are distinct source keywords — they do not alias each other
7980

81+
**Cross-language queries:**
82+
- `functions` → finds Rust fn, Go func, TS function (all function-like symbols)
83+
- `fns` → Rust only, `funcs` → Go only, `function` → TS only
84+
8085
## Query DSL
8186

8287
### Grammar
@@ -88,6 +93,7 @@ source = source_kind [implementing_clause] [in_clause] [where_clause]
8893
source_kind = "symbols" | "structs" | "functions" | "methods" | "traits"
8994
| "enums" | "impls" | "consts" | "types" | "modules"
9095
| "classes" | "interfaces" | "records"
96+
| "functions" | "namespaces"
9197
| "symbol" <name> | "deps" [<name>] | "refs" [<name>]
9298
implementing_clause = "implementing" <name>
9399
in_clause = "in" '<path_substring>'
@@ -216,7 +222,7 @@ smartgrep map --format json | jq '.[].dir'
216222

217223
### Pattern 7 — Cross-language queries
218224

219-
Same DSL works across Rust, Java, and Go. Use language-native kinds:
225+
Same DSL works across Rust, Java, Go, and TypeScript. Use language-native kinds:
220226

221227
```bash
222228
# Go: methods on a specific receiver type
@@ -239,6 +245,15 @@ smartgrep query "traits where visibility = public | with deps | show name, file,
239245

240246
# Rust: structs implementing a trait
241247
smartgrep query "structs implementing Display"
248+
249+
# TypeScript: find decorated classes
250+
smartgrep query "classes where attributes contains '@Injectable' | with fields"
251+
252+
# TypeScript: interfaces in a specific directory
253+
smartgrep query "interfaces where file contains 'src/types/' | with fields"
254+
255+
# TypeScript: namespaces
256+
smartgrep query "namespaces | with fields"
242257
```
243258

244259
---

CLAUDE.md

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,14 +31,14 @@ Parser (tree-sitter) → IR → Index Builder → Index → Command
3131

3232
## Key design decisions
3333
- IR exists so we can add languages by writing one parser, without changing the index builder or commands
34-
- Parsers are tested against fixture files — small .rs/.java files in tests/fixtures/
34+
- Parsers are tested against fixture files — small .rs/.java/.go/.ts files in tests/fixtures/
3535
- Index builder is tested with hand-built IR — no parsing needed
3636
- Commands are tested with hand-built Index — no builder needed
3737
- Auto-indexing: queries trigger indexing implicitly. Re-indexes when source files change.
3838

3939
## File layout
4040
- `src/ir/` — IR types and validation
41-
- `src/parser/` — tree-sitter parsers (rust.rs, later java.rs)
41+
- `src/parser/` — tree-sitter parsers (rust.rs, java.rs, go.rs, typescript.rs); `mod.rs` has `parse_by_extension` dispatch
4242
- `src/index/` — index types, builder, storage, auto-detection
4343
- `src/commands/` — CLI commands (context, ls, show, deps, refs)
4444
- `src/format/` — text table and JSON output
@@ -52,9 +52,16 @@ Symbols use language-native kind strings, not a shared enum:
5252
- **Rust:** fn, method, struct, enum, trait, impl, const, type, mod
5353
- **Java:** class, interface, enum, method, record
5454
- **Go:** func, method, struct, interface, const, type
55+
- **TypeScript:** function, class, interface, enum, type, method, const, namespace
5556

5657
Dependency kinds: Call (was FunctionCall), TypeRef (was TypeReference), Implements (was TraitImpl)
5758

59+
### Cross-language queries
60+
Umbrella terms find symbols across all languages:
61+
- `functions` → finds Rust `fn`, Go `func`, TS `function`
62+
- `fns` → Rust only, `funcs` → Go only, `function` → TS only
63+
Language-specific terms target one language. Shared terms like `structs`, `interfaces`, `enums` work as before.
64+
5865
### Prefer smartgrep query for compound questions
5966
```bash
6067
# Instead of multiple grep/read calls, compose one query:
@@ -93,12 +100,15 @@ smartgrep query "functions where name starts_with 'New' and file contains 'servi
93100
```
94101

95102
### Language notes
96-
- **Go/Java interfaces** → use `interfaces` (kind="interface")
103+
- **Go/Java/TS interfaces** → use `interfaces` (kind="interface")
97104
- **Rust traits** → use `traits` (kind="trait", Rust only)
98-
- **`interfaces` and `traits` are distinct**`interfaces` matches Java/Go interface, `traits` matches Rust trait
105+
- **`interfaces` and `traits` are distinct**`interfaces` matches Java/Go/TS interface, `traits` matches Rust trait
99106
- **Go method receivers** → stored in `parent` field (e.g., `methods where parent = MultiGateway`)
100107
- **Generated code** → filter out with `where file not contains '.pb.go'`
101108
- **`implementing` clause**`structs implementing Display` finds types that implement a trait/interface
109+
- **TS decorators** → stored in `attributes` (e.g., `classes where attributes contains '@Injectable'`)
110+
- **TS namespaces**`namespaces` or `namespace` (kind="namespace", TS only)
111+
- **node_modules** → automatically skipped during indexing
102112

103113
### When to use smartgrep vs file reading
104114
- **Use smartgrep**: finding symbols, understanding structure, exploring dependencies, listing functions/structs

Cargo.lock

Lines changed: 12 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ tree-sitter = "0.24"
1818
tree-sitter-rust = "0.23"
1919
tree-sitter-java = "0.23"
2020
tree-sitter-go = "0.23"
21+
tree-sitter-typescript = "0.23"
2122
serde = { version = "1", features = ["derive"] }
2223
serde_json = "1"
2324
walkdir = "2"

SKILL.md

Lines changed: 29 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,24 +6,24 @@ Full agent reference: `AGENTS_README.md` in this repo (if available) or see inli
66

77
## Supported languages
88

9-
smartgrep parses **Rust, Java, and Go** only. It has no knowledge of other file types.
9+
smartgrep parses **Rust, Java, Go, and TypeScript** only. It has no knowledge of other file types.
1010

11-
**Use smartgrep for:** `.rs`, `.go`, `.java` files — structural questions about code.
11+
**Use smartgrep for:** `.rs`, `.go`, `.java`, `.ts`, `.tsx` files — structural questions about code.
1212

13-
**For everything else** (`.md`, `.yaml`, `.toml`, `.json`, `.py`, `.ts`, `.js`, Dockerfiles, config files, documentation) — handle as you normally would without smartgrep. It has no knowledge of these file types.
13+
**For everything else** (`.md`, `.yaml`, `.toml`, `.json`, `.py`, `.js`, Dockerfiles, config files, documentation) — handle as you normally would without smartgrep. It has no knowledge of these file types.
1414

1515
Using smartgrep for code does not mean ignoring non-code files. Documentation and config often contain context the code index cannot surface.
1616

1717
## When to use smartgrep
1818

1919
Use it when the user asks about:
20-
- Code structure, architecture, or organization in Rust, Java, or Go files
21-
- Finding classes, functions, interfaces, enums, records, structs, traits
20+
- Code structure, architecture, or organization in Rust, Java, Go, or TypeScript files
21+
- Finding classes, functions, interfaces, enums, records, structs, traits, namespaces
2222
- Dependencies between symbols or what references a symbol
23-
- Exploring an unfamiliar Rust/Java/Go codebase
23+
- Exploring an unfamiliar Rust/Java/Go/TypeScript codebase
2424
- Finding implementations of an interface or trait
2525
- Listing endpoints, services, controllers, consumers
26-
- Any structural question that would otherwise need multiple grep/read calls across `.rs`, `.go`, or `.java` files
26+
- Any structural question that would otherwise need multiple grep/read calls across `.rs`, `.go`, `.java`, `.ts`, or `.tsx` files
2727

2828
## Detecting availability
2929

@@ -99,16 +99,21 @@ Symbols use language-native kind strings (not a shared enum):
9999
| **Rust** | fn, method, struct, enum, trait, impl, const, type, mod |
100100
| **Java** | class, interface, enum, method, record |
101101
| **Go** | func, method, struct, interface, const, type |
102+
| **TypeScript** | function, class, interface, enum, type, method, const, namespace |
102103

103104
**Dependency kinds:** Call, TypeRef, Implements
104105

105-
**`interfaces` vs `traits`:** `interfaces` = Java/Go interface; `traits` = Rust trait. They are distinct.
106+
**`interfaces` vs `traits`:** `interfaces` = Java/Go/TS interface; `traits` = Rust trait. They are distinct.
107+
108+
**Cross-language queries:**
109+
- `functions` → finds Rust fn, Go func, TS function (all function-like symbols)
110+
- `fns` → Rust only, `funcs` → Go only, `function` → TS only
106111

107112
## Query DSL
108113

109114
### Sources
110-
- `structs`, `functions`, `methods`, `traits`, `enums`, `symbols` — list by kind
111-
- `interfaces` — Java/Go interfaces (not Rust traits)
115+
- `structs`, `functions`, `methods`, `traits`, `enums`, `symbols`, `namespaces` — list by kind
116+
- `interfaces` — Java/Go/TS interfaces (not Rust traits)
112117
- `traits` — Rust traits only
113118
- `symbol <name>` — single symbol lookup
114119
- `deps [name]` — dependencies (optionally for a symbol)
@@ -153,6 +158,18 @@ Semicolon-separated: `"structs | limit 5 ; functions | limit 5"`
153158
| List services | `smartgrep query "structs where attributes contains '@Service' \| show name, file"` |
154159
| Public API in a directory | `smartgrep query "functions where file contains 'src/api' and visibility = public \| show name, file, signature"` |
155160

161+
### TypeScript examples
162+
```bash
163+
# TypeScript: find decorated classes
164+
smartgrep query "classes where attributes contains '@Injectable' | with fields"
165+
166+
# TypeScript: interfaces in a specific directory
167+
smartgrep query "interfaces where file contains 'src/types/' | with fields"
168+
169+
# TypeScript: all exported functions (cross-language)
170+
smartgrep query "functions where visibility = public and file contains 'src/'"
171+
```
172+
156173
## Large-repo warnings
157174

158175
**Never run bare `smartgrep map` or `smartgrep ls functions` on a repo with 100+ files.** The output can be hundreds of lines and burns context budget without adding value. Always scope first:
@@ -170,6 +187,8 @@ smartgrep query "functions where file contains 'src/api/' | show name, file | li
170187

171188
**Generated files are excluded by default.** Bindgen output, protobuf stubs, and vendor code are filtered out automatically. Use `--include-generated` only when you specifically need to inspect generated code.
172189

190+
**node_modules** is automatically skipped during indexing.
191+
173192
## Output guidance
174193

175194
Present results concisely. Summarize patterns ("5 controllers, all in src/api/"). Quote symbol names and file paths precisely. Use smartgrep output directly rather than re-reading files unless the user needs full implementation bodies.

src/commands/context.rs

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ use anyhow::Result;
55
use crate::daemon::client;
66
use crate::format::OutputFormat;
77
use crate::index::auto;
8-
use crate::parser::go as go_parser;
9-
use crate::parser::java as java_parser;
10-
use crate::parser::rust as rust_parser;
118

129
/// Run the `context` command: parse a single file and print its symbols.
1310
pub fn run(file: &Path, format_str: &str, use_daemon: bool) -> Result<()> {
@@ -28,16 +25,7 @@ pub fn run(file: &Path, format_str: &str, use_daemon: bool) -> Result<()> {
2825
let source = std::fs::read_to_string(file)
2926
.map_err(|e| anyhow::anyhow!("Cannot read {}: {}", file.display(), e))?;
3027

31-
let ext = file.extension().and_then(|e| e.to_str()).unwrap_or("");
32-
let ir = match ext {
33-
"go" => go_parser::parse_file(file, &source)?,
34-
"java" => java_parser::parse_file(file, &source)?,
35-
"rs" => rust_parser::parse_file(file, &source)?,
36-
_ => return Err(anyhow::anyhow!(
37-
"Unsupported file type '.{}'. smartgrep supports .rs, .java, and .go files.",
38-
ext
39-
)),
40-
};
28+
let ir = crate::parser::parse_by_extension(file, &source)?;
4129

4230
let output = match format_str.parse::<OutputFormat>().unwrap() {
4331
OutputFormat::Json => crate::format::json::format_symbols(&ir),

src/commands/ls.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,9 @@ pub fn run(symbol_type: &Option<String>, in_path: &Option<String>, format_str: &
2727

2828
let kind_filter = symbol_type.as_deref().and_then(normalize_kind_filter);
2929

30-
let mut symbols: Vec<_> = if let Some(ref kind) = kind_filter {
31-
index.by_kind(kind)
30+
let mut symbols: Vec<_> = if let Some(ref kinds) = kind_filter {
31+
let kind_refs: Vec<&str> = kinds.iter().map(|s| s.as_str()).collect();
32+
index.by_kinds(&kind_refs)
3233
} else {
3334
index.symbols.iter().collect()
3435
};

src/commands/map.rs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,14 +172,14 @@ fn group_by_dir(
172172

173173
fn symbol_sort_order(kind: &str) -> u8 {
174174
match kind {
175-
"struct" | "class" | "record" | "enum" | "trait" | "interface" | "type" | "const" => 0,
175+
"struct" | "class" | "record" | "enum" | "trait" | "interface" | "type" | "const" | "namespace" => 0,
176176
_ => 1,
177177
}
178178
}
179179

180180
fn inline_name(s: &Symbol) -> String {
181181
match s.kind.as_str() {
182-
"fn" | "func" => s.name.clone(),
182+
"fn" | "func" | "function" => s.name.clone(),
183183
"struct" => format!("struct {}", s.name),
184184
"class" => format!("class {}", s.name),
185185
"record" => format!("record {}", s.name),
@@ -189,6 +189,7 @@ fn inline_name(s: &Symbol) -> String {
189189
"type" => format!("type {}", s.name),
190190
"const" => format!("const {}", s.name),
191191
"mod" => format!("mod {}", s.name),
192+
"namespace" => format!("namespace {}", s.name),
192193
_ => s.name.clone(),
193194
}
194195
}
@@ -201,7 +202,8 @@ fn kind_label(kind: &str) -> Option<(&'static str, u8)> {
201202
"type" => Some(("type", 3)),
202203
"const" => Some(("const", 4)),
203204
"mod" => Some(("mod", 5)),
204-
"fn" | "func" => Some(("fn", 6)),
205+
"fn" | "func" | "function" => Some(("fn", 6)),
206+
"namespace" => Some(("ns", 7)),
205207
_ => None,
206208
}
207209
}

src/commands/show.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ fn format_symbol_detail(sym: &Symbol, alias: Option<&path_alias::PathAlias>) ->
9595
}
9696

9797
// Params (for functions/methods)
98-
if (sym.kind == "fn" || sym.kind == "func" || sym.kind == "method") && !sym.params.is_empty() {
98+
if (sym.kind == "fn" || sym.kind == "func" || sym.kind == "function" || sym.kind == "method") && !sym.params.is_empty() {
9999
let param_strs: Vec<String> = sym
100100
.params
101101
.iter()

0 commit comments

Comments
 (0)