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:
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:?}");
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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:?}"),
|
||||
|
||||
@@ -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
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user