style: cargo fmt --all (round 4 ingest log feature follow-up)

Phase C4 executor 의 마지막 `fix(test): clippy + fmt fixes` commit 이
test file 부분만 fmt 적용. workspace 전체 fmt 누락 발견 → cargo fmt --all
적용. 모든 import alphabetical reorder + line wrapping 정합.

추가 untracked artifact 동시 commit:
- docs/superpowers/specs/2026-05-28-v0.20-ingest-log-spec.md (491 line, ACCEPT)
- docs/superpowers/plans/2026-05-28-v0.20-ingest-log-plan.md (616 line, ACCEPT)

workspace test: 1370 passed / 0 failed / 50 ignored, ingest_log_smoke green.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-28 04:18:40 +00:00
parent 445b096215
commit 685007789a
235 changed files with 6520 additions and 3955 deletions

View File

@@ -300,12 +300,7 @@ fn build_blocks(
if units.is_empty() {
// Completely empty file or whitespace/comments only.
let total = lines.len() as u32;
units.push((
"<module>".to_string(),
1,
total.max(1),
false,
));
units.push(("<module>".to_string(), 1, total.max(1), false));
}
// If there is only glue (no real unit) the single pushed "<top-level>"
// label should be "<module>" — rename it now.
@@ -383,10 +378,7 @@ fn recover_typedef_alias(node: tree_sitter::Node, source: &str) -> Option<String
/// Handles the common shapes: direct `type_identifier`, or one wrapped
/// in pointer / function declarator nodes (the alias is always the
/// rightmost `type_identifier` descendant).
fn extract_typedef_alias_name<'a>(
decl: tree_sitter::Node,
source: &'a str,
) -> Option<&'a str> {
fn extract_typedef_alias_name<'a>(decl: tree_sitter::Node, source: &'a str) -> Option<&'a str> {
if decl.kind() == "type_identifier" {
return Some(&source[decl.start_byte()..decl.end_byte()]);
}
@@ -490,7 +482,10 @@ mod tests {
let src = "int *find(int *arr, int n) { return arr; }\n";
let doc = tests_support::extract_c(src, "x/find.c");
let s = syms(&doc);
assert!(s.iter().any(|x| x == "find"), "ptr-return fn missing: {s:?}");
assert!(
s.iter().any(|x| x == "find"),
"ptr-return fn missing: {s:?}"
);
}
#[test]
@@ -695,7 +690,10 @@ void print_result(int v) {
let s = syms(&doc);
// Two real functions + one glue block
assert!(s.iter().any(|x| x == "compute"), "compute missing: {s:?}");
assert!(s.iter().any(|x| x == "print_result"), "print_result missing: {s:?}");
assert!(
s.iter().any(|x| x == "print_result"),
"print_result missing: {s:?}"
);
assert!(
s.iter().any(|x| x == "<top-level>"),
"<top-level> glue missing: {s:?}"
@@ -711,10 +709,7 @@ void noop(void) {}
";
let a = tests_support::extract_c(src, "x/det.c");
for _ in 0..20 {
assert_eq!(
tests_support::extract_c(src, "x/det.c").blocks,
a.blocks
);
assert_eq!(tests_support::extract_c(src, "x/det.c").blocks, a.blocks);
}
}
}

View File

