Skip to content

fix(chunker): add TOML/PS1 file recognition for search indexing - #29

Open
aspnmy wants to merge 1 commit into
aeroxy:mainfrom
aspnmy:fix/chunker-toml-ps1
Open

fix(chunker): add TOML/PS1 file recognition for search indexing#29
aspnmy wants to merge 1 commit into
aeroxy:mainfrom
aspnmy:fix/chunker-toml-ps1

Conversation

@aspnmy

@aspnmy aspnmy commented Jun 19, 2026

Copy link
Copy Markdown

本 PR 做了什么

修复 ast-bro 搜索索引器(chunker)对 TOML 和 PowerShell 文件的识别缺陷。
chunker::is_indexable() 此前仅依赖 SupportLang::from_path() 检测文件类型,
ast_grep_language 枚举中不包含 TOML/PowerShell variant,
导致 .toml / .ps1 / .psm1 / .psd1 文件被搜索索引器跳过。

新增 ChunkerKind::Plain 变体,通过空行分段策略支持这些纯文本格式。

为什么需要

map / digest 命令可以通过 can_parse_for_hook() 中的硬编码扩展名列表正确
解析 TOML/PS1 文件,但 index / search 命令走独立的 is_indexable() 路径,
两条路径不一致导致:

  • ast-bro map Cargo.toml ✅ 可解析
  • ast-bro search "serde" ❌ 不返回 Cargo.toml 结果
  • ast-bro map monitor.ps1 ✅ 可解析
  • ast-bro search "Get-ProcessInfo" ❌ 不返回 monitor.ps1 结果

审查者验证方式

如何验证

# 运行 chunker 测试套件
cargo test -p ast-bro --lib search::chunker::tests
# 验证新测试通过
cargo test is_indexable_accepts_toml
cargo test is_indexable_accepts_powershell

效果对比

改前: is_indexable("Cargo.toml")None,文件不进入索引
改后: is_indexable("Cargo.toml")Some(ChunkerKind::Plain("toml")),文件被空行分段索引

已测试平台

系统 状态
🍏 macOS ⚠️ 未测试
🪟 Windows ✅ 已测试 (x86_64-pc-windows-msvc)
🐧 Linux ✅ 已测试 (x86_64-unknown-linux-gnu)

验证命令

cd ast-bro
cargo check              #
cargo clippy -- -D warnings  # ✅ 0 warnings
cargo fmt --check        #
cargo test --lib         # ✅ 308 passed, 0 failed

风险与范围

  • 主要风险: 低——仅影响搜索索引器的文件类型检测逻辑,不改变已有格式的索引行为
  • 未验证范围: TOML/PS1 文件的实际搜索质量(索引→嵌入→搜索完整链路)、大文件的分段表现
  • 破坏性变更:

关联 Issue

English Translation

What this PR does

Fixes the search indexer (chunker) to recognize TOML and PowerShell files.
Previously chunker::is_indexable() relied solely on SupportLang::from_path(),
which lacks TOML/PowerShell variants in ast_grep_language, causing
.toml / .ps1 files to be silently skipped by the indexer.

Adds a ChunkerKind::Plain variant with paragraph-boundary splitting.

Why it's needed

map/digest parse TOML/PS1 correctly via hardcoded extension checks in
can_parse_for_hook(), but index/search use a separate is_indexable()
path — creating an inconsistency where map works but search doesn't.

Reviewer Test Plan

cargo test -p ast-bro --lib search::chunker::tests  # 16 passed

Evidence (Before & After)

Before: is_indexable("Cargo.toml")None
After: is_indexable("Cargo.toml")Some(ChunkerKind::Plain("toml"))

Risk & Scope

  • Main risk: Low — only affects chunker file detection logic
  • Breaking changes: None

AI Assistance Disclosure

本 PR 使用 aspnmy-Qwen-Auto-Cli 辅助实现和审查。

Summary by CodeRabbit

New Features

  • Search and indexing capabilities have been extended to support TOML configuration files, including Cargo.toml and other .toml files
  • Added support for indexing and searching PowerShell script files (.ps1, .psm1, .psd1)
  • Enhanced content parsing for plain text formats with intelligent splitting at paragraph boundaries

The chunker.is_indexable() relied on SupportLang::from_path() which does
not recognize .toml or .ps1/.psm1/.psd1 extensions. Meanwhile, map/digest
had hardcoded extension checks for these formats via can_parse_for_hook().

