refactor(chunk): migrated chunk naming to type-safe ChunkKind enum
- Replaced string-based chunk naming with type-safe ChunkKind enum across 35+ AST classifier modules. - Refactored ChunkNode to use optional identifier and ChunkKind instead of name string for semantic clarity. - Created new kind.rs module with ChunkKind enum (150+ variants) and trait-based metadata system. - Removed string prefix-based chunk type detection and replaced with ChunkKind enum matching throughout. - Updated chunk API signatures to accept ChunkKind and optional identifier instead of formatted names.
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct AstroClassifier;
|
||||
|
||||
@@ -27,89 +27,116 @@ impl LangClassifier for AstroClassifier {
|
||||
fn classify_astro_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"frontmatter" => Some(classify_frontmatter(node, source)),
|
||||
"frontmatter_js_block" => Some(group_candidate(node, "code", source)),
|
||||
"frontmatter_js_block" => Some(group_candidate(node, ChunkKind::Code, source)),
|
||||
"element" => classify_element(node, source),
|
||||
"script_element" => Some(classify_script_element(node, source)),
|
||||
"style_element" => Some(classify_style_element(node, source)),
|
||||
"html_interpolation" => Some(classify_html_interpolation(node, source)),
|
||||
"attribute_interpolation" => Some(classify_attribute_interpolation(node, source)),
|
||||
"attribute_js_expr" => Some(group_candidate(node, "expr", source)),
|
||||
"text" => Some(group_candidate(node, "text", source)),
|
||||
"attribute_js_expr" => Some(group_candidate(node, ChunkKind::Expression, source)),
|
||||
"text" => Some(group_candidate(node, ChunkKind::Text, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_frontmatter<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
make_container_chunk(
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
"frontmatter".to_string(),
|
||||
ChunkKind::Frontmatter,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["frontmatter_js_block"]),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn classify_element<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
let tag_name = extract_tag_name(node, source)?;
|
||||
let prefix = if is_component_name(tag_name.as_str()) {
|
||||
"component"
|
||||
let recurse = Some(recurse_self(node, ChunkContext::ClassBody));
|
||||
if is_component_name(tag_name.as_str()) {
|
||||
Some(force_container(make_explicit_candidate(
|
||||
node,
|
||||
ChunkKind::Tag,
|
||||
format!("component_{tag_name}"),
|
||||
source,
|
||||
recurse,
|
||||
)))
|
||||
} else {
|
||||
"tag"
|
||||
};
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("{prefix}_{tag_name}"),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
Some(force_container(make_container_chunk(
|
||||
node,
|
||||
ChunkKind::Tag,
|
||||
Some(tag_name),
|
||||
source,
|
||||
recurse,
|
||||
)))
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_script_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = if has_attribute(node, "is:inline", source) {
|
||||
"script_inline"
|
||||
} else {
|
||||
"script"
|
||||
};
|
||||
let identifier = has_attribute(node, "is:inline", source).then_some("inline".to_string());
|
||||
// The Astro grammar exposes script bodies as `raw_text`, not nested JS AST.
|
||||
make_named_chunk(node, name.to_string(), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Script, identifier, source, None)
|
||||
}
|
||||
|
||||
fn classify_style_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = if has_attribute(node, "define:vars", source) {
|
||||
"style_vars"
|
||||
let identifier = if has_attribute(node, "define:vars", source) {
|
||||
Some("vars".to_string())
|
||||
} else if has_attribute(node, "is:global", source) {
|
||||
"style_global"
|
||||
Some("global".to_string())
|
||||
} else {
|
||||
"style"
|
||||
None
|
||||
};
|
||||
// The Astro grammar exposes style bodies as `raw_text`, so the section itself
|
||||
// is the truthful chunk boundary.
|
||||
make_named_chunk(node, name.to_string(), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Style, identifier, source, None)
|
||||
}
|
||||
|
||||
fn classify_html_interpolation<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = child_by_kind(node, &["permissible_text"])
|
||||
.and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte())))
|
||||
.map_or_else(|| "expr".to_string(), |expr| format!("expr_{expr}"));
|
||||
let identifier = child_by_kind(node, &["permissible_text"])
|
||||
.and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte())));
|
||||
|
||||
if let Some(nested_element) =
|
||||
child_by_kind(node, &["element", "script_element", "style_element"])
|
||||
{
|
||||
make_container_chunk(
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
name,
|
||||
ChunkKind::Expression,
|
||||
identifier,
|
||||
source,
|
||||
Some(recurse_self(nested_element, ChunkContext::ClassBody)),
|
||||
)
|
||||
))
|
||||
} else {
|
||||
make_named_chunk(node, name, source, None)
|
||||
make_kind_chunk(node, ChunkKind::Expression, identifier, source, None)
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_attribute_interpolation<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = child_by_kind(node, &["attribute_js_expr"])
|
||||
let identifier = child_by_kind(node, &["attribute_js_expr"])
|
||||
.and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte())))
|
||||
.map_or_else(|| "attr_expr".to_string(), |expr| format!("attr_expr_{expr}"));
|
||||
make_named_chunk(node, name, source, None)
|
||||
.map_or_else(|| "expr".to_string(), |expr| format!("expr_{expr}"));
|
||||
make_kind_chunk(node, ChunkKind::Attr, Some(identifier), source, None)
|
||||
}
|
||||
|
||||
fn make_explicit_candidate<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> {
|
||||
candidate.force_recurse = true;
|
||||
candidate
|
||||
}
|
||||
|
||||
fn extract_tag_name(node: Node<'_>, source: &str) -> Option<String> {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct ShellBuildClassifier;
|
||||
|
||||
@@ -29,7 +29,8 @@ impl LangClassifier for ShellBuildClassifier {
|
||||
Self::extract_rule_target(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("rule_{name}"),
|
||||
ChunkKind::Rule,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["recipe"]),
|
||||
))
|
||||
@@ -37,30 +38,32 @@ impl LangClassifier for ShellBuildClassifier {
|
||||
"variable_assignment" | "shell_assignment" => {
|
||||
let name =
|
||||
Self::extract_var_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("var_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None))
|
||||
},
|
||||
"define_directive" => {
|
||||
let name =
|
||||
Self::extract_var_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("define_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Define, Some(name), source, None))
|
||||
},
|
||||
"conditional" => Some(positional_candidate(node, "if", source)),
|
||||
"conditional" => Some(positional_candidate(node, ChunkKind::If, source)),
|
||||
// Bash commands and pipelines
|
||||
"command" | "pipeline" => Some(group_candidate(node, "stmts", source)),
|
||||
"command" | "pipeline" => Some(group_candidate(node, ChunkKind::Statements, source)),
|
||||
// Bash control flow
|
||||
"if_statement" => Some(positional_candidate(node, "if", source)),
|
||||
"case_statement" => Some(positional_candidate(node, "switch", source)),
|
||||
"while_statement" | "for_statement" => Some(positional_candidate(node, "loop", source)),
|
||||
"if_statement" => Some(positional_candidate(node, ChunkKind::If, source)),
|
||||
"case_statement" => Some(positional_candidate(node, ChunkKind::Switch, source)),
|
||||
"while_statement" | "for_statement" => {
|
||||
Some(positional_candidate(node, ChunkKind::Loop, source))
|
||||
},
|
||||
// Bash function definition
|
||||
"function_definition" => Some(named_candidate(
|
||||
node,
|
||||
"fn",
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
// Diff nodes
|
||||
"hunks" => Some(group_candidate(node, "hunks", source)),
|
||||
"file_change" => Some(named_candidate(node, "file", source, None)),
|
||||
"hunks" => Some(group_candidate(node, ChunkKind::Hunks, source)),
|
||||
"file_change" => Some(named_candidate(node, ChunkKind::File, source, None)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -71,11 +74,13 @@ impl LangClassifier for ShellBuildClassifier {
|
||||
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"if_statement" => Some(positional_candidate(node, "if", source)),
|
||||
"case_statement" => Some(positional_candidate(node, "switch", source)),
|
||||
"while_statement" | "for_statement" => Some(positional_candidate(node, "loop", source)),
|
||||
"command" | "pipeline" => Some(group_candidate(node, "stmts", source)),
|
||||
"subshell" => Some(positional_candidate(node, "block", source)),
|
||||
"if_statement" => Some(positional_candidate(node, ChunkKind::If, source)),
|
||||
"case_statement" => Some(positional_candidate(node, ChunkKind::Switch, source)),
|
||||
"while_statement" | "for_statement" => {
|
||||
Some(positional_candidate(node, ChunkKind::Loop, source))
|
||||
},
|
||||
"command" | "pipeline" => Some(group_candidate(node, ChunkKind::Statements, source)),
|
||||
"subshell" => Some(positional_candidate(node, ChunkKind::Block, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*, defaults::classify_var_decl};
|
||||
use super::{classify::LangClassifier, common::*, defaults::classify_var_decl, kind::ChunkKind};
|
||||
|
||||
pub struct CCppClassifier;
|
||||
|
||||
@@ -94,30 +94,27 @@ fn extract_c_field_name(node: Node<'_>, source: &str) -> Option<String> {
|
||||
extract_c_declarator_name(decl, source)
|
||||
}
|
||||
|
||||
/// Build a prefixed name for a C/C++ function node using declarator traversal.
|
||||
fn c_prefixed_fn_name(prefix: &str, node: Node<'_>, source: &str) -> String {
|
||||
let identifier =
|
||||
extract_c_function_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
format!("{prefix}_{identifier}")
|
||||
}
|
||||
|
||||
impl LangClassifier for CCppClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
// ── Imports ──
|
||||
"include_directive" | "preproc_include" | "using_directive" | "using_statement"
|
||||
| "import_declaration" | "module_import" => Some(group_candidate(node, "imports", source)),
|
||||
| "import_declaration" | "module_import" => {
|
||||
Some(group_candidate(node, ChunkKind::Imports, source))
|
||||
},
|
||||
|
||||
// ── Functions ──
|
||||
"function_definition" | "function_declaration" => Some(make_named_chunk(
|
||||
"function_definition" | "function_declaration" => Some(make_kind_chunk(
|
||||
node,
|
||||
c_prefixed_fn_name("fn", node, source),
|
||||
ChunkKind::Function,
|
||||
extract_c_function_name(node, source),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
"constructor_definition" => Some(make_named_chunk(
|
||||
"constructor_definition" => Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
@@ -144,47 +141,50 @@ impl LangClassifier for CCppClassifier {
|
||||
candidate.checksum_start_byte = node.start_byte();
|
||||
Some(candidate)
|
||||
},
|
||||
None => Some(named_candidate(
|
||||
None => Some(make_candidate(
|
||||
node,
|
||||
"template",
|
||||
source,
|
||||
ChunkKind::Template,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
source,
|
||||
)),
|
||||
}
|
||||
},
|
||||
|
||||
// ── Containers ──
|
||||
"class_specifier" | "class_declaration" | "class_interface" | "class_implementation" => {
|
||||
Some(container_candidate(node, "class", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node)))
|
||||
},
|
||||
"struct_specifier" | "struct_declaration" => {
|
||||
Some(container_candidate(node, "struct", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node)))
|
||||
},
|
||||
"enum_specifier" | "enum_declaration" => {
|
||||
Some(container_candidate(node, "enum", source, recurse_enum(node)))
|
||||
Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)))
|
||||
},
|
||||
"namespace_definition" => {
|
||||
Some(container_candidate(node, "mod", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node)))
|
||||
},
|
||||
"union_declaration" => {
|
||||
Some(container_candidate(node, "union", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Union, source, recurse_class(node)))
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_alias_declaration" | "user_defined_type_definition" => {
|
||||
Some(named_candidate(node, "type", source, recurse_class(node)))
|
||||
Some(named_candidate(node, ChunkKind::Type, source, recurse_class(node)))
|
||||
},
|
||||
|
||||
// ── Variables / assignments ──
|
||||
"variable_declaration" => Some(classify_var_decl(node, source)),
|
||||
"assignment_statement" | "property_declaration" => {
|
||||
Some(group_candidate(node, "decls", source))
|
||||
Some(group_candidate(node, ChunkKind::Declarations, source))
|
||||
},
|
||||
|
||||
// ── Macros ──
|
||||
"macro_definition" => Some(named_candidate(
|
||||
node,
|
||||
"macro",
|
||||
ChunkKind::Macro,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
@@ -194,7 +194,7 @@ impl LangClassifier for CCppClassifier {
|
||||
| "do_statement" | "try_block" => Some(classify_function_c(node, source)),
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" => Some(group_candidate(node, "stmts", source)),
|
||||
"expression_statement" => Some(group_candidate(node, ChunkKind::Statements, source)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -208,16 +208,18 @@ impl LangClassifier for CCppClassifier {
|
||||
.or_else(|| extract_identifier(node, source))
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
if name == "constructor" {
|
||||
Some(make_named_chunk(
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
))
|
||||
} else {
|
||||
Some(make_named_chunk(
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
))
|
||||
@@ -225,40 +227,41 @@ impl LangClassifier for CCppClassifier {
|
||||
},
|
||||
|
||||
// ── Constructors ──
|
||||
"constructor_definition" | "constructor_declaration" => Some(make_named_chunk(
|
||||
"constructor_definition" | "constructor_declaration" => Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
|
||||
// ── Fields ──
|
||||
"field_declaration" => Some(match extract_c_field_name(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
}),
|
||||
|
||||
// ── Enum variants ──
|
||||
"enum_constant" => Some(match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None),
|
||||
None => group_candidate(node, "variants", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Variants, source),
|
||||
}),
|
||||
|
||||
// ── Nested containers ──
|
||||
"class_specifier" | "class_declaration" | "class_interface" | "class_implementation" => {
|
||||
Some(container_candidate(node, "class", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node)))
|
||||
},
|
||||
"struct_specifier" | "struct_declaration" => {
|
||||
Some(container_candidate(node, "struct", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node)))
|
||||
},
|
||||
"enum_specifier" | "enum_declaration" => {
|
||||
Some(container_candidate(node, "enum", source, recurse_enum(node)))
|
||||
Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)))
|
||||
},
|
||||
"union_declaration" => {
|
||||
Some(container_candidate(node, "union", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Union, source, recurse_class(node)))
|
||||
},
|
||||
"namespace_definition" => {
|
||||
Some(container_candidate(node, "mod", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node)))
|
||||
},
|
||||
|
||||
// ── Templates (class body) ──
|
||||
@@ -281,17 +284,20 @@ impl LangClassifier for CCppClassifier {
|
||||
candidate.checksum_start_byte = node.start_byte();
|
||||
Some(candidate)
|
||||
},
|
||||
None => Some(named_candidate(
|
||||
None => Some(make_candidate(
|
||||
node,
|
||||
"template",
|
||||
source,
|
||||
ChunkKind::Template,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
source,
|
||||
)),
|
||||
}
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_alias_declaration" => Some(named_candidate(node, "type", source, None)),
|
||||
"type_alias_declaration" => Some(named_candidate(node, ChunkKind::Type, source, None)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -306,68 +312,39 @@ fn classify_function_c<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandid
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
match node.kind() {
|
||||
"if_statement" => {
|
||||
make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source)
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"switch_statement" => {
|
||||
make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"try_block" | "catch_clause" | "finally_clause" => {
|
||||
make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"for_statement" => {
|
||||
make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"while_statement" => {
|
||||
make_candidate(node, ChunkKind::While, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"do_statement" => {
|
||||
make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"switch_statement" => make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"try_block" | "catch_clause" | "finally_clause" => make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"for_statement" => make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"while_statement" => make_candidate(
|
||||
node,
|
||||
"while".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"do_statement" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"variable_declaration" => {
|
||||
let span = line_span(node.start_position().row + 1, node.end_position().row + 1);
|
||||
if span > 1 {
|
||||
if let Some(name) = extract_single_declarator_name(node, source) {
|
||||
make_named_chunk(node, format!("var_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
} else {
|
||||
group_candidate(node, "variable", source)
|
||||
group_candidate(node, ChunkKind::Variable, source)
|
||||
}
|
||||
} else {
|
||||
group_candidate(node, "variable", source)
|
||||
group_candidate(node, ChunkKind::Variable, source)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
let kind = ChunkKind::from_sanitized_kind(kind_name.as_str());
|
||||
group_candidate(node, kind, source)
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct ClojureClassifier;
|
||||
|
||||
@@ -25,35 +25,29 @@ fn form_name(node: Node<'_>, source: &str) -> Option<String> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Build a prefixed name from a Clojure form, falling back to "anonymous".
|
||||
fn form_prefixed_name(node: Node<'_>, prefix: &str, source: &str) -> String {
|
||||
let name = form_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
format!("{prefix}_{name}")
|
||||
}
|
||||
|
||||
/// Classify a `list_lit` Clojure form based on its head symbol.
|
||||
fn classify_form<'t>(node: Node<'t>, source: &str, at_root: bool) -> RawChunkCandidate<'t> {
|
||||
let Some(head) = form_head(node, source) else {
|
||||
return positional_candidate(node, "form", source);
|
||||
return positional_candidate(node, ChunkKind::Form, source);
|
||||
};
|
||||
match head.as_str() {
|
||||
"ns" | "require" | "use" | "import" | "refer-clojure" => {
|
||||
group_candidate(node, "imports", source)
|
||||
group_candidate(node, ChunkKind::Imports, source)
|
||||
},
|
||||
"defn" | "defn-" | "defmacro" | "defmulti" | "defmethod" => {
|
||||
make_named_chunk(node, form_prefixed_name(node, "fn", source), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Function, form_name(node, source), source, None)
|
||||
},
|
||||
"def" | "defonce" => {
|
||||
make_named_chunk(node, form_prefixed_name(node, "decl", source), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Decl, form_name(node, source), source, None)
|
||||
},
|
||||
"defprotocol" => {
|
||||
make_container_chunk(node, form_prefixed_name(node, "proto", source), source, None)
|
||||
make_container_chunk(node, ChunkKind::Proto, form_name(node, source), source, None)
|
||||
},
|
||||
"deftype" | "defrecord" | "extend-type" | "extend-protocol" => {
|
||||
make_container_chunk(node, form_prefixed_name(node, "type", source), source, None)
|
||||
make_container_chunk(node, ChunkKind::Type, form_name(node, source), source, None)
|
||||
},
|
||||
_ if at_root => positional_candidate(node, "form", source),
|
||||
_ => group_candidate(node, "block", source),
|
||||
_ if at_root => positional_candidate(node, ChunkKind::Form, source),
|
||||
_ => group_candidate(node, ChunkKind::Block, source),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct CMakeClassifier;
|
||||
|
||||
@@ -47,7 +47,8 @@ fn classify_definition<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandi
|
||||
let name = nth_argument_name(header, 0, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["body"]),
|
||||
))
|
||||
@@ -57,20 +58,23 @@ fn classify_definition<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandi
|
||||
let name = nth_argument_name(header, 0, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("macro_{name}"),
|
||||
ChunkKind::Macro,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["body"]),
|
||||
))
|
||||
},
|
||||
"if_condition" => Some(make_container_chunk(
|
||||
node,
|
||||
"if".to_string(),
|
||||
ChunkKind::If,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
)),
|
||||
"foreach_loop" | "while_loop" => Some(make_container_chunk(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
ChunkKind::Loop,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["body"]),
|
||||
)),
|
||||
@@ -85,37 +89,38 @@ fn classify_command<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidat
|
||||
|
||||
let command = command_name(node, source)?;
|
||||
Some(match command.as_str() {
|
||||
"cmake_minimum_required" => make_named_chunk(node, "version_gate".to_string(), source, None),
|
||||
"cmake_minimum_required" => make_kind_chunk(node, ChunkKind::VersionGate, None, source, None),
|
||||
"project" => {
|
||||
let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("project_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Project, Some(name), source, None)
|
||||
},
|
||||
"include" | "find_package" => group_candidate(node, "imports", source),
|
||||
"include" | "find_package" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"option" => {
|
||||
let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("option_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Option, Some(name), source, None)
|
||||
},
|
||||
"set" => {
|
||||
let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("var_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
},
|
||||
"add_library" | "add_executable" | "add_custom_target" => {
|
||||
let name = nth_argument_name(node, 0, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("target_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Target, Some(name), source, None)
|
||||
},
|
||||
"install" | "export" => group_candidate(node, "install", source),
|
||||
other => group_candidate(node, &format!("cmd_{other}"), source),
|
||||
"install" | "export" => group_candidate(node, ChunkKind::Install, source),
|
||||
other => make_kind_chunk(node, ChunkKind::Cmd, Some(other.to_string()), source, None),
|
||||
})
|
||||
}
|
||||
|
||||
fn classify_if_child<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"if_command" => Some(group_candidate(node, "cond", source)),
|
||||
"elseif_command" => Some(positional_candidate(node, "elif", source)),
|
||||
"else_command" => Some(positional_candidate(node, "else", source)),
|
||||
"if_command" => Some(group_candidate(node, ChunkKind::Cond, source)),
|
||||
"elseif_command" => Some(positional_candidate(node, ChunkKind::Elif, source)),
|
||||
"else_command" => Some(positional_candidate(node, ChunkKind::Else, source)),
|
||||
"body" => Some(make_container_chunk(
|
||||
node,
|
||||
"block".to_string(),
|
||||
ChunkKind::Block,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
)),
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*, defaults::classify_var_decl};
|
||||
use super::{classify::LangClassifier, common::*, defaults::classify_var_decl, kind::ChunkKind};
|
||||
|
||||
pub struct CSharpJavaClassifier;
|
||||
|
||||
@@ -13,54 +13,57 @@ impl LangClassifier for CSharpJavaClassifier {
|
||||
"import_declaration"
|
||||
| "using_directive"
|
||||
| "package_declaration"
|
||||
| "namespace_statement" => Some(group_candidate(node, "imports", source)),
|
||||
| "namespace_statement" => Some(group_candidate(node, ChunkKind::Imports, source)),
|
||||
|
||||
// ── Functions ──
|
||||
"method_declaration" => Some(named_candidate(
|
||||
node,
|
||||
"meth",
|
||||
ChunkKind::Method,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
"function_declaration" | "function_definition" => Some(named_candidate(
|
||||
node,
|
||||
"fn",
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
|
||||
// ── Constructors ──
|
||||
"constructor_declaration" => Some(make_named_chunk(
|
||||
"constructor_declaration" => Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
|
||||
// ── Containers ──
|
||||
"class_declaration" => {
|
||||
Some(container_candidate(node, "class", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node)))
|
||||
},
|
||||
"interface_declaration" => {
|
||||
Some(container_candidate(node, "iface", source, recurse_interface(node)))
|
||||
Some(container_candidate(node, ChunkKind::Iface, source, recurse_interface(node)))
|
||||
},
|
||||
"enum_declaration" => {
|
||||
Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)))
|
||||
},
|
||||
"enum_declaration" => Some(container_candidate(node, "enum", source, recurse_enum(node))),
|
||||
"namespace_declaration" | "file_scoped_namespace_declaration" => {
|
||||
Some(container_candidate(node, "mod", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node)))
|
||||
},
|
||||
"struct_declaration" | "record_declaration" => {
|
||||
Some(container_candidate(node, "struct", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node)))
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_alias_declaration" => {
|
||||
Some(named_candidate(node, "type", source, recurse_class(node)))
|
||||
Some(named_candidate(node, ChunkKind::Type, source, recurse_class(node)))
|
||||
},
|
||||
|
||||
// ── Variables / assignments ──
|
||||
"variable_declaration" | "lexical_declaration" => Some(classify_var_decl(node, source)),
|
||||
"property_declaration" | "state_variable_declaration" => {
|
||||
Some(group_candidate(node, "decls", source))
|
||||
Some(group_candidate(node, ChunkKind::Declarations, source))
|
||||
},
|
||||
|
||||
// ── Control flow (top-level scripts) ──
|
||||
@@ -70,7 +73,7 @@ impl LangClassifier for CSharpJavaClassifier {
|
||||
},
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" => Some(group_candidate(node, "stmts", source)),
|
||||
"expression_statement" => Some(group_candidate(node, ChunkKind::Statements, source)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -80,33 +83,37 @@ impl LangClassifier for CSharpJavaClassifier {
|
||||
match node.kind() {
|
||||
// ── Container declarations (inside namespace/class bodies) ──
|
||||
"class_declaration" => {
|
||||
Some(container_candidate(node, "class", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node)))
|
||||
},
|
||||
"interface_declaration" => {
|
||||
Some(container_candidate(node, "iface", source, recurse_interface(node)))
|
||||
Some(container_candidate(node, ChunkKind::Iface, source, recurse_interface(node)))
|
||||
},
|
||||
"enum_declaration" => {
|
||||
Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)))
|
||||
},
|
||||
"enum_declaration" => Some(container_candidate(node, "enum", source, recurse_enum(node))),
|
||||
"struct_declaration" | "record_declaration" => {
|
||||
Some(container_candidate(node, "struct", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Struct, source, recurse_class(node)))
|
||||
},
|
||||
"namespace_declaration" | "file_scoped_namespace_declaration" => {
|
||||
Some(container_candidate(node, "mod", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Module, source, recurse_class(node)))
|
||||
},
|
||||
|
||||
// ── Methods ──
|
||||
"method_declaration" | "function_declaration" | "function_definition" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
if name == "constructor" {
|
||||
Some(make_named_chunk(
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
))
|
||||
} else {
|
||||
Some(make_named_chunk(
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
))
|
||||
@@ -114,9 +121,10 @@ impl LangClassifier for CSharpJavaClassifier {
|
||||
},
|
||||
|
||||
// ── Constructors ──
|
||||
"constructor_declaration" | "secondary_constructor" => Some(make_named_chunk(
|
||||
"constructor_declaration" | "secondary_constructor" => Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
@@ -126,21 +134,21 @@ impl LangClassifier for CSharpJavaClassifier {
|
||||
| "property_declaration"
|
||||
| "constant_declaration"
|
||||
| "event_field_declaration" => Some(match extract_field_name(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
}),
|
||||
|
||||
// ── Enum members ──
|
||||
"enum_member_declaration" | "enum_constant" | "enum_entry" => {
|
||||
Some(match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None),
|
||||
None => group_candidate(node, "variants", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Variants, source),
|
||||
})
|
||||
},
|
||||
|
||||
// ── Static blocks ──
|
||||
"class_static_block" => {
|
||||
Some(make_named_chunk(node, "static_init".to_string(), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::StaticInit, None, source, None))
|
||||
},
|
||||
|
||||
_ => None,
|
||||
@@ -176,79 +184,44 @@ fn classify_function_csharp_java<'tree>(
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
match node.kind() {
|
||||
"if_statement" => {
|
||||
make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source)
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"switch_statement" | "switch_expression" => {
|
||||
make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"try_statement" | "catch_clause" | "finally_clause" => {
|
||||
make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"for_statement" => {
|
||||
make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"foreach_statement" => {
|
||||
make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"while_statement" => {
|
||||
make_candidate(node, ChunkKind::While, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"do_statement" => {
|
||||
make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"switch_statement" | "switch_expression" => make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"try_statement" | "catch_clause" | "finally_clause" => make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"for_statement" => make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"foreach_statement" => make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"while_statement" => make_candidate(
|
||||
node,
|
||||
"while".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"do_statement" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"variable_declaration" | "lexical_declaration" => {
|
||||
let span = line_span(node.start_position().row + 1, node.end_position().row + 1);
|
||||
if span > 1 {
|
||||
if let Some(name) = extract_single_declarator_name(node, source) {
|
||||
make_named_chunk(node, format!("var_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
} else {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
group_from_sanitized(node, source)
|
||||
}
|
||||
} else {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
group_from_sanitized(node, source)
|
||||
}
|
||||
},
|
||||
_ => {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
},
|
||||
_ => group_from_sanitized(node, source),
|
||||
}
|
||||
}
|
||||
|
||||
fn group_from_sanitized<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
let kind = ChunkKind::from_sanitized_kind(kind_name.as_str());
|
||||
make_candidate(node, kind, kind_name, NameStyle::Group, None, None, source)
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct CssClassifier;
|
||||
|
||||
@@ -29,7 +29,8 @@ fn classify_rule_set<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t>
|
||||
let name = extract_css_selector(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_container_chunk(
|
||||
node,
|
||||
format!("rule_{name}"),
|
||||
ChunkKind::Rule,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["block"]),
|
||||
)
|
||||
@@ -39,10 +40,10 @@ fn classify_rule_set<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t>
|
||||
/// named container.
|
||||
fn classify_at_rule<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = extract_css_selector(node, source).unwrap_or_else(|| "rule".to_string());
|
||||
// keyframes_statement uses `keyframe_block_list` as body; others use `block`
|
||||
make_container_chunk(
|
||||
node,
|
||||
format!("at_{name}"),
|
||||
ChunkKind::At,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["block", "keyframe_block_list"]),
|
||||
)
|
||||
@@ -53,19 +54,16 @@ fn classify_at_rule<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
fn classify_css_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"rule_set" => Some(classify_rule_set(node, source)),
|
||||
// tree-sitter-css emits specific `*_statement` kinds for known at-rules
|
||||
// and generic `at_rule` for unknown ones (e.g. @font-face).
|
||||
"at_rule" | "media_statement" | "keyframes_statement" | "supports_statement" => {
|
||||
Some(classify_at_rule(node, source))
|
||||
},
|
||||
"keyframe_block" => Some(named_candidate(
|
||||
node,
|
||||
"frame",
|
||||
ChunkKind::Frame,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// Top-level or nested property declarations.
|
||||
"declaration" => Some(group_candidate(node, "fields", source)),
|
||||
"declaration" => Some(group_candidate(node, ChunkKind::Fields, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct DataFormatsClassifier;
|
||||
|
||||
@@ -33,65 +33,89 @@ fn classify_data_node<'t>(
|
||||
// Key-value pairs (JSON pairs, YAML mappings)
|
||||
"pair" => {
|
||||
let name = extract_pair_key(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("key_{name}"), source, recurse_value_container(node)))
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
ChunkKind::Key,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_value_container(node),
|
||||
))
|
||||
},
|
||||
"block_mapping_pair" | "flow_pair" => {
|
||||
let name = extract_yaml_key(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("key_{name}"), source, recurse_value_container(node)))
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
ChunkKind::Key,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_value_container(node),
|
||||
))
|
||||
},
|
||||
// TOML tables
|
||||
"table" => Some(container_candidate(
|
||||
node,
|
||||
"table",
|
||||
ChunkKind::Table,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// TOML array tables [[...]]
|
||||
"table_array_element" => Some(container_candidate(
|
||||
// TOML array tables
|
||||
"table_array_element" => Some(make_candidate(
|
||||
node,
|
||||
"table_array",
|
||||
source,
|
||||
ChunkKind::Table,
|
||||
"table_array".to_string(),
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
source,
|
||||
)),
|
||||
// TOML inline tables
|
||||
"inline_table" => Some(make_container_chunk(
|
||||
node,
|
||||
"table".to_string(),
|
||||
ChunkKind::Table,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// JSON objects
|
||||
"object" => Some(make_container_chunk(
|
||||
node,
|
||||
"object".to_string(),
|
||||
ChunkKind::Object,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// JSON arrays
|
||||
"array" => Some(make_container_chunk(
|
||||
node,
|
||||
"array".to_string(),
|
||||
ChunkKind::Array,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// YAML block/flow mappings
|
||||
"block_mapping" | "flow_mapping" => Some(make_container_chunk(
|
||||
node,
|
||||
"map".to_string(),
|
||||
ChunkKind::Map,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// YAML block/flow sequences
|
||||
"block_sequence" | "flow_sequence" => Some(make_container_chunk(
|
||||
node,
|
||||
"list".to_string(),
|
||||
ChunkKind::List,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
// YAML sequence items (only when nested, not at root level)
|
||||
"block_sequence_item" if !is_root => Some(positional_candidate(node, "item", source)),
|
||||
"block_sequence_item" if !is_root => {
|
||||
Some(positional_candidate(node, ChunkKind::Item, source))
|
||||
},
|
||||
// Nix-style attributes that appear in data contexts
|
||||
"attribute" => Some(named_candidate(node, "attr", source, recurse_value_container(node))),
|
||||
"attribute" => {
|
||||
Some(named_candidate(node, ChunkKind::Attr, source, recurse_value_container(node)))
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct DockerfileClassifier;
|
||||
|
||||
@@ -53,7 +53,15 @@ fn classify_command_instruction<'t>(node: Node<'t>, source: &str) -> Option<RawC
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
Some(make_container_chunk(node, chunk_name.to_string(), source, recurse_command(node)))
|
||||
Some(make_candidate(
|
||||
node,
|
||||
ChunkKind::Cmd,
|
||||
chunk_name.to_string(),
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_command(node),
|
||||
source,
|
||||
))
|
||||
}
|
||||
|
||||
impl LangClassifier for DockerfileClassifier {
|
||||
@@ -61,36 +69,37 @@ impl LangClassifier for DockerfileClassifier {
|
||||
match node.kind() {
|
||||
"from_instruction" => {
|
||||
let name = extract_stage_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("stage_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Stage, Some(name), source, None))
|
||||
},
|
||||
"arg_instruction" => {
|
||||
let name = extract_arg_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("arg_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Arg, Some(name), source, None))
|
||||
},
|
||||
"env_instruction" => {
|
||||
let name = extract_pair_key(node, "env_pair", source)
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("env_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Env, Some(name), source, None))
|
||||
},
|
||||
"label_instruction" => {
|
||||
let name = extract_pair_key(node, "label_pair", source)
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_named_chunk(node, format!("label_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Label, Some(name), source, None))
|
||||
},
|
||||
"run_instruction" | "cmd_instruction" | "entrypoint_instruction" => {
|
||||
classify_command_instruction(node, source)
|
||||
},
|
||||
"healthcheck_instruction" => Some(make_container_chunk(
|
||||
node,
|
||||
"healthcheck".to_string(),
|
||||
ChunkKind::Healthcheck,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["cmd_instruction"]),
|
||||
)),
|
||||
"copy_instruction" => Some(group_candidate(node, "copy", source)),
|
||||
"add_instruction" => Some(group_candidate(node, "add", source)),
|
||||
"workdir_instruction" => Some(group_candidate(node, "workdir", source)),
|
||||
"expose_instruction" => Some(group_candidate(node, "expose", source)),
|
||||
"user_instruction" => Some(group_candidate(node, "user", source)),
|
||||
"copy_instruction" => Some(group_candidate(node, ChunkKind::Copy, source)),
|
||||
"add_instruction" => Some(group_candidate(node, ChunkKind::Add, source)),
|
||||
"workdir_instruction" => Some(group_candidate(node, ChunkKind::Workdir, source)),
|
||||
"expose_instruction" => Some(group_candidate(node, ChunkKind::Expose, source)),
|
||||
"user_instruction" => Some(group_candidate(node, ChunkKind::User, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -102,8 +111,8 @@ impl LangClassifier for DockerfileClassifier {
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"cmd_instruction" => classify_command_instruction(node, source),
|
||||
"shell_command" => Some(group_candidate(node, "shell", source)),
|
||||
"json_string_array" => Some(group_candidate(node, "argv", source)),
|
||||
"shell_command" => Some(group_candidate(node, ChunkKind::Shell, source)),
|
||||
"json_string_array" => Some(group_candidate(node, ChunkKind::Argv, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct ElixirClassifier;
|
||||
|
||||
@@ -21,44 +21,49 @@ fn classify_call<'t>(node: Node<'t>, source: &str, at_root: bool) -> RawChunkCan
|
||||
match target.as_str() {
|
||||
"defmodule" => make_container_chunk(
|
||||
node,
|
||||
format!("mod_{}", name()),
|
||||
ChunkKind::Module,
|
||||
Some(name()),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::ClassBody),
|
||||
),
|
||||
"defprotocol" => make_container_chunk(
|
||||
node,
|
||||
format!("proto_{}", name()),
|
||||
ChunkKind::Proto,
|
||||
Some(name()),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::ClassBody),
|
||||
),
|
||||
"defimpl" => make_container_chunk(
|
||||
node,
|
||||
format!("impl_{}", name()),
|
||||
ChunkKind::Impl,
|
||||
Some(name()),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::ClassBody),
|
||||
),
|
||||
"def" | "defp" | "defdelegate" | "defguard" | "defguardp" | "defn" | "defnp" => {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{}", name()),
|
||||
ChunkKind::Function,
|
||||
Some(name()),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
},
|
||||
"defmacro" | "defmacrop" => make_named_chunk(
|
||||
"defmacro" | "defmacrop" => make_kind_chunk(
|
||||
node,
|
||||
format!("macro_{}", name()),
|
||||
ChunkKind::Macro,
|
||||
Some(name()),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
"alias" | "import" | "require" | "use" => group_candidate(node, "imports", source),
|
||||
"defstruct" | "defexception" => group_candidate(node, "decls", source),
|
||||
"if" | "unless" => positional_candidate(node, "if", source),
|
||||
"case" | "cond" | "receive" => positional_candidate(node, "switch", source),
|
||||
"for" => positional_candidate(node, "for", source),
|
||||
"try" | "with" => positional_candidate(node, "block", source),
|
||||
_ if at_root => group_candidate(node, "stmts", source),
|
||||
_ => group_candidate(node, "block", source),
|
||||
"alias" | "import" | "require" | "use" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"defstruct" | "defexception" => group_candidate(node, ChunkKind::Declarations, source),
|
||||
"if" | "unless" => positional_candidate(node, ChunkKind::If, source),
|
||||
"case" | "cond" | "receive" => positional_candidate(node, ChunkKind::Switch, source),
|
||||
"for" => positional_candidate(node, ChunkKind::For, source),
|
||||
"try" | "with" => positional_candidate(node, ChunkKind::Block, source),
|
||||
_ if at_root => group_candidate(node, ChunkKind::Statements, source),
|
||||
_ => group_candidate(node, ChunkKind::Block, source),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct ErlangClassifier;
|
||||
|
||||
@@ -54,28 +54,34 @@ impl LangClassifier for ErlangClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"module_attribute" => {
|
||||
make_named_chunk(node, format!("mod_{}", erlang_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Module, erlang_name(node, source), source, None)
|
||||
},
|
||||
"export_attribute" | "export_type_attribute" => group_candidate(node, "exports", source),
|
||||
"import_attribute" => group_candidate(node, "imports", source),
|
||||
"pp_include" | "pp_include_lib" => group_candidate(node, "includes", source),
|
||||
"export_attribute" | "export_type_attribute" => {
|
||||
group_candidate(node, ChunkKind::Exports, source)
|
||||
},
|
||||
"import_attribute" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"pp_include" | "pp_include_lib" => group_candidate(node, ChunkKind::Includes, source),
|
||||
"pp_define" => {
|
||||
make_named_chunk(node, format!("macro_{}", erlang_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Macro, erlang_name(node, source), source, None)
|
||||
},
|
||||
"record_decl" => make_container_chunk(
|
||||
"record_decl" => make_candidate(
|
||||
node,
|
||||
ChunkKind::Struct,
|
||||
format!("record_{}", erlang_name(node, source)?),
|
||||
source,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
source,
|
||||
),
|
||||
"type_alias" => {
|
||||
make_named_chunk(node, format!("type_{}", erlang_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Type, erlang_name(node, source), source, None)
|
||||
},
|
||||
// The Erlang grammar exposes each top-level clause as its own `fun_decl`.
|
||||
// Keep that shape instead of inventing a synthetic merged function node.
|
||||
"fun_decl" => make_named_chunk(
|
||||
"fun_decl" => make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{}", erlang_name(node, source)?),
|
||||
ChunkKind::Function,
|
||||
erlang_name(node, source),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
@@ -87,7 +93,7 @@ impl LangClassifier for ErlangClassifier {
|
||||
fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"record_field" => {
|
||||
make_named_chunk(node, format!("field_{}", erlang_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Field, erlang_name(node, source), source, None)
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
@@ -95,42 +101,71 @@ impl LangClassifier for ErlangClassifier {
|
||||
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"function_clause" => make_named_chunk(
|
||||
"function_clause" => make_kind_chunk(
|
||||
node,
|
||||
format!("clause_{}", erlang_name(node, source)?),
|
||||
ChunkKind::Clause,
|
||||
erlang_name(node, source),
|
||||
source,
|
||||
recurse_clause_body(node),
|
||||
),
|
||||
"fun_clause" | "cr_clause" => {
|
||||
make_named_chunk(node, "clause".to_string(), source, recurse_clause_body(node))
|
||||
},
|
||||
"receive_after" => {
|
||||
make_named_chunk(node, "after".to_string(), source, recurse_clause_body(node))
|
||||
},
|
||||
"catch_clause" => {
|
||||
make_named_chunk(node, "catch".to_string(), source, recurse_clause_body(node))
|
||||
},
|
||||
"receive_expr" => make_named_chunk(
|
||||
"fun_clause" | "cr_clause" => make_candidate(
|
||||
node,
|
||||
"receive".to_string(),
|
||||
ChunkKind::Clause,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_clause_body(node),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
"case_expr" => make_named_chunk(
|
||||
"receive_after" => make_candidate(
|
||||
node,
|
||||
"case".to_string(),
|
||||
ChunkKind::After,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_clause_body(node),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
"try_expr" => make_named_chunk(
|
||||
"catch_clause" => make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
ChunkKind::Catch,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_clause_body(node),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
"anonymous_fun" => make_named_chunk(
|
||||
"receive_expr" => make_candidate(
|
||||
node,
|
||||
"fn_anonymous".to_string(),
|
||||
ChunkKind::Receive,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
source,
|
||||
),
|
||||
"case_expr" => make_candidate(
|
||||
node,
|
||||
ChunkKind::Case,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
source,
|
||||
),
|
||||
"try_expr" => make_candidate(
|
||||
node,
|
||||
ChunkKind::Try,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
source,
|
||||
),
|
||||
"anonymous_fun" => make_kind_chunk(
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
Some("anonymous".to_string()),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct GoClassifier;
|
||||
|
||||
@@ -8,26 +8,28 @@ impl LangClassifier for GoClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
// ── Imports / package ──
|
||||
"import_declaration" | "package_clause" => Some(group_candidate(node, "imports", source)),
|
||||
"import_declaration" | "package_clause" => {
|
||||
Some(group_candidate(node, ChunkKind::Imports, source))
|
||||
},
|
||||
|
||||
// ── Variables ──
|
||||
"const_declaration" | "var_declaration" | "short_var_declaration" => {
|
||||
Some(match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("var_{name}"), source, None),
|
||||
None => group_candidate(node, "decls", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Declarations, source),
|
||||
})
|
||||
},
|
||||
|
||||
// ── Functions ──
|
||||
"function_declaration" => Some(named_candidate(
|
||||
node,
|
||||
"fn",
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
"method_declaration" => Some(named_candidate(
|
||||
node,
|
||||
"fn",
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
@@ -45,7 +47,7 @@ impl LangClassifier for GoClassifier {
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" | "go_statement" | "defer_statement" | "send_statement" => {
|
||||
Some(group_candidate(node, "stmts", source))
|
||||
Some(group_candidate(node, ChunkKind::Statements, source))
|
||||
},
|
||||
|
||||
_ => None,
|
||||
@@ -55,17 +57,17 @@ impl LangClassifier for GoClassifier {
|
||||
fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
// ── Methods ──
|
||||
"method_spec" => Some(named_candidate(node, "meth", source, None)),
|
||||
"method_spec" => Some(named_candidate(node, ChunkKind::Method, source, None)),
|
||||
|
||||
// ── Fields ──
|
||||
"field_declaration" | "embedded_field" => Some(match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
}),
|
||||
|
||||
// ── Field / method lists ──
|
||||
"field_declaration_list" => Some(group_candidate(node, "fields", source)),
|
||||
"method_spec_list" => Some(group_candidate(node, "methods", source)),
|
||||
"field_declaration_list" => Some(group_candidate(node, ChunkKind::Fields, source)),
|
||||
"method_spec_list" => Some(group_candidate(node, ChunkKind::Methods, source)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -76,48 +78,48 @@ impl LangClassifier for GoClassifier {
|
||||
// ── Control flow ──
|
||||
"if_statement" => Some(make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
ChunkKind::If,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
"switch_statement" | "expression_switch_statement" | "type_switch_statement" => {
|
||||
Some(make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
ChunkKind::Switch,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
))
|
||||
},
|
||||
"select_statement" => Some(make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
ChunkKind::Switch,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
|
||||
// ── Loops ──
|
||||
"for_statement" => Some(make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
ChunkKind::For,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
|
||||
// ── Blocks ──
|
||||
"go_statement" | "defer_statement" | "send_statement" => {
|
||||
Some(group_candidate(node, "stmts", source))
|
||||
Some(group_candidate(node, ChunkKind::Statements, source))
|
||||
},
|
||||
|
||||
// ── Variables ──
|
||||
@@ -125,14 +127,12 @@ impl LangClassifier for GoClassifier {
|
||||
let span = line_span(node.start_position().row + 1, node.end_position().row + 1);
|
||||
Some(if span > 1 {
|
||||
if let Some(name) = extract_identifier(node, source) {
|
||||
make_named_chunk(node, format!("var_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
} else {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
group_from_sanitized(node, source)
|
||||
}
|
||||
} else {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
group_from_sanitized(node, source)
|
||||
})
|
||||
},
|
||||
|
||||
@@ -147,30 +147,18 @@ fn classify_function_go<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
match node.kind() {
|
||||
"if_statement" => {
|
||||
make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source)
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"switch_statement"
|
||||
| "expression_switch_statement"
|
||||
| "type_switch_statement"
|
||||
| "select_statement" => make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"for_statement" => make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
_ => group_candidate(node, "stmts", source),
|
||||
| "select_statement" => {
|
||||
make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"for_statement" => {
|
||||
make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
_ => group_candidate(node, ChunkKind::Statements, source),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -192,15 +180,22 @@ fn classify_type_decl<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t>
|
||||
return make_container_chunk_from(
|
||||
node,
|
||||
spec,
|
||||
format!("type_{name}"),
|
||||
ChunkKind::Type,
|
||||
Some(name),
|
||||
source,
|
||||
Some(recurse),
|
||||
);
|
||||
}
|
||||
return make_named_chunk_from(node, spec, format!("type_{name}"), source, None);
|
||||
return make_kind_chunk_from(node, spec, ChunkKind::Type, Some(name), source, None);
|
||||
}
|
||||
|
||||
group_candidate(node, "decls", source)
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
}
|
||||
|
||||
fn group_from_sanitized<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
let kind = ChunkKind::from_sanitized_kind(kind_name.as_str());
|
||||
make_candidate(node, kind, kind_name, NameStyle::Group, None, None, source)
|
||||
}
|
||||
|
||||
/// For a `type_spec`, find a `struct_type` or `interface_type` child and return
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct GraphqlClassifier;
|
||||
|
||||
@@ -39,21 +39,21 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCan
|
||||
match node.kind() {
|
||||
"schema_definition" => Some(make_container_chunk(
|
||||
node,
|
||||
"schema".to_string(),
|
||||
ChunkKind::Schema,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)),
|
||||
"directive_definition" => Some(make_container_chunk(
|
||||
"directive_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
format!(
|
||||
"directive_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Directive,
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string()),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["arguments_definition"]),
|
||||
)),
|
||||
"scalar_type_definition" => Some(make_named_chunk(
|
||||
"scalar_type_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
ChunkKind::Type,
|
||||
format!(
|
||||
"scalar_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -63,42 +63,35 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCan
|
||||
)),
|
||||
"object_type_definition" => Some(make_container_chunk(
|
||||
node,
|
||||
format!(
|
||||
"type_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Type,
|
||||
extract_graphql_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["fields_definition"]),
|
||||
)),
|
||||
"interface_type_definition" => Some(make_container_chunk(
|
||||
node,
|
||||
format!(
|
||||
"interface_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Interface,
|
||||
extract_graphql_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["fields_definition"]),
|
||||
)),
|
||||
"union_type_definition" => Some(make_named_chunk(
|
||||
"union_type_definition" => Some(make_kind_chunk(
|
||||
node,
|
||||
format!(
|
||||
"union_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Union,
|
||||
extract_graphql_name(node, source),
|
||||
source,
|
||||
None,
|
||||
)),
|
||||
"enum_type_definition" => Some(make_container_chunk(
|
||||
node,
|
||||
format!(
|
||||
"enum_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Enum,
|
||||
extract_graphql_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["enum_values_definition"]),
|
||||
)),
|
||||
"input_object_type_definition" => Some(make_container_chunk(
|
||||
"input_object_type_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
ChunkKind::Type,
|
||||
format!(
|
||||
"input_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -106,14 +99,16 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCan
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["input_fields_definition"]),
|
||||
)),
|
||||
"operation_definition" => Some(make_container_chunk(
|
||||
"operation_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
ChunkKind::Operation,
|
||||
extract_graphql_operation_chunk_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["selection_set"]),
|
||||
)),
|
||||
"fragment_definition" => Some(make_container_chunk(
|
||||
"fragment_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
ChunkKind::Operation,
|
||||
format!(
|
||||
"fragment_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -127,12 +122,10 @@ fn classify_graphql_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCan
|
||||
|
||||
fn classify_graphql_class<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"root_operation_type_definition" => Some(make_named_chunk(
|
||||
"root_operation_type_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
format!(
|
||||
"root_{}",
|
||||
extract_graphql_operation_type(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Root,
|
||||
extract_graphql_operation_type(node, source).unwrap_or_else(|| "anonymous".to_string()),
|
||||
source,
|
||||
None,
|
||||
)),
|
||||
@@ -141,14 +134,15 @@ fn classify_graphql_class<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCa
|
||||
let recurse = recurse_into(node, ChunkContext::ClassBody, &[], &["arguments_definition"]);
|
||||
Some(match recurse {
|
||||
Some(recurse) => {
|
||||
make_container_chunk(node, format!("field_{name}"), source, Some(recurse))
|
||||
make_container_chunk(node, ChunkKind::Field, Some(name), source, Some(recurse))
|
||||
},
|
||||
None => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
})
|
||||
},
|
||||
"input_value_definition" => Some(classify_graphql_input_value(node, source)),
|
||||
"enum_value_definition" => Some(make_named_chunk(
|
||||
"enum_value_definition" => Some(make_named_graphql_chunk(
|
||||
node,
|
||||
ChunkKind::Variant,
|
||||
format!(
|
||||
"value_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -177,16 +171,18 @@ fn classify_graphql_selection<'t>(node: Node<'t>, source: &str) -> Option<RawChu
|
||||
Some(recurse) => make_container_chunk_from(
|
||||
node,
|
||||
child,
|
||||
format!("field_{name}"),
|
||||
ChunkKind::Field,
|
||||
Some(name),
|
||||
source,
|
||||
Some(recurse),
|
||||
),
|
||||
None => make_named_chunk_from(node, child, format!("field_{name}"), source, None),
|
||||
None => make_kind_chunk_from(node, child, ChunkKind::Field, Some(name), source, None),
|
||||
})
|
||||
},
|
||||
"fragment_spread" => Some(make_named_chunk_from(
|
||||
"fragment_spread" => Some(make_named_graphql_chunk_from(
|
||||
node,
|
||||
child,
|
||||
ChunkKind::Operation,
|
||||
format!(
|
||||
"spread_{}",
|
||||
extract_graphql_name(child, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -197,7 +193,8 @@ fn classify_graphql_selection<'t>(node: Node<'t>, source: &str) -> Option<RawChu
|
||||
"inline_fragment" => Some(make_container_chunk_from(
|
||||
node,
|
||||
child,
|
||||
"inline_fragment".to_string(),
|
||||
ChunkKind::InlineFragment,
|
||||
None,
|
||||
source,
|
||||
recurse_into(child, ChunkContext::FunctionBody, &[], &["selection_set"]),
|
||||
)),
|
||||
@@ -206,18 +203,49 @@ fn classify_graphql_selection<'t>(node: Node<'t>, source: &str) -> Option<RawChu
|
||||
}
|
||||
|
||||
fn classify_graphql_input_value<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let prefix = match node.parent().map(|parent| parent.kind()) {
|
||||
Some("input_fields_definition") => "field",
|
||||
_ => "arg",
|
||||
};
|
||||
make_named_chunk(
|
||||
let name = extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
match node.parent().map(|parent| parent.kind()) {
|
||||
Some("input_fields_definition") => {
|
||||
make_kind_chunk(node, ChunkKind::Field, Some(name), source, None)
|
||||
},
|
||||
_ => make_kind_chunk(node, ChunkKind::Arg, Some(name), source, None),
|
||||
}
|
||||
}
|
||||
|
||||
fn make_named_graphql_chunk<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
node,
|
||||
format!(
|
||||
"{prefix}_{}",
|
||||
extract_graphql_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_named_graphql_chunk_from<'t>(
|
||||
range_node: Node<'t>,
|
||||
signature_node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
range_node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(signature_node, source),
|
||||
recurse,
|
||||
source,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct HaskellScalaClassifier;
|
||||
|
||||
@@ -10,35 +10,49 @@ impl LangClassifier for HaskellScalaClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
// ── Imports / packages ──
|
||||
"import_declaration" => group_candidate(node, "imports", source),
|
||||
"package_declaration" => group_candidate(node, "imports", source),
|
||||
"import_declaration" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"package_declaration" => group_candidate(node, ChunkKind::Imports, source),
|
||||
|
||||
// ── Haskell module ──
|
||||
"module" => container_candidate(node, "mod", source, recurse_class(node)),
|
||||
"module" => container_candidate(node, ChunkKind::Module, source, recurse_class(node)),
|
||||
|
||||
// ── Functions ──
|
||||
"function_declaration" => {
|
||||
named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
"function_definition" => {
|
||||
named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
"function_declaration" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
"function_definition" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
|
||||
// ── Containers (Scala) ──
|
||||
"class_definition" => container_candidate(node, "class", source, recurse_class(node)),
|
||||
"object_definition" => container_candidate(node, "mod", source, recurse_class(node)),
|
||||
"trait_definition" => container_candidate(node, "iface", source, recurse_interface(node)),
|
||||
"class_definition" => {
|
||||
container_candidate(node, ChunkKind::Class, source, recurse_class(node))
|
||||
},
|
||||
"object_definition" => {
|
||||
container_candidate(node, ChunkKind::Module, source, recurse_class(node))
|
||||
},
|
||||
"trait_definition" => {
|
||||
container_candidate(node, ChunkKind::Iface, source, recurse_interface(node))
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_alias_declaration" | "type_item" => {
|
||||
named_candidate(node, "type", source, recurse_class(node))
|
||||
named_candidate(node, ChunkKind::Type, source, recurse_class(node))
|
||||
},
|
||||
|
||||
// ── Variables / assignments ──
|
||||
"variable_declaration" | "assignment" => group_candidate(node, "decls", source),
|
||||
"variable_declaration" | "assignment" => {
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
},
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" => group_candidate(node, "stmts", source),
|
||||
"expression_statement" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -50,16 +64,18 @@ impl LangClassifier for HaskellScalaClassifier {
|
||||
"function_declaration" | "function_definition" | "method_definition" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
if name == "constructor" {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
} else {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
@@ -69,8 +85,8 @@ impl LangClassifier for HaskellScalaClassifier {
|
||||
// ── Fields ──
|
||||
"variable_declaration" | "property_declaration" => {
|
||||
match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
}
|
||||
},
|
||||
|
||||
@@ -82,42 +98,36 @@ impl LangClassifier for HaskellScalaClassifier {
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
Some(match node.kind() {
|
||||
// ── Control flow ──
|
||||
"if_statement" => make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"if_statement" => {
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"match_expression" => make_candidate(
|
||||
node,
|
||||
"match".to_string(),
|
||||
ChunkKind::Match,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"for_expression" | "while_expression" => make_candidate(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
ChunkKind::Loop,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
|
||||
// ── Blocks ──
|
||||
"block_expression" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
ChunkKind::Block,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct HtmlXmlClassifier;
|
||||
|
||||
@@ -20,12 +20,13 @@ fn classify_element<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidat
|
||||
let recurse_target = child_by_kind(node, &["content"]).unwrap_or(node);
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("tag_{tag_name}"),
|
||||
ChunkKind::Tag,
|
||||
Some(tag_name),
|
||||
source,
|
||||
Some(recurse_self(recurse_target, ChunkContext::ClassBody)),
|
||||
))
|
||||
},
|
||||
"text_node" => Some(group_candidate(node, "text", source)),
|
||||
"text_node" => Some(group_candidate(node, ChunkKind::Text, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct IniClassifier;
|
||||
|
||||
@@ -25,20 +25,25 @@ fn classify_ini_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandida
|
||||
Some(match node.kind() {
|
||||
"section" => make_container_chunk(
|
||||
node,
|
||||
format!("section_{}", ini_name(node, source)?),
|
||||
ChunkKind::Section,
|
||||
Some(ini_name(node, source)?),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
),
|
||||
// INI permits settings before any section header; keep them as first-class
|
||||
// chunks instead of forcing them under a synthetic container.
|
||||
"setting" => make_named_chunk(node, format!("key_{}", ini_name(node, source)?), source, None),
|
||||
"setting" => {
|
||||
make_kind_chunk(node, ChunkKind::Key, Some(ini_name(node, source)?), source, None)
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn classify_ini_class<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"setting" => make_named_chunk(node, format!("key_{}", ini_name(node, source)?), source, None),
|
||||
"setting" => {
|
||||
make_kind_chunk(node, ChunkKind::Key, Some(ini_name(node, source)?), source, None)
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -26,7 +26,9 @@ use serde::Serialize;
|
||||
use serde_json::{Map, Value};
|
||||
|
||||
use crate::chunk::{
|
||||
build_chunk_tree, chunk_checksum, line_start_offsets,
|
||||
build_chunk_tree, chunk_checksum,
|
||||
kind::ChunkKind,
|
||||
line_start_offsets,
|
||||
types::{ChunkNode, ChunkTree},
|
||||
};
|
||||
|
||||
@@ -395,7 +397,8 @@ pub fn build_notebook_tree_from_virtual(
|
||||
let mut chunks: Vec<ChunkNode> = Vec::with_capacity(1 + regions.len() * 2);
|
||||
chunks.push(ChunkNode {
|
||||
path: String::new(),
|
||||
name: "root".to_string(),
|
||||
identifier: None,
|
||||
kind: ChunkKind::Root,
|
||||
leaf: false,
|
||||
parent_path: None,
|
||||
children: Vec::new(),
|
||||
@@ -437,7 +440,6 @@ pub fn build_notebook_tree_from_virtual(
|
||||
if body_has_content {
|
||||
let sub_tree = build_chunk_tree(body, cell_language_str.as_str())
|
||||
.map_err(|err| format!("Failed to parse cell_{} body: {err}", region.cell_num))?;
|
||||
// sub_tree.chunks[0] is the sub-root; skip it.
|
||||
for sub_chunk in sub_tree.chunks.into_iter().skip(1) {
|
||||
let translated_path = format!("{}.{}", cell_path, sub_chunk.path);
|
||||
let translated_parent = match sub_chunk.parent_path.as_deref() {
|
||||
@@ -458,7 +460,8 @@ pub fn build_notebook_tree_from_virtual(
|
||||
let line_shift = region.content_line.saturating_sub(1);
|
||||
chunks.push(ChunkNode {
|
||||
path: translated_path.clone(),
|
||||
name: sub_chunk.name,
|
||||
identifier: sub_chunk.identifier,
|
||||
kind: sub_chunk.kind,
|
||||
leaf: sub_chunk.leaf,
|
||||
parent_path: translated_parent,
|
||||
children: translated_children,
|
||||
@@ -484,13 +487,11 @@ pub fn build_notebook_tree_from_virtual(
|
||||
group: false,
|
||||
});
|
||||
}
|
||||
// Collect direct children (those whose parent_path equals cell_path).
|
||||
for sub_path in sub_tree.root_children {
|
||||
cell_children_paths.push(format!("{cell_path}.{sub_path}"));
|
||||
}
|
||||
}
|
||||
|
||||
// Cell parent chunk: spans from the marker line through the body.
|
||||
let cell_line_count = {
|
||||
let body_lines = if body_has_content {
|
||||
if body.ends_with('\n') {
|
||||
@@ -501,13 +502,14 @@ pub fn build_notebook_tree_from_virtual(
|
||||
} else {
|
||||
0
|
||||
};
|
||||
1 + body_lines as u32 // +1 for the marker line
|
||||
1 + body_lines as u32
|
||||
};
|
||||
let cell_end_line = region.marker_line + cell_line_count.saturating_sub(1);
|
||||
let cell_leaf = cell_children_paths.is_empty();
|
||||
chunks.push(ChunkNode {
|
||||
path: cell_path.clone(),
|
||||
name: cell_path.clone(),
|
||||
identifier: Some(cell_path.clone()),
|
||||
kind: ChunkKind::Cell,
|
||||
leaf: cell_leaf,
|
||||
parent_path: Some(String::new()),
|
||||
children: cell_children_paths,
|
||||
|
||||
@@ -2,7 +2,9 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*, defaults::promote_assigned_expression};
|
||||
use super::{
|
||||
classify::LangClassifier, common::*, defaults::promote_assigned_expression, kind::ChunkKind,
|
||||
};
|
||||
|
||||
pub struct JsTsClassifier;
|
||||
|
||||
@@ -19,7 +21,7 @@ impl LangClassifier for JsTsClassifier {
|
||||
|
||||
// ── Imports ──
|
||||
"import_statement" | "import_declaration" => {
|
||||
Some(group_candidate(node, "imports", source))
|
||||
Some(group_candidate(node, ChunkKind::Imports, source))
|
||||
},
|
||||
|
||||
// ── Variables ──
|
||||
@@ -33,25 +35,30 @@ impl LangClassifier for JsTsClassifier {
|
||||
// ── Functions ──
|
||||
"function_declaration" => Some(named_candidate(
|
||||
node,
|
||||
"fn",
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
|
||||
// ── Containers ──
|
||||
"class_declaration" => {
|
||||
Some(container_candidate(node, "class", source, recurse_class(node)))
|
||||
Some(container_candidate(node, ChunkKind::Class, source, recurse_class(node)))
|
||||
},
|
||||
"interface_declaration" => {
|
||||
Some(container_candidate(node, "interface", source, recurse_interface(node)))
|
||||
Some(container_candidate(node, ChunkKind::Interface, source, recurse_interface(node)))
|
||||
},
|
||||
"enum_declaration" => Some(container_candidate(node, "enum", source, recurse_enum(node))),
|
||||
"internal_module" => {
|
||||
Some(container_candidate(node, "mod", source, recurse_internal_module(node)))
|
||||
"enum_declaration" => {
|
||||
Some(container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)))
|
||||
},
|
||||
"internal_module" => Some(container_candidate(
|
||||
node,
|
||||
ChunkKind::Module,
|
||||
source,
|
||||
recurse_internal_module(node),
|
||||
)),
|
||||
|
||||
// ── Types ──
|
||||
"type_alias_declaration" => Some(named_candidate(node, "type", source, None)),
|
||||
"type_alias_declaration" => Some(named_candidate(node, ChunkKind::Type, source, None)),
|
||||
|
||||
// ── Control flow at top level ──
|
||||
"if_statement" | "switch_statement" | "switch_expression" | "try_statement"
|
||||
@@ -65,9 +72,9 @@ impl LangClassifier for JsTsClassifier {
|
||||
.into_iter()
|
||||
.find(|c| c.kind() == "internal_module");
|
||||
if let Some(ns) = inner {
|
||||
Some(container_candidate(ns, "mod", source, recurse_internal_module(ns)))
|
||||
Some(container_candidate(ns, ChunkKind::Module, source, recurse_internal_module(ns)))
|
||||
} else {
|
||||
Some(group_candidate(node, "stmts", source))
|
||||
Some(group_candidate(node, ChunkKind::Statements, source))
|
||||
}
|
||||
},
|
||||
|
||||
@@ -85,9 +92,10 @@ impl LangClassifier for JsTsClassifier {
|
||||
"lexical_declaration" | "variable_declaration" => Some(classify_var_decl_js(node, source)),
|
||||
|
||||
// ── Constructor ──
|
||||
"constructor" => Some(make_named_chunk(
|
||||
"constructor" => Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)),
|
||||
@@ -96,16 +104,18 @@ impl LangClassifier for JsTsClassifier {
|
||||
"method_definition" | "method_signature" | "abstract_method_signature" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
if name == "constructor" {
|
||||
Some(make_named_chunk(
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
))
|
||||
} else {
|
||||
Some(make_named_chunk(
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
))
|
||||
@@ -119,23 +129,23 @@ impl LangClassifier for JsTsClassifier {
|
||||
| "property_signature"
|
||||
| "property_declaration"
|
||||
| "abstract_class_field" => match extract_identifier(node, source) {
|
||||
Some(name) => Some(make_named_chunk(node, format!("field_{name}"), source, None)),
|
||||
None => Some(group_candidate(node, "fields", source)),
|
||||
Some(name) => Some(make_kind_chunk(node, ChunkKind::Field, Some(name), source, None)),
|
||||
None => Some(group_candidate(node, ChunkKind::Fields, source)),
|
||||
},
|
||||
|
||||
// ── Enum members ──
|
||||
"enum_assignment" | "enum_member_declaration" => match extract_identifier(node, source) {
|
||||
Some(name) => Some(make_named_chunk(node, format!("variant_{name}"), source, None)),
|
||||
None => Some(group_candidate(node, "variants", source)),
|
||||
Some(name) => Some(make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None)),
|
||||
None => Some(group_candidate(node, ChunkKind::Variants, source)),
|
||||
},
|
||||
|
||||
// ── Static blocks ──
|
||||
"class_static_block" => {
|
||||
Some(make_named_chunk(node, "static_init".to_string(), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::StaticInit, None, source, None))
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_alias_declaration" => Some(named_candidate(node, "type", source, None)),
|
||||
"type_alias_declaration" => Some(named_candidate(node, ChunkKind::Type, source, None)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -153,41 +163,54 @@ fn classify_function_js<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
match node.kind() {
|
||||
// ── Control flow ──
|
||||
"if_statement" => make_named_chunk(node, "if".to_string(), source, fn_recurse()),
|
||||
"switch_statement" | "switch_expression" => {
|
||||
make_named_chunk(node, "switch".to_string(), source, fn_recurse())
|
||||
"if_statement" => {
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"switch_statement" | "switch_expression" => {
|
||||
make_candidate(node, ChunkKind::Switch, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"try_statement" => {
|
||||
make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"try_statement" => make_named_chunk(node, "try".to_string(), source, fn_recurse()),
|
||||
|
||||
// ── Loops ──
|
||||
"for_statement" => make_named_chunk(node, "for".to_string(), source, fn_recurse()),
|
||||
"for_in_statement" => make_named_chunk(node, "for_in".to_string(), source, fn_recurse()),
|
||||
"for_of_statement" => make_named_chunk(node, "for_of".to_string(), source, fn_recurse()),
|
||||
"while_statement" => make_named_chunk(node, "while".to_string(), source, fn_recurse()),
|
||||
"do_statement" => make_named_chunk(node, "block".to_string(), source, fn_recurse()),
|
||||
"for_statement" => {
|
||||
make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"for_in_statement" => {
|
||||
make_candidate(node, ChunkKind::ForIn, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"for_of_statement" => {
|
||||
make_candidate(node, ChunkKind::ForOf, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"while_statement" => {
|
||||
make_candidate(node, ChunkKind::While, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"do_statement" => {
|
||||
make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Blocks ──
|
||||
"with_statement" => make_named_chunk(node, "block".to_string(), source, fn_recurse()),
|
||||
"with_statement" => {
|
||||
make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Variables ──
|
||||
"lexical_declaration" | "variable_declaration" => {
|
||||
let span = line_span(node.start_position().row + 1, node.end_position().row + 1);
|
||||
if span > 1 {
|
||||
if let Some(name) = extract_single_declarator_name(node, source) {
|
||||
make_named_chunk(node, format!("var_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
} else {
|
||||
group_candidate(node, &sanitize_node_kind(node.kind()), source)
|
||||
group_from_sanitized(node, source)
|
||||
}
|
||||
} else {
|
||||
group_candidate(node, &sanitize_node_kind(node.kind()), source)
|
||||
group_from_sanitized(node, source)
|
||||
}
|
||||
},
|
||||
|
||||
// ── Fallback ──
|
||||
_ => {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
},
|
||||
_ => group_from_sanitized(node, source),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,11 +232,11 @@ fn classify_var_decl_js<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'
|
||||
match value.kind() {
|
||||
"arrow_function" | "function_expression" | "function" => {
|
||||
let recurse = recurse_body(value, ChunkContext::FunctionBody);
|
||||
return make_named_chunk(node, format!("fn_{name}"), source, recurse);
|
||||
return make_kind_chunk(node, ChunkKind::Function, Some(name), source, recurse);
|
||||
},
|
||||
"class" | "class_expression" => {
|
||||
let recurse = recurse_class(value);
|
||||
return make_container_chunk(node, format!("class_{name}"), source, recurse);
|
||||
return make_container_chunk(node, ChunkKind::Class, Some(name), source, recurse);
|
||||
},
|
||||
_ => {},
|
||||
}
|
||||
@@ -221,9 +244,15 @@ fn classify_var_decl_js<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'
|
||||
}
|
||||
// Not promoted — fall back to var_NAME or group.
|
||||
if let Some(name) = extract_single_declarator_name(node, source) {
|
||||
return make_named_chunk(node, format!("var_{name}"), source, None);
|
||||
return make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None);
|
||||
}
|
||||
group_candidate(node, "decls", source)
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
}
|
||||
|
||||
fn group_from_sanitized<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
let kind = ChunkKind::from_sanitized_kind(kind_name.as_str());
|
||||
make_candidate(node, kind, kind_name, NameStyle::Group, None, None, source)
|
||||
}
|
||||
|
||||
// ── Export statement ─────────────────────────────────────────────────────
|
||||
@@ -244,9 +273,9 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid
|
||||
let Some(child) = inner else {
|
||||
// `export { foo } from "bar"` with no inner declaration node.
|
||||
return if is_default {
|
||||
make_named_chunk(node, "default_export".to_string(), source, None)
|
||||
make_kind_chunk(node, ChunkKind::DefaultExport, None, source, None)
|
||||
} else {
|
||||
group_candidate(node, "stmts", source)
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -254,34 +283,48 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid
|
||||
"class_declaration" => {
|
||||
let recurse = recurse_class(child);
|
||||
if is_default {
|
||||
make_container_chunk_from(node, child, "default_export".to_string(), source, recurse)
|
||||
make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse)
|
||||
} else {
|
||||
make_container_chunk_from(
|
||||
node,
|
||||
child,
|
||||
prefixed_name("class", child, source),
|
||||
ChunkKind::Class,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse,
|
||||
)
|
||||
}
|
||||
},
|
||||
"function_declaration" => {
|
||||
"function_declaration"
|
||||
| "function"
|
||||
| "function_expression"
|
||||
| "arrow_function"
|
||||
| "generator_function"
|
||||
| "generator_function_declaration" => {
|
||||
let recurse = recurse_body(child, ChunkContext::FunctionBody);
|
||||
if is_default {
|
||||
make_named_chunk_from(node, child, "default_export".to_string(), source, recurse)
|
||||
make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse)
|
||||
} else {
|
||||
make_named_chunk_from(node, child, prefixed_name("fn", child, source), source, recurse)
|
||||
make_kind_chunk_from(
|
||||
node,
|
||||
child,
|
||||
ChunkKind::Function,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse,
|
||||
)
|
||||
}
|
||||
},
|
||||
"interface_declaration" => {
|
||||
let recurse = recurse_interface(child);
|
||||
if is_default {
|
||||
make_container_chunk_from(node, child, "default_export".to_string(), source, recurse)
|
||||
make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse)
|
||||
} else {
|
||||
make_container_chunk_from(
|
||||
node,
|
||||
child,
|
||||
prefixed_name("interface", child, source),
|
||||
ChunkKind::Interface,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse,
|
||||
)
|
||||
@@ -289,20 +332,28 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid
|
||||
},
|
||||
"type_alias_declaration" => {
|
||||
if is_default {
|
||||
make_named_chunk_from(node, child, "default_export".to_string(), source, None)
|
||||
make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, None)
|
||||
} else {
|
||||
make_named_chunk_from(node, child, prefixed_name("type", child, source), source, None)
|
||||
make_kind_chunk_from(
|
||||
node,
|
||||
child,
|
||||
ChunkKind::Type,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
None,
|
||||
)
|
||||
}
|
||||
},
|
||||
"enum_declaration" => {
|
||||
let recurse = recurse_enum(child);
|
||||
if is_default {
|
||||
make_container_chunk_from(node, child, "default_export".to_string(), source, recurse)
|
||||
make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse)
|
||||
} else {
|
||||
make_container_chunk_from(
|
||||
node,
|
||||
child,
|
||||
prefixed_name("enum", child, source),
|
||||
ChunkKind::Enum,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse,
|
||||
)
|
||||
@@ -311,12 +362,13 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid
|
||||
"internal_module" => {
|
||||
let recurse = recurse_internal_module(child);
|
||||
if is_default {
|
||||
make_container_chunk_from(node, child, "default_export".to_string(), source, recurse)
|
||||
make_container_chunk_from(node, child, ChunkKind::DefaultExport, None, source, recurse)
|
||||
} else {
|
||||
make_container_chunk_from(
|
||||
node,
|
||||
child,
|
||||
prefixed_name("mod", child, source),
|
||||
ChunkKind::Module,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse,
|
||||
)
|
||||
@@ -324,7 +376,7 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid
|
||||
},
|
||||
"lexical_declaration" | "variable_declaration" => {
|
||||
if is_default {
|
||||
make_named_chunk_from(node, child, "default_export".to_string(), source, None)
|
||||
make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, None)
|
||||
} else {
|
||||
promote_assigned_expression(node, child, source)
|
||||
.unwrap_or_else(|| super::defaults::classify_var_decl(child, source))
|
||||
@@ -333,16 +385,14 @@ fn classify_export_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandid
|
||||
_ => {
|
||||
// expression_statement, re-exports, or anything else.
|
||||
if is_default {
|
||||
make_named_chunk_from(node, child, "default_export".to_string(), source, None)
|
||||
make_kind_chunk_from(node, child, ChunkKind::DefaultExport, None, source, None)
|
||||
} else {
|
||||
group_candidate(child, "stmts", source)
|
||||
group_candidate(child, ChunkKind::Statements, source)
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// ── Decorated definition ─────────────────────────────────────────────────
|
||||
|
||||
/// Unwrap `@decorator` wrappers (TS/Python `decorated_definition`) to find
|
||||
/// the inner class or function definition.
|
||||
fn classify_decorated<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
@@ -356,16 +406,22 @@ fn classify_decorated<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t>
|
||||
match inner {
|
||||
Some(child) if child.kind() == "class_declaration" || child.kind() == "class_definition" => {
|
||||
let recurse = recurse_class(child);
|
||||
make_container_chunk(node, prefixed_name("class", child, source), source, recurse)
|
||||
make_container_chunk(
|
||||
node,
|
||||
ChunkKind::Class,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse,
|
||||
)
|
||||
},
|
||||
Some(child) => {
|
||||
// function_declaration | function_definition
|
||||
let name = extract_identifier(child, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("fn_{name}"), source, {
|
||||
make_kind_chunk(node, ChunkKind::Function, Some(name), source, {
|
||||
let context = ChunkContext::FunctionBody;
|
||||
recurse_into(child, context, &["body"], &["block"])
|
||||
})
|
||||
},
|
||||
None => positional_candidate(node, "block", source),
|
||||
None => positional_candidate(node, ChunkKind::Block, source),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct JustClassifier;
|
||||
|
||||
@@ -42,17 +42,18 @@ fn classify_just_root_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkC
|
||||
Some(match node.kind() {
|
||||
"setting" => {
|
||||
let name = extract_setting_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("setting_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Setting, Some(name), source, None)
|
||||
},
|
||||
"alias" => {
|
||||
let name = extract_alias_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("alias_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Alias, Some(name), source, None)
|
||||
},
|
||||
"recipe" => {
|
||||
let name = extract_recipe_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_container_chunk(
|
||||
node,
|
||||
format!("recipe_{name}"),
|
||||
ChunkKind::Recipe,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["recipe_body"]),
|
||||
)
|
||||
@@ -65,8 +66,8 @@ fn classify_just_body_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkC
|
||||
Some(match node.kind() {
|
||||
// Just recipe bodies are line-oriented; tree-sitter exposes shell lines as
|
||||
// `recipe_line` leaves rather than a nested shell AST.
|
||||
"recipe_line" => group_candidate(node, "cmd", source),
|
||||
"shebang" => make_named_chunk(node, "shebang".to_string(), source, None),
|
||||
"recipe_line" => group_candidate(node, ChunkKind::Cmd, source),
|
||||
"shebang" => make_kind_chunk(node, ChunkKind::Shebang, None, source, None),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,36 +2,38 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct MarkupClassifier;
|
||||
|
||||
impl MarkupClassifier {
|
||||
fn classify_section<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = extract_markdown_heading(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_container_chunk(
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
format!("section_{name}"),
|
||||
ChunkKind::Section,
|
||||
Some(name),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn classify_block_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name =
|
||||
extract_glimmer_block_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_container_chunk(
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
format!("block_{name}"),
|
||||
ChunkKind::Block,
|
||||
Some(name),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn classify_mustache_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name =
|
||||
extract_glimmer_mustache_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("mustache_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Mustache, Some(name), source, None)
|
||||
}
|
||||
|
||||
/// Classify HTML-like element nodes that appear inside handlebars blocks.
|
||||
@@ -40,14 +42,15 @@ impl MarkupClassifier {
|
||||
"element" | "script_element" | "style_element" | "element_node" => {
|
||||
let name =
|
||||
extract_element_tag_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_container_chunk(
|
||||
Some(force_container(make_container_chunk(
|
||||
node,
|
||||
format!("tag_{name}"),
|
||||
ChunkKind::Tag,
|
||||
Some(name),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
)))
|
||||
},
|
||||
"text_node" => Some(group_candidate(node, "text", source)),
|
||||
"text_node" => Some(group_candidate(node, ChunkKind::Text, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -87,6 +90,11 @@ impl LangClassifier for MarkupClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> {
|
||||
candidate.force_recurse = true;
|
||||
candidate
|
||||
}
|
||||
|
||||
/// Extract heading text from a Markdown `section` node's `atx_heading` or
|
||||
/// `setext_heading` child.
|
||||
fn extract_markdown_heading(node: Node<'_>, source: &str) -> Option<String> {
|
||||
|
||||
@@ -6,10 +6,23 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*, defaults::classify_var_decl};
|
||||
use super::{classify::LangClassifier, common::*, defaults::classify_var_decl, kind::ChunkKind};
|
||||
|
||||
pub struct MiscClassifier;
|
||||
|
||||
fn sanitized_group_candidate<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let sanitized = sanitize_node_kind(node.kind());
|
||||
let kind = ChunkKind::from_sanitized_kind(sanitized.as_str());
|
||||
// For unknown kinds that fall back to `Chunk`, preserve the original
|
||||
// tree-sitter kind as the identifier so the path stays informative.
|
||||
let identifier = if kind == ChunkKind::Chunk {
|
||||
Some(sanitized)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
make_candidate(node, kind, identifier, NameStyle::Group, None, None, source)
|
||||
}
|
||||
|
||||
impl LangClassifier for MiscClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
let fn_recurse = || {
|
||||
@@ -37,21 +50,21 @@ impl LangClassifier for MiscClassifier {
|
||||
| "import_list"
|
||||
| "import_header"
|
||||
| "package_header"
|
||||
| "package_declaration" => group_candidate(node, "imports", source),
|
||||
| "package_declaration" => group_candidate(node, ChunkKind::Imports, source),
|
||||
|
||||
// ── Variables / assignments ──
|
||||
"lexical_declaration" | "variable_declaration" => classify_var_decl(node, source),
|
||||
"const_declaration" | "var_declaration" => match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("var_{name}"), source, None),
|
||||
None => group_candidate(node, "decls", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Declarations, source),
|
||||
},
|
||||
"assignment" | "property_declaration" | "state_variable_declaration" => {
|
||||
group_candidate(node, "decls", source)
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
},
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" | "global_statement" | "command" | "pipeline" | "function_call" => {
|
||||
group_candidate(node, "stmts", source)
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
},
|
||||
|
||||
// ── Functions ──
|
||||
@@ -59,61 +72,74 @@ impl LangClassifier for MiscClassifier {
|
||||
| "function_definition"
|
||||
| "procedure_declaration"
|
||||
| "overloaded_procedure_declaration"
|
||||
| "test_declaration" => named_candidate(node, "fn", source, fn_recurse()),
|
||||
"method_declaration" => {
|
||||
named_candidate(node, "meth", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
| "test_declaration" => named_candidate(node, ChunkKind::Function, source, fn_recurse()),
|
||||
"method_declaration" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Method,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
"constructor_definition"
|
||||
| "constructor_declaration"
|
||||
| "secondary_constructor"
|
||||
| "init_declaration"
|
||||
| "fallback_receive_definition" => make_named_chunk(
|
||||
| "fallback_receive_definition" => make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
|
||||
// ── Containers ──
|
||||
"class_declaration" | "class_definition" => {
|
||||
container_candidate(node, "class", source, recurse_class(node))
|
||||
container_candidate(node, ChunkKind::Class, source, recurse_class(node))
|
||||
},
|
||||
"interface_declaration" | "protocol_declaration" => {
|
||||
container_candidate(node, "iface", source, recurse_interface(node))
|
||||
container_candidate(node, ChunkKind::Iface, source, recurse_interface(node))
|
||||
},
|
||||
"struct_declaration" | "object_declaration" => {
|
||||
container_candidate(node, "struct", source, recurse_class(node))
|
||||
container_candidate(node, ChunkKind::Struct, source, recurse_class(node))
|
||||
},
|
||||
"enum_declaration" | "enum_definition" => {
|
||||
container_candidate(node, "enum", source, recurse_enum(node))
|
||||
container_candidate(node, ChunkKind::Enum, source, recurse_enum(node))
|
||||
},
|
||||
"trait_definition" | "class" => {
|
||||
container_candidate(node, "trait", source, recurse_class(node))
|
||||
container_candidate(node, ChunkKind::Trait, source, recurse_class(node))
|
||||
},
|
||||
"contract_declaration" | "library_declaration" | "trait_declaration" => {
|
||||
container_candidate(node, "contract", source, recurse_class(node))
|
||||
container_candidate(node, ChunkKind::Contract, source, recurse_class(node))
|
||||
},
|
||||
"namespace_declaration"
|
||||
| "namespace_definition"
|
||||
| "module_definition"
|
||||
| "extension_definition" => container_candidate(node, "mod", source, module_recurse()),
|
||||
| "extension_definition" => {
|
||||
container_candidate(node, ChunkKind::Module, source, module_recurse())
|
||||
},
|
||||
|
||||
// ── Types / aliases ──
|
||||
"type_alias_declaration" | "const_type_declaration" | "opaque_declaration" => {
|
||||
named_candidate(node, "type", source, recurse_class(node))
|
||||
named_candidate(node, ChunkKind::Type, source, recurse_class(node))
|
||||
},
|
||||
|
||||
// ── Macros ──
|
||||
"macro_definition" | "modifier_definition" => {
|
||||
named_candidate(node, "macro", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
"macro_definition" | "modifier_definition" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Macro,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
|
||||
// ── Systems (Verilog etc.) ──
|
||||
"covergroup_declaration" | "checker_declaration" => {
|
||||
container_candidate(node, "group", source, recurse_class(node))
|
||||
container_candidate(node, ChunkKind::Group, source, recurse_class(node))
|
||||
},
|
||||
"module_declaration" => {
|
||||
container_candidate(node, ChunkKind::Module, source, recurse_class(node))
|
||||
},
|
||||
"union_declaration" => {
|
||||
container_candidate(node, ChunkKind::Union, source, recurse_class(node))
|
||||
},
|
||||
"module_declaration" => container_candidate(node, "mod", source, recurse_class(node)),
|
||||
"union_declaration" => container_candidate(node, "union", source, recurse_class(node)),
|
||||
|
||||
// ── Control flow at top level → delegate to function-level ──
|
||||
"if_statement"
|
||||
@@ -145,9 +171,10 @@ impl LangClassifier for MiscClassifier {
|
||||
"constructor"
|
||||
| "constructor_declaration"
|
||||
| "secondary_constructor"
|
||||
| "init_declaration" => make_named_chunk(
|
||||
| "init_declaration" => make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
@@ -166,16 +193,18 @@ impl LangClassifier for MiscClassifier {
|
||||
| "singleton_method" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
if name == "constructor" {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
} else {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
@@ -193,8 +222,8 @@ impl LangClassifier for MiscClassifier {
|
||||
| "const_declaration"
|
||||
| "constant_declaration"
|
||||
| "event_field_declaration" => match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
},
|
||||
|
||||
// ── Enum variants ──
|
||||
@@ -203,27 +232,27 @@ impl LangClassifier for MiscClassifier {
|
||||
| "enum_constant"
|
||||
| "enum_entry"
|
||||
| "enum_variant" => match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None),
|
||||
None => group_candidate(node, "variants", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Variants, source),
|
||||
},
|
||||
|
||||
// ── Other fields ──
|
||||
"field_declaration" | "embedded_field" | "container_field" | "binding" => {
|
||||
match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
}
|
||||
},
|
||||
|
||||
// ── Method specs ──
|
||||
"method_spec" => named_candidate(node, "meth", source, None),
|
||||
"method_spec" => named_candidate(node, ChunkKind::Method, source, None),
|
||||
|
||||
// ── Field / method lists ──
|
||||
"field_declaration_list" => group_candidate(node, "fields", source),
|
||||
"method_spec_list" => group_candidate(node, "methods", source),
|
||||
"field_declaration_list" => group_candidate(node, ChunkKind::Fields, source),
|
||||
"method_spec_list" => group_candidate(node, ChunkKind::Methods, source),
|
||||
|
||||
// ── Static initializer ──
|
||||
"class_static_block" => make_named_chunk(node, "static_init".to_string(), source, None),
|
||||
"class_static_block" => make_kind_chunk(node, ChunkKind::StaticInit, None, source, None),
|
||||
|
||||
// ── Decorated definitions ──
|
||||
"decorated_definition" => {
|
||||
@@ -233,7 +262,7 @@ impl LangClassifier for MiscClassifier {
|
||||
if let Some(child) = inner {
|
||||
let name =
|
||||
extract_identifier(child, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(node, format!("fn_{name}"), source, {
|
||||
make_kind_chunk(node, ChunkKind::Function, Some(name), source, {
|
||||
let context = ChunkContext::FunctionBody;
|
||||
recurse_into(child, context, &["body"], &["block"])
|
||||
})
|
||||
@@ -248,15 +277,15 @@ impl LangClassifier for MiscClassifier {
|
||||
| "attribute"
|
||||
| "pair"
|
||||
| "block_mapping_pair"
|
||||
| "flow_pair" => group_candidate(node, "fields", source),
|
||||
| "flow_pair" => group_candidate(node, ChunkKind::Fields, source),
|
||||
|
||||
// ── Types inside classes ──
|
||||
"type_item" | "type_alias_declaration" | "type_alias" => {
|
||||
named_candidate(node, "type", source, None)
|
||||
named_candidate(node, ChunkKind::Type, source, None)
|
||||
},
|
||||
|
||||
// ── Const / macro inside classes ──
|
||||
"const_item" | "macro_invocation" => group_candidate(node, "fields", source),
|
||||
"const_item" | "macro_invocation" => group_candidate(node, ChunkKind::Fields, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -266,15 +295,9 @@ impl LangClassifier for MiscClassifier {
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
Some(match node.kind() {
|
||||
// ── Control flow: conditionals ──
|
||||
"if_statement" | "unless" | "guard_statement" => make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"if_statement" | "unless" | "guard_statement" => {
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Control flow: switches ──
|
||||
"switch_statement"
|
||||
@@ -287,87 +310,72 @@ impl LangClassifier for MiscClassifier {
|
||||
| "receive_statement"
|
||||
| "yul_switch_statement" => make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
ChunkKind::Switch,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
|
||||
// ── Control flow: try/catch ──
|
||||
"try_statement" | "try_block" | "catch_clause" | "finally_clause"
|
||||
| "assembly_statement" => make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
| "assembly_statement" => {
|
||||
make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Loops: for variants (with Python-like check) ──
|
||||
"for_statement" | "for_in_statement" | "for_of_statement" => {
|
||||
let name = if looks_like_python_statement(node, source) {
|
||||
"loop".to_string()
|
||||
let kind = if looks_like_python_statement(node, source) {
|
||||
ChunkKind::Loop
|
||||
} else {
|
||||
sanitize_node_kind(node.kind())
|
||||
match node.kind() {
|
||||
"for_statement" => ChunkKind::For,
|
||||
"for_in_statement" => ChunkKind::ForIn,
|
||||
"for_of_statement" => ChunkKind::ForOf,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
};
|
||||
make_candidate(node, name, NameStyle::Named, None, fn_recurse(), false, source)
|
||||
make_candidate(node, kind, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Loops: while ──
|
||||
"while_statement" => {
|
||||
let name = if looks_like_python_statement(node, source) {
|
||||
"loop"
|
||||
let kind = if looks_like_python_statement(node, source) {
|
||||
ChunkKind::Loop
|
||||
} else {
|
||||
"while"
|
||||
ChunkKind::While
|
||||
};
|
||||
make_candidate(
|
||||
node,
|
||||
name.to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
)
|
||||
make_candidate(node, kind, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Blocks ──
|
||||
"do_statement" | "with_statement" | "do_block" | "subshell" | "async_block"
|
||||
| "unsafe_block" | "const_block" | "block_expression" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
ChunkKind::Block,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
|
||||
// ── Loops: foreach ──
|
||||
"foreach_statement" => make_candidate(
|
||||
node,
|
||||
"for".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"foreach_statement" => {
|
||||
make_candidate(node, ChunkKind::For, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
|
||||
// ── Statements ──
|
||||
"defer_statement" | "go_statement" | "send_statement" => {
|
||||
group_candidate(node, "stmts", source)
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
},
|
||||
|
||||
// ── Positional candidates ──
|
||||
"elif_clause" => positional_candidate(node, "elif", source),
|
||||
"except_clause" => positional_candidate(node, "except", source),
|
||||
"when_statement" => positional_candidate(node, "when", source),
|
||||
"match_expression" | "match_block" => positional_candidate(node, "match", source),
|
||||
"elif_clause" => positional_candidate(node, ChunkKind::Elif, source),
|
||||
"except_clause" => positional_candidate(node, ChunkKind::Except, source),
|
||||
"when_statement" => positional_candidate(node, ChunkKind::When, source),
|
||||
"match_expression" | "match_block" => positional_candidate(node, ChunkKind::Match, source),
|
||||
|
||||
// ── Loops / misc expressions ──
|
||||
"loop_expression"
|
||||
@@ -378,7 +386,7 @@ impl LangClassifier for MiscClassifier {
|
||||
| "nosuspend_statement"
|
||||
| "suspend_statement"
|
||||
| "yul_if_statement"
|
||||
| "yul_for_statement" => positional_candidate(node, "loop", source),
|
||||
| "yul_for_statement" => positional_candidate(node, ChunkKind::Loop, source),
|
||||
|
||||
// ── Variable declarations ──
|
||||
"lexical_declaration"
|
||||
@@ -390,14 +398,12 @@ impl LangClassifier for MiscClassifier {
|
||||
let span = line_span(node.start_position().row + 1, node.end_position().row + 1);
|
||||
if span > 1 {
|
||||
if let Some(name) = extract_single_declarator_name(node, source) {
|
||||
make_named_chunk(node, format!("var_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
} else {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
sanitized_group_candidate(node, source)
|
||||
}
|
||||
} else {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
sanitized_group_candidate(node, source)
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct NixHclClassifier;
|
||||
|
||||
@@ -59,16 +59,21 @@ fn recurse_nix_binding_value(node: Node<'_>) -> Option<RecurseSpec<'_>> {
|
||||
|
||||
fn classify_nix_binding<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = extract_nix_binding_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
let chunk_name = format!("attr_{name}");
|
||||
let expression = node.child_by_field_name("expression");
|
||||
if let Some(expression) = expression
|
||||
&& matches!(
|
||||
expression.kind(),
|
||||
"attrset_expression" | "let_attrset_expression" | "rec_attrset_expression"
|
||||
) {
|
||||
return make_container_chunk(node, chunk_name, source, recurse_nix_attrset(expression));
|
||||
return make_container_chunk(
|
||||
node,
|
||||
ChunkKind::Attr,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_nix_attrset(expression),
|
||||
);
|
||||
}
|
||||
make_named_chunk(node, chunk_name, source, recurse_nix_binding_value(node))
|
||||
make_kind_chunk(node, ChunkKind::Attr, Some(name), source, recurse_nix_binding_value(node))
|
||||
}
|
||||
|
||||
impl LangClassifier for NixHclClassifier {
|
||||
@@ -77,11 +82,14 @@ impl LangClassifier for NixHclClassifier {
|
||||
// Nix top-level attrsets should recurse into their binding_set so the file exposes
|
||||
// structural attr chunks instead of a single opaque attrset_expr leaf.
|
||||
"attrset_expression" | "let_attrset_expression" | "rec_attrset_expression" => {
|
||||
Some(make_container_chunk(
|
||||
Some(make_candidate(
|
||||
node,
|
||||
sanitize_node_kind(node.kind()),
|
||||
source,
|
||||
ChunkKind::Attrs,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_nix_attrset(node),
|
||||
source,
|
||||
))
|
||||
},
|
||||
// Older tree-sitter-nix revisions used `attribute`; current grammars expose `binding`.
|
||||
@@ -91,29 +99,44 @@ impl LangClassifier for NixHclClassifier {
|
||||
if let Some(name) = extract_hcl_block_name(node, source) {
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("block_{name}"),
|
||||
ChunkKind::Block,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["body"]),
|
||||
))
|
||||
} else {
|
||||
Some(group_candidate(node, "hunks", source))
|
||||
Some(group_candidate(node, ChunkKind::Hunks, source))
|
||||
}
|
||||
},
|
||||
// Nix expressions
|
||||
"function_expression" | "let_expression" => {
|
||||
Some(named_candidate(node, "expr", source, recurse_value_container(node)))
|
||||
},
|
||||
"function_expression" | "let_expression" => Some(named_candidate(
|
||||
node,
|
||||
ChunkKind::Expression,
|
||||
source,
|
||||
recurse_value_container(node),
|
||||
)),
|
||||
// Nix inherit
|
||||
"inherit" => Some(group_candidate(node, "imports", source)),
|
||||
"inherit" => Some(group_candidate(node, ChunkKind::Imports, source)),
|
||||
// Variable/assignment declarations
|
||||
"variable_declaration" | "assignment" => Some(group_candidate(node, "decls", source)),
|
||||
"variable_declaration" | "assignment" => {
|
||||
Some(group_candidate(node, ChunkKind::Declarations, source))
|
||||
},
|
||||
// HCL top-level block types
|
||||
"provider" | "resource" | "data" | "locals" | "variable" | "output" | "module" => {
|
||||
Some(container_candidate(
|
||||
let kind = match node.kind() {
|
||||
"locals" => ChunkKind::BlockLocals,
|
||||
"variable" => ChunkKind::Variable,
|
||||
"module" => ChunkKind::Module,
|
||||
_ => ChunkKind::Block,
|
||||
};
|
||||
Some(make_candidate(
|
||||
node,
|
||||
sanitize_node_kind(node.kind()).as_str(),
|
||||
source,
|
||||
kind,
|
||||
prefixed_name(sanitize_node_kind(node.kind()).as_str(), node, source),
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["body"]),
|
||||
source,
|
||||
))
|
||||
},
|
||||
_ => None,
|
||||
@@ -126,18 +149,22 @@ impl LangClassifier for NixHclClassifier {
|
||||
"block" => extract_hcl_block_name(node, source).map(|name| {
|
||||
make_container_chunk(
|
||||
node,
|
||||
format!("block_{name}"),
|
||||
ChunkKind::Block,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["body"]),
|
||||
)
|
||||
}),
|
||||
// Nested Nix attrset values recurse into their binding_set just like top-level ones.
|
||||
"attrset_expression" | "let_attrset_expression" | "rec_attrset_expression" => {
|
||||
Some(make_container_chunk(
|
||||
Some(make_candidate(
|
||||
node,
|
||||
sanitize_node_kind(node.kind()),
|
||||
source,
|
||||
ChunkKind::Attrs,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse_nix_attrset(node),
|
||||
source,
|
||||
))
|
||||
},
|
||||
// Nested Nix binding
|
||||
@@ -149,8 +176,8 @@ impl LangClassifier for NixHclClassifier {
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
// Nix control flow
|
||||
"if_expression" => Some(positional_candidate(node, "if", source)),
|
||||
"let_expression" => Some(positional_candidate(node, "block", source)),
|
||||
"if_expression" => Some(positional_candidate(node, ChunkKind::If, source)),
|
||||
"let_expression" => Some(positional_candidate(node, ChunkKind::Block, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct OcamlClassifier;
|
||||
|
||||
@@ -13,22 +13,24 @@ impl LangClassifier for OcamlClassifier {
|
||||
|
||||
fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"method_definition" => Some(make_named_chunk(
|
||||
"method_definition" => Some(make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{}", ocaml_named_text(node, source, &["method_name"])?),
|
||||
ChunkKind::Function,
|
||||
ocaml_named_text(node, source, &["method_name"]),
|
||||
source,
|
||||
ocaml_method_recurse(node),
|
||||
)),
|
||||
"method_specification" => Some(make_named_chunk(
|
||||
"method_specification" => Some(make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{}", ocaml_named_text(node, source, &["method_name"])?),
|
||||
ChunkKind::Function,
|
||||
ocaml_named_text(node, source, &["method_name"]),
|
||||
source,
|
||||
None,
|
||||
)),
|
||||
"instance_variable_definition" => {
|
||||
Some(match ocaml_named_text(node, source, &["instance_variable_name"]) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
})
|
||||
},
|
||||
_ => classify_ocaml_item(node, source),
|
||||
@@ -37,23 +39,32 @@ impl LangClassifier for OcamlClassifier {
|
||||
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
"function_expression" | "match_expression" => Some(make_named_chunk(
|
||||
"function_expression" | "match_expression" => Some(make_candidate(
|
||||
node,
|
||||
"match".to_string(),
|
||||
source,
|
||||
ChunkKind::Match,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
source,
|
||||
)),
|
||||
"match_case" => Some(make_named_chunk(
|
||||
"match_case" => Some(make_candidate(
|
||||
node,
|
||||
"case".to_string(),
|
||||
source,
|
||||
ChunkKind::Case,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
source,
|
||||
)),
|
||||
"let_expression" => Some(make_named_chunk(
|
||||
"let_expression" => Some(make_candidate(
|
||||
node,
|
||||
"let".to_string(),
|
||||
source,
|
||||
ChunkKind::Let,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
source,
|
||||
)),
|
||||
_ => classify_ocaml_item(node, source),
|
||||
}
|
||||
@@ -62,47 +73,60 @@ impl LangClassifier for OcamlClassifier {
|
||||
|
||||
fn classify_ocaml_item<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"open_module" => group_candidate(node, "imports", source),
|
||||
"open_module" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"module_definition" => make_container_chunk(
|
||||
node,
|
||||
format!("mod_{}", ocaml_named_text(node, source, &["module_name"])?),
|
||||
ChunkKind::Module,
|
||||
ocaml_named_text(node, source, &["module_name"]),
|
||||
source,
|
||||
ocaml_module_recurse(node),
|
||||
),
|
||||
"module_type_definition" => make_container_chunk(
|
||||
"module_type_definition" => make_candidate(
|
||||
node,
|
||||
ChunkKind::Interface,
|
||||
format!("modtype_{}", ocaml_named_text(node, source, &["module_type_name"])?),
|
||||
source,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
ocaml_module_type_recurse(node),
|
||||
source,
|
||||
),
|
||||
"class_definition" => make_container_chunk(
|
||||
node,
|
||||
format!("class_{}", ocaml_named_text(node, source, &["class_name"])?),
|
||||
ChunkKind::Class,
|
||||
ocaml_named_text(node, source, &["class_name"]),
|
||||
source,
|
||||
ocaml_class_recurse(node),
|
||||
),
|
||||
"class_type_definition" => make_container_chunk(
|
||||
"class_type_definition" => make_candidate(
|
||||
node,
|
||||
ChunkKind::Iface,
|
||||
format!("classtype_{}", ocaml_named_text(node, source, &["class_type_name"])?),
|
||||
source,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
ocaml_class_type_recurse(node),
|
||||
source,
|
||||
),
|
||||
"type_definition" => make_named_chunk(
|
||||
"type_definition" => make_kind_chunk(
|
||||
node,
|
||||
format!("type_{}", ocaml_named_text(node, source, &["type_constructor"])?),
|
||||
ChunkKind::Type,
|
||||
ocaml_named_text(node, source, &["type_constructor"]),
|
||||
source,
|
||||
None,
|
||||
),
|
||||
"exception_definition" => make_named_chunk(
|
||||
"exception_definition" => make_candidate(
|
||||
node,
|
||||
ChunkKind::Constructor,
|
||||
format!("exception_{}", ocaml_named_text(node, source, &["constructor_name"])?),
|
||||
source,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
None,
|
||||
source,
|
||||
),
|
||||
"value_definition" => classify_ocaml_value_definition(node, source)?,
|
||||
"value_specification" => make_named_chunk(
|
||||
"value_specification" => make_kind_chunk(
|
||||
node,
|
||||
format!("val_{}", ocaml_named_text(node, source, &["value_name"])?),
|
||||
ChunkKind::Val,
|
||||
ocaml_named_text(node, source, &["value_name"]),
|
||||
source,
|
||||
None,
|
||||
),
|
||||
@@ -117,9 +141,9 @@ fn classify_ocaml_value_definition<'t>(
|
||||
let name = ocaml_named_text(node, source, &["value_name"])?;
|
||||
let recurse = ocaml_value_recurse(node);
|
||||
if ocaml_value_definition_is_function(node) {
|
||||
Some(make_named_chunk(node, format!("fn_{name}"), source, recurse))
|
||||
Some(make_kind_chunk(node, ChunkKind::Function, Some(name), source, recurse))
|
||||
} else {
|
||||
Some(make_named_chunk(node, format!("val_{name}"), source, recurse))
|
||||
Some(make_kind_chunk(node, ChunkKind::Val, Some(name), source, recurse))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct PerlClassifier;
|
||||
|
||||
@@ -25,30 +25,22 @@ fn classify_perl_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandid
|
||||
|
||||
Some(match node.kind() {
|
||||
"package_statement" => {
|
||||
make_named_chunk(node, format!("mod_{}", perl_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Module, Some(perl_name(node, source)?), source, None)
|
||||
},
|
||||
"use_statement" => group_candidate(node, "imports", source),
|
||||
"subroutine_declaration_statement" => {
|
||||
make_named_chunk(node, format!("fn_{}", perl_name(node, source)?), source, body_recurse())
|
||||
"use_statement" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"subroutine_declaration_statement" => make_kind_chunk(
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
Some(perl_name(node, source)?),
|
||||
source,
|
||||
body_recurse(),
|
||||
),
|
||||
"conditional_statement" => {
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, body_recurse(), source)
|
||||
},
|
||||
"for_statement" | "loop_statement" => {
|
||||
make_candidate(node, ChunkKind::Loop, None, NameStyle::Named, None, body_recurse(), source)
|
||||
},
|
||||
"conditional_statement" => make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
body_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"for_statement" | "loop_statement" => make_candidate(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
body_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"expression_statement" => classify_perl_statement(node, source),
|
||||
_ => return None,
|
||||
})
|
||||
@@ -56,9 +48,9 @@ fn classify_perl_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandid
|
||||
|
||||
fn classify_perl_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
if perl_declares_variable(node) {
|
||||
group_candidate(node, "decls", source)
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
} else {
|
||||
group_candidate(node, "stmts", source)
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,29 +2,32 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct PowershellClassifier;
|
||||
|
||||
impl LangClassifier for PowershellClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"param_block" => group_candidate(node, "params", source),
|
||||
"param_block" => group_candidate(node, ChunkKind::Parameters, source),
|
||||
"statement_list" => make_container_chunk(
|
||||
node,
|
||||
"body".to_string(),
|
||||
ChunkKind::Body,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::Root)),
|
||||
),
|
||||
"class_statement" => make_container_chunk(
|
||||
node,
|
||||
format!("class_{}", powershell_name(node, source)?),
|
||||
ChunkKind::Class,
|
||||
Some(powershell_name(node, source)?),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
),
|
||||
"function_statement" => make_named_chunk(
|
||||
"function_statement" => make_container_chunk(
|
||||
node,
|
||||
format!("fn_{}", powershell_name(node, source)?),
|
||||
ChunkKind::Function,
|
||||
Some(powershell_name(node, source)?),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
@@ -32,7 +35,7 @@ impl LangClassifier for PowershellClassifier {
|
||||
"switch_statement" | "if_statement" | "foreach_statement" => {
|
||||
return self.classify_function(node, source);
|
||||
},
|
||||
"flow_control_statement" => group_candidate(node, "stmts", source),
|
||||
"flow_control_statement" => group_candidate(node, ChunkKind::Statements, source),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -40,8 +43,8 @@ impl LangClassifier for PowershellClassifier {
|
||||
fn classify_class<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"class_property_definition" => match powershell_name(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
},
|
||||
"class_method_definition" => classify_class_method(node, source)?,
|
||||
_ => return None,
|
||||
@@ -50,51 +53,60 @@ impl LangClassifier for PowershellClassifier {
|
||||
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"class_method_parameter_list" | "param_block" => group_candidate(node, "params", source),
|
||||
"class_method_parameter_list" | "param_block" => {
|
||||
group_candidate(node, ChunkKind::Parameters, source)
|
||||
},
|
||||
"script_block" => make_container_chunk(
|
||||
node,
|
||||
block_name_for_parent(node).to_string(),
|
||||
block_kind_for_parent(node),
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
"script_block_body" | "statement_block" => make_container_chunk(
|
||||
node,
|
||||
"block".to_string(),
|
||||
ChunkKind::Block,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_list"]),
|
||||
),
|
||||
"pipeline" => classify_powershell_pipeline(node, source),
|
||||
"if_statement" => make_named_chunk(
|
||||
"if_statement" => make_container_chunk(
|
||||
node,
|
||||
"if".to_string(),
|
||||
ChunkKind::If,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_block"]),
|
||||
),
|
||||
"foreach_statement" => make_named_chunk(
|
||||
"foreach_statement" => make_container_chunk(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
ChunkKind::Loop,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_block"]),
|
||||
),
|
||||
"switch_statement" => make_named_chunk(
|
||||
"switch_statement" => make_container_chunk(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
ChunkKind::Switch,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["switch_body"]),
|
||||
),
|
||||
"switch_clauses" => make_container_chunk(
|
||||
node,
|
||||
"cases".to_string(),
|
||||
ChunkKind::Cases,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
"switch_clause" => make_named_chunk(
|
||||
"switch_clause" => make_container_chunk(
|
||||
node,
|
||||
"case".to_string(),
|
||||
ChunkKind::Case,
|
||||
None,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["statement_block"]),
|
||||
),
|
||||
"flow_control_statement" => group_candidate(node, "stmts", source),
|
||||
"flow_control_statement" => group_candidate(node, ChunkKind::Statements, source),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
@@ -107,15 +119,16 @@ impl LangClassifier for PowershellClassifier {
|
||||
fn classify_class_method<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
let name = powershell_name(node, source)?;
|
||||
let class_name = powershell_name(node.parent()?, source)?;
|
||||
let chunk_name = if name == "new" || name == class_name {
|
||||
"constructor".to_string()
|
||||
let (kind, identifier) = if name == "new" || name == class_name {
|
||||
(ChunkKind::Constructor, None)
|
||||
} else {
|
||||
format!("fn_{name}")
|
||||
(ChunkKind::Function, Some(name))
|
||||
};
|
||||
|
||||
Some(make_named_chunk(
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
chunk_name,
|
||||
kind,
|
||||
identifier,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
))
|
||||
@@ -125,23 +138,24 @@ fn classify_powershell_pipeline<'t>(node: Node<'t>, source: &str) -> RawChunkCan
|
||||
if let Some(command_name) = powershell_command_name(node, source)
|
||||
&& matches!(command_name.as_str(), "using" | "using-module" | "Import-Module")
|
||||
{
|
||||
return group_candidate(node, "imports", source);
|
||||
return group_candidate(node, ChunkKind::Imports, source);
|
||||
}
|
||||
|
||||
if let Some((name, script_block)) = assigned_script_block(node, source) {
|
||||
return make_container_chunk_from(
|
||||
node,
|
||||
node,
|
||||
format!("block_{name}"),
|
||||
ChunkKind::Block,
|
||||
Some(name),
|
||||
source,
|
||||
Some(recurse_self(script_block, ChunkContext::FunctionBody)),
|
||||
);
|
||||
}
|
||||
|
||||
if child_by_kind(node, &["assignment_expression"]).is_some() {
|
||||
group_candidate(node, "decls", source)
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
} else {
|
||||
group_candidate(node, "stmts", source)
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,10 +184,10 @@ fn find_script_block(node: Node<'_>) -> Option<Node<'_>> {
|
||||
None
|
||||
}
|
||||
|
||||
fn block_name_for_parent(node: Node<'_>) -> &'static str {
|
||||
fn block_kind_for_parent(node: Node<'_>) -> ChunkKind {
|
||||
match node.parent().map(|parent| parent.kind()) {
|
||||
Some("function_statement" | "class_method_definition") => "body",
|
||||
_ => "block",
|
||||
Some("function_statement" | "class_method_definition") => ChunkKind::Body,
|
||||
_ => ChunkKind::Block,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct ProtoClassifier;
|
||||
|
||||
@@ -23,23 +23,26 @@ impl LangClassifier for ProtoClassifier {
|
||||
|
||||
fn classify_proto_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"syntax" | "package" => group_candidate(node, "headers", source),
|
||||
"import" => group_candidate(node, "imports", source),
|
||||
"option" => group_candidate(node, "options", source),
|
||||
"message" => make_container_chunk(
|
||||
"syntax" | "package" => group_candidate(node, ChunkKind::Headers, source),
|
||||
"import" => group_candidate(node, ChunkKind::Imports, source),
|
||||
"option" => group_candidate(node, ChunkKind::Options, source),
|
||||
"message" => make_named_proto_chunk(
|
||||
node,
|
||||
ChunkKind::Type,
|
||||
format!("msg_{}", proto_name(node, source)?),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["message_body"]),
|
||||
),
|
||||
"enum" => make_container_chunk(
|
||||
node,
|
||||
format!("enum_{}", proto_name(node, source)?),
|
||||
ChunkKind::Enum,
|
||||
proto_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["enum_body"]),
|
||||
),
|
||||
"service" => make_container_chunk(
|
||||
"service" => make_named_proto_chunk(
|
||||
node,
|
||||
ChunkKind::Interface,
|
||||
format!("service_{}", proto_name(node, source)?),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
@@ -50,36 +53,40 @@ fn classify_proto_root<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandi
|
||||
|
||||
fn classify_proto_class<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"option" => group_candidate(node, "options", source),
|
||||
"option" => group_candidate(node, ChunkKind::Options, source),
|
||||
"field" if is_proto_message_field(node) => {
|
||||
make_named_chunk(node, format!("field_{}", proto_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Field, proto_name(node, source), source, None)
|
||||
},
|
||||
"oneof" => make_container_chunk(
|
||||
"oneof" => make_named_proto_chunk(
|
||||
node,
|
||||
ChunkKind::Either,
|
||||
format!("oneof_{}", proto_name(node, source)?),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
),
|
||||
"oneof_field" => {
|
||||
make_named_chunk(node, format!("field_{}", proto_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Field, proto_name(node, source), source, None)
|
||||
},
|
||||
"message" => make_container_chunk(
|
||||
"message" => make_named_proto_chunk(
|
||||
node,
|
||||
ChunkKind::Type,
|
||||
format!("msg_{}", proto_name(node, source)?),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["message_body"]),
|
||||
),
|
||||
"enum" => make_container_chunk(
|
||||
node,
|
||||
format!("enum_{}", proto_name(node, source)?),
|
||||
ChunkKind::Enum,
|
||||
proto_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["enum_body"]),
|
||||
),
|
||||
"enum_field" => {
|
||||
make_named_chunk(node, format!("variant_{}", proto_name(node, source)?), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Variant, proto_name(node, source), source, None)
|
||||
},
|
||||
"rpc" => make_container_chunk(
|
||||
"rpc" => make_named_proto_chunk(
|
||||
node,
|
||||
ChunkKind::Proc,
|
||||
format!("rpc_{}", proto_name(node, source)?),
|
||||
source,
|
||||
proto_rpc_recurse(node),
|
||||
@@ -88,6 +95,24 @@ fn classify_proto_class<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCand
|
||||
})
|
||||
}
|
||||
|
||||
fn make_named_proto_chunk<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
fn is_proto_message_field(node: Node<'_>) -> bool {
|
||||
node
|
||||
.parent()
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct PythonClassifier;
|
||||
|
||||
@@ -11,16 +11,17 @@ impl LangClassifier for PythonClassifier {
|
||||
match node.kind() {
|
||||
// ── Imports ──
|
||||
"import_statement" | "import_from_statement" => {
|
||||
Some(group_candidate(node, "imports", source))
|
||||
Some(group_candidate(node, ChunkKind::Imports, source))
|
||||
},
|
||||
|
||||
// ── Variables / assignments ──
|
||||
"assignment" => Some(group_candidate(node, "decls", source)),
|
||||
"assignment" => Some(group_candidate(node, ChunkKind::Declarations, source)),
|
||||
|
||||
// ── Functions ──
|
||||
"function_definition" => Some(make_named_chunk(
|
||||
"function_definition" => Some(make_kind_chunk(
|
||||
node,
|
||||
prefixed_name("fn", node, source),
|
||||
ChunkKind::Function,
|
||||
extract_identifier(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &["body"], &["block"]),
|
||||
)),
|
||||
@@ -28,7 +29,8 @@ impl LangClassifier for PythonClassifier {
|
||||
// ── Containers ──
|
||||
"class_definition" => Some(make_container_chunk(
|
||||
node,
|
||||
prefixed_name("class", node, source),
|
||||
ChunkKind::Class,
|
||||
extract_identifier(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &["body"], &["block"]),
|
||||
)),
|
||||
@@ -39,7 +41,7 @@ impl LangClassifier for PythonClassifier {
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" | "global_statement" => {
|
||||
Some(group_candidate(node, "stmts", source))
|
||||
Some(group_candidate(node, ChunkKind::Statements, source))
|
||||
},
|
||||
|
||||
// ── Decorated ──
|
||||
@@ -54,14 +56,20 @@ impl LangClassifier for PythonClassifier {
|
||||
// ── Methods ──
|
||||
"function_definition" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
let chunk_name = if name == "__init__" || name == "__new__" {
|
||||
"constructor".to_string()
|
||||
let kind = if name == "__init__" || name == "__new__" {
|
||||
ChunkKind::Constructor
|
||||
} else {
|
||||
format!("fn_{name}")
|
||||
ChunkKind::Function
|
||||
};
|
||||
Some(make_named_chunk(
|
||||
let identifier = if kind == ChunkKind::Constructor {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
};
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
chunk_name,
|
||||
kind,
|
||||
identifier,
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &["body"], &["block"]),
|
||||
))
|
||||
@@ -75,14 +83,20 @@ impl LangClassifier for PythonClassifier {
|
||||
if let Some(child) = inner {
|
||||
let name =
|
||||
extract_identifier(child, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
let chunk_name = if name == "__init__" || name == "__new__" {
|
||||
"constructor".to_string()
|
||||
let kind = if name == "__init__" || name == "__new__" {
|
||||
ChunkKind::Constructor
|
||||
} else {
|
||||
format!("fn_{name}")
|
||||
ChunkKind::Function
|
||||
};
|
||||
Some(make_named_chunk(
|
||||
let identifier = if kind == ChunkKind::Constructor {
|
||||
None
|
||||
} else {
|
||||
Some(name)
|
||||
};
|
||||
Some(make_kind_chunk(
|
||||
node,
|
||||
chunk_name,
|
||||
kind,
|
||||
identifier,
|
||||
source,
|
||||
recurse_into(child, ChunkContext::FunctionBody, &["body"], &["block"]),
|
||||
))
|
||||
@@ -92,10 +106,12 @@ impl LangClassifier for PythonClassifier {
|
||||
},
|
||||
|
||||
// ── Fields ──
|
||||
"expression_statement" | "assignment" => Some(group_candidate(node, "fields", source)),
|
||||
"expression_statement" | "assignment" => {
|
||||
Some(group_candidate(node, ChunkKind::Fields, source))
|
||||
},
|
||||
|
||||
// ── Type aliases ──
|
||||
"type_alias_statement" => Some(named_candidate(node, "type", source, None)),
|
||||
"type_alias_statement" => Some(named_candidate(node, ChunkKind::Type, source, None)),
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -106,48 +122,50 @@ impl LangClassifier for PythonClassifier {
|
||||
// ── Control flow ──
|
||||
"if_statement" => Some(make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
ChunkKind::If,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
"for_statement" | "while_statement" => Some(make_candidate(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
ChunkKind::Loop,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
"try_statement" => Some(make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
ChunkKind::Try,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
"with_statement" => Some(make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
ChunkKind::Block,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
false,
|
||||
source,
|
||||
)),
|
||||
|
||||
// ── Positional ──
|
||||
"elif_clause" => Some(positional_candidate(node, "elif", source)),
|
||||
"except_clause" => Some(positional_candidate(node, "except", source)),
|
||||
"match_statement" => Some(positional_candidate(node, "match", source)),
|
||||
"elif_clause" => Some(positional_candidate(node, ChunkKind::Elif, source)),
|
||||
"except_clause" => Some(positional_candidate(node, ChunkKind::Except, source)),
|
||||
"match_statement" => Some(positional_candidate(node, ChunkKind::Match, source)),
|
||||
|
||||
// ── Variables ──
|
||||
"expression_statement" | "assignment" => Some(group_candidate(node, "stmts", source)),
|
||||
"expression_statement" | "assignment" => {
|
||||
Some(group_candidate(node, ChunkKind::Statements, source))
|
||||
},
|
||||
|
||||
_ => None,
|
||||
}
|
||||
@@ -160,36 +178,18 @@ fn classify_function_python<'t>(node: Node<'t>, source: &str) -> RawChunkCandida
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
match node.kind() {
|
||||
"if_statement" => {
|
||||
make_candidate(node, "if".to_string(), NameStyle::Named, None, fn_recurse(), false, source)
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"for_statement" | "while_statement" => make_candidate(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"try_statement" => make_candidate(
|
||||
node,
|
||||
"try".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"with_statement" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
_ => group_candidate(node, "stmts", source),
|
||||
"for_statement" | "while_statement" => {
|
||||
make_candidate(node, ChunkKind::Loop, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"try_statement" => {
|
||||
make_candidate(node, ChunkKind::Try, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"with_statement" => {
|
||||
make_candidate(node, ChunkKind::Block, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
_ => group_candidate(node, ChunkKind::Statements, source),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -200,16 +200,18 @@ fn classify_decorated<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t>
|
||||
match inner {
|
||||
Some(child) if child.kind() == "class_definition" => make_container_chunk(
|
||||
node,
|
||||
prefixed_name("class", child, source),
|
||||
ChunkKind::Class,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse_into(child, ChunkContext::ClassBody, &["body"], &["block"]),
|
||||
),
|
||||
Some(child) if child.kind() == "function_definition" => make_named_chunk(
|
||||
Some(child) if child.kind() == "function_definition" => make_kind_chunk(
|
||||
node,
|
||||
prefixed_name("fn", child, source),
|
||||
ChunkKind::Function,
|
||||
extract_identifier(child, source),
|
||||
source,
|
||||
recurse_into(child, ChunkContext::FunctionBody, &["body"], &["block"]),
|
||||
),
|
||||
_ => positional_candidate(node, "block", source),
|
||||
_ => positional_candidate(node, ChunkKind::Block, source),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct RClassifier;
|
||||
|
||||
@@ -10,21 +10,23 @@ impl LangClassifier for RClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
// ── Imports ──
|
||||
"call" if is_import_call(node, source) => group_candidate(node, "imports", source),
|
||||
"call" => group_candidate(node, "stmts", source),
|
||||
"call" if is_import_call(node, source) => {
|
||||
group_candidate(node, ChunkKind::Imports, source)
|
||||
},
|
||||
"call" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
// ── Function / value assignments ──
|
||||
"binary_operator" => classify_assignment(node, source, ChunkScope::Root)?,
|
||||
|
||||
// ── Control flow at script scope ──
|
||||
"if_statement" => control_candidate(node, "if", source, recurse_if(node)),
|
||||
"if_statement" => control_candidate(node, ChunkKind::If, source, recurse_if(node)),
|
||||
"for_statement" | "while_statement" | "repeat_statement" => {
|
||||
control_candidate(node, "loop", source, recurse_loop(node))
|
||||
control_candidate(node, ChunkKind::Loop, source, recurse_loop(node))
|
||||
},
|
||||
|
||||
// ── Bare expressions ──
|
||||
"identifier" | "subset" | "subset2" | "extract_operator" => {
|
||||
group_candidate(node, "stmts", source)
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
},
|
||||
|
||||
_ => return None,
|
||||
@@ -37,14 +39,14 @@ impl LangClassifier for RClassifier {
|
||||
"binary_operator" => classify_assignment(node, source, ChunkScope::Function)?,
|
||||
|
||||
// ── Control flow ──
|
||||
"if_statement" => control_candidate(node, "if", source, recurse_if(node)),
|
||||
"if_statement" => control_candidate(node, ChunkKind::If, source, recurse_if(node)),
|
||||
"for_statement" | "while_statement" | "repeat_statement" => {
|
||||
control_candidate(node, "loop", source, recurse_loop(node))
|
||||
control_candidate(node, ChunkKind::Loop, source, recurse_loop(node))
|
||||
},
|
||||
|
||||
// ── Calls / bare expressions ──
|
||||
"call" | "identifier" | "subset" | "subset2" | "extract_operator" | "break" | "next"
|
||||
| "return" => group_candidate(node, "stmts", source),
|
||||
| "return" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -66,10 +68,11 @@ fn classify_assignment<'t>(
|
||||
|
||||
if rhs.kind() == "function_definition" {
|
||||
let name = simple_lhs_name(lhs, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
return Some(make_named_chunk_from(
|
||||
return Some(make_kind_chunk_from(
|
||||
node,
|
||||
rhs,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(rhs, ChunkContext::FunctionBody),
|
||||
));
|
||||
@@ -77,16 +80,16 @@ fn classify_assignment<'t>(
|
||||
|
||||
match (scope, simple_lhs_name(lhs, source)) {
|
||||
(ChunkScope::Root, Some(name)) => {
|
||||
Some(make_named_chunk(node, format!("var_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None))
|
||||
},
|
||||
(ChunkScope::Function, Some(name)) if spans_multiple_lines(node) => {
|
||||
Some(make_named_chunk(node, format!("var_{name}"), source, None))
|
||||
Some(make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None))
|
||||
},
|
||||
_ => Some(group_candidate(
|
||||
node,
|
||||
match scope {
|
||||
ChunkScope::Root => "decls",
|
||||
ChunkScope::Function => "stmts",
|
||||
ChunkScope::Root => ChunkKind::Declarations,
|
||||
ChunkScope::Function => ChunkKind::Statements,
|
||||
},
|
||||
source,
|
||||
)),
|
||||
@@ -132,11 +135,11 @@ fn recurse_loop(node: Node<'_>) -> Option<RecurseSpec<'_>> {
|
||||
|
||||
fn control_candidate<'t>(
|
||||
node: Node<'t>,
|
||||
name: &str,
|
||||
kind: ChunkKind,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(node, name.to_string(), NameStyle::Named, None, recurse, true, source)
|
||||
make_candidate(node, kind, None, NameStyle::Named, None, recurse, source)
|
||||
}
|
||||
|
||||
fn spans_multiple_lines(node: Node<'_>) -> bool {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct RubyLuaClassifier;
|
||||
|
||||
@@ -20,38 +20,46 @@ impl LangClassifier for RubyLuaClassifier {
|
||||
let target = extract_identifier(node, source);
|
||||
match target.as_deref() {
|
||||
Some("require" | "require_relative" | "load" | "autoload") => {
|
||||
group_candidate(node, "imports", source)
|
||||
group_candidate(node, ChunkKind::Imports, source)
|
||||
},
|
||||
_ => group_candidate(node, "stmts", source),
|
||||
_ => group_candidate(node, ChunkKind::Statements, source),
|
||||
}
|
||||
},
|
||||
|
||||
// ── Functions ──
|
||||
"function_definition" => {
|
||||
named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
"method" | "singleton_method" => {
|
||||
named_candidate(node, "fn", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
"function_definition" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
"method" | "singleton_method" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
|
||||
// ── Containers ──
|
||||
"class" => container_candidate(node, "class", source, recurse_class(node)),
|
||||
"module" => container_candidate(node, "mod", source, recurse_class(node)),
|
||||
"class" => container_candidate(node, ChunkKind::Class, source, recurse_class(node)),
|
||||
"module" => container_candidate(node, ChunkKind::Module, source, recurse_class(node)),
|
||||
|
||||
// ── Control flow (top-level scripts) ──
|
||||
"if_statement" | "unless" | "while_statement" | "for_statement" => {
|
||||
return Some(
|
||||
self
|
||||
.classify_function(node, source)
|
||||
.unwrap_or_else(|| group_candidate(node, "stmts", source)),
|
||||
.unwrap_or_else(|| group_candidate(node, ChunkKind::Statements, source)),
|
||||
);
|
||||
},
|
||||
|
||||
// ── Assignments ──
|
||||
"assignment" => group_candidate(node, "decls", source),
|
||||
"assignment" => group_candidate(node, ChunkKind::Declarations, source),
|
||||
|
||||
// ── Statements ──
|
||||
"expression_statement" | "function_call" => group_candidate(node, "stmts", source),
|
||||
"expression_statement" | "function_call" => {
|
||||
group_candidate(node, ChunkKind::Statements, source)
|
||||
},
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -63,16 +71,18 @@ impl LangClassifier for RubyLuaClassifier {
|
||||
"method" | "singleton_method" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
if name == "initialize" {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
"constructor".to_string(),
|
||||
ChunkKind::Constructor,
|
||||
None,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
} else {
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
@@ -80,14 +90,14 @@ impl LangClassifier for RubyLuaClassifier {
|
||||
},
|
||||
|
||||
// ── Nested containers ──
|
||||
"class" => container_candidate(node, "class", source, recurse_class(node)),
|
||||
"module" => container_candidate(node, "mod", source, recurse_class(node)),
|
||||
"class" => container_candidate(node, ChunkKind::Class, source, recurse_class(node)),
|
||||
"module" => container_candidate(node, ChunkKind::Module, source, recurse_class(node)),
|
||||
|
||||
// ── Fields / constants ──
|
||||
"assignment" => group_candidate(node, "fields", source),
|
||||
"assignment" => group_candidate(node, ChunkKind::Fields, source),
|
||||
|
||||
// ── Calls (include, attr_reader, etc.) and bare identifiers (private) ──
|
||||
"call" | "command" | "identifier" => group_candidate(node, "stmts", source),
|
||||
"call" | "command" | "identifier" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -97,36 +107,30 @@ impl LangClassifier for RubyLuaClassifier {
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
Some(match node.kind() {
|
||||
// ── Control flow ──
|
||||
"if_statement" | "unless" => make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"if_statement" | "unless" => {
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"case_statement" | "case_match" => make_candidate(
|
||||
node,
|
||||
"switch".to_string(),
|
||||
ChunkKind::Switch,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"while_statement" | "for_statement" => make_candidate(
|
||||
node,
|
||||
"loop".to_string(),
|
||||
ChunkKind::Loop,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
|
||||
// ── Variables ──
|
||||
"assignment" => group_candidate(node, "stmts", source),
|
||||
"assignment" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct RustClassifier;
|
||||
|
||||
@@ -10,56 +10,62 @@ impl LangClassifier for RustClassifier {
|
||||
fn classify_root<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
// ── Imports ──
|
||||
"use_declaration" | "extern_crate_declaration" => group_candidate(node, "imports", source),
|
||||
"use_declaration" | "extern_crate_declaration" => {
|
||||
group_candidate(node, ChunkKind::Imports, source)
|
||||
},
|
||||
|
||||
// ── Functions ──
|
||||
"function_item" | "function_definition" => named_candidate(
|
||||
node,
|
||||
"fn",
|
||||
ChunkKind::Function,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody)
|
||||
.or_else(|| recurse_into(node, ChunkContext::FunctionBody, &["body"], &["block"])),
|
||||
),
|
||||
|
||||
// ── Containers ──
|
||||
"struct_item" => container_candidate(node, "struct", source, recurse_class(node)),
|
||||
"enum_item" => container_candidate(node, "enum", source, recurse_enum(node)),
|
||||
"trait_item" => container_candidate(node, "trait", source, recurse_class(node)),
|
||||
"struct_item" => container_candidate(node, ChunkKind::Struct, source, recurse_class(node)),
|
||||
"enum_item" => container_candidate(node, ChunkKind::Enum, source, recurse_enum(node)),
|
||||
"trait_item" => container_candidate(node, ChunkKind::Trait, source, recurse_class(node)),
|
||||
"mod_item" | "foreign_block" => {
|
||||
container_candidate(node, "mod", source, recurse_class(node))
|
||||
container_candidate(node, ChunkKind::Module, source, recurse_class(node))
|
||||
},
|
||||
"impl_item" => {
|
||||
let name = extract_impl_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_container_chunk(
|
||||
node,
|
||||
format!("impl_{name}"),
|
||||
ChunkKind::Impl,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &["body"], &["declaration_list"]),
|
||||
)
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_item" => named_candidate(node, "type", source, recurse_class(node)),
|
||||
"type_item" => named_candidate(node, ChunkKind::Type, source, recurse_class(node)),
|
||||
|
||||
// ── Macros ──
|
||||
"macro_definition" | "macro_rule" => {
|
||||
named_candidate(node, "macro", source, recurse_body(node, ChunkContext::FunctionBody))
|
||||
},
|
||||
"macro_definition" | "macro_rule" => named_candidate(
|
||||
node,
|
||||
ChunkKind::Macro,
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
),
|
||||
|
||||
// ── Statics / consts ──
|
||||
"static_item" | "const_item" => group_candidate(node, "decls", source),
|
||||
"static_item" | "const_item" => group_candidate(node, ChunkKind::Declarations, source),
|
||||
|
||||
// ── Attributes ──
|
||||
"inner_attribute_item" => group_candidate(node, "attrs", source),
|
||||
"inner_attribute_item" => group_candidate(node, ChunkKind::Attrs, source),
|
||||
|
||||
// ── Variables ──
|
||||
"let_declaration" => match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("var_{name}"), source, None),
|
||||
None => group_candidate(node, "decls", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Declarations, source),
|
||||
},
|
||||
|
||||
// ── Expression statements ──
|
||||
"expression_statement" => group_candidate(node, "stmts", source),
|
||||
"expression_statement" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -70,34 +76,35 @@ impl LangClassifier for RustClassifier {
|
||||
// ── Methods ──
|
||||
"function_item" | "function_definition" => {
|
||||
let name = extract_identifier(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_named_chunk(
|
||||
make_kind_chunk(
|
||||
node,
|
||||
format!("fn_{name}"),
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse_body(node, ChunkContext::FunctionBody),
|
||||
)
|
||||
},
|
||||
|
||||
// ── Types ──
|
||||
"type_item" | "type_alias" => named_candidate(node, "type", source, None),
|
||||
"type_item" | "type_alias" => named_candidate(node, ChunkKind::Type, source, None),
|
||||
|
||||
// ── Fields ──
|
||||
"field_declaration" => match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("field_{name}"), source, None),
|
||||
None => group_candidate(node, "fields", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Field, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Fields, source),
|
||||
},
|
||||
|
||||
// ── Enum variants ──
|
||||
"enum_variant" => match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("variant_{name}"), source, None),
|
||||
None => group_candidate(node, "variants", source),
|
||||
Some(name) => make_kind_chunk(node, ChunkKind::Variant, Some(name), source, None),
|
||||
None => group_candidate(node, ChunkKind::Variants, source),
|
||||
},
|
||||
|
||||
// ── Consts / macros in class body ──
|
||||
"const_item" | "macro_invocation" => group_candidate(node, "fields", source),
|
||||
"const_item" | "macro_invocation" => group_candidate(node, ChunkKind::Fields, source),
|
||||
|
||||
// ── Attributes (absorbed by the framework, but handle explicitly) ──
|
||||
"attribute_item" => return None, // absorbed by is_absorbable_attr
|
||||
"attribute_item" => return None,
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
@@ -107,28 +114,22 @@ impl LangClassifier for RustClassifier {
|
||||
let fn_recurse = || recurse_body(node, ChunkContext::FunctionBody);
|
||||
Some(match node.kind() {
|
||||
// ── Control flow ──
|
||||
"if_expression" => make_candidate(
|
||||
node,
|
||||
"if".to_string(),
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
"match_expression" => positional_candidate(node, "match", source),
|
||||
"if_expression" => {
|
||||
make_candidate(node, ChunkKind::If, None, NameStyle::Named, None, fn_recurse(), source)
|
||||
},
|
||||
"match_expression" => positional_candidate(node, ChunkKind::Match, source),
|
||||
"loop_expression" | "while_expression" | "for_expression" => {
|
||||
positional_candidate(node, "loop", source)
|
||||
positional_candidate(node, ChunkKind::Loop, source)
|
||||
},
|
||||
|
||||
// ── Blocks ──
|
||||
"unsafe_block" | "async_block" | "const_block" | "block_expression" => make_candidate(
|
||||
node,
|
||||
"block".to_string(),
|
||||
ChunkKind::Block,
|
||||
None,
|
||||
NameStyle::Named,
|
||||
None,
|
||||
fn_recurse(),
|
||||
false,
|
||||
source,
|
||||
),
|
||||
|
||||
@@ -137,16 +138,18 @@ impl LangClassifier for RustClassifier {
|
||||
let span = line_span(node.start_position().row + 1, node.end_position().row + 1);
|
||||
if span > 1 {
|
||||
match extract_identifier(node, source) {
|
||||
Some(name) => make_named_chunk(node, format!("var_{name}"), source, None),
|
||||
None => group_candidate(node, "let", source),
|
||||
Some(name) => {
|
||||
make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None)
|
||||
},
|
||||
None => group_candidate(node, ChunkKind::Let, source),
|
||||
}
|
||||
} else {
|
||||
group_candidate(node, "let", source)
|
||||
group_candidate(node, ChunkKind::Let, source)
|
||||
}
|
||||
},
|
||||
|
||||
// ── Expression statements ──
|
||||
"expression_statement" => group_candidate(node, "stmts", source),
|
||||
"expression_statement" => group_candidate(node, ChunkKind::Statements, source),
|
||||
|
||||
_ => return None,
|
||||
})
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct SqlClassifier;
|
||||
|
||||
@@ -40,9 +40,10 @@ fn classify_sql_statement_root<'t>(node: Node<'t>, source: &str) -> Option<RawCh
|
||||
}
|
||||
|
||||
if children.iter().any(|child| is_sql_query_kind(child.kind())) {
|
||||
return Some(make_container_chunk(
|
||||
return Some(make_named_sql_chunk(
|
||||
node,
|
||||
"query".to_string(),
|
||||
ChunkKind::Query,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
));
|
||||
@@ -57,29 +58,26 @@ fn classify_sql_root_node<'t>(
|
||||
source: &str,
|
||||
) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"create_schema" => make_named_chunk_from(
|
||||
"create_schema" => make_kind_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
format!(
|
||||
"schema_{}",
|
||||
extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Schema,
|
||||
extract_sql_identifier(node, source),
|
||||
source,
|
||||
None,
|
||||
),
|
||||
"create_table" => make_container_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
format!(
|
||||
"table_{}",
|
||||
extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Table,
|
||||
extract_sql_object_name(node, source),
|
||||
source,
|
||||
recurse_into(node, ChunkContext::ClassBody, &[], &["column_definitions"]),
|
||||
),
|
||||
"create_view" => make_container_chunk_from(
|
||||
"create_view" => make_named_sql_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
ChunkKind::Query,
|
||||
format!(
|
||||
"view_{}",
|
||||
extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -87,9 +85,10 @@ fn classify_sql_root_node<'t>(
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["create_query"]),
|
||||
),
|
||||
"create_materialized_view" => make_container_chunk_from(
|
||||
"create_materialized_view" => make_named_sql_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
ChunkKind::Query,
|
||||
format!(
|
||||
"matview_{}",
|
||||
extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -100,16 +99,15 @@ fn classify_sql_root_node<'t>(
|
||||
"create_function" => make_container_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
format!(
|
||||
"fn_{}",
|
||||
extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
ChunkKind::Function,
|
||||
extract_sql_object_name(node, source),
|
||||
source,
|
||||
recurse_sql_function_query(node),
|
||||
),
|
||||
"create_trigger" => make_named_chunk_from(
|
||||
"create_trigger" => make_named_sql_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
ChunkKind::Function,
|
||||
format!(
|
||||
"trigger_{}",
|
||||
extract_sql_object_name(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -117,9 +115,10 @@ fn classify_sql_root_node<'t>(
|
||||
source,
|
||||
None,
|
||||
),
|
||||
"create_index" => make_named_chunk_from(
|
||||
"create_index" => make_named_sql_chunk_from(
|
||||
range_node,
|
||||
node,
|
||||
ChunkKind::Key,
|
||||
format!(
|
||||
"index_{}",
|
||||
extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -133,24 +132,19 @@ fn classify_sql_root_node<'t>(
|
||||
|
||||
fn classify_sql_class<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"column_definition" => make_named_chunk(
|
||||
node,
|
||||
format!(
|
||||
"field_{}",
|
||||
extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
),
|
||||
source,
|
||||
None,
|
||||
),
|
||||
"column_definition" => {
|
||||
make_kind_chunk(node, ChunkKind::Field, extract_sql_identifier(node, source), source, None)
|
||||
},
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn classify_sql_function<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
if node.kind() == "statement" {
|
||||
return Some(make_container_chunk(
|
||||
return Some(make_named_sql_chunk(
|
||||
node,
|
||||
"query".to_string(),
|
||||
ChunkKind::Query,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
));
|
||||
@@ -161,10 +155,11 @@ fn classify_sql_function<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCan
|
||||
|
||||
fn classify_sql_query_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
Some(match node.kind() {
|
||||
"insert" => group_candidate(node, "stmts", source),
|
||||
"keyword_with" => group_candidate(node, "with", source),
|
||||
"cte" => make_container_chunk(
|
||||
"insert" => group_candidate(node, ChunkKind::Statements, source),
|
||||
"keyword_with" => group_candidate(node, ChunkKind::With, source),
|
||||
"cte" => make_named_sql_chunk(
|
||||
node,
|
||||
ChunkKind::With,
|
||||
format!(
|
||||
"cte_{}",
|
||||
extract_sql_identifier(node, source).unwrap_or_else(|| "anonymous".to_string())
|
||||
@@ -172,22 +167,60 @@ fn classify_sql_query_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkC
|
||||
source,
|
||||
recurse_into(node, ChunkContext::FunctionBody, &[], &["statement"]),
|
||||
),
|
||||
"select" => positional_candidate(node, "select", source),
|
||||
"from" => make_container_chunk(
|
||||
"select" => positional_candidate(node, ChunkKind::Select, source),
|
||||
"from" => make_named_sql_chunk(
|
||||
node,
|
||||
ChunkKind::Query,
|
||||
"from".to_string(),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::FunctionBody)),
|
||||
),
|
||||
"relation" => group_candidate(node, "relations", source),
|
||||
"join" => positional_candidate(node, "join", source),
|
||||
"where" => positional_candidate(node, "where", source),
|
||||
"group_by" => positional_candidate(node, "group_by", source),
|
||||
"order_by" => positional_candidate(node, "order_by", source),
|
||||
"relation" => group_candidate(node, ChunkKind::Relations, source),
|
||||
"join" => positional_candidate(node, ChunkKind::Join, source),
|
||||
"where" => positional_candidate(node, ChunkKind::Where, source),
|
||||
"group_by" => positional_candidate(node, ChunkKind::GroupBy, source),
|
||||
"order_by" => positional_candidate(node, ChunkKind::OrderBy, source),
|
||||
_ => return None,
|
||||
})
|
||||
}
|
||||
|
||||
fn make_named_sql_chunk<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_named_sql_chunk_from<'t>(
|
||||
range_node: Node<'t>,
|
||||
signature_node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'t>>,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
range_node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(signature_node, source),
|
||||
recurse,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
fn recurse_sql_function_query(node: Node<'_>) -> Option<RecurseSpec<'_>> {
|
||||
let body = child_by_kind(node, &["function_body"])?;
|
||||
recurse_into(body, ChunkContext::FunctionBody, &[], &["statement"])
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct SvelteClassifier;
|
||||
|
||||
@@ -35,15 +35,15 @@ fn classify_svelte_node<'t>(
|
||||
"snippet_statement" => Some(classify_snippet_statement(node, source)),
|
||||
"if_statement" => Some(classify_if_statement(node, source)),
|
||||
"else_if_statement" => Some(classify_else_if_statement(node, source)),
|
||||
"else_statement" => Some(make_block_chunk(node, "else", source)),
|
||||
"else_statement" => Some(classify_else_statement(node, source)),
|
||||
"each_statement" => Some(classify_each_statement(node, source)),
|
||||
"await_statement" => Some(classify_await_statement(node, source)),
|
||||
"then_statement" => Some(classify_then_statement(node, source)),
|
||||
"catch_statement" => Some(classify_catch_statement(node, source)),
|
||||
"render_expr" => Some(classify_render_expr(node, source)),
|
||||
"html_interpolation" => Some(group_candidate(node, "html", source)),
|
||||
"interpolation" => Some(group_candidate(node, "interpolation", source)),
|
||||
"expression" => Some(group_candidate(node, "expr", source)),
|
||||
"html_interpolation" => Some(group_candidate(node, ChunkKind::Html, source)),
|
||||
"interpolation" => Some(group_candidate(node, ChunkKind::Interpolation, source)),
|
||||
"expression" => Some(group_candidate(node, ChunkKind::Expression, source)),
|
||||
"element" if include_plain_elements || element_has_structure(node) => {
|
||||
classify_element(node, source)
|
||||
},
|
||||
@@ -52,103 +52,145 @@ fn classify_svelte_node<'t>(
|
||||
}
|
||||
|
||||
fn classify_script_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = if has_attribute(node, "module", source)
|
||||
let kind = if has_attribute(node, "module", source)
|
||||
|| attribute_value(node, "context", source).as_deref() == Some("module")
|
||||
{
|
||||
"script_module".to_string()
|
||||
ChunkKind::ScriptModule
|
||||
} else {
|
||||
"script".to_string()
|
||||
ChunkKind::Script
|
||||
};
|
||||
|
||||
// The grammar exposes script contents as a single `raw_text` child, so the
|
||||
// element boundary is the most truthful chunk.
|
||||
make_named_chunk(node, name, source, None)
|
||||
positional_candidate(node, kind, source)
|
||||
}
|
||||
|
||||
fn classify_style_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = if has_attribute(node, "scoped", source) {
|
||||
"style_scoped".to_string()
|
||||
let kind = if has_attribute(node, "scoped", source) {
|
||||
ChunkKind::StyleScoped
|
||||
} else {
|
||||
"style".to_string()
|
||||
ChunkKind::Style
|
||||
};
|
||||
make_named_chunk(node, name, source, None)
|
||||
positional_candidate(node, kind, source)
|
||||
}
|
||||
|
||||
fn classify_snippet_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = child_by_kind(node, &["snippet_start_expr"])
|
||||
let identifier = child_by_kind(node, &["snippet_start_expr"])
|
||||
.and_then(|start| child_by_kind(start, &["snippet_name"]))
|
||||
.and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte())))
|
||||
.map_or_else(|| "snippet".to_string(), |name| format!("snippet_{name}"));
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = block_expr_name(node, source, "if_start_expr", &["raw_text_expr"], "if");
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_else_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = block_expr_name(node, source, "else_if_expr", &["raw_text_expr"], "else_if");
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_each_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = block_expr_name(node, source, "each_start_expr", &["raw_text_each"], "each");
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_await_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = block_expr_name(node, source, "await_start_expr", &["raw_text_expr"], "await");
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_then_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = block_expr_name(node, source, "then_expr", &["raw_text_expr"], "then");
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_catch_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = block_expr_name(node, source, "catch_expr", &["raw_text_expr"], "catch");
|
||||
make_block_chunk(node, &name, source)
|
||||
}
|
||||
|
||||
fn classify_render_expr<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = child_by_kind(node, &["snippet_name"])
|
||||
.and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte())))
|
||||
.map_or_else(|| "render".to_string(), |name| format!("render_{name}"));
|
||||
make_named_chunk(node, name, source, None)
|
||||
}
|
||||
|
||||
fn classify_element<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
let tag_name = extract_markup_tag_name(node, source)?;
|
||||
Some(make_container_chunk(
|
||||
.and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte())));
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
format!("tag_{tag_name}"),
|
||||
ChunkKind::Snippet,
|
||||
identifier,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
}
|
||||
|
||||
fn make_block_chunk<'t>(node: Node<'t>, name: &str, source: &str) -> RawChunkCandidate<'t> {
|
||||
make_container_chunk(
|
||||
fn classify_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let identifier = block_expr_identifier(node, source, "if_start_expr", &["raw_text_expr"]);
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
name.to_string(),
|
||||
ChunkKind::If,
|
||||
identifier,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
fn block_expr_name(
|
||||
fn classify_else_if_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let identifier = block_expr_identifier(node, source, "else_if_expr", &["raw_text_expr"])
|
||||
.map_or_else(|| "if".to_string(), |expr| format!("if_{expr}"));
|
||||
make_named_container_chunk(node, ChunkKind::Else, identifier, source)
|
||||
}
|
||||
|
||||
fn classify_else_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
ChunkKind::Else,
|
||||
None,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
}
|
||||
|
||||
fn classify_each_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let expr = block_expr_identifier(node, source, "each_start_expr", &["raw_text_each"]);
|
||||
let id = expr.map_or_else(|| "each".to_string(), |expr| format!("each_{expr}"));
|
||||
make_named_container_chunk(node, ChunkKind::Loop, id, source)
|
||||
}
|
||||
|
||||
fn classify_await_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let expr = block_expr_identifier(node, source, "await_start_expr", &["raw_text_expr"]);
|
||||
let id = expr.map_or_else(|| "await".to_string(), |expr| format!("await_{expr}"));
|
||||
make_named_container_chunk(node, ChunkKind::With, id, source)
|
||||
}
|
||||
|
||||
fn classify_then_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let expr = block_expr_identifier(node, source, "then_expr", &["raw_text_expr"]);
|
||||
let id = expr.map_or_else(|| "then".to_string(), |expr| format!("then_{expr}"));
|
||||
make_named_container_chunk(node, ChunkKind::After, id, source)
|
||||
}
|
||||
|
||||
fn classify_catch_statement<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let identifier = block_expr_identifier(node, source, "catch_expr", &["raw_text_expr"]);
|
||||
force_container(make_container_chunk(
|
||||
node,
|
||||
ChunkKind::Catch,
|
||||
identifier,
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
}
|
||||
|
||||
fn classify_render_expr<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let identifier = child_by_kind(node, &["snippet_name"])
|
||||
.and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte())));
|
||||
make_kind_chunk(node, ChunkKind::Render, identifier, source, None)
|
||||
}
|
||||
|
||||
fn classify_element<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
let tag_name = extract_markup_tag_name(node, source)?;
|
||||
Some(force_container(make_container_chunk(
|
||||
node,
|
||||
ChunkKind::Tag,
|
||||
Some(tag_name),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)))
|
||||
}
|
||||
|
||||
fn make_named_container_chunk<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
force_container(make_candidate(
|
||||
node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
source,
|
||||
))
|
||||
}
|
||||
|
||||
const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> {
|
||||
candidate.force_recurse = true;
|
||||
candidate
|
||||
}
|
||||
|
||||
fn block_expr_identifier(
|
||||
node: Node<'_>,
|
||||
source: &str,
|
||||
header_kind: &str,
|
||||
expr_kinds: &[&str],
|
||||
prefix: &str,
|
||||
) -> String {
|
||||
) -> Option<String> {
|
||||
child_by_kind(node, &[header_kind])
|
||||
.and_then(|header| child_by_kind(header, expr_kinds))
|
||||
.and_then(|expr| sanitize_identifier(node_text(source, expr.start_byte(), expr.end_byte())))
|
||||
.map_or_else(|| prefix.to_string(), |expr| format!("{prefix}_{expr}"))
|
||||
}
|
||||
|
||||
fn element_has_structure(node: Node<'_>) -> bool {
|
||||
|
||||
@@ -11,9 +11,10 @@ use super::{
|
||||
classify::LangClassifier,
|
||||
common::{
|
||||
ChunkContext, RawChunkCandidate, RecurseSpec, child_by_kind, extract_identifier,
|
||||
group_candidate, make_container_chunk, make_container_chunk_from, make_named_chunk,
|
||||
group_candidate, make_container_chunk, make_container_chunk_from, make_kind_chunk,
|
||||
positional_candidate, recurse_self, sanitize_identifier,
|
||||
},
|
||||
kind::ChunkKind,
|
||||
types::ChunkNode,
|
||||
};
|
||||
|
||||
@@ -24,25 +25,32 @@ impl LangClassifier for TlaplusClassifier {
|
||||
match node.kind() {
|
||||
"module" => Some(make_container_chunk(
|
||||
node,
|
||||
prefixed(node, "mod", source),
|
||||
ChunkKind::Module,
|
||||
tla_identifier(node, source),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::Root)),
|
||||
)),
|
||||
"variable_declaration" | "constant_declaration" | "recursive_declaration" => {
|
||||
Some(group_candidate(node, "decls", source))
|
||||
},
|
||||
"operator_definition" => {
|
||||
Some(make_named_chunk(node, prefixed(node, "operator", source), source, None))
|
||||
Some(group_candidate(node, ChunkKind::Declarations, source))
|
||||
},
|
||||
"operator_definition" => Some(make_kind_chunk(
|
||||
node,
|
||||
ChunkKind::Operator,
|
||||
tla_identifier(node, source),
|
||||
source,
|
||||
None,
|
||||
)),
|
||||
"module_definition" => Some(make_container_chunk(
|
||||
node,
|
||||
prefixed(node, "mod", source),
|
||||
ChunkKind::Module,
|
||||
tla_identifier(node, source),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::Root)),
|
||||
)),
|
||||
"pcal_algorithm" => Some(make_container_chunk(
|
||||
node,
|
||||
prefixed(node, "algo", source),
|
||||
ChunkKind::Algo,
|
||||
tla_identifier(node, source),
|
||||
source,
|
||||
recurse_child(node, "pcal_algorithm_body", ChunkContext::ClassBody),
|
||||
)),
|
||||
@@ -50,7 +58,8 @@ impl LangClassifier for TlaplusClassifier {
|
||||
make_container_chunk_from(
|
||||
node,
|
||||
algorithm,
|
||||
prefixed(algorithm, "algo", source),
|
||||
ChunkKind::Algo,
|
||||
tla_identifier(algorithm, source),
|
||||
source,
|
||||
recurse_child(algorithm, "pcal_algorithm_body", ChunkContext::ClassBody),
|
||||
)
|
||||
@@ -63,17 +72,19 @@ impl LangClassifier for TlaplusClassifier {
|
||||
match node.kind() {
|
||||
"pcal_procedure" => Some(make_container_chunk(
|
||||
node,
|
||||
prefixed(node, "proc", source),
|
||||
ChunkKind::Proc,
|
||||
tla_identifier(node, source),
|
||||
source,
|
||||
recurse_child(node, "pcal_algorithm_body", ChunkContext::ClassBody),
|
||||
)),
|
||||
"pcal_process" => Some(make_container_chunk(
|
||||
node,
|
||||
prefixed(node, "process", source),
|
||||
ChunkKind::Process,
|
||||
tla_identifier(node, source),
|
||||
source,
|
||||
recurse_child(node, "pcal_algorithm_body", ChunkContext::ClassBody),
|
||||
)),
|
||||
"pcal_var_decls" => Some(group_candidate(node, "decls", source)),
|
||||
"pcal_var_decls" => Some(group_candidate(node, ChunkKind::Declarations, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -81,12 +92,12 @@ impl LangClassifier for TlaplusClassifier {
|
||||
fn classify_function<'t>(&self, node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
match node.kind() {
|
||||
// PlusCal control flow
|
||||
"pcal_if" => Some(positional_candidate(node, "if", source)),
|
||||
"pcal_while" => Some(positional_candidate(node, "loop", source)),
|
||||
"pcal_either" => Some(positional_candidate(node, "either", source)),
|
||||
"pcal_with" => Some(positional_candidate(node, "with", source)),
|
||||
"pcal_if" => Some(positional_candidate(node, ChunkKind::If, source)),
|
||||
"pcal_while" => Some(positional_candidate(node, ChunkKind::Loop, source)),
|
||||
"pcal_either" => Some(positional_candidate(node, ChunkKind::Either, source)),
|
||||
"pcal_with" => Some(positional_candidate(node, ChunkKind::With, source)),
|
||||
// PlusCal assignments
|
||||
"pcal_assign" => Some(group_candidate(node, "stmts", source)),
|
||||
"pcal_assign" => Some(group_candidate(node, ChunkKind::Statements, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -108,11 +119,10 @@ impl LangClassifier for TlaplusClassifier {
|
||||
parent: &RawChunkCandidate<'_>,
|
||||
_children: &[RawChunkCandidate<'_>],
|
||||
) -> bool {
|
||||
let name = parent.base_name.as_str();
|
||||
name.starts_with("mod_")
|
||||
|| name.starts_with("algo_")
|
||||
|| name.starts_with("proc_")
|
||||
|| name.starts_with("process_")
|
||||
matches!(
|
||||
parent.kind,
|
||||
ChunkKind::Module | ChunkKind::Algo | ChunkKind::Proc | ChunkKind::Process
|
||||
)
|
||||
}
|
||||
|
||||
fn post_process(
|
||||
@@ -174,14 +184,11 @@ impl LangClassifier for TlaplusClassifier {
|
||||
}
|
||||
}
|
||||
|
||||
fn prefixed(node: Node<'_>, prefix: &str, source: &str) -> String {
|
||||
let name = extract_identifier(node, source)
|
||||
.or_else(|| {
|
||||
child_by_kind(node, &["identifier"])
|
||||
.and_then(|child| sanitize_identifier(child.utf8_text(source.as_bytes()).ok()?))
|
||||
})
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
format!("{prefix}_{name}")
|
||||
fn tla_identifier(node: Node<'_>, source: &str) -> Option<String> {
|
||||
extract_identifier(node, source).or_else(|| {
|
||||
child_by_kind(node, &["identifier"])
|
||||
.and_then(|child| sanitize_identifier(child.utf8_text(source.as_bytes()).ok()?))
|
||||
})
|
||||
}
|
||||
|
||||
fn recurse_child<'tree>(
|
||||
@@ -255,7 +262,8 @@ fn translation_chunk(
|
||||
let checksum = super::chunk_checksum(&source.as_bytes()[start_byte as usize..end_byte as usize]);
|
||||
ChunkNode {
|
||||
path,
|
||||
name: format!("translation_{}", range.start_line),
|
||||
identifier: Some(range.start_line.to_string()),
|
||||
kind: ChunkKind::Translation,
|
||||
leaf: true,
|
||||
parent_path,
|
||||
children: Vec::new(),
|
||||
@@ -299,10 +307,6 @@ fn byte_range_for_lines(source: &str, start_line: u32, end_line: u32) -> (u32, u
|
||||
}
|
||||
if byte == b'\n' {
|
||||
current_line += 1;
|
||||
if current_line > end_line {
|
||||
end_byte = byte_index + 1;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::{classify::LangClassifier, common::*};
|
||||
use super::{classify::LangClassifier, common::*, kind::ChunkKind};
|
||||
|
||||
pub struct VueClassifier;
|
||||
|
||||
@@ -43,59 +43,58 @@ fn classify_nested_node<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCand
|
||||
"start_tag" => classify_start_tag(node, source),
|
||||
"directive_attribute" => Some(classify_directive_attribute(node, source)),
|
||||
"attribute" => Some(classify_attribute(node, source)),
|
||||
"interpolation" => Some(make_named_chunk(node, "expr".to_string(), source, None)),
|
||||
"text" => Some(group_candidate(node, "text", source)),
|
||||
"raw_text" => Some(group_candidate(node, "text", source)),
|
||||
"interpolation" => Some(make_kind_chunk(node, ChunkKind::Expression, None, source, None)),
|
||||
"text" => Some(group_candidate(node, ChunkKind::Text, source)),
|
||||
"raw_text" => Some(group_candidate(node, ChunkKind::Text, source)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_template_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = extract_slot_name(node, source)
|
||||
.map_or_else(|| "template".to_string(), |slot_name| format!("slot_{slot_name}"));
|
||||
make_container_chunk(node, name, source, Some(recurse_self(node, ChunkContext::ClassBody)))
|
||||
let recurse = Some(recurse_self(node, ChunkContext::ClassBody));
|
||||
if let Some(slot_name) = extract_slot_name(node, source) {
|
||||
force_container(make_container_chunk(node, ChunkKind::Slot, Some(slot_name), source, recurse))
|
||||
} else {
|
||||
force_container(make_container_chunk(node, ChunkKind::Template, None, source, recurse))
|
||||
}
|
||||
}
|
||||
|
||||
fn classify_script_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = if has_attribute(node, "setup", source) {
|
||||
"script_setup".to_string()
|
||||
let kind = if has_attribute(node, "setup", source) {
|
||||
ChunkKind::ScriptSetup
|
||||
} else if attribute_value(node, "context", source).as_deref() == Some("module") {
|
||||
"script_module".to_string()
|
||||
ChunkKind::ScriptModule
|
||||
} else {
|
||||
"script".to_string()
|
||||
ChunkKind::Script
|
||||
};
|
||||
// tree-sitter-vue exposes script bodies as `raw_text`, not injected JS/TS.
|
||||
make_named_chunk(node, name, source, None)
|
||||
positional_candidate(node, kind, source)
|
||||
}
|
||||
|
||||
fn classify_style_element<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = if has_attribute(node, "scoped", source) {
|
||||
"style_scoped".to_string()
|
||||
let kind = if has_attribute(node, "scoped", source) {
|
||||
ChunkKind::StyleScoped
|
||||
} else {
|
||||
"style".to_string()
|
||||
ChunkKind::Style
|
||||
};
|
||||
// Styles are likewise exposed as `raw_text`, so preserve only the SFC block.
|
||||
make_named_chunk(node, name, source, None)
|
||||
positional_candidate(node, kind, source)
|
||||
}
|
||||
|
||||
fn classify_custom_block<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let tag_name = extract_markup_tag_name(node, source).unwrap_or_else(|| "anonymous".to_string());
|
||||
make_container_chunk(
|
||||
node,
|
||||
format!("custom_{tag_name}"),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
)
|
||||
make_named_container_chunk(node, ChunkKind::Custom, tag_name, source)
|
||||
}
|
||||
|
||||
fn classify_element<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
let tag_name = extract_markup_tag_name(node, source)?;
|
||||
Some(make_container_chunk(
|
||||
Some(force_container(make_container_chunk(
|
||||
node,
|
||||
format!("tag_{tag_name}"),
|
||||
ChunkKind::Tag,
|
||||
Some(tag_name),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
)))
|
||||
}
|
||||
|
||||
fn classify_start_tag<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandidate<'t>> {
|
||||
@@ -108,19 +107,14 @@ fn classify_start_tag<'t>(node: Node<'t>, source: &str) -> Option<RawChunkCandid
|
||||
let tag_name = child_by_kind(node, &["tag_name"])
|
||||
.and_then(|tag| sanitize_identifier(node_text(source, tag.start_byte(), tag.end_byte())))
|
||||
.unwrap_or_else(|| "anonymous".to_string());
|
||||
Some(make_container_chunk(
|
||||
node,
|
||||
format!("attrs_{tag_name}"),
|
||||
source,
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
))
|
||||
Some(make_named_container_chunk(node, ChunkKind::Attrs, tag_name, source))
|
||||
}
|
||||
|
||||
fn classify_attribute<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
let name = child_by_kind(node, &["attribute_name"])
|
||||
.and_then(|name| sanitize_identifier(node_text(source, name.start_byte(), name.end_byte())))
|
||||
.unwrap_or_else(|| "attr".to_string());
|
||||
make_named_chunk(node, format!("attr_{name}"), source, None)
|
||||
make_kind_chunk(node, ChunkKind::Attr, Some(name), source, None)
|
||||
}
|
||||
|
||||
fn classify_directive_attribute<'t>(node: Node<'t>, source: &str) -> RawChunkCandidate<'t> {
|
||||
@@ -131,16 +125,75 @@ fn classify_directive_attribute<'t>(node: Node<'t>, source: &str) -> RawChunkCan
|
||||
.filter(|mods| !mods.is_empty())
|
||||
.map(|mods| format!("_{mods}"))
|
||||
.unwrap_or_default();
|
||||
let chunk_name = if raw.starts_with('@') {
|
||||
format!("on_{directive_name}{modifier_suffix}")
|
||||
if raw.starts_with('@') {
|
||||
make_named_leaf_chunk(
|
||||
node,
|
||||
ChunkKind::Directive,
|
||||
format!("on_{directive_name}{modifier_suffix}"),
|
||||
source,
|
||||
)
|
||||
} else if raw.starts_with(':') {
|
||||
format!("bind_{directive_name}{modifier_suffix}")
|
||||
make_named_leaf_chunk(
|
||||
node,
|
||||
ChunkKind::Directive,
|
||||
format!("bind_{directive_name}{modifier_suffix}"),
|
||||
source,
|
||||
)
|
||||
} else if raw.starts_with('#') {
|
||||
format!("slot_{directive_name}{modifier_suffix}")
|
||||
make_kind_chunk(
|
||||
node,
|
||||
ChunkKind::Slot,
|
||||
Some(format!("{directive_name}{modifier_suffix}")),
|
||||
source,
|
||||
None,
|
||||
)
|
||||
} else {
|
||||
format!("dir_{directive_name}{modifier_suffix}")
|
||||
};
|
||||
make_named_chunk(node, chunk_name, source, None)
|
||||
make_named_leaf_chunk(
|
||||
node,
|
||||
ChunkKind::Directive,
|
||||
format!("dir_{directive_name}{modifier_suffix}"),
|
||||
source,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn make_named_leaf_chunk<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
make_candidate(
|
||||
node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
None,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
fn make_named_container_chunk<'t>(
|
||||
node: Node<'t>,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'t> {
|
||||
force_container(make_candidate(
|
||||
node,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
Some(recurse_self(node, ChunkContext::ClassBody)),
|
||||
source,
|
||||
))
|
||||
}
|
||||
|
||||
const fn force_container(mut candidate: RawChunkCandidate<'_>) -> RawChunkCandidate<'_> {
|
||||
candidate.force_recurse = true;
|
||||
candidate
|
||||
}
|
||||
|
||||
fn extract_markup_tag_name(node: Node<'_>, source: &str) -> Option<String> {
|
||||
|
||||
@@ -6,7 +6,10 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::types::ChunkNode;
|
||||
use super::{
|
||||
kind::{ChunkKind, SummaryStyle},
|
||||
types::ChunkNode,
|
||||
};
|
||||
use crate::env_uint;
|
||||
|
||||
// ── Configuration (environment overrides) ────────────────────────────────
|
||||
@@ -19,6 +22,12 @@ env_uint! {
|
||||
pub static MIN_RECURSE_SAVINGS: usize = "PI_CHUNK_MIN_SAVINGS" or 4 => [1, usize::MAX];
|
||||
}
|
||||
|
||||
/// Always recurse into named (non-group) chunks when children exist,
|
||||
/// regardless of the leaf threshold. Disabled via `PI_CHUNK_ALWAYS_RECURSE=0`.
|
||||
pub static ALWAYS_RECURSE: std::sync::LazyLock<bool> = std::sync::LazyLock::new(|| {
|
||||
std::env::var("PI_CHUNK_ALWAYS_RECURSE").map_or(true, |v| !matches!(v.as_str(), "0" | "false"))
|
||||
});
|
||||
|
||||
// ── Internal types ───────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
@@ -43,7 +52,8 @@ pub struct RecurseSpec<'tree> {
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct RawChunkCandidate<'tree> {
|
||||
pub base_name: String,
|
||||
pub identifier: Option<String>,
|
||||
pub kind: ChunkKind,
|
||||
pub name_style: NameStyle,
|
||||
pub range_start_byte: usize,
|
||||
pub range_end_byte: usize,
|
||||
@@ -88,20 +98,21 @@ const fn end_row_as_line(start: tree_sitter::Point, end: tree_sitter::Point) ->
|
||||
|
||||
pub fn make_candidate<'tree>(
|
||||
node: Node<'tree>,
|
||||
base_name: String,
|
||||
kind: ChunkKind,
|
||||
identifier: impl Into<Option<String>>,
|
||||
name_style: NameStyle,
|
||||
signature: Option<String>,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
force_recurse: bool,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
let identifier = identifier.into();
|
||||
let start = node.start_position();
|
||||
let end = node.end_position();
|
||||
let base_name = canonical_chunk_name(node, base_name.as_str(), source);
|
||||
let summary = summary_for_node(node, base_name.as_str(), signature.as_deref(), source);
|
||||
let summary = summary_for_node(node, kind, identifier.as_deref(), signature.as_deref(), source);
|
||||
let start_byte = node.start_byte();
|
||||
RawChunkCandidate {
|
||||
base_name,
|
||||
identifier,
|
||||
kind,
|
||||
name_style,
|
||||
range_start_byte: start_byte,
|
||||
range_end_byte: node.end_byte(),
|
||||
@@ -109,96 +120,99 @@ pub fn make_candidate<'tree>(
|
||||
range_start_line: start.row + 1,
|
||||
range_end_line: end_row_as_line(start, end),
|
||||
signature: summary,
|
||||
error: name_style == NameStyle::Error,
|
||||
groupable: matches!(name_style, NameStyle::Group),
|
||||
error: kind == ChunkKind::Error,
|
||||
groupable: kind.traits().groupable,
|
||||
has_leading_comment: false,
|
||||
force_recurse,
|
||||
force_recurse: kind.traits().container,
|
||||
recurse,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn group_candidate<'tree>(
|
||||
node: Node<'tree>,
|
||||
base_name: &str,
|
||||
kind: ChunkKind,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_candidate(node, base_name.to_string(), NameStyle::Group, None, None, false, source)
|
||||
make_candidate(node, kind, None, NameStyle::Group, None, None, source)
|
||||
}
|
||||
|
||||
pub fn positional_candidate<'tree>(
|
||||
node: Node<'tree>,
|
||||
base_name: &str,
|
||||
kind: ChunkKind,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_candidate(node, base_name.to_string(), NameStyle::Named, None, None, false, source)
|
||||
make_candidate(node, kind, None, NameStyle::Named, None, None, source)
|
||||
}
|
||||
|
||||
pub fn named_candidate<'tree>(
|
||||
node: Node<'tree>,
|
||||
prefix: &str,
|
||||
kind: ChunkKind,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_named_chunk(node, prefixed_name(prefix, node, source), source, recurse)
|
||||
make_kind_chunk(node, kind, extract_identifier(node, source), source, recurse)
|
||||
}
|
||||
|
||||
pub fn container_candidate<'tree>(
|
||||
node: Node<'tree>,
|
||||
prefix: &str,
|
||||
kind: ChunkKind,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_container_chunk(node, prefixed_name(prefix, node, source), source, recurse)
|
||||
make_kind_chunk(node, kind, extract_identifier(node, source), source, recurse)
|
||||
}
|
||||
|
||||
pub fn make_named_chunk<'tree>(
|
||||
pub fn make_kind_chunk<'tree>(
|
||||
node: Node<'tree>,
|
||||
name: String,
|
||||
kind: ChunkKind,
|
||||
identifier: Option<String>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_candidate(
|
||||
node,
|
||||
name,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse,
|
||||
false,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_named_chunk_from<'tree>(
|
||||
pub fn make_kind_chunk_from<'tree>(
|
||||
range_node: Node<'tree>,
|
||||
signature_node: Node<'tree>,
|
||||
name: String,
|
||||
kind: ChunkKind,
|
||||
identifier: Option<String>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_candidate(
|
||||
range_node,
|
||||
name,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(signature_node, source),
|
||||
recurse,
|
||||
false,
|
||||
source,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn make_container_chunk<'tree>(
|
||||
node: Node<'tree>,
|
||||
name: String,
|
||||
kind: ChunkKind,
|
||||
identifier: Option<String>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_candidate(
|
||||
node,
|
||||
name,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(node, source),
|
||||
recurse,
|
||||
true,
|
||||
source,
|
||||
)
|
||||
}
|
||||
@@ -206,17 +220,18 @@ pub fn make_container_chunk<'tree>(
|
||||
pub fn make_container_chunk_from<'tree>(
|
||||
range_node: Node<'tree>,
|
||||
signature_node: Node<'tree>,
|
||||
name: String,
|
||||
kind: ChunkKind,
|
||||
identifier: Option<String>,
|
||||
source: &str,
|
||||
recurse: Option<RecurseSpec<'tree>>,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
make_candidate(
|
||||
range_node,
|
||||
name,
|
||||
kind,
|
||||
identifier,
|
||||
NameStyle::Named,
|
||||
signature_for_node(signature_node, source),
|
||||
recurse,
|
||||
true,
|
||||
source,
|
||||
)
|
||||
}
|
||||
@@ -231,12 +246,9 @@ pub fn prefixed_name(prefix: &str, node: Node<'_>, source: &str) -> String {
|
||||
|
||||
/// Derive a semantic name from a node's kind and/or identifier.
|
||||
pub fn infer_named_candidate<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> {
|
||||
let kind_prefix = sanitize_node_kind(node.kind());
|
||||
let name = match extract_identifier(node, source) {
|
||||
Some(id) => format!("{kind_prefix}_{id}"),
|
||||
None => kind_prefix,
|
||||
};
|
||||
make_named_chunk(node, name, source, None)
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
let kind = ChunkKind::from_sanitized_kind(kind_name.as_str());
|
||||
make_kind_chunk(node, kind, extract_identifier(node, source), source, None)
|
||||
}
|
||||
|
||||
// ── Tree navigation helpers ──────────────────────────────────────────────
|
||||
@@ -634,67 +646,6 @@ pub fn signature_for_node(node: Node<'_>, source: &str) -> Option<String> {
|
||||
|
||||
// ── Summary / canonical naming ───────────────────────────────────────────
|
||||
|
||||
fn is_function_like_kind(kind: &str) -> bool {
|
||||
matches!(
|
||||
kind,
|
||||
"function_declaration"
|
||||
| "function_definition"
|
||||
| "function_item"
|
||||
| "procedure_declaration"
|
||||
| "overloaded_procedure_declaration"
|
||||
| "function_definition_header"
|
||||
| "test_declaration"
|
||||
| "method_definition"
|
||||
| "method_signature"
|
||||
| "abstract_method_signature"
|
||||
| "method_declaration"
|
||||
| "protocol_function_declaration"
|
||||
| "method"
|
||||
| "singleton_method"
|
||||
)
|
||||
}
|
||||
|
||||
fn is_variable_decl_kind(kind: &str) -> bool {
|
||||
matches!(
|
||||
kind,
|
||||
"lexical_declaration"
|
||||
| "variable_declaration"
|
||||
| "const_declaration"
|
||||
| "var_declaration"
|
||||
| "let_declaration"
|
||||
| "short_var_declaration"
|
||||
)
|
||||
}
|
||||
|
||||
fn canonical_chunk_name(node: Node<'_>, base_name: &str, source: &str) -> String {
|
||||
if is_function_like_kind(node.kind())
|
||||
&& !base_name.starts_with("fn_")
|
||||
&& base_name != "constructor"
|
||||
&& let Some(name) = extract_identifier(node, source)
|
||||
{
|
||||
return format!("fn_{name}");
|
||||
}
|
||||
if is_variable_decl_kind(node.kind())
|
||||
&& !base_name.starts_with("var_")
|
||||
&& !base_name.starts_with("fn_")
|
||||
&& !base_name.starts_with("class_")
|
||||
&& let Some(name) = extract_single_declarator_name(node, source)
|
||||
{
|
||||
return format!("var_{name}");
|
||||
}
|
||||
if (base_name == "expression" || base_name == "expr")
|
||||
&& let Some(name) = extract_identifier(node, source)
|
||||
{
|
||||
return format!("expr_{name}");
|
||||
}
|
||||
if base_name == "return"
|
||||
&& let Some(name) = extract_identifier(node, source)
|
||||
{
|
||||
return format!("ret_{name}");
|
||||
}
|
||||
base_name.to_string()
|
||||
}
|
||||
|
||||
fn normalize_summary_text(summary: &str) -> Option<String> {
|
||||
let summary = collapse_whitespace(summary.trim())
|
||||
.trim_end_matches('{')
|
||||
@@ -709,22 +660,29 @@ fn normalize_summary_text(summary: &str) -> Option<String> {
|
||||
}
|
||||
}
|
||||
|
||||
fn summarize_function_node(canonical_name: &str, raw_signature: &str) -> String {
|
||||
let name = canonical_name.strip_prefix("fn_").unwrap_or(canonical_name);
|
||||
fn summarize_function_node(
|
||||
kind: ChunkKind,
|
||||
identifier: Option<&str>,
|
||||
raw_signature: &str,
|
||||
) -> String {
|
||||
let name = identifier.unwrap_or_else(|| kind.prefix());
|
||||
let tail = function_signature(raw_signature)
|
||||
.or_else(|| python_function_signature(raw_signature))
|
||||
.or_else(|| rust_function_signature(raw_signature))
|
||||
.unwrap_or_else(|| raw_signature.to_string());
|
||||
let tail = tail.replacen("): ", ") → ", 1);
|
||||
format!("fn {name}{tail}")
|
||||
format!("{} {name}{tail}", kind.prefix())
|
||||
}
|
||||
|
||||
fn summarize_variable_node(node: Node<'_>, canonical_name: &str, source: &str) -> Option<String> {
|
||||
fn summarize_variable_node(
|
||||
node: Node<'_>,
|
||||
kind: ChunkKind,
|
||||
identifier: Option<&str>,
|
||||
source: &str,
|
||||
) -> Option<String> {
|
||||
let header = normalized_header(source, node.start_byte(), node.end_byte());
|
||||
let keyword = header.split_whitespace().next()?;
|
||||
let name = canonical_name
|
||||
.strip_prefix("var_")
|
||||
.unwrap_or(canonical_name);
|
||||
let name = identifier.unwrap_or_else(|| kind.prefix());
|
||||
Some(format!("{keyword} {name}"))
|
||||
}
|
||||
|
||||
@@ -734,20 +692,22 @@ fn summarize_statement_node(node: Node<'_>, source: &str) -> Option<String> {
|
||||
|
||||
pub fn summary_for_node(
|
||||
node: Node<'_>,
|
||||
canonical_name: &str,
|
||||
kind: ChunkKind,
|
||||
identifier: Option<&str>,
|
||||
raw_signature: Option<&str>,
|
||||
source: &str,
|
||||
) -> Option<String> {
|
||||
if canonical_name == "imports" {
|
||||
return Some("imports".to_string());
|
||||
}
|
||||
if canonical_name.starts_with("fn_")
|
||||
&& let Some(signature) = raw_signature
|
||||
{
|
||||
return Some(summarize_function_node(canonical_name, signature));
|
||||
}
|
||||
if canonical_name.starts_with("var_") {
|
||||
return summarize_variable_node(node, canonical_name, source);
|
||||
match kind.traits().summary {
|
||||
SummaryStyle::Imports => return Some("imports".to_string()),
|
||||
SummaryStyle::Function => {
|
||||
if let Some(signature) = raw_signature {
|
||||
return Some(summarize_function_node(kind, identifier, signature));
|
||||
}
|
||||
},
|
||||
SummaryStyle::Variable => {
|
||||
return summarize_variable_node(node, kind, identifier, source);
|
||||
},
|
||||
SummaryStyle::Default => {},
|
||||
}
|
||||
if matches!(
|
||||
node.kind(),
|
||||
|
||||
@@ -6,40 +6,32 @@
|
||||
|
||||
use tree_sitter::Node;
|
||||
|
||||
use super::common::*;
|
||||
|
||||
// ── Root-level default ──────────────────────────────────────────────────
|
||||
use super::{common::*, kind::ChunkKind};
|
||||
|
||||
pub fn classify_root_default<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> {
|
||||
infer_named_candidate(node, source)
|
||||
}
|
||||
|
||||
// ── Class-level default ─────────────────────────────────────────────────
|
||||
|
||||
pub fn classify_class_default<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> {
|
||||
infer_named_candidate(node, source)
|
||||
}
|
||||
|
||||
// ── Function-level default ──────────────────────────────────────────────
|
||||
|
||||
pub fn classify_function_default<'tree>(
|
||||
node: Node<'tree>,
|
||||
source: &str,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
let kind_name = sanitize_node_kind(node.kind());
|
||||
group_candidate(node, &kind_name, source)
|
||||
group_candidate(node, ChunkKind::from_sanitized_kind(kind_name.as_str()), source)
|
||||
}
|
||||
|
||||
// ── Variable declaration classification (shared) ────────────────────────
|
||||
|
||||
pub fn classify_var_decl<'tree>(node: Node<'tree>, source: &str) -> RawChunkCandidate<'tree> {
|
||||
if let Some(candidate) = promote_assigned_expression(node, node, source) {
|
||||
return candidate;
|
||||
}
|
||||
if let Some(name) = extract_single_declarator_name(node, source) {
|
||||
return make_named_chunk(node, format!("var_{name}"), source, None);
|
||||
return make_kind_chunk(node, ChunkKind::Variable, Some(name), source, None);
|
||||
}
|
||||
group_candidate(node, "decls", source)
|
||||
group_candidate(node, ChunkKind::Declarations, source)
|
||||
}
|
||||
|
||||
pub fn promote_assigned_expression<'tree>(
|
||||
@@ -62,14 +54,22 @@ pub fn promote_assigned_expression<'tree>(
|
||||
match value.kind() {
|
||||
"arrow_function" | "function_expression" | "function" => {
|
||||
let recurse = recurse_body(value, ChunkContext::FunctionBody);
|
||||
Some(make_named_chunk_from(range_node, value, format!("fn_{name}"), source, recurse))
|
||||
Some(make_kind_chunk_from(
|
||||
range_node,
|
||||
value,
|
||||
ChunkKind::Function,
|
||||
Some(name),
|
||||
source,
|
||||
recurse,
|
||||
))
|
||||
},
|
||||
"class" | "class_expression" => {
|
||||
let recurse = recurse_class(value);
|
||||
Some(make_container_chunk_from(
|
||||
range_node,
|
||||
value,
|
||||
format!("class_{name}"),
|
||||
ChunkKind::Class,
|
||||
Some(name),
|
||||
source,
|
||||
recurse,
|
||||
))
|
||||
|
||||
@@ -5,9 +5,10 @@ use crate::chunk::{
|
||||
denormalize_from_tabs, detect_file_indent_char, detect_file_indent_step,
|
||||
normalize_leading_whitespace_char, reindent_inserted_block, strip_content_prefixes,
|
||||
},
|
||||
kind::ChunkKind,
|
||||
resolve::{
|
||||
ParsedSelector, chunk_region_range, chunk_supports_region, resolve_chunk_selector,
|
||||
resolve_chunk_with_crc, sanitize_chunk_selector, sanitize_crc, split_selector_crc_and_region,
|
||||
ParsedSelector, chunk_region_range, resolve_chunk_selector, resolve_chunk_with_crc,
|
||||
sanitize_chunk_selector, sanitize_crc, split_selector_crc_and_region,
|
||||
},
|
||||
state::{ChunkState, ChunkStateInner},
|
||||
types::{
|
||||
@@ -309,11 +310,6 @@ fn resolve_edit_target(
|
||||
validate_batch_crc(resolved.chunk, resolved.crc.as_deref(), requires_checksum)?;
|
||||
}
|
||||
let chunk = resolved.chunk.clone();
|
||||
if let Some(r) = region
|
||||
&& !chunk_supports_region(&chunk, r)
|
||||
{
|
||||
return Err(format!("Chunk \"{}\" does not support @{}.", chunk.path, r.as_str()));
|
||||
}
|
||||
Ok(ResolvedEditTarget { chunk, region })
|
||||
}
|
||||
|
||||
@@ -341,7 +337,7 @@ fn apply_replace(
|
||||
let anchor = target.chunk;
|
||||
let (region_start, region_end) = match target.region {
|
||||
None => (anchor.start_byte as usize, anchor.end_byte as usize),
|
||||
Some(r) => chunk_region_range(&anchor, r)?,
|
||||
Some(r) => chunk_region_range(&anchor, r),
|
||||
};
|
||||
|
||||
// Scoped find/replace: locate a literal substring inside the chunk and replace
|
||||
@@ -461,7 +457,7 @@ fn apply_delete(
|
||||
let anchor = target.chunk;
|
||||
|
||||
if let Some(r) = target.region {
|
||||
let (range_start, range_end) = chunk_region_range(&anchor, r)?;
|
||||
let (range_start, range_end) = chunk_region_range(&anchor, r);
|
||||
state.source = replace_byte_range(&state.source, range_start, range_end, "");
|
||||
} else {
|
||||
let offsets = line_offsets(&state.source);
|
||||
@@ -934,11 +930,12 @@ fn chunk_slice(text: &str, chunk: &ChunkNode) -> String {
|
||||
.join("\n")
|
||||
}
|
||||
|
||||
fn is_container_like_chunk(chunk: &ChunkNode) -> bool {
|
||||
const fn is_container_like_chunk(chunk: &ChunkNode) -> bool {
|
||||
let traits = chunk.kind.traits();
|
||||
!chunk.leaf
|
||||
|| ["class_", "type_", "interface_", "enum_", "struct_", "impl_", "trait_", "mod_"]
|
||||
.iter()
|
||||
.any(|prefix| chunk.name.starts_with(prefix))
|
||||
|| traits.container
|
||||
|| traits.has_addressable_members
|
||||
|| traits.always_preserve_children
|
||||
}
|
||||
|
||||
fn go_receiver_belongs_to_type(source: &str, chunk: &ChunkNode, type_name: &str) -> bool {
|
||||
@@ -952,11 +949,14 @@ fn go_receiver_belongs_to_type(source: &str, chunk: &ChunkNode, type_name: &str)
|
||||
}
|
||||
|
||||
fn owned_container_end_line(state: &ChunkStateInner, anchor: &ChunkNode) -> u32 {
|
||||
if state.language != "go" || !anchor.path.starts_with("type_") {
|
||||
if state.language != "go" || anchor.kind != ChunkKind::Type {
|
||||
return anchor.end_line;
|
||||
}
|
||||
|
||||
let type_name = anchor.path.trim_start_matches("type_");
|
||||
let type_name = anchor
|
||||
.identifier
|
||||
.as_deref()
|
||||
.unwrap_or_else(|| anchor.kind.prefix());
|
||||
let mut owned_end_line = anchor.end_line;
|
||||
let mut top_level_chunks = state
|
||||
.tree
|
||||
@@ -977,7 +977,7 @@ fn owned_container_end_line(state: &ChunkStateInner, anchor: &ChunkNode) -> u32
|
||||
if chunk.start_line < owned_end_line {
|
||||
continue;
|
||||
}
|
||||
if chunk.name.starts_with("fn_")
|
||||
if chunk.kind == ChunkKind::Function
|
||||
&& go_receiver_belongs_to_type(&state.source, chunk, type_name)
|
||||
{
|
||||
owned_end_line = chunk.end_line;
|
||||
@@ -1018,7 +1018,7 @@ fn body_insertion_point(
|
||||
at_end: bool,
|
||||
file_indent_char: char,
|
||||
file_indent_step: usize,
|
||||
) -> Result<InsertionPoint, String> {
|
||||
) -> InsertionPoint {
|
||||
let offsets = line_offsets(&state.source);
|
||||
let indent = compute_insert_indent(state, anchor, true, file_indent_char, file_indent_step);
|
||||
if at_end {
|
||||
@@ -1034,13 +1034,13 @@ fn body_insertion_point(
|
||||
} else {
|
||||
last_child.indent_char.repeat(last_child.indent as usize)
|
||||
};
|
||||
return Ok(InsertionPoint {
|
||||
return InsertionPoint {
|
||||
offset: line_end_offset(&offsets, last_child.end_line, &state.source),
|
||||
indent: child_indent,
|
||||
});
|
||||
};
|
||||
}
|
||||
let (_, body_end) = chunk_region_range(anchor, ChunkRegion::Inner)?;
|
||||
return Ok(InsertionPoint { offset: body_end, indent });
|
||||
let (_, body_end) = chunk_region_range(anchor, ChunkRegion::Inner);
|
||||
return InsertionPoint { offset: body_end, indent };
|
||||
}
|
||||
|
||||
if let Some(first_child_path) = anchor.children.first()
|
||||
@@ -1050,13 +1050,13 @@ fn body_insertion_point(
|
||||
.iter()
|
||||
.find(|chunk| &chunk.path == first_child_path)
|
||||
{
|
||||
return Ok(InsertionPoint {
|
||||
return InsertionPoint {
|
||||
offset: line_start_offset(&offsets, first_child.start_line, &state.source),
|
||||
indent,
|
||||
});
|
||||
};
|
||||
}
|
||||
let (body_start, _) = chunk_region_range(anchor, ChunkRegion::Inner)?;
|
||||
Ok(InsertionPoint { offset: body_start, indent })
|
||||
let (body_start, _) = chunk_region_range(anchor, ChunkRegion::Inner);
|
||||
InsertionPoint { offset: body_start, indent }
|
||||
}
|
||||
|
||||
fn resolve_insertion_point(
|
||||
@@ -1080,13 +1080,13 @@ fn resolve_insertion_point(
|
||||
// Inner first-child position
|
||||
(Some(ChunkRegion::Inner), ChunkEditOp::Before | ChunkEditOp::Prepend)
|
||||
| (Some(ChunkRegion::Head), ChunkEditOp::After | ChunkEditOp::Append) => Ok((
|
||||
body_insertion_point(state, anchor, false, file_indent_char, file_indent_step)?,
|
||||
body_insertion_point(state, anchor, false, file_indent_char, file_indent_step),
|
||||
InsertPosition::FirstChild,
|
||||
)),
|
||||
// Inner last-child position
|
||||
(Some(ChunkRegion::Inner), ChunkEditOp::After | ChunkEditOp::Append)
|
||||
| (Some(ChunkRegion::Tail), ChunkEditOp::Before | ChunkEditOp::Prepend) => Ok((
|
||||
body_insertion_point(state, anchor, true, file_indent_char, file_indent_step)?,
|
||||
body_insertion_point(state, anchor, true, file_indent_char, file_indent_step),
|
||||
InsertPosition::LastChild,
|
||||
)),
|
||||
(_, ChunkEditOp::Replace | ChunkEditOp::Delete) => {
|
||||
@@ -1207,35 +1207,22 @@ fn container_has_interior_content(state: &ChunkStateInner, anchor: &ChunkNode) -
|
||||
/// children (methods) want blank line spacing. Containers whose children are
|
||||
/// all packed declarations (struct fields, enum variants) are tightly packed.
|
||||
fn children_want_blank_line_spacing(state: &ChunkStateInner, anchor: &ChunkNode) -> bool {
|
||||
// Root children are always top-level declarations, separated by blank lines.
|
||||
if anchor.path.is_empty() {
|
||||
return true;
|
||||
}
|
||||
// If the container has no chunk children, fall back to spaced (preserves
|
||||
// existing behavior for containers with interior content but no parsed
|
||||
// children).
|
||||
if anchor.children.is_empty() {
|
||||
return true;
|
||||
}
|
||||
// Packed children are declarations that belong tightly together without
|
||||
// blank line separators: struct fields, enum variants, etc.
|
||||
let all_packed = anchor.children.iter().all(|child_path| {
|
||||
state
|
||||
.tree
|
||||
.chunks
|
||||
.iter()
|
||||
.any(|c| c.path == *child_path && is_packed_child(&c.name))
|
||||
.any(|c| c.path == *child_path && c.kind.traits().packed)
|
||||
});
|
||||
!all_packed
|
||||
}
|
||||
|
||||
/// Returns true if a chunk name indicates a packed (tightly-spaced) child.
|
||||
/// These are declarations like struct fields and enum variants that don't
|
||||
/// need blank line separators between them.
|
||||
fn is_packed_child(name: &str) -> bool {
|
||||
name.starts_with("field_") || name.starts_with("variant_")
|
||||
}
|
||||
|
||||
/// Returns true if sibling insertions around `anchor` should have blank line
|
||||
/// spacing. Checks whether the anchor's parent container uses spaced or packed
|
||||
/// layout.
|
||||
@@ -2088,7 +2075,12 @@ mod tests {
|
||||
.tree
|
||||
.chunks
|
||||
.iter()
|
||||
.find(|c| c.name.starts_with("stmts"))
|
||||
.find(|c| {
|
||||
c.path
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.is_some_and(|leaf| leaf.starts_with("stmts"))
|
||||
})
|
||||
.expect("stmts chunk should exist");
|
||||
assert!(stmts.group, "stmts chunk should be marked as group");
|
||||
|
||||
@@ -2419,7 +2411,59 @@ mod tests {
|
||||
|
||||
assert_eq!(
|
||||
result.diff_after, "class Server {\n start() {\n return 42;\n }\n}\n",
|
||||
"nested body replace should produce correct 2-level indent"
|
||||
"4-space: nested body replace should produce correct 2-level indent"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_body_replace_preserves_correct_indentation_2space() {
|
||||
// 2-space file: method body at 2 levels of indent.
|
||||
let source = "class Server {\n start() {\n work();\n }\n}\n";
|
||||
let state = state_for(source, "typescript");
|
||||
let chunk = state
|
||||
.inner()
|
||||
.chunk("class_Server.fn_start")
|
||||
.expect("fn_start");
|
||||
|
||||
let result = apply_single_edit(&state, "test.ts", EditOperation {
|
||||
op: ChunkEditOp::Replace,
|
||||
sel: Some(format!("class_Server.fn_start#{}@inner", chunk.checksum)),
|
||||
crc: None,
|
||||
region: None,
|
||||
content: Some("\treturn 42;\n".to_owned()),
|
||||
find: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
result.diff_after, "class Server {\n start() {\n return 42;\n }\n}\n",
|
||||
"2-space: nested body replace should produce correct 2-level indent"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn nested_body_replace_with_excess_tabs_corrected() {
|
||||
// Agent accidentally includes base padding (2 tabs instead of 1).
|
||||
// Correction mechanism should strip common indent and produce correct output.
|
||||
let source = "class Server {\n start() {\n work();\n }\n}\n";
|
||||
let state = state_for(source, "typescript");
|
||||
let chunk = state
|
||||
.inner()
|
||||
.chunk("class_Server.fn_start")
|
||||
.expect("fn_start");
|
||||
|
||||
let result = apply_single_edit(&state, "test.ts", EditOperation {
|
||||
op: ChunkEditOp::Replace,
|
||||
sel: Some(format!("class_Server.fn_start#{}@inner", chunk.checksum)),
|
||||
crc: None,
|
||||
region: None,
|
||||
content: Some("\t\tif (x) {\n\t\t\ty();\n\t\t}\n".to_owned()),
|
||||
find: None,
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
result.diff_after,
|
||||
"class Server {\n start() {\n if (x) {\n y();\n }\n }\n}\n",
|
||||
"2-space: excess tabs should be corrected via dedent"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2492,7 +2536,12 @@ mod tests {
|
||||
.tree
|
||||
.chunks
|
||||
.iter()
|
||||
.find(|c| c.name.starts_with("list"))
|
||||
.find(|c| {
|
||||
c.path
|
||||
.rsplit('.')
|
||||
.next()
|
||||
.is_some_and(|leaf| leaf.starts_with("list"))
|
||||
})
|
||||
.expect("list chunk");
|
||||
|
||||
let result = apply_single_edit(&state, "test.md", EditOperation {
|
||||
|
||||
@@ -509,7 +509,7 @@ fn hashline_prefix_len(line: &str) -> Option<usize> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chunk::types::ChunkNode;
|
||||
use crate::chunk::{kind::ChunkKind, types::ChunkNode};
|
||||
|
||||
fn chunk(
|
||||
path: &str,
|
||||
@@ -518,9 +518,18 @@ mod tests {
|
||||
indent: u32,
|
||||
indent_char: &str,
|
||||
) -> ChunkNode {
|
||||
let kind = match path.split_once('_').map_or(path, |(prefix, _)| prefix) {
|
||||
"fn" => ChunkKind::Function,
|
||||
"class" => ChunkKind::Class,
|
||||
"stmts" => ChunkKind::Statements,
|
||||
_ => ChunkKind::Chunk,
|
||||
};
|
||||
ChunkNode {
|
||||
path: path.to_owned(),
|
||||
name: path.to_owned(),
|
||||
identifier: path
|
||||
.split_once('_')
|
||||
.and_then(|(_, identifier)| (!identifier.is_empty()).then_some(identifier.to_owned())),
|
||||
kind,
|
||||
leaf: children.is_empty(),
|
||||
parent_path: parent_path.map(str::to_owned),
|
||||
children: children.iter().map(|child| (*child).to_owned()).collect(),
|
||||
|
||||
@@ -0,0 +1,675 @@
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum ChunkKind {
|
||||
Add,
|
||||
After,
|
||||
Alias,
|
||||
Algo,
|
||||
Arg,
|
||||
Argv,
|
||||
Array,
|
||||
At,
|
||||
Attr,
|
||||
AttrExpr,
|
||||
Attrs,
|
||||
Block,
|
||||
BlockIf,
|
||||
BlockLocals,
|
||||
Body,
|
||||
Case,
|
||||
Cases,
|
||||
Catch,
|
||||
Cell,
|
||||
Class,
|
||||
Clause,
|
||||
Cmd,
|
||||
Code,
|
||||
Cond,
|
||||
Constructor,
|
||||
Contract,
|
||||
Copy,
|
||||
Custom,
|
||||
Declarations,
|
||||
Decl,
|
||||
DefaultExport,
|
||||
Define,
|
||||
Directive,
|
||||
Either,
|
||||
Elif,
|
||||
Else,
|
||||
Enum,
|
||||
Env,
|
||||
Error,
|
||||
Except,
|
||||
Exports,
|
||||
Expose,
|
||||
Expression,
|
||||
Field,
|
||||
Fields,
|
||||
File,
|
||||
Frame,
|
||||
Function,
|
||||
For,
|
||||
ForIn,
|
||||
ForOf,
|
||||
Form,
|
||||
Frontmatter,
|
||||
Group,
|
||||
GroupBy,
|
||||
Headers,
|
||||
Healthcheck,
|
||||
Html,
|
||||
Hunks,
|
||||
If,
|
||||
Iface,
|
||||
Impl,
|
||||
Imports,
|
||||
Includes,
|
||||
InlineFragment,
|
||||
Install,
|
||||
Interface,
|
||||
Interpolation,
|
||||
Item,
|
||||
Join,
|
||||
Key,
|
||||
KeyScripts,
|
||||
Label,
|
||||
Let,
|
||||
List,
|
||||
Loop,
|
||||
Macro,
|
||||
Map,
|
||||
Markdown,
|
||||
Match,
|
||||
Method,
|
||||
Methods,
|
||||
Module,
|
||||
Mustache,
|
||||
Object,
|
||||
Operation,
|
||||
Operator,
|
||||
Option,
|
||||
Options,
|
||||
OrderBy,
|
||||
Parameters,
|
||||
Preamble,
|
||||
Proc,
|
||||
Process,
|
||||
Project,
|
||||
Proto,
|
||||
Py,
|
||||
Python,
|
||||
Query,
|
||||
Receive,
|
||||
Recipe,
|
||||
Relations,
|
||||
Render,
|
||||
Return,
|
||||
Root,
|
||||
Rule,
|
||||
Schema,
|
||||
Script,
|
||||
ScriptModule,
|
||||
ScriptSetup,
|
||||
Section,
|
||||
Select,
|
||||
Setting,
|
||||
Shebang,
|
||||
Shell,
|
||||
Slot,
|
||||
Snippet,
|
||||
Source,
|
||||
Stage,
|
||||
StaticInit,
|
||||
Statements,
|
||||
Struct,
|
||||
Style,
|
||||
StyleScoped,
|
||||
Switch,
|
||||
Table,
|
||||
Tag,
|
||||
Target,
|
||||
Template,
|
||||
Text,
|
||||
Trait,
|
||||
Translation,
|
||||
Try,
|
||||
Ts,
|
||||
Type,
|
||||
Typescript,
|
||||
Union,
|
||||
User,
|
||||
Val,
|
||||
Variable,
|
||||
Variant,
|
||||
Variants,
|
||||
VersionGate,
|
||||
When,
|
||||
Where,
|
||||
While,
|
||||
With,
|
||||
Workdir,
|
||||
Chunk,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum SummaryStyle {
|
||||
Function,
|
||||
Variable,
|
||||
Imports,
|
||||
Default,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub struct ChunkTraits {
|
||||
pub groupable: bool,
|
||||
pub packed: bool,
|
||||
pub addressable_leaf: bool,
|
||||
pub always_preserve_children: bool,
|
||||
pub has_addressable_members: bool,
|
||||
pub summary: SummaryStyle,
|
||||
pub container: bool,
|
||||
}
|
||||
|
||||
const DEFAULT_TRAITS: ChunkTraits = ChunkTraits {
|
||||
groupable: false,
|
||||
packed: false,
|
||||
addressable_leaf: false,
|
||||
always_preserve_children: false,
|
||||
has_addressable_members: false,
|
||||
summary: SummaryStyle::Default,
|
||||
container: false,
|
||||
};
|
||||
|
||||
const GROUP_TRAITS: ChunkTraits = ChunkTraits { groupable: true, ..DEFAULT_TRAITS };
|
||||
|
||||
const CONTAINER_TRAITS: ChunkTraits = ChunkTraits { container: true, ..DEFAULT_TRAITS };
|
||||
|
||||
const ADDRESSABLE_CONTAINER_TRAITS: ChunkTraits =
|
||||
ChunkTraits { container: true, has_addressable_members: true, ..DEFAULT_TRAITS };
|
||||
|
||||
const PRESERVE_CHILDREN_TRAITS: ChunkTraits =
|
||||
ChunkTraits { container: true, always_preserve_children: true, ..DEFAULT_TRAITS };
|
||||
|
||||
const PACKED_LEAF_TRAITS: ChunkTraits =
|
||||
ChunkTraits { packed: true, addressable_leaf: true, ..DEFAULT_TRAITS };
|
||||
|
||||
const FUNCTION_TRAITS: ChunkTraits =
|
||||
ChunkTraits { summary: SummaryStyle::Function, ..DEFAULT_TRAITS };
|
||||
|
||||
const VARIABLE_TRAITS: ChunkTraits =
|
||||
ChunkTraits { summary: SummaryStyle::Variable, ..DEFAULT_TRAITS };
|
||||
|
||||
const IMPORTS_TRAITS: ChunkTraits =
|
||||
ChunkTraits { groupable: true, summary: SummaryStyle::Imports, ..DEFAULT_TRAITS };
|
||||
|
||||
impl ChunkKind {
|
||||
pub const fn prefix(self) -> &'static str {
|
||||
match self {
|
||||
Self::Add => "add",
|
||||
Self::After => "after",
|
||||
Self::Alias => "alias",
|
||||
Self::Algo => "algo",
|
||||
Self::Arg => "arg",
|
||||
Self::Argv => "argv",
|
||||
Self::Array => "array",
|
||||
Self::At => "at",
|
||||
Self::Attr => "attr",
|
||||
Self::AttrExpr => "attr_expr",
|
||||
Self::Attrs => "attrs",
|
||||
Self::Block => "block",
|
||||
Self::BlockIf => "block_if",
|
||||
Self::BlockLocals => "block_locals",
|
||||
Self::Body => "body",
|
||||
Self::Case => "case",
|
||||
Self::Cases => "cases",
|
||||
Self::Catch => "catch",
|
||||
Self::Cell => "cell",
|
||||
Self::Class => "class",
|
||||
Self::Clause => "clause",
|
||||
Self::Cmd => "cmd",
|
||||
Self::Code => "code",
|
||||
Self::Cond => "cond",
|
||||
Self::Constructor => "constructor",
|
||||
Self::Contract => "contract",
|
||||
Self::Copy => "copy",
|
||||
Self::Custom => "custom",
|
||||
Self::Declarations => "decls",
|
||||
Self::Decl => "decl",
|
||||
Self::DefaultExport => "default_export",
|
||||
Self::Define => "define",
|
||||
Self::Directive => "directive",
|
||||
Self::Either => "either",
|
||||
Self::Elif => "elif",
|
||||
Self::Else => "else",
|
||||
Self::Enum => "enum",
|
||||
Self::Env => "env",
|
||||
Self::Error => "error",
|
||||
Self::Except => "except",
|
||||
Self::Exports => "exports",
|
||||
Self::Expose => "expose",
|
||||
Self::Expression => "expr",
|
||||
Self::Field => "field",
|
||||
Self::Fields => "fields",
|
||||
Self::File => "file",
|
||||
Self::Frame => "frame",
|
||||
Self::Function => "fn",
|
||||
Self::For => "for",
|
||||
Self::ForIn => "for_in",
|
||||
Self::ForOf => "for_of",
|
||||
Self::Form => "form",
|
||||
Self::Frontmatter => "frontmatter",
|
||||
Self::Group => "group",
|
||||
Self::GroupBy => "group_by",
|
||||
Self::Headers => "headers",
|
||||
Self::Healthcheck => "healthcheck",
|
||||
Self::Html => "html",
|
||||
Self::Hunks => "hunks",
|
||||
Self::If => "if",
|
||||
Self::Iface => "iface",
|
||||
Self::Impl => "impl",
|
||||
Self::Imports => "imports",
|
||||
Self::Includes => "includes",
|
||||
Self::InlineFragment => "inline_fragment",
|
||||
Self::Install => "install",
|
||||
Self::Interface => "interface",
|
||||
Self::Interpolation => "interpolation",
|
||||
Self::Item => "item",
|
||||
Self::Join => "join",
|
||||
Self::Key => "key",
|
||||
Self::KeyScripts => "key_scripts",
|
||||
Self::Label => "label",
|
||||
Self::Let => "let",
|
||||
Self::List => "list",
|
||||
Self::Loop => "loop",
|
||||
Self::Macro => "macro",
|
||||
Self::Map => "map",
|
||||
Self::Markdown => "markdown",
|
||||
Self::Match => "match",
|
||||
Self::Method => "meth",
|
||||
Self::Methods => "methods",
|
||||
Self::Module => "mod",
|
||||
Self::Mustache => "mustache",
|
||||
Self::Object => "object",
|
||||
Self::Operation => "operation",
|
||||
Self::Operator => "operator",
|
||||
Self::Option => "option",
|
||||
Self::Options => "options",
|
||||
Self::OrderBy => "order_by",
|
||||
Self::Parameters => "params",
|
||||
Self::Preamble => "preamble",
|
||||
Self::Proc => "proc",
|
||||
Self::Process => "process",
|
||||
Self::Project => "project",
|
||||
Self::Proto => "proto",
|
||||
Self::Py => "py",
|
||||
Self::Python => "python",
|
||||
Self::Query => "query",
|
||||
Self::Receive => "receive",
|
||||
Self::Recipe => "recipe",
|
||||
Self::Relations => "relations",
|
||||
Self::Render => "render",
|
||||
Self::Return => "ret",
|
||||
Self::Root => "root",
|
||||
Self::Rule => "rule",
|
||||
Self::Schema => "schema",
|
||||
Self::Script => "script",
|
||||
Self::ScriptModule => "script_module",
|
||||
Self::ScriptSetup => "script_setup",
|
||||
Self::Section => "section",
|
||||
Self::Select => "select",
|
||||
Self::Setting => "setting",
|
||||
Self::Shebang => "shebang",
|
||||
Self::Shell => "shell",
|
||||
Self::Slot => "slot",
|
||||
Self::Snippet => "snippet",
|
||||
Self::Source => "source",
|
||||
Self::Stage => "stage",
|
||||
Self::StaticInit => "static_init",
|
||||
Self::Statements => "stmts",
|
||||
Self::Struct => "struct",
|
||||
Self::Style => "style",
|
||||
Self::StyleScoped => "style_scoped",
|
||||
Self::Switch => "switch",
|
||||
Self::Table => "table",
|
||||
Self::Tag => "tag",
|
||||
Self::Target => "target",
|
||||
Self::Template => "template",
|
||||
Self::Text => "text",
|
||||
Self::Trait => "trait",
|
||||
Self::Translation => "translation",
|
||||
Self::Try => "try",
|
||||
Self::Ts => "ts",
|
||||
Self::Type => "type",
|
||||
Self::Typescript => "typescript",
|
||||
Self::Union => "union",
|
||||
Self::User => "user",
|
||||
Self::Val => "val",
|
||||
Self::Variable => "var",
|
||||
Self::Variant => "variant",
|
||||
Self::Variants => "variants",
|
||||
Self::VersionGate => "version_gate",
|
||||
Self::When => "when",
|
||||
Self::Where => "where",
|
||||
Self::While => "while",
|
||||
Self::With => "with",
|
||||
Self::Workdir => "workdir",
|
||||
Self::Chunk => "chunk",
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn traits(self) -> &'static ChunkTraits {
|
||||
match self {
|
||||
Self::Add => &GROUP_TRAITS,
|
||||
Self::After => &GROUP_TRAITS,
|
||||
Self::Alias => &DEFAULT_TRAITS,
|
||||
Self::Algo => &CONTAINER_TRAITS,
|
||||
Self::Arg => &DEFAULT_TRAITS,
|
||||
Self::Argv => &DEFAULT_TRAITS,
|
||||
Self::Array => &DEFAULT_TRAITS,
|
||||
Self::At => &DEFAULT_TRAITS,
|
||||
Self::Attr => &DEFAULT_TRAITS,
|
||||
Self::AttrExpr => &DEFAULT_TRAITS,
|
||||
Self::Attrs => &CONTAINER_TRAITS,
|
||||
Self::Block => &DEFAULT_TRAITS,
|
||||
Self::BlockIf => &DEFAULT_TRAITS,
|
||||
Self::BlockLocals => &DEFAULT_TRAITS,
|
||||
Self::Body => &DEFAULT_TRAITS,
|
||||
Self::Case => &DEFAULT_TRAITS,
|
||||
Self::Cases => &GROUP_TRAITS,
|
||||
Self::Catch => &DEFAULT_TRAITS,
|
||||
Self::Cell => &CONTAINER_TRAITS,
|
||||
Self::Class => &CONTAINER_TRAITS,
|
||||
Self::Clause => &DEFAULT_TRAITS,
|
||||
Self::Cmd => &GROUP_TRAITS,
|
||||
Self::Code => &GROUP_TRAITS,
|
||||
Self::Cond => &DEFAULT_TRAITS,
|
||||
Self::Constructor => &FUNCTION_TRAITS,
|
||||
Self::Contract => &DEFAULT_TRAITS,
|
||||
Self::Copy => &GROUP_TRAITS,
|
||||
Self::Custom => &DEFAULT_TRAITS,
|
||||
Self::Declarations => &GROUP_TRAITS,
|
||||
Self::Decl => &DEFAULT_TRAITS,
|
||||
Self::DefaultExport => &DEFAULT_TRAITS,
|
||||
Self::Define => &DEFAULT_TRAITS,
|
||||
Self::Directive => &DEFAULT_TRAITS,
|
||||
Self::Either => &DEFAULT_TRAITS,
|
||||
Self::Elif => &DEFAULT_TRAITS,
|
||||
Self::Else => &DEFAULT_TRAITS,
|
||||
Self::Enum => &ADDRESSABLE_CONTAINER_TRAITS,
|
||||
Self::Env => &DEFAULT_TRAITS,
|
||||
Self::Error => &DEFAULT_TRAITS,
|
||||
Self::Except => &DEFAULT_TRAITS,
|
||||
Self::Exports => &GROUP_TRAITS,
|
||||
Self::Expose => &DEFAULT_TRAITS,
|
||||
Self::Expression => &DEFAULT_TRAITS,
|
||||
Self::Field => &PACKED_LEAF_TRAITS,
|
||||
Self::Fields => &GROUP_TRAITS,
|
||||
Self::File => &DEFAULT_TRAITS,
|
||||
Self::Frame => &DEFAULT_TRAITS,
|
||||
Self::Function => &FUNCTION_TRAITS,
|
||||
Self::For => &DEFAULT_TRAITS,
|
||||
Self::ForIn => &DEFAULT_TRAITS,
|
||||
Self::ForOf => &DEFAULT_TRAITS,
|
||||
Self::Form => &DEFAULT_TRAITS,
|
||||
Self::Frontmatter => &DEFAULT_TRAITS,
|
||||
Self::Group => &DEFAULT_TRAITS,
|
||||
Self::GroupBy => &DEFAULT_TRAITS,
|
||||
Self::Headers => &GROUP_TRAITS,
|
||||
Self::Healthcheck => &DEFAULT_TRAITS,
|
||||
Self::Html => &GROUP_TRAITS,
|
||||
Self::Hunks => &GROUP_TRAITS,
|
||||
Self::If => &DEFAULT_TRAITS,
|
||||
Self::Iface => &PRESERVE_CHILDREN_TRAITS,
|
||||
Self::Impl => &CONTAINER_TRAITS,
|
||||
Self::Imports => &IMPORTS_TRAITS,
|
||||
Self::Includes => &GROUP_TRAITS,
|
||||
Self::InlineFragment => &DEFAULT_TRAITS,
|
||||
Self::Install => &DEFAULT_TRAITS,
|
||||
Self::Interface => &PRESERVE_CHILDREN_TRAITS,
|
||||
Self::Interpolation => &GROUP_TRAITS,
|
||||
Self::Item => &DEFAULT_TRAITS,
|
||||
Self::Join => &DEFAULT_TRAITS,
|
||||
Self::Key => &DEFAULT_TRAITS,
|
||||
Self::KeyScripts => &DEFAULT_TRAITS,
|
||||
Self::Label => &DEFAULT_TRAITS,
|
||||
Self::Let => &DEFAULT_TRAITS,
|
||||
Self::List => &DEFAULT_TRAITS,
|
||||
Self::Loop => &DEFAULT_TRAITS,
|
||||
Self::Macro => &DEFAULT_TRAITS,
|
||||
Self::Map => &DEFAULT_TRAITS,
|
||||
Self::Markdown => &DEFAULT_TRAITS,
|
||||
Self::Match => &DEFAULT_TRAITS,
|
||||
Self::Method => &DEFAULT_TRAITS,
|
||||
Self::Methods => &GROUP_TRAITS,
|
||||
Self::Module => &CONTAINER_TRAITS,
|
||||
Self::Mustache => &DEFAULT_TRAITS,
|
||||
Self::Object => &DEFAULT_TRAITS,
|
||||
Self::Operation => &DEFAULT_TRAITS,
|
||||
Self::Operator => &DEFAULT_TRAITS,
|
||||
Self::Option => &DEFAULT_TRAITS,
|
||||
Self::Options => &GROUP_TRAITS,
|
||||
Self::OrderBy => &DEFAULT_TRAITS,
|
||||
Self::Parameters => &GROUP_TRAITS,
|
||||
Self::Preamble => &DEFAULT_TRAITS,
|
||||
Self::Proc => &CONTAINER_TRAITS,
|
||||
Self::Process => &CONTAINER_TRAITS,
|
||||
Self::Project => &DEFAULT_TRAITS,
|
||||
Self::Proto => &DEFAULT_TRAITS,
|
||||
Self::Py => &DEFAULT_TRAITS,
|
||||
Self::Python => &DEFAULT_TRAITS,
|
||||
Self::Query => &DEFAULT_TRAITS,
|
||||
Self::Receive => &DEFAULT_TRAITS,
|
||||
Self::Recipe => &DEFAULT_TRAITS,
|
||||
Self::Relations => &DEFAULT_TRAITS,
|
||||
Self::Render => &DEFAULT_TRAITS,
|
||||
Self::Return => &DEFAULT_TRAITS,
|
||||
Self::Root => &DEFAULT_TRAITS,
|
||||
Self::Rule => &DEFAULT_TRAITS,
|
||||
Self::Schema => &DEFAULT_TRAITS,
|
||||
Self::Script => &DEFAULT_TRAITS,
|
||||
Self::ScriptModule => &DEFAULT_TRAITS,
|
||||
Self::ScriptSetup => &DEFAULT_TRAITS,
|
||||
Self::Section => &DEFAULT_TRAITS,
|
||||
Self::Select => &DEFAULT_TRAITS,
|
||||
Self::Setting => &DEFAULT_TRAITS,
|
||||
Self::Shebang => &DEFAULT_TRAITS,
|
||||
Self::Shell => &DEFAULT_TRAITS,
|
||||
Self::Slot => &DEFAULT_TRAITS,
|
||||
Self::Snippet => &DEFAULT_TRAITS,
|
||||
Self::Source => &DEFAULT_TRAITS,
|
||||
Self::Stage => &DEFAULT_TRAITS,
|
||||
Self::StaticInit => &DEFAULT_TRAITS,
|
||||
Self::Statements => &GROUP_TRAITS,
|
||||
Self::Struct => &ADDRESSABLE_CONTAINER_TRAITS,
|
||||
Self::Style => &DEFAULT_TRAITS,
|
||||
Self::StyleScoped => &DEFAULT_TRAITS,
|
||||
Self::Switch => &DEFAULT_TRAITS,
|
||||
Self::Table => &DEFAULT_TRAITS,
|
||||
Self::Tag => &DEFAULT_TRAITS,
|
||||
Self::Target => &DEFAULT_TRAITS,
|
||||
Self::Template => &DEFAULT_TRAITS,
|
||||
Self::Text => &GROUP_TRAITS,
|
||||
Self::Trait => &PRESERVE_CHILDREN_TRAITS,
|
||||
Self::Translation => &DEFAULT_TRAITS,
|
||||
Self::Try => &DEFAULT_TRAITS,
|
||||
Self::Ts => &DEFAULT_TRAITS,
|
||||
Self::Type => &ADDRESSABLE_CONTAINER_TRAITS,
|
||||
Self::Typescript => &DEFAULT_TRAITS,
|
||||
Self::Union => &DEFAULT_TRAITS,
|
||||
Self::User => &DEFAULT_TRAITS,
|
||||
Self::Val => &DEFAULT_TRAITS,
|
||||
Self::Variable => &VARIABLE_TRAITS,
|
||||
Self::Variant => &PACKED_LEAF_TRAITS,
|
||||
Self::Variants => &DEFAULT_TRAITS,
|
||||
Self::VersionGate => &DEFAULT_TRAITS,
|
||||
Self::When => &DEFAULT_TRAITS,
|
||||
Self::Where => &DEFAULT_TRAITS,
|
||||
Self::While => &DEFAULT_TRAITS,
|
||||
Self::With => &GROUP_TRAITS,
|
||||
Self::Workdir => &DEFAULT_TRAITS,
|
||||
Self::Chunk => &DEFAULT_TRAITS,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn path_segment(self, identifier: Option<&str>) -> String {
|
||||
match identifier {
|
||||
Some(identifier) => format!("{}_{identifier}", self.prefix()),
|
||||
None => self.prefix().to_string(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_sanitized_kind(kind: &str) -> Self {
|
||||
match kind {
|
||||
"add" => Self::Add,
|
||||
"after" => Self::After,
|
||||
"alias" => Self::Alias,
|
||||
"algo" => Self::Algo,
|
||||
"arg" => Self::Arg,
|
||||
"argv" => Self::Argv,
|
||||
"array" => Self::Array,
|
||||
"at" => Self::At,
|
||||
"attr" => Self::Attr,
|
||||
"attr_expr" => Self::AttrExpr,
|
||||
"attrs" => Self::Attrs,
|
||||
"block" => Self::Block,
|
||||
"block_if" => Self::BlockIf,
|
||||
"block_locals" => Self::BlockLocals,
|
||||
"body" => Self::Body,
|
||||
"case" => Self::Case,
|
||||
"cases" => Self::Cases,
|
||||
"catch" => Self::Catch,
|
||||
"cell" => Self::Cell,
|
||||
"class" => Self::Class,
|
||||
"clause" => Self::Clause,
|
||||
"cmd" => Self::Cmd,
|
||||
"code" => Self::Code,
|
||||
"cond" => Self::Cond,
|
||||
"constructor" => Self::Constructor,
|
||||
"contract" => Self::Contract,
|
||||
"copy" => Self::Copy,
|
||||
"custom" => Self::Custom,
|
||||
"decls" => Self::Declarations,
|
||||
"decl" => Self::Decl,
|
||||
"default_export" => Self::DefaultExport,
|
||||
"define" => Self::Define,
|
||||
"directive" => Self::Directive,
|
||||
"either" => Self::Either,
|
||||
"elif" => Self::Elif,
|
||||
"else" => Self::Else,
|
||||
"enum" => Self::Enum,
|
||||
"env" => Self::Env,
|
||||
"error" => Self::Error,
|
||||
"except" => Self::Except,
|
||||
"exports" => Self::Exports,
|
||||
"expose" => Self::Expose,
|
||||
"expr" => Self::Expression,
|
||||
"field" => Self::Field,
|
||||
"fields" => Self::Fields,
|
||||
"file" => Self::File,
|
||||
"frame" => Self::Frame,
|
||||
"fn" => Self::Function,
|
||||
"for" => Self::For,
|
||||
"for_in" => Self::ForIn,
|
||||
"for_of" => Self::ForOf,
|
||||
"form" => Self::Form,
|
||||
"frontmatter" => Self::Frontmatter,
|
||||
"group" => Self::Group,
|
||||
"group_by" => Self::GroupBy,
|
||||
"headers" => Self::Headers,
|
||||
"healthcheck" => Self::Healthcheck,
|
||||
"html" => Self::Html,
|
||||
"hunks" => Self::Hunks,
|
||||
"if" => Self::If,
|
||||
"iface" => Self::Iface,
|
||||
"impl" => Self::Impl,
|
||||
"imports" => Self::Imports,
|
||||
"includes" => Self::Includes,
|
||||
"inline_fragment" => Self::InlineFragment,
|
||||
"install" => Self::Install,
|
||||
"interface" => Self::Interface,
|
||||
"interpolation" => Self::Interpolation,
|
||||
"item" => Self::Item,
|
||||
"join" => Self::Join,
|
||||
"key" => Self::Key,
|
||||
"key_scripts" => Self::KeyScripts,
|
||||
"label" => Self::Label,
|
||||
"let" => Self::Let,
|
||||
"list" => Self::List,
|
||||
"loop" => Self::Loop,
|
||||
"macro" => Self::Macro,
|
||||
"map" => Self::Map,
|
||||
"markdown" => Self::Markdown,
|
||||
"match" => Self::Match,
|
||||
"meth" => Self::Method,
|
||||
"methods" => Self::Methods,
|
||||
"mod" => Self::Module,
|
||||
"mustache" => Self::Mustache,
|
||||
"object" => Self::Object,
|
||||
"operation" => Self::Operation,
|
||||
"operator" => Self::Operator,
|
||||
"option" => Self::Option,
|
||||
"options" => Self::Options,
|
||||
"order_by" => Self::OrderBy,
|
||||
"params" => Self::Parameters,
|
||||
"preamble" => Self::Preamble,
|
||||
"proc" => Self::Proc,
|
||||
"process" => Self::Process,
|
||||
"project" => Self::Project,
|
||||
"proto" => Self::Proto,
|
||||
"py" => Self::Py,
|
||||
"python" => Self::Python,
|
||||
"query" => Self::Query,
|
||||
"receive" => Self::Receive,
|
||||
"recipe" => Self::Recipe,
|
||||
"relations" => Self::Relations,
|
||||
"render" => Self::Render,
|
||||
"ret" => Self::Return,
|
||||
"root" => Self::Root,
|
||||
"rule" => Self::Rule,
|
||||
"schema" => Self::Schema,
|
||||
"script" => Self::Script,
|
||||
"script_module" => Self::ScriptModule,
|
||||
"script_setup" => Self::ScriptSetup,
|
||||
"section" => Self::Section,
|
||||
"select" => Self::Select,
|
||||
"setting" => Self::Setting,
|
||||
"shebang" => Self::Shebang,
|
||||
"shell" => Self::Shell,
|
||||
"slot" => Self::Slot,
|
||||
"snippet" => Self::Snippet,
|
||||
"source" => Self::Source,
|
||||
"stage" => Self::Stage,
|
||||
"static_init" => Self::StaticInit,
|
||||
"stmts" => Self::Statements,
|
||||
"struct" => Self::Struct,
|
||||
"style" => Self::Style,
|
||||
"style_scoped" => Self::StyleScoped,
|
||||
"switch" => Self::Switch,
|
||||
"table" => Self::Table,
|
||||
"tag" => Self::Tag,
|
||||
"target" => Self::Target,
|
||||
"template" => Self::Template,
|
||||
"text" => Self::Text,
|
||||
"trait" => Self::Trait,
|
||||
"translation" => Self::Translation,
|
||||
"try" => Self::Try,
|
||||
"ts" => Self::Ts,
|
||||
"type" => Self::Type,
|
||||
"typescript" => Self::Typescript,
|
||||
"union" => Self::Union,
|
||||
"user" => Self::User,
|
||||
"val" => Self::Val,
|
||||
"var" => Self::Variable,
|
||||
"variant" => Self::Variant,
|
||||
"variants" => Self::Variants,
|
||||
"version_gate" => Self::VersionGate,
|
||||
"when" => Self::When,
|
||||
"where" => Self::Where,
|
||||
"while" => Self::While,
|
||||
"with" => Self::With,
|
||||
"workdir" => Self::Workdir,
|
||||
"chunk" => Self::Chunk,
|
||||
_ => Self::Chunk,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,8 @@ pub(crate) mod resolve;
|
||||
pub(crate) mod state;
|
||||
pub mod types;
|
||||
|
||||
pub mod kind;
|
||||
|
||||
// Per-language classifiers
|
||||
mod ast_astro;
|
||||
mod ast_bash_make_diff;
|
||||
@@ -65,6 +67,7 @@ use xxhash_rust::xxh64::xxh64;
|
||||
use self::{
|
||||
classify::{LangClassifier, classifier_for},
|
||||
common::*,
|
||||
kind::ChunkKind,
|
||||
};
|
||||
pub use self::{
|
||||
state::ChunkState,
|
||||
@@ -76,8 +79,8 @@ use crate::{chunk::types::ChunkAnchorStyle, language::SupportLang};
|
||||
|
||||
/// Format one chunk anchor string for a node at `depth` using `style` and
|
||||
/// optional checksum omission.
|
||||
#[napi(js_name = "formatAnchor")]
|
||||
pub fn format_anchor_napi(
|
||||
#[napi]
|
||||
pub fn format_anchor(
|
||||
name: String,
|
||||
checksum: String,
|
||||
style: ChunkAnchorStyle,
|
||||
@@ -125,7 +128,8 @@ pub(crate) fn build_chunk_tree(source: &str, language: &str) -> Result<ChunkTree
|
||||
|
||||
acc.chunks.insert(0, ChunkNode {
|
||||
path: String::new(),
|
||||
name: "root".to_string(),
|
||||
identifier: None,
|
||||
kind: ChunkKind::Root,
|
||||
leaf: false,
|
||||
parent_path: None,
|
||||
children: root_children.clone(),
|
||||
@@ -202,7 +206,8 @@ fn build_blank_line_tree(
|
||||
) -> ChunkTree {
|
||||
let mut chunks = vec![ChunkNode {
|
||||
path: String::new(),
|
||||
name: "root".to_string(),
|
||||
identifier: None,
|
||||
kind: ChunkKind::Root,
|
||||
leaf: false,
|
||||
parent_path: None,
|
||||
children: Vec::new(),
|
||||
@@ -250,7 +255,8 @@ fn build_blank_line_tree(
|
||||
root_children.push(name.clone());
|
||||
chunks.push(ChunkNode {
|
||||
path: name.clone(),
|
||||
name: name.clone(),
|
||||
identifier: Some(name.clone()),
|
||||
kind: ChunkKind::Chunk,
|
||||
leaf: true,
|
||||
parent_path: Some(String::new()),
|
||||
children: Vec::new(),
|
||||
@@ -302,10 +308,11 @@ fn build_chunk(
|
||||
acc: &mut ChunkAccumulator,
|
||||
classifier: &dyn classify::LangClassifier,
|
||||
) -> String {
|
||||
let segment = candidate.kind.path_segment(candidate.identifier.as_deref());
|
||||
let path = if parent_path.is_empty() {
|
||||
candidate.base_name.clone()
|
||||
segment
|
||||
} else {
|
||||
format!("{parent_path}.{}", candidate.base_name)
|
||||
format!("{parent_path}.{segment}")
|
||||
};
|
||||
let line_count = candidate
|
||||
.range_end_line
|
||||
@@ -331,10 +338,12 @@ fn build_chunk(
|
||||
&& recurse.is_some()
|
||||
&& recurse_parse_errors == 0
|
||||
&& should_collapse_trivial_children(&candidate, &child_candidates);
|
||||
let always_recurse = *ALWAYS_RECURSE && !candidate.groupable && !child_candidates.is_empty();
|
||||
let should_recurse = !candidate.error
|
||||
&& recurse.is_some()
|
||||
&& !should_collapse
|
||||
&& (candidate.force_recurse
|
||||
|| always_recurse
|
||||
|| recurse_parse_errors > 0
|
||||
|| (line_count > *LEAF_THRESHOLD
|
||||
&& recursion_narrows_scope(line_count, &child_candidates)));
|
||||
@@ -351,7 +360,8 @@ fn build_chunk(
|
||||
let (indent, indent_char) = detect_indent(source, candidate.range_start_byte);
|
||||
acc.chunks.push(ChunkNode {
|
||||
path: path.clone(),
|
||||
name: candidate.base_name,
|
||||
identifier: candidate.identifier,
|
||||
kind: candidate.kind,
|
||||
leaf,
|
||||
parent_path: Some(parent_path.to_string()),
|
||||
children,
|
||||
@@ -432,15 +442,7 @@ fn classify_node<'tree>(
|
||||
classifier: &dyn LangClassifier,
|
||||
) -> RawChunkCandidate<'tree> {
|
||||
if node.is_error() || node.kind() == "ERROR" {
|
||||
return make_candidate(
|
||||
node,
|
||||
"<error>".to_string(),
|
||||
NameStyle::Error,
|
||||
None,
|
||||
None,
|
||||
false,
|
||||
source,
|
||||
);
|
||||
return make_candidate(node, ChunkKind::Error, None, NameStyle::Error, None, None, source);
|
||||
}
|
||||
|
||||
// Try language-specific classifier first, then fall back to defaults.
|
||||
@@ -499,7 +501,8 @@ fn group_candidates(candidates: Vec<RawChunkCandidate<'_>>) -> Vec<RawChunkCandi
|
||||
let next_line_count = line_span(candidate.range_start_line, candidate.range_end_line);
|
||||
let can_merge = last.groupable
|
||||
&& candidate.groupable
|
||||
&& last.base_name == candidate.base_name
|
||||
&& last.kind == candidate.kind
|
||||
&& last.identifier == candidate.identifier
|
||||
&& !candidate.has_leading_comment
|
||||
&& candidate.range_start_line <= last.range_end_line + 1
|
||||
&& last_line_count + next_line_count <= *MAX_CHUNK_LINES;
|
||||
@@ -518,36 +521,44 @@ fn group_candidates(candidates: Vec<RawChunkCandidate<'_>>) -> Vec<RawChunkCandi
|
||||
fn assign_unique_names(mut candidates: Vec<RawChunkCandidate<'_>>) -> Vec<RawChunkCandidate<'_>> {
|
||||
let mut totals = HashMap::<String, usize>::new();
|
||||
for candidate in &candidates {
|
||||
*totals.entry(candidate.base_name.clone()).or_insert(0) += 1;
|
||||
let key = candidate.kind.path_segment(candidate.identifier.as_deref());
|
||||
*totals.entry(key).or_insert(0) += 1;
|
||||
}
|
||||
let mut seen = HashMap::<String, usize>::new();
|
||||
|
||||
for candidate in &mut candidates {
|
||||
let count = seen.entry(candidate.base_name.clone()).or_insert(0);
|
||||
let key = candidate.kind.path_segment(candidate.identifier.as_deref());
|
||||
let count = seen.entry(key.clone()).or_insert(0);
|
||||
*count += 1;
|
||||
let occurrence = *count;
|
||||
let total = *totals.get(candidate.base_name.as_str()).unwrap_or(&1);
|
||||
let total = *totals.get(key.as_str()).unwrap_or(&1);
|
||||
|
||||
candidate.base_name = match candidate.name_style {
|
||||
candidate.identifier = match candidate.name_style {
|
||||
NameStyle::Error => {
|
||||
if total > 1 {
|
||||
format!("error_{occurrence}")
|
||||
Some(occurrence.to_string())
|
||||
} else {
|
||||
"error".to_string()
|
||||
None
|
||||
}
|
||||
},
|
||||
NameStyle::Named => {
|
||||
if total > 1 {
|
||||
format!("{}_{}", candidate.base_name, occurrence)
|
||||
match candidate.identifier.as_deref() {
|
||||
Some(identifier) => Some(format!("{identifier}_{occurrence}")),
|
||||
None => Some(occurrence.to_string()),
|
||||
}
|
||||
} else {
|
||||
candidate.base_name.clone()
|
||||
candidate.identifier.clone()
|
||||
}
|
||||
},
|
||||
NameStyle::Group => {
|
||||
if total == 1 || occurrence == 1 {
|
||||
candidate.base_name.clone()
|
||||
candidate.identifier.clone()
|
||||
} else {
|
||||
format!("{}_{}", candidate.base_name, occurrence)
|
||||
match candidate.identifier.as_deref() {
|
||||
Some(identifier) => Some(format!("{identifier}_{occurrence}")),
|
||||
None => Some(occurrence.to_string()),
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
@@ -582,19 +593,11 @@ fn should_collapse_trivial_children(
|
||||
return false;
|
||||
}
|
||||
|
||||
let has_addressable_leaf_members = children.iter().all(|child| {
|
||||
child.base_name.starts_with("field_") || child.base_name.starts_with("variant_")
|
||||
});
|
||||
if has_addressable_leaf_members
|
||||
&& (parent.base_name.starts_with("struct_")
|
||||
|| parent.base_name.starts_with("enum_")
|
||||
|| parent.base_name.starts_with("type_"))
|
||||
{
|
||||
let has_addressable_leaf_members = children.iter().all(|child| child.kind.traits().packed);
|
||||
if has_addressable_leaf_members && parent.kind.traits().has_addressable_members {
|
||||
return false;
|
||||
}
|
||||
// Trait and interface members should always be addressable so that
|
||||
// individual method signatures can be edited.
|
||||
if parent.base_name.starts_with("trait_") || parent.base_name.starts_with("interface_") {
|
||||
if parent.kind.traits().always_preserve_children {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -738,7 +741,8 @@ fn insert_preamble_chunk(
|
||||
);
|
||||
let preamble = ChunkNode {
|
||||
path: "preamble".to_string(),
|
||||
name: "preamble".to_string(),
|
||||
identifier: None,
|
||||
kind: ChunkKind::Preamble,
|
||||
leaf: true,
|
||||
parent_path: Some(String::new()),
|
||||
children: Vec::new(),
|
||||
@@ -772,7 +776,7 @@ mod tests {
|
||||
state::ChunkState,
|
||||
types::{ChunkAnchorStyle, ReadRenderParams},
|
||||
};
|
||||
use crate::language::SupportLang;
|
||||
use crate::{chunk::ChunkKind, language::SupportLang};
|
||||
|
||||
fn assert_supported_sample(language: &str, source: &str) {
|
||||
let tree = build_chunk_tree(source, language)
|
||||
@@ -1044,7 +1048,12 @@ function main(): void {{
|
||||
|
||||
let tree = build_chunk_tree(source, "typescript").expect("tree should build");
|
||||
assert!(tree.parse_errors > 0);
|
||||
assert!(tree.chunks.iter().any(|chunk| chunk.name == "error"));
|
||||
assert!(
|
||||
tree
|
||||
.chunks
|
||||
.iter()
|
||||
.any(|chunk| chunk.kind == ChunkKind::Error && chunk.identifier.is_none())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -1411,15 +1420,15 @@ impl Config {
|
||||
let attrset = tree
|
||||
.chunks
|
||||
.iter()
|
||||
.find(|chunk| chunk.path == "attrset_expr")
|
||||
.expect("attrset_expr chunk");
|
||||
.find(|chunk| chunk.path == "attrs")
|
||||
.expect("attrs chunk");
|
||||
assert!(!tree.fallback, "nix should use tree-sitter chunking");
|
||||
assert!(!attrset.leaf, "top-level attrset should recurse into bindings");
|
||||
assert!(
|
||||
attrset
|
||||
.children
|
||||
.iter()
|
||||
.any(|child| child == "attrset_expr.attr_hello"),
|
||||
.any(|child| child == "attrs.attr_hello"),
|
||||
"expected attr_hello child, got {:?}",
|
||||
attrset.children
|
||||
);
|
||||
@@ -1427,7 +1436,7 @@ impl Config {
|
||||
attrset
|
||||
.children
|
||||
.iter()
|
||||
.any(|child| child == "attrset_expr.attr_nested"),
|
||||
.any(|child| child == "attrs.attr_nested"),
|
||||
"expected attr_nested child, got {:?}",
|
||||
attrset.children
|
||||
);
|
||||
@@ -1624,7 +1633,7 @@ impl Config {
|
||||
.expect("listing should succeed");
|
||||
assert!(result.text.contains("sample.ts chunks:"));
|
||||
assert!(result.text.contains("fn_run#"));
|
||||
assert!(result.text.contains("regions: outer, head, inner, tail"));
|
||||
// Region listing removed — all chunks accept all regions now.
|
||||
assert!(!result.text.contains("return 1"));
|
||||
}
|
||||
|
||||
|
||||
@@ -275,13 +275,13 @@ fn chunk_body_anchor_indent(
|
||||
})
|
||||
}
|
||||
|
||||
const fn chunk_anchor_label(chunk: &ChunkNode, style: ChunkAnchorStyle) -> &str {
|
||||
fn chunk_anchor_label(chunk: &ChunkNode, style: ChunkAnchorStyle) -> String {
|
||||
match style {
|
||||
ChunkAnchorStyle::Full | ChunkAnchorStyle::FullOmit => chunk.path.as_str(),
|
||||
ChunkAnchorStyle::Full | ChunkAnchorStyle::FullOmit => chunk.path.clone(),
|
||||
ChunkAnchorStyle::Kind
|
||||
| ChunkAnchorStyle::KindOmit
|
||||
| ChunkAnchorStyle::Bare
|
||||
| ChunkAnchorStyle::None => chunk.name.as_str(),
|
||||
| ChunkAnchorStyle::None => chunk.kind.path_segment(chunk.identifier.as_deref()),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -700,7 +700,10 @@ fn emit_chunk_subtree(
|
||||
);
|
||||
let style = ctx.anchor_style.with_omit_checksum(ctx.omit_checksum);
|
||||
let anchor_label = chunk_anchor_label(chunk, style);
|
||||
push_meta(ctx, style.render(&anchor_indent, anchor_label, chunk.checksum.as_str()));
|
||||
push_meta(
|
||||
ctx,
|
||||
style.render(&anchor_indent, anchor_label.as_str(), chunk.checksum.as_str()),
|
||||
);
|
||||
return;
|
||||
},
|
||||
Some(ChunkFocusMode::Container | ChunkFocusMode::Expanded) => {
|
||||
@@ -730,7 +733,7 @@ fn emit_chunk_subtree(
|
||||
);
|
||||
let style = ctx.anchor_style.with_omit_checksum(ctx.omit_checksum);
|
||||
let anchor_label = chunk_anchor_label(chunk, style);
|
||||
push_meta(ctx, style.render(&anchor_indent, anchor_label, chunk.checksum.as_str()));
|
||||
push_meta(ctx, style.render(&anchor_indent, anchor_label.as_str(), chunk.checksum.as_str()));
|
||||
}
|
||||
if !has_kids {
|
||||
if ctx.show_leaf_preview
|
||||
@@ -784,7 +787,10 @@ fn emit_chunk_subtree(
|
||||
);
|
||||
let style = ctx.anchor_style.with_omit_checksum(ctx.omit_checksum);
|
||||
let anchor_label = chunk_anchor_label(chunk, style);
|
||||
push_meta(ctx, style.render_close(&anchor_indent, anchor_label, chunk.checksum.as_str()));
|
||||
push_meta(
|
||||
ctx,
|
||||
style.render_close(&anchor_indent, anchor_label.as_str(), chunk.checksum.as_str()),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -5,8 +5,6 @@ use crate::chunk::{
|
||||
types::{ChunkNode, ChunkRegion, ChunkTree},
|
||||
};
|
||||
|
||||
const CHUNK_NAME_PREFIXES: &[&str] =
|
||||
&["fn_", "var_", "class_", "stmts_", "type_", "interface_", "enum_", "const_"];
|
||||
const CHECKSUM_ALPHABET: &str = "ZPMQVRWSNKTXJBYH";
|
||||
|
||||
pub struct ResolvedChunk<'a> {
|
||||
@@ -196,39 +194,15 @@ fn root_chunk(state: &ChunkStateInner) -> Result<&ChunkNode, String> {
|
||||
.ok_or_else(|| "Chunk tree is missing the root chunk".to_owned())
|
||||
}
|
||||
|
||||
pub const fn chunk_supports_region(chunk: &ChunkNode, region: ChunkRegion) -> bool {
|
||||
pub fn chunk_region_range(chunk: &ChunkNode, region: ChunkRegion) -> (usize, usize) {
|
||||
let start = chunk.start_byte as usize;
|
||||
let end = chunk.end_byte as usize;
|
||||
let pro_end = chunk.prologue_end_byte.map_or(start, |b| b as usize);
|
||||
let epi_start = chunk.epilogue_start_byte.map_or(end, |b| b as usize);
|
||||
match region {
|
||||
ChunkRegion::Head | ChunkRegion::Inner | ChunkRegion::Tail => {
|
||||
chunk.prologue_end_byte.is_some() && chunk.epilogue_start_byte.is_some()
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn chunk_region_range(
|
||||
chunk: &ChunkNode,
|
||||
region: ChunkRegion,
|
||||
) -> Result<(usize, usize), String> {
|
||||
match region {
|
||||
ChunkRegion::Head => Ok((
|
||||
chunk.start_byte as usize,
|
||||
chunk
|
||||
.prologue_end_byte
|
||||
.ok_or_else(|| format!("Chunk \"{}\" does not support @head.", chunk.path))? as usize,
|
||||
)),
|
||||
ChunkRegion::Inner => Ok((
|
||||
chunk
|
||||
.prologue_end_byte
|
||||
.ok_or_else(|| format!("Chunk \"{}\" does not support @inner.", chunk.path))? as usize,
|
||||
chunk
|
||||
.epilogue_start_byte
|
||||
.ok_or_else(|| format!("Chunk \"{}\" does not support @inner.", chunk.path))? as usize,
|
||||
)),
|
||||
ChunkRegion::Tail => Ok((
|
||||
chunk
|
||||
.epilogue_start_byte
|
||||
.ok_or_else(|| format!("Chunk \"{}\" does not support @tail.", chunk.path))? as usize,
|
||||
chunk.end_byte as usize,
|
||||
)),
|
||||
ChunkRegion::Head => (start, pro_end),
|
||||
ChunkRegion::Inner => (pro_end, epi_start),
|
||||
ChunkRegion::Tail => (epi_start, end),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -289,12 +263,14 @@ fn resolve_chunk_selector_impl<'a>(
|
||||
}
|
||||
|
||||
if !cleaned.contains('.') {
|
||||
let prefixed = CHUNK_NAME_PREFIXES
|
||||
.iter()
|
||||
.map(|prefix| format!("{prefix}{cleaned}"))
|
||||
.collect::<Vec<_>>();
|
||||
let prefixed_matches =
|
||||
collect_unique_matches(prefixed.iter().flat_map(|name| state.chunks_by_leaf(name)));
|
||||
let prefixed_matches = collect_unique_matches(state.tree.chunks.iter().filter(|chunk| {
|
||||
if chunk.path.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let leaf = chunk.path.rsplit('.').next().unwrap_or(chunk.path.as_str());
|
||||
let expected = chunk.kind.path_segment(Some(cleaned));
|
||||
leaf == expected || leaf == cleaned
|
||||
}));
|
||||
if !prefixed_matches.is_empty() {
|
||||
return resolve_matches(
|
||||
prefixed_matches,
|
||||
@@ -472,17 +448,12 @@ fn kind_path_matches(candidate: &ChunkNode, kind_segments: &[&str]) -> bool {
|
||||
.zip(path_segments)
|
||||
.all(|(kind, segment)| {
|
||||
segment == *kind
|
||||
|| segment.starts_with(&format!("{kind}_"))
|
||||
|| strip_known_chunk_prefix(segment) == Some(*kind)
|
||||
|| segment
|
||||
.split_once('_')
|
||||
.is_some_and(|(prefix, identifier)| prefix == *kind || identifier == *kind)
|
||||
})
|
||||
}
|
||||
|
||||
fn strip_known_chunk_prefix(segment: &str) -> Option<&str> {
|
||||
CHUNK_NAME_PREFIXES
|
||||
.iter()
|
||||
.find_map(|prefix| segment.strip_prefix(prefix))
|
||||
}
|
||||
|
||||
/// Format a chunk path with its CRC suffix, e.g. `fn_start#ABCD`.
|
||||
fn format_chunk_ref(tree: &ChunkTree, path: &str) -> String {
|
||||
if let Some(chunk) = find_chunk_by_path(tree, path) {
|
||||
@@ -678,6 +649,7 @@ fn chunk_read_path_separator_index(value: &str) -> Option<usize> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::chunk::kind::ChunkKind;
|
||||
|
||||
fn chunk(
|
||||
path: &str,
|
||||
@@ -685,26 +657,36 @@ mod tests {
|
||||
parent_path: Option<&str>,
|
||||
children: Vec<&str>,
|
||||
) -> ChunkNode {
|
||||
let leaf = path.rsplit('.').next().unwrap_or(path);
|
||||
let kind = match leaf.split_once('_').map_or(leaf, |(prefix, _)| prefix) {
|
||||
"fn" => ChunkKind::Function,
|
||||
"class" => ChunkKind::Class,
|
||||
"try" => ChunkKind::Try,
|
||||
_ => ChunkKind::Chunk,
|
||||
};
|
||||
ChunkNode {
|
||||
path: path.to_owned(),
|
||||
name: path.rsplit('.').next().unwrap_or(path).to_owned(),
|
||||
leaf: children.is_empty(),
|
||||
parent_path: parent_path.map(str::to_owned),
|
||||
children: children.into_iter().map(str::to_owned).collect(),
|
||||
signature: None,
|
||||
start_line: 1,
|
||||
end_line: 1,
|
||||
line_count: 1,
|
||||
start_byte: 0,
|
||||
end_byte: 0,
|
||||
path: path.to_owned(),
|
||||
identifier: leaf
|
||||
.split_once('_')
|
||||
.and_then(|(_, identifier)| (!identifier.is_empty()).then_some(identifier.to_owned())),
|
||||
kind,
|
||||
leaf: children.is_empty(),
|
||||
parent_path: parent_path.map(str::to_owned),
|
||||
children: children.into_iter().map(str::to_owned).collect(),
|
||||
signature: None,
|
||||
start_line: 1,
|
||||
end_line: 1,
|
||||
line_count: 1,
|
||||
start_byte: 0,
|
||||
end_byte: 0,
|
||||
checksum_start_byte: 0,
|
||||
prologue_end_byte: None,
|
||||
prologue_end_byte: None,
|
||||
epilogue_start_byte: None,
|
||||
checksum: checksum.to_owned(),
|
||||
error: false,
|
||||
indent: 0,
|
||||
indent_char: " ".to_owned(),
|
||||
group: false,
|
||||
checksum: checksum.to_owned(),
|
||||
error: false,
|
||||
indent: 0,
|
||||
indent_char: " ".to_owned(),
|
||||
group: false,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ use super::{
|
||||
build_chunk_tree,
|
||||
indent::{detect_file_indent_char, detect_file_indent_step, normalize_to_tabs},
|
||||
resolve::{
|
||||
ParsedSelector, chunk_region_range, chunk_supports_region, format_region_ref,
|
||||
resolve_chunk_selector, resolve_chunk_with_crc, split_selector_crc_and_region,
|
||||
ParsedSelector, chunk_region_range, format_region_ref, resolve_chunk_selector,
|
||||
resolve_chunk_with_crc, split_selector_crc_and_region,
|
||||
},
|
||||
};
|
||||
use crate::chunk::types::{
|
||||
@@ -273,8 +273,8 @@ impl ChunkState {
|
||||
}
|
||||
|
||||
/// Look up [`ChunkInfo`] for a chunk selector path.
|
||||
#[napi(js_name = "chunk")]
|
||||
pub fn chunk_info_for_path(&self, chunk_path: String) -> Option<ChunkInfo> {
|
||||
#[napi]
|
||||
pub fn chunk(&self, chunk_path: String) -> Option<ChunkInfo> {
|
||||
let mut warnings = Vec::new();
|
||||
resolve_chunk_selector(self.inner(), Some(chunk_path.as_str()), &mut warnings)
|
||||
.ok()
|
||||
@@ -413,14 +413,9 @@ impl ChunkState {
|
||||
if selector.as_deref() == Some("?") {
|
||||
let mut lines = vec![format!("{} chunks:", params.display_path)];
|
||||
for chunk in self.inner.chunks().filter(|chunk| !chunk.path.is_empty()) {
|
||||
let supported_regions = if chunk_supports_region(chunk, ChunkRegion::Inner) {
|
||||
"outer, head, inner, tail"
|
||||
} else {
|
||||
"outer"
|
||||
};
|
||||
lines.push(format!(
|
||||
" {}#{} L{}-L{} regions: {}",
|
||||
chunk.path, chunk.checksum, chunk.start_line, chunk.end_line, supported_regions
|
||||
" {}#{} L{}-L{}",
|
||||
chunk.path, chunk.checksum, chunk.start_line, chunk.end_line,
|
||||
));
|
||||
}
|
||||
return Ok(ReadResult { text: lines.join("\n"), chunk: None });
|
||||
@@ -448,24 +443,6 @@ impl ChunkState {
|
||||
// selector and loses the region suffix.
|
||||
let selector_ref = format_region_ref(chunk, region);
|
||||
|
||||
if let Some(r) = region
|
||||
&& !chunk_supports_region(chunk, r)
|
||||
{
|
||||
return Ok(ReadResult {
|
||||
text: format!(
|
||||
"{}:{}\n\nChunk \"{}\" does not support @{}.",
|
||||
params.display_path,
|
||||
chunk.path,
|
||||
chunk.path,
|
||||
r.as_str(),
|
||||
),
|
||||
chunk: Some(ChunkReadTarget {
|
||||
status: ChunkReadStatus::UnsupportedRegion,
|
||||
selector: selector_ref,
|
||||
}),
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(absolute_line_range) = params.absolute_line_range {
|
||||
let req_start = absolute_line_range.start_line;
|
||||
let req_end = absolute_line_range.end_line;
|
||||
@@ -492,18 +469,7 @@ impl ChunkState {
|
||||
|
||||
if let Some(target_region) = region {
|
||||
let masked_source = mask_chunk_display_source(self.inner.source(), self.inner.language());
|
||||
let (start, end) = match chunk_region_range(chunk, target_region) {
|
||||
Ok(range) => range,
|
||||
Err(err) => {
|
||||
return Ok(ReadResult {
|
||||
text: format!("{}\n\n{}", params.display_path, err),
|
||||
chunk: Some(ChunkReadTarget {
|
||||
status: ChunkReadStatus::UnsupportedRegion,
|
||||
selector: selector_ref,
|
||||
}),
|
||||
});
|
||||
},
|
||||
};
|
||||
let (start, end) = chunk_region_range(chunk, target_region);
|
||||
let tab_replacement = params.tab_replacement.as_deref().unwrap_or(" ");
|
||||
let normalize_indent = params.normalize_indent.unwrap_or(false).then(|| {
|
||||
(
|
||||
@@ -598,7 +564,7 @@ fn normalize_language(language: &str) -> String {
|
||||
fn chunk_info(chunk: &ChunkNode) -> ChunkInfo {
|
||||
ChunkInfo {
|
||||
path: chunk.path.clone(),
|
||||
name: chunk.name.clone(),
|
||||
identifier: chunk.identifier.clone(),
|
||||
checksum: chunk.checksum.clone(),
|
||||
start_line: chunk.start_line,
|
||||
end_line: chunk.end_line,
|
||||
|
||||
@@ -7,7 +7,7 @@ use crate::chunk::{kind::ChunkKind, state::ChunkState};
|
||||
#[derive(Clone)]
|
||||
pub struct ChunkNode {
|
||||
pub path: String,
|
||||
pub name: String,
|
||||
pub identifier: Option<String>,
|
||||
pub kind: ChunkKind,
|
||||
pub leaf: bool,
|
||||
pub parent_path: Option<String>,
|
||||
@@ -58,8 +58,8 @@ pub struct ChunkTree {
|
||||
pub struct ChunkInfo {
|
||||
/// Chunk selector path within the tree.
|
||||
pub path: String,
|
||||
/// Short display name for the chunk (e.g. symbol or region label).
|
||||
pub name: String,
|
||||
/// Bare chunk identifier (without kind prefix), if available.
|
||||
pub identifier: Option<String>,
|
||||
/// Stable checksum anchor for this chunk.
|
||||
pub checksum: String,
|
||||
/// 1-based start line in the source file (inclusive).
|
||||
|
||||
@@ -1390,7 +1390,7 @@ fn grep_sync(
|
||||
let use_gitignore = options.gitignore.unwrap_or(true);
|
||||
let use_cache = options.cache.unwrap_or(false);
|
||||
let glob_set = glob_util::try_compile_glob(options.glob.as_deref(), true)?;
|
||||
let type_filter = resolve_type_filter(options.r#type.as_deref());
|
||||
let type_filter = resolve_type_filter(options.type_filter.as_deref());
|
||||
|
||||
let params = SearchParams {
|
||||
context_before,
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Breaking Changes
|
||||
|
||||
- Changed `ChunkRegion` enum values from `Container`, `Prologue`, `Body`, `Epilogue` to `Head`, `Inner`, `Tail` with updated semantics for region targeting
|
||||
@@ -28,6 +27,7 @@
|
||||
|
||||
### Changed
|
||||
|
||||
- Changed `ChunkInfo.name` field to optional `identifier` field — now provides bare chunk identifier without kind prefix instead of display name
|
||||
- Updated `region` parameter documentation in `EditOperation` to clarify full chunk targeting when omitted instead of container-scoped default
|
||||
- Updated `ChunkEditOp` documentation to reflect region-scoped semantics — operations now target specific regions rather than chunk structure positions
|
||||
- Changed `ChunkEditOp.Replace` documentation to clarify substring replacement via `find` parameter instead of line-based replacement
|
||||
|
||||
Vendored
+2
-2
@@ -412,8 +412,8 @@ export declare enum ChunkFocusMode {
|
||||
export interface ChunkInfo {
|
||||
/** Chunk selector path within the tree. */
|
||||
path: string
|
||||
/** Short display name for the chunk (e.g. symbol or region label). */
|
||||
name: string
|
||||
/** Bare chunk identifier (without kind prefix), if available. */
|
||||
identifier?: string
|
||||
/** Stable checksum anchor for this chunk. */
|
||||
checksum: string
|
||||
/** 1-based start line in the source file (inclusive). */
|
||||
|
||||
Reference in New Issue
Block a user