@@ -98,9 +98,8 @@ impl Extractor for CppAstExtractor {
let parser_version = self.parser_version();
let doc_id = id_for_doc(&asset.workspace_path, &asset.asset_id, &parser_version);
let source = String::from_utf8(bytes.to_vec()).map_err(|e| {
anyhow::anyhow!("kebab-parse-code: C++ source is not valid UTF-8: {e}")
})?;
let source = String::from_utf8(bytes.to_vec())
.map_err(|e| anyhow::anyhow!("kebab-parse-code: C++ source is not valid UTF-8: {e}"))?;
let blocks = build_blocks_top(&source, &doc_id)?;
let unit_count = blocks.len() as u32;
@@ -309,9 +308,7 @@ fn build_blocks(
flush_glue(glue, units);
let name_node = child.child_by_field_name("name");
let body = child
.child_by_field_name("body")
.unwrap_or(child);
let body = child.child_by_field_name("body").unwrap_or(child);
match name_node {
None => {
@@ -335,7 +332,8 @@ fn build_blocks(
let mut new_prefix = prefix.to_vec();
let mut nc = nn.walk();
for seg in nn.named_children(&mut nc) {
new_prefix.push(source[seg.start_byte()..seg.end_byte()].to_string());
new_prefix
.push(source[seg.start_byte()..seg.end_byte()].to_string());
}
build_blocks(body, source, &new_prefix, units, glue);
flush_glue(glue, units);
@@ -528,11 +526,7 @@ fn unwrap_to_fn_declarator<'a>(
}
/// Given the innermost name node of a function_declarator, produce the symbol.
fn extract_name_node(
inner: tree_sitter::Node,
source: &str,
prefix: &[String],
) -> Option<String> {
fn extract_name_node(inner: tree_sitter::Node, source: &str, prefix: &[String]) -> Option<String> {
match inner.kind() {
"identifier" | "field_identifier" => {
let name = &source[inner.start_byte()..inner.end_byte()];
@@ -652,7 +646,9 @@ pub(crate) mod tests_support {
workspace_root: &root,
config: &cfg,
};
CppAstExtractor::new().extract(&ctx, src.as_bytes()).unwrap()
CppAstExtractor::new()
.extract(&ctx, src.as_bytes())
.unwrap()
}
}
@@ -710,10 +706,19 @@ namespace ns {
let doc = tests_support::extract_cpp(src, "x/foo.cpp");
let s = syms(&doc);
assert!(s.iter().any(|x| x == "ns::Foo"), "ns::Foo missing: {s:?}");
assert!(s.iter().any(|x| x == "ns::Foo::method"), "method missing: {s:?}");
assert!(
s.iter().any(|x| x == "ns::Foo::method"),
"method missing: {s:?}"
);
assert!(s.iter().any(|x| x == "ns::Foo::Foo"), "ctor missing: {s:?}");
assert!(s.iter().any(|x| x == "ns::Foo::~Foo"), "dtor missing: {s:?}");
assert!(s.iter().any(|x| x == "ns::Foo::operator+"), "op+ missing: {s:?}");
assert!(
s.iter().any(|x| x == "ns::Foo::~Foo"),
"dtor missing: {s:?}"
);
assert!(
s.iter().any(|x| x == "ns::Foo::operator+"),
"op+ missing: {s:?}"
);
}
#[test]
@@ -794,7 +799,10 @@ concept Printable = requires(T t) { t.print(); };
let doc = tests_support::extract_cpp(src, "x/foo.cpp");
let s = syms(&doc);
assert!(s.iter().any(|x| x == "Color"), "Color missing: {s:?}");
assert!(s.iter().any(|x| x == "Printable"), "Printable missing: {s:?}");
assert!(
s.iter().any(|x| x == "Printable"),
"Printable missing: {s:?}"
);
}
#[test]
@@ -839,7 +847,10 @@ class Foo {
let src = "#include <vector>\nusing namespace std;\n";
let doc = tests_support::extract_cpp(src, "x/glue.cpp");
let s = syms(&doc);
assert!(s.iter().any(|x| x == "<module>"), "expected <module>: got {s:?}");
assert!(
s.iter().any(|x| x == "<module>"),
"expected <module>: got {s:?}"
);
}
#[test]
@@ -877,7 +888,10 @@ void free_fn() {}
";
let a = tests_support::extract_cpp(src, "x/foo.cpp");
for _ in 0..20 {
assert_eq!(tests_support::extract_cpp(src, "x/foo.cpp").blocks, a.blocks);
assert_eq!(
tests_support::extract_cpp(src, "x/foo.cpp").blocks,
a.blocks
);
}
}
}

View File

@@ -363,7 +363,11 @@ fn flush_glue(
// imports (1A's `only_mod_decls` analog). The post-pass demotes any
// `<module>` to `<top-level>` if the file produced any real unit.
let only_imports = glue.iter().all(|(is_import, _, _)| *is_import == 1);
let label = if only_imports { "<module>" } else { "<top-level>" };
let label = if only_imports {
"<module>"
} else {
"<top-level>"
};
units.push((join_symbol(mod_prefix, &[], label), s, e, false));
glue.clear();
}
@@ -429,8 +433,7 @@ mod tests {
"got {syms:?}"
);
assert!(
syms.iter()
.any(|s| s == "chunk.(MdHeadingV1Chunker).Name2"),
syms.iter().any(|s| s == "chunk.(MdHeadingV1Chunker).Name2"),
"got {syms:?}"
);
assert!(syms.iter().any(|s| s == "chunk.Stringer"), "got {syms:?}");

View File

@@ -83,8 +83,9 @@ impl Extractor for JavaAstExtractor {
let parser_version = self.parser_version();
let doc_id = id_for_doc(&asset.workspace_path, &asset.asset_id, &parser_version);
let source = String::from_utf8(bytes.to_vec())
.map_err(|e| anyhow::anyhow!("kebab-parse-code: Java source is not valid UTF-8: {e}"))?;
let source = String::from_utf8(bytes.to_vec()).map_err(|e| {
anyhow::anyhow!("kebab-parse-code: Java source is not valid UTF-8: {e}")
})?;
let blocks = build_blocks(&source, &doc_id)?;
let unit_count = blocks.len() as u32;
@@ -302,9 +303,7 @@ fn walk_top(
let s = unit_start(&child);
let e = child.end_position().row as u32 + 1;
match child.kind() {
"class_declaration"
| "interface_declaration"
| "record_declaration" => {
"class_declaration" | "interface_declaration" | "record_declaration" => {
if let Some(name) = node_name_text(&child, src) {
glue.retain(|(_, gs, _)| *gs < s);
flush_glue(glue, units, mod_prefix, mod_path);
@@ -426,7 +425,11 @@ fn flush_glue(
// imports (1A's `only_mod_decls` analog). The post-pass demotes any
// `<module>` to `<top-level>` if the file produced any real unit.
let only_imports = glue.iter().all(|(is_import, _, _)| *is_import == 1);
let label = if only_imports { "<module>" } else { "<top-level>" };
let label = if only_imports {
"<module>"
} else {
"<top-level>"
};
units.push((join_symbol(mod_prefix, mod_path, label), s, e, false));
glue.clear();
}
@@ -482,7 +485,8 @@ mod tests {
syms.sort();
// package extracted from source = com.kebab.chunk
assert!(
syms.iter().any(|s| s == "com.kebab.chunk.MdHeadingV1Chunker"),
syms.iter()
.any(|s| s == "com.kebab.chunk.MdHeadingV1Chunker"),
"got {syms:?}"
);
// constructor — Java convention is class-name-as-method-name

View File

@@ -293,7 +293,8 @@ fn build_blocks(
let inner_kind = inner.kind();
match inner_kind {
"function_declaration" | "class_declaration" => {
let name_opt = name_text(&inner, src).map(std::string::ToString::to_string);
let name_opt =
name_text(&inner, src).map(std::string::ToString::to_string);
if let Some(name) = name_opt {
glue.retain(|(_, gs, _)| *gs < outer_s);
flush_glue(glue, units, mod_prefix, mod_path);
@@ -332,9 +333,9 @@ fn build_blocks(
| "function_declaration"
| "class"
| "class_declaration" => {
let name_opt = name_text(&value, src).map(std::string::ToString::to_string);
let leaf =
name_opt.as_deref().unwrap_or("default").to_string();
let name_opt =
name_text(&value, src).map(std::string::ToString::to_string);
let leaf = name_opt.as_deref().unwrap_or("default").to_string();
glue.retain(|(_, gs, _)| *gs < outer_s);
flush_glue(glue, units, mod_prefix, mod_path);
let sym = join_symbol(mod_prefix, mod_path, &leaf);
@@ -383,7 +384,11 @@ fn build_blocks(
let s = glue.iter().map(|(_, a, _)| *a).min().unwrap();
let e = glue.iter().map(|(_, _, b)| *b).max().unwrap();
let only_module = glue.iter().all(|(is_mod, _, _)| *is_mod == 1);
let label = if only_module { "<module>" } else { "<top-level>" };
let label = if only_module {
"<module>"
} else {
"<top-level>"
};
units.push((join_symbol(mod_prefix, mod_path, label), s, e, false));
glue.clear();
}
@@ -442,9 +447,10 @@ mod tests {
use kebab_core::{Block, MediaType, SourceSpan};
fn extract_fixture(workspace_path: &str) -> kebab_core::CanonicalDocument {
let bytes = std::fs::read(
concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/sample.js"),
)
let bytes = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/sample.js"
))
.unwrap();
let asset = crate::rust::tests_support::fixed_code_asset(workspace_path, "javascript");
let cfg = kebab_core::ExtractConfig::default();

View File

@@ -503,7 +503,11 @@ fn flush_glue(
// imports. The post-pass demotes any `<module>` to `<top-level>` if
// the file produced any real unit.
let only_imports = glue.iter().all(|(is_import, _, _)| *is_import == 1);
let label = if only_imports { "<module>" } else { "<top-level>" };
let label = if only_imports {
"<module>"
} else {
"<top-level>"
};
units.push((join_symbol(mod_prefix, mod_path, label), s, e, false));
glue.clear();
}

View File

@@ -57,16 +57,25 @@ mod tests {
#[test]
fn module_path_for_python_strips_src_roots_and_extensions() {
assert_eq!(module_path_for_python("kebab_eval/metrics.py"), "kebab_eval.metrics");
assert_eq!(module_path_for_python("kebab_eval/__init__.py"), "kebab_eval");
assert_eq!(module_path_for_python("src/foo/bar.py"), "foo.bar");
assert_eq!(module_path_for_python("crates/x/src/foo/bar.py"), "foo.bar");
assert_eq!(module_path_for_python("a/b/c.pyi"), "a.b.c");
assert_eq!(module_path_for_python("standalone.py"), "standalone");
assert_eq!(module_path_for_python("src/__init__.py"), "");
assert_eq!(
module_path_for_python("kebab_eval/metrics.py"),
"kebab_eval.metrics"
);
assert_eq!(
module_path_for_python("kebab_eval/__init__.py"),
"kebab_eval"
);
assert_eq!(module_path_for_python("src/foo/bar.py"), "foo.bar");
assert_eq!(module_path_for_python("crates/x/src/foo/bar.py"), "foo.bar");
assert_eq!(module_path_for_python("a/b/c.pyi"), "a.b.c");
assert_eq!(module_path_for_python("standalone.py"), "standalone");
assert_eq!(module_path_for_python("src/__init__.py"), "");
// `tests/` is NOT a stripped source-root — it is preserved as
// part of the module path so test symbols stay namespaced.
assert_eq!(module_path_for_python("tests/test_foo.py"), "tests.test_foo");
assert_eq!(
module_path_for_python("tests/test_foo.py"),
"tests.test_foo"
);
}
#[test]
@@ -75,8 +84,11 @@ mod tests {
let p = format!("src/search/retriever/Retriever.{ext}");
assert_eq!(module_path_for_tsjs(&p), "src/search/retriever/Retriever");
}
assert_eq!(module_path_for_tsjs("foo.ts"), "foo");
assert_eq!(module_path_for_tsjs("a/b/c.ts"), "a/b/c");
assert_eq!(module_path_for_tsjs("packages/x/src/Foo.ts"), "packages/x/src/Foo");
assert_eq!(module_path_for_tsjs("foo.ts"), "foo");
assert_eq!(module_path_for_tsjs("a/b/c.ts"), "a/b/c");
assert_eq!(
module_path_for_tsjs("packages/x/src/Foo.ts"),
"packages/x/src/Foo"
);
}
}

View File

@@ -19,12 +19,12 @@ pub mod rust;
pub(crate) mod scaffold;
pub mod typescript;
pub use c::{PARSER_VERSION as C_PARSER_VERSION, CAstExtractor};
pub use cpp::{PARSER_VERSION as CPP_PARSER_VERSION, CppAstExtractor};
pub use go::{PARSER_VERSION as GO_PARSER_VERSION, GoAstExtractor};
pub use java::{PARSER_VERSION as JAVA_PARSER_VERSION, JavaAstExtractor};
pub use javascript::{PARSER_VERSION as JS_PARSER_VERSION, JavascriptAstExtractor};
pub use kotlin::{PARSER_VERSION as KOTLIN_PARSER_VERSION, KotlinAstExtractor};
pub use c::{CAstExtractor, PARSER_VERSION as C_PARSER_VERSION};
pub use cpp::{CppAstExtractor, PARSER_VERSION as CPP_PARSER_VERSION};
pub use go::{GoAstExtractor, PARSER_VERSION as GO_PARSER_VERSION};
pub use java::{JavaAstExtractor, PARSER_VERSION as JAVA_PARSER_VERSION};
pub use javascript::{JavascriptAstExtractor, PARSER_VERSION as JS_PARSER_VERSION};
pub use kotlin::{KotlinAstExtractor, PARSER_VERSION as KOTLIN_PARSER_VERSION};
pub use lang::{module_path_for_python, module_path_for_tsjs};
pub use python::{PARSER_VERSION as PYTHON_PARSER_VERSION, PythonAstExtractor};
pub use repo::{RepoMeta, detect_repo};

View File

@@ -319,12 +319,23 @@ fn build_blocks(
// demotes any `<module>` to `<top-level>` if the file produced
// any real unit.
let only_imports = glue.iter().all(|(is_import, _, _)| *is_import == 1);
let label = if only_imports { "<module>" } else { "<top-level>" };
let label = if only_imports {
"<module>"
} else {
"<top-level>"
};
units.push((join_symbol(mod_prefix, mod_path, label), s, e, false));
glue.clear();
}
walk(tree.root_node(), source, mod_prefix, &[], &mut units, &mut glue);
walk(
tree.root_node(),
source,
mod_prefix,
&[],
&mut units,
&mut glue,
);
// `<module>` is correct only when the file produced no real unit.
// Otherwise the import-only group becomes `<top-level>` (same
@@ -373,17 +384,18 @@ mod tests {
use kebab_core::{Block, MediaType, SourceSpan};
fn extract_fixture() -> kebab_core::CanonicalDocument {
let bytes = std::fs::read(
concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/sample.py"),
)
let bytes = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/sample.py"
))
.unwrap();
let asset = crate::rust::tests_support::fixed_code_asset(
"kebab_eval/metrics.py", "python",
);
let asset = crate::rust::tests_support::fixed_code_asset("kebab_eval/metrics.py", "python");
let cfg = kebab_core::ExtractConfig::default();
let root = std::path::PathBuf::from("/tmp");
let ctx = kebab_core::ExtractContext {
asset: &asset, workspace_root: &root, config: &cfg,
asset: &asset,
workspace_root: &root,
config: &cfg,
};
PythonAstExtractor::new().extract(&ctx, &bytes).unwrap()
}
@@ -399,16 +411,20 @@ mod tests {
#[test]
fn python_units_carry_module_prefixed_symbols() {
let doc = extract_fixture();
let mut syms: Vec<String> = doc.blocks.iter().map(|b| match b {
Block::Code(c) => match &c.common.source_span {
SourceSpan::Code { symbol, lang, .. } => {
assert_eq!(lang.as_deref(), Some("python"));
symbol.clone().unwrap()
}
_ => panic!("expected SourceSpan::Code"),
},
other => panic!("expected Block::Code, got {other:?}"),
}).collect();
let mut syms: Vec<String> = doc
.blocks
.iter()
.map(|b| match b {
Block::Code(c) => match &c.common.source_span {
SourceSpan::Code { symbol, lang, .. } => {
assert_eq!(lang.as_deref(), Some("python"));
symbol.clone().unwrap()
}
_ => panic!("expected SourceSpan::Code"),
},
other => panic!("expected Block::Code, got {other:?}"),
})
.collect();
syms.sort();
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.free"));
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.Foo"));
@@ -416,8 +432,14 @@ mod tests {
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.Foo.name"));
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.Outer"));
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.Outer.Inner"));
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.Outer.Inner.helper"));
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.with_decorator"));
assert!(
syms.iter()
.any(|s| s == "kebab_eval.metrics.Outer.Inner.helper")
);
assert!(
syms.iter()
.any(|s| s == "kebab_eval.metrics.with_decorator")
);
assert!(syms.iter().any(|s| s == "kebab_eval.metrics.<top-level>"));
// The `@no_type_check` decorator on `free` is folded into its
// unit's line range (decorated_definition unwrap).
@@ -426,12 +448,17 @@ mod tests {
SourceSpan::Code{symbol,..} if symbol.as_deref()==Some("kebab_eval.metrics.free")) => Some(c.code.clone()),
_ => None,
}).unwrap();
assert!(free_src.contains("@no_type_check"), "decorator folded in: {free_src}");
assert!(
free_src.contains("@no_type_check"),
"decorator folded in: {free_src}"
);
}
#[test]
fn deterministic_across_runs() {
let a = extract_fixture();
for _ in 0..50 { assert_eq!(extract_fixture().blocks, a.blocks); }
for _ in 0..50 {
assert_eq!(extract_fixture().blocks, a.blocks);
}
}
}

View File

@@ -32,10 +32,18 @@ pub fn detect_repo(path: &Path) -> Option<RepoMeta> {
if dotgit.is_dir() {
let name = cur.file_name()?.to_string_lossy().into_owned();
let (branch, commit) = read_head(cur);
return Some(RepoMeta { name, branch, commit });
return Some(RepoMeta {
name,
branch,
commit,
});
} else if dotgit.is_file() {
let name = cur.file_name()?.to_string_lossy().into_owned();
return Some(RepoMeta { name, branch: None, commit: None });
return Some(RepoMeta {
name,
branch: None,
commit: None,
});
}
cur = cur.parent()?;
}
@@ -50,10 +58,7 @@ fn read_head(repo_dir: &Path) -> (Option<String>, Option<String>) {
.flatten()
.map(|n| n.shorten().to_string())
.or_else(|| Some("detached".to_string()));
let commit = repo
.head_id()
.ok()
.map(|id| id.to_string());
let commit = repo.head_id().ok().map(|id| id.to_string());
(branch, commit)
}
Err(_) => (None, None),

View File

@@ -224,8 +224,8 @@ fn build_blocks(
let s = unit_start(&child);
let e = child.end_position().row as u32 + 1;
match child.kind() {
"function_item" | "struct_item" | "enum_item" | "union_item"
| "trait_item" | "type_item" => {
"function_item" | "struct_item" | "enum_item" | "union_item" | "trait_item"
| "type_item" => {
if let Some(name) = node_name(&child, src) {
// Gap 2: a leading attribute/comment that this unit
// re-absorbs (via `unit_start`'s upward extension to
@@ -296,8 +296,12 @@ fn build_blocks(
glue.push((1, s, e));
}
}
"use_declaration" | "extern_crate_declaration" | "const_item"
| "static_item" | "attribute_item" | "macro_invocation" => {
"use_declaration"
| "extern_crate_declaration"
| "const_item"
| "static_item"
| "attribute_item"
| "macro_invocation" => {
glue.push((0, s, e));
}
_ => {}
@@ -320,7 +324,11 @@ fn build_blocks(
// requires the *whole file* to have produced zero real units; that
// demotion to `<top-level>` happens in the post-pass below.
let only_mod_decls = glue.iter().all(|(is_mod, _, _)| *is_mod == 1);
let label = if only_mod_decls { "<module>" } else { "<top-level>" };
let label = if only_mod_decls {
"<module>"
} else {
"<top-level>"
};
// Module-path-prefix the label so glue from `mod inner` carries
// module context (`inner::<top-level>`) and doesn't collide with
// file-top-level glue. `prefix` is empty at file top level, so the
@@ -379,14 +387,19 @@ mod tests {
use kebab_core::{Block, MediaType, SourceSpan};
fn extract_fixture() -> kebab_core::CanonicalDocument {
let bytes = std::fs::read(
concat!(env!("CARGO_MANIFEST_DIR"), "/tests/fixtures/sample.rs"),
)
let bytes = std::fs::read(concat!(
env!("CARGO_MANIFEST_DIR"),
"/tests/fixtures/sample.rs"
))
.unwrap();
let asset = tests_support::fixed_code_asset("crates/x/src/sample.rs", "rust");
let cfg = kebab_core::ExtractConfig::default();
let root = std::path::PathBuf::from("/tmp");
let ctx = kebab_core::ExtractContext { asset: &asset, workspace_root: &root, config: &cfg };
let ctx = kebab_core::ExtractContext {
asset: &asset,
workspace_root: &root,
config: &cfg,
};
RustAstExtractor::new().extract(&ctx, &bytes).unwrap()
}
@@ -406,7 +419,12 @@ mod tests {
.iter()
.map(|b| match b {
Block::Code(c) => match &c.common.source_span {
SourceSpan::Code { symbol, line_start, line_end, lang } => {
SourceSpan::Code {
symbol,
line_start,
line_end,
lang,
} => {
assert_eq!(lang.as_deref(), Some("rust"));
(symbol.clone().unwrap(), *line_start, *line_end)
}
@@ -428,7 +446,10 @@ mod tests {
Block::Code(c) if matches!(&c.common.source_span, SourceSpan::Code{symbol,..} if symbol.as_deref()==Some("parse")) => Some(c.code.clone()),
_ => None,
}).unwrap();
assert!(parse_src.contains("/// Doc comment on a free fn."), "doc comment folded in: {parse_src}");
assert!(
parse_src.contains("/// Doc comment on a free fn."),
"doc comment folded in: {parse_src}"
);
}
/// Run the extractor on an in-memory Rust source string (no fixture
@@ -437,7 +458,11 @@ mod tests {
let asset = tests_support::fixed_code_asset("crates/x/src/inline.rs", "rust");
let cfg = kebab_core::ExtractConfig::default();
let root = std::path::PathBuf::from("/tmp");
let ctx = kebab_core::ExtractContext { asset: &asset, workspace_root: &root, config: &cfg };
let ctx = kebab_core::ExtractContext {
asset: &asset,
workspace_root: &root,
config: &cfg,
};
let doc = RustAstExtractor::new()
.extract(&ctx, source.as_bytes())
.unwrap();
@@ -445,9 +470,7 @@ mod tests {
.iter()
.map(|b| match b {
Block::Code(c) => match &c.common.source_span {
SourceSpan::Code { symbol, .. } => {
(symbol.clone().unwrap(), c.code.clone())
}
SourceSpan::Code { symbol, .. } => (symbol.clone().unwrap(), c.code.clone()),
_ => panic!("code block must carry SourceSpan::Code"),
},
other => panic!("expected Block::Code, got {other:?}"),

View File

@@ -299,9 +299,7 @@ fn build_blocks(
}
}
}
"interface_declaration"
| "type_alias_declaration"
| "enum_declaration" => {
"interface_declaration" | "type_alias_declaration" | "enum_declaration" => {
if let Some(name) = name_text(&child, src) {
glue.retain(|(_, gs, _)| *gs < s);
flush_glue(glue, units, mod_prefix, mod_path);
@@ -326,22 +324,18 @@ fn build_blocks(
| "interface_declaration"
| "type_alias_declaration"
| "enum_declaration" => {
let name_opt = name_text(&inner, src).map(std::string::ToString::to_string);
let name_opt =
name_text(&inner, src).map(std::string::ToString::to_string);
if let Some(name) = name_opt {
glue.retain(|(_, gs, _)| *gs < outer_s);
flush_glue(glue, units, mod_prefix, mod_path);
let sym =
join_symbol(mod_prefix, mod_path, &name);
let sym = join_symbol(mod_prefix, mod_path, &name);
units.push((sym, outer_s, outer_e, true));
if inner_kind == "class_declaration" {
if let Some(body) =
inner.child_by_field_name("body")
{
if let Some(body) = inner.child_by_field_name("body") {
let mut np = mod_path.to_vec();
np.push(name);
walk_class_body(
body, src, mod_prefix, &np, units,
);
walk_class_body(body, src, mod_prefix, &np, units);
}
}
} else {
@@ -354,8 +348,7 @@ fn build_blocks(
// `default` defensively.
glue.retain(|(_, gs, _)| *gs < outer_s);
flush_glue(glue, units, mod_prefix, mod_path);
let sym =
join_symbol(mod_prefix, mod_path, "default");
let sym = join_symbol(mod_prefix, mod_path, "default");
units.push((sym, outer_s, outer_e, true));
}
}
@@ -377,27 +370,17 @@ fn build_blocks(
| "class_declaration" => {
let name_opt =
name_text(&value, src).map(std::string::ToString::to_string);
let leaf = name_opt
.as_deref()
.unwrap_or("default")
.to_string();
let leaf = name_opt.as_deref().unwrap_or("default").to_string();
glue.retain(|(_, gs, _)| *gs < outer_s);
flush_glue(glue, units, mod_prefix, mod_path);
let sym = join_symbol(mod_prefix, mod_path, &leaf);
units.push((sym, outer_s, outer_e, true));
// Recurse into class body if we have one.
if matches!(
value.kind(),
"class" | "class_declaration"
) {
if let Some(body) =
value.child_by_field_name("body")
{
if matches!(value.kind(), "class" | "class_declaration") {
if let Some(body) = value.child_by_field_name("body") {
let mut np = mod_path.to_vec();
np.push(leaf);
walk_class_body(
body, src, mod_prefix, &np, units,
);
walk_class_body(body, src, mod_prefix, &np, units);
}
}
}
@@ -442,7 +425,11 @@ fn build_blocks(
let s = glue.iter().map(|(_, a, _)| *a).min().unwrap();
let e = glue.iter().map(|(_, _, b)| *b).max().unwrap();
let only_module = glue.iter().all(|(is_mod, _, _)| *is_mod == 1);
let label = if only_module { "<module>" } else { "<top-level>" };
let label = if only_module {
"<module>"
} else {
"<top-level>"
};
units.push((join_symbol(mod_prefix, mod_path, label), s, e, false));
glue.clear();
}
@@ -514,9 +501,7 @@ mod tests {
workspace_root: &root,
config: &cfg,
};
TypescriptAstExtractor::new()
.extract(&ctx, &bytes)
.unwrap()
TypescriptAstExtractor::new().extract(&ctx, &bytes).unwrap()
}
fn symbols(doc: &kebab_core::CanonicalDocument) -> Vec<String> {
@@ -565,10 +550,7 @@ mod tests {
fn tsx_uses_tsx_grammar_and_emits_units() {
let doc = extract_fixture("sample.tsx", "src/sample.tsx");
let syms = symbols(&doc);
assert!(
syms.iter().any(|s| s == "src/sample.Hello"),
"got {syms:?}"
);
assert!(syms.iter().any(|s| s == "src/sample.Hello"), "got {syms:?}");
assert!(
syms.iter().any(|s| s == "src/sample.<top-level>"),
"arrow fn + import should roll into top-level glue"
@@ -579,7 +561,10 @@ mod tests {
fn deterministic_across_runs() {
let a = extract_fixture("sample.ts", "src/sample.ts");
for _ in 0..30 {
assert_eq!(extract_fixture("sample.ts", "src/sample.ts").blocks, a.blocks);
assert_eq!(
extract_fixture("sample.ts", "src/sample.ts").blocks,
a.blocks
);
}
}