Changes:
- Add ChunkerKind::Plain(&str) variant for non-tree-sitter formats
- Extend is_indexable() with explicit .toml/.ps1/.psm1/.psd1 checks
- Add paragraph_split_points() for plain-text chunking at blank lines
- Add tests: is_indexable_accepts_toml, is_indexable_accepts_powershell
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a Plain(&'static str) variant to ChunkerKind in src/search/chunker.rs. Extends is_indexable to return ChunkerKind::Plain for .toml and PowerShell (.ps1, .psm1, .psd1) extensions. Wires the variant into chunk_source via a new paragraph_split_points function that splits on blank-line boundaries.

Changes

Plain-text chunking support

Layer / File(s) Summary
ChunkerKind::Plain variant, indexable extensions, and dispatch
src/search/chunker.rs
Adds the Plain(&'static str) enum variant, updates language_name to handle it, extends is_indexable to map .toml and PowerShell extensions to ChunkerKind::Plain(...), and adds a Plain(_) match arm in chunk_source that routes to the new splitter.
paragraph_split_points implementation and tests
src/search/chunker.rs
Implements paragraph_split_points, which scans for consecutive \n bytes to emit split byte offsets from start through source.len(). Adds unit tests asserting .toml and PowerShell extensions are accepted by is_indexable.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A bunny hops through text today,
No grammar tree to guide the way!
Blank lines become the splitting seams,
For TOML configs and PowerShell dreams.
🐇 Plain chunks for all, hooray! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'fix(chunker): add TOML/PS1 file recognition for search indexing' accurately and concisely describes the main change: extending file type support for search indexing with explicit recognition of TOML and PowerShell files.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Code Review

This pull request adds support for indexing plain-text formats, specifically TOML and PowerShell files, using a new paragraph-based chunking strategy that splits content at blank-line boundaries. The review feedback correctly identifies a bug in the paragraph-splitting logic, which fails to recognize boundaries in files with CRLF line endings (common on Windows and in PowerShell scripts), and provides a robust code suggestion to handle carriage returns.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread src/search/chunker.rs
Comment on lines +167 to +189
fn paragraph_split_points(source: &str) -> Vec<usize> {
let mut points = vec![0usize];
let bytes = source.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == b'\n' {
let start = i;
while i < len && bytes[i] == b'\n' {
i += 1;
}
if i - start >= 2 && i < len {
points.push(i);
}
} else {
i += 1;
}
}
if *points.last().unwrap() < source.len() {
points.push(source.len());
}
points
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The current implementation of paragraph_split_points only checks for consecutive \n bytes to identify paragraph boundaries (blank lines). However, on Windows platforms or in repositories using CRLF line endings (\r\n), a blank line is represented by \r\n\r\n. In this case, the bytes are \n, \r, \n, so bytes[i] == b'\n' followed by bytes[i+1] == b'\r' prevents the while loop from matching consecutive \ns. As a result, i - start will be 1, and the boundary will not be detected. This means paragraph splitting will completely fail for CRLF files (such as PowerShell scripts, which are highly likely to use CRLF on Windows).

We can fix this by explicitly handling optional \r characters between consecutive \n bytes.

fn paragraph_split_points(source: &str) -> Vec<usize> {
    let mut points = vec![0usize];
    let bytes = source.as_bytes();
    let len = bytes.len();
    let mut i = 0;
    while i < len {
        if bytes[i] == b'\n' {
            let mut next = i + 1;
            if next < len && bytes[next] == b'\r' {
                next += 1;
            }
            if next < len && bytes[next] == b'\n' {
                while next < len && (bytes[next] == b'\n' || bytes[next] == b'\r') {
                    next += 1;
                }
                points.push(next);
                i = next;
            } else {
                i += 1;
            }
        } else {
            i += 1;
        }
    }
    if *points.last().unwrap() < source.len() {
        points.push(source.len());
    }
    points
}

@aeroxy

aeroxy commented Jun 23, 2026

Copy link
Copy Markdown
Owner

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/search/chunker.rs (1)

426-443: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Tests should assert the exact ChunkerKind::Plain mapping, not only is_some().

These tests currently pass even if .toml/PowerShell later map to a different chunker kind. As written, they don’t lock the contract introduced in this PR.

Proposed test tightening
 #[test]
 fn is_indexable_accepts_toml() {
-    assert!(
-        is_indexable(&PathBuf::from("Cargo.toml")).is_some(),
-        "expected .toml to be indexable"
+    assert_eq!(
+        is_indexable(&PathBuf::from("Cargo.toml")),
+        Some(ChunkerKind::Plain("toml"))
     );
 }

 #[test]
 fn is_indexable_accepts_powershell() {
     for ext in ["ps1", "psm1", "psd1"] {
         let p = PathBuf::from(format!("script.{}", ext));
-        assert!(
-            is_indexable(&p).is_some(),
-            "expected .{} to be indexable",
-            ext
-        );
+        assert_eq!(
+            is_indexable(&p),
+            Some(ChunkerKind::Plain("powershell")),
+            "expected .{} to map to plain powershell chunker",
+            ext
+        );
     }
 }
🤖 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/search/chunker.rs` around lines 426 - 443, The tests
is_indexable_accepts_toml and is_indexable_accepts_powershell currently only
check that is_indexable returns Some using is_some(), but they should assert the
exact ChunkerKind value being returned. Replace the is_some() checks with
assertions that verify the returned value equals Some(ChunkerKind::Plain) to
properly enforce the contract introduced in this PR. This ensures the tests will
fail if these file types are later mapped to a different chunker kind.
🤖 Prompt for all review comments with 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.

Inline comments:
In `@src/search/chunker.rs`:
- Around line 165-190: The paragraph_split_points function only detects
Unix-style newlines (LF, `\n`) when identifying blank-line boundaries, causing
Windows-style line endings (CRLF, `\r\n`) to not trigger paragraph splits.
Modify the logic in the while loop that checks for consecutive newlines to also
account for carriage return characters (`\r`). When counting consecutive newline
characters to determine if a blank line exists (currently checking if i - start
>= 2), ensure the logic correctly handles cases where `\r\n` sequences appear,
treating them as part of the blank line boundary detection instead of stopping
at just `\n` characters.

---

Nitpick comments:
In `@src/search/chunker.rs`:
- Around line 426-443: The tests is_indexable_accepts_toml and
is_indexable_accepts_powershell currently only check that is_indexable returns
Some using is_some(), but they should assert the exact ChunkerKind value being
returned. Replace the is_some() checks with assertions that verify the returned
value equals Some(ChunkerKind::Plain) to properly enforce the contract
introduced in this PR. This ensures the tests will fail if these file types are
later mapped to a different chunker kind.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 15476f2d-0aad-4138-98c0-30a664b5757b

📥 Commits

Reviewing files that changed from the base of the PR and between 18430fa and 6862e91.

📒 Files selected for processing (1)
  • src/search/chunker.rs

Comment thread src/search/chunker.rs
Comment on lines +165 to +190
/// Split at blank-line boundaries (consecutive newlines).
/// Used for plain-text formats (TOML, PowerShell) that lack tree-sitter grammars.
fn paragraph_split_points(source: &str) -> Vec<usize> {
let mut points = vec![0usize];
let bytes = source.as_bytes();
let len = bytes.len();
let mut i = 0;
while i < len {
if bytes[i] == b'\n' {
let start = i;
while i < len && bytes[i] == b'\n' {
i += 1;
}
if i - start >= 2 && i < len {
points.push(i);
}
} else {
i += 1;
}
}
if *points.last().unwrap() < source.len() {
points.push(source.len());
}
points
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

CRLF blank lines are not split as paragraphs.

paragraph_split_points only treats consecutive \n bytes as a boundary, so Windows-style blank lines (\r\n\r\n) won’t trigger splits. That makes plain chunking behavior inconsistent across platforms and defeats the intended paragraph boundary behavior for common PowerShell/TOML files.

Proposed fix
 fn paragraph_split_points(source: &str) -> Vec<usize> {
     let mut points = vec![0usize];
     let bytes = source.as_bytes();
     let len = bytes.len();
     let mut i = 0;
+
+    let linebreak_len = |b: &[u8], pos: usize| -> Option<usize> {
+        match b.get(pos).copied() {
+            Some(b'\n') => Some(1),
+            Some(b'\r') => {
+                if b.get(pos + 1) == Some(&b'\n') {
+                    Some(2)
+                } else {
+                    Some(1)
+                }
+            }
+            _ => None,
+        }
+    };
+
     while i < len {
-        if bytes[i] == b'\n' {
-            let start = i;
-            while i < len && bytes[i] == b'\n' {
-                i += 1;
-            }
-            if i - start >= 2 && i < len {
+        if linebreak_len(bytes, i).is_some() {
+            let mut breaks = 0usize;
+            while let Some(lb) = linebreak_len(bytes, i) {
+                breaks += 1;
+                i += lb;
+            }
+            if breaks >= 2 && i < len {
                 points.push(i);
             }
         } else {
             i += 1;
         }
     }
     if *points.last().unwrap() < source.len() {
         points.push(source.len());
     }
     points
 }
🤖 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/search/chunker.rs` around lines 165 - 190, The paragraph_split_points
function only detects Unix-style newlines (LF, `\n`) when identifying blank-line
boundaries, causing Windows-style line endings (CRLF, `\r\n`) to not trigger
paragraph splits. Modify the logic in the while loop that checks for consecutive
newlines to also account for carriage return characters (`\r`). When counting
consecutive newline characters to determine if a blank line exists (currently
checking if i - start >= 2), ensure the logic correctly handles cases where
`\r\n` sequences appear, treating them as part of the blank line boundary
detection instead of stopping at just `\n` characters.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants