feat: establish first-class lexical language parity #2
5 changed files with 111 additions and 16 deletions
feat(scan): model Rust impl scopes and call forms
commit
ee8b7d2fb5
|
|
@ -14,7 +14,7 @@ contracts and fixtures reach the same standard.
|
|||
| Language | Useful current coverage | Important gaps | Tier |
|
||||
| --- | --- | --- | --- |
|
||||
| Dart | Classes, mixins, enums, extensions, methods, named constructors, accessors, import/export/part directives, `extends`/`with`/`implements`/`on`, and ordinary calls | operators, generic relationship targets, conditional directives, and multiline declarations | first-class target |
|
||||
| Rust | Structs, enums, traits, ordinary/async/const/unsafe/extern functions, simple `use`, calls, and raw-string masking | impl/module ownership, unions/type aliases, macros, turbofish calls, and braced/glob imports | first-class target |
|
||||
| Rust | Structs, enums, traits, ordinary/async/const/unsafe/extern functions, simple inherent/trait impl ownership, ordinary/turbofish/macro calls, braced `use` resolution, and raw-string masking | module ownership, unions/type aliases, nested generic impl headers, glob imports, and macro definitions | first-class target |
|
||||
| C | Files, basic includes, structs, and narrow function forms through the shared C/C++ profile | distinct C identity, enums/typedefs/function pointers, multiline declarations, macros, and declaration masking | first-class target |
|
||||
| C++ | Basic classes/structs, narrow function forms, calls, and local include resolution | templates, constructors/destructors/operators, multiple inheritance, namespaces, overloads, lambdas, qualifiers, and multiline declarations | first-class target |
|
||||
| JavaScript | Named functions, simple classes/extends, ES imports, and call-shaped expressions | methods, arrows/function expressions, CommonJS, dynamic imports, re-exports, decorators, and template literals | first-class target |
|
||||
|
|
|
|||
56
src/scan.rs
56
src/scan.rs
|
|
@ -279,6 +279,23 @@ impl ScanState {
|
|||
}
|
||||
}
|
||||
|
||||
if let Some(pattern) = patterns.enclosing_scope.as_ref()
|
||||
&& let Some(name) = pattern
|
||||
.captures(line)
|
||||
.and_then(|captures| captures.name("name"))
|
||||
&& let Some(id) = self
|
||||
.symbols_by_name
|
||||
.get(&(language.into(), name.as_str().into()))
|
||||
.and_then(|ids| (ids.len() == 1).then(|| ids[0].clone()))
|
||||
&& self.node_kind(&id) == Some(NodeKind::Class)
|
||||
{
|
||||
scopes.push(Scope {
|
||||
id,
|
||||
indent,
|
||||
brace_depth: brace_depth + 1,
|
||||
});
|
||||
}
|
||||
|
||||
if let Some(captures) = patterns.class.captures(line)
|
||||
&& let Some(name) = captures.name("name")
|
||||
{
|
||||
|
|
@ -365,7 +382,7 @@ impl ScanState {
|
|||
if let Some(target) = captures.name("name").or_else(|| captures.name("name2")) {
|
||||
self.pending.push(PendingRelationship {
|
||||
source: file_id.into(),
|
||||
target_name: clean_target(target.as_str()),
|
||||
target_name: clean_import_target(target.as_str(), language),
|
||||
kind: EdgeKind::Imports,
|
||||
language: language.into(),
|
||||
member_call: false,
|
||||
|
|
@ -376,7 +393,11 @@ impl ScanState {
|
|||
}
|
||||
}
|
||||
|
||||
for captures in patterns.call.captures_iter(&call_line) {
|
||||
for captures in patterns
|
||||
.call
|
||||
.iter()
|
||||
.flat_map(|pattern| pattern.captures_iter(&call_line))
|
||||
{
|
||||
let Some(target_match) = captures.name("name") else {
|
||||
continue;
|
||||
};
|
||||
|
|
@ -602,8 +623,9 @@ struct Patterns {
|
|||
class: Regex,
|
||||
function: Vec<Regex>,
|
||||
import: Regex,
|
||||
call: Regex,
|
||||
call: Vec<Regex>,
|
||||
relationships: Option<Regex>,
|
||||
enclosing_scope: Option<Regex>,
|
||||
}
|
||||
|
||||
impl Patterns {
|
||||
|
|
@ -651,6 +673,13 @@ impl Patterns {
|
|||
None,
|
||||
),
|
||||
};
|
||||
let mut calls = vec![r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\("];
|
||||
if language == "rust" {
|
||||
calls.extend([
|
||||
r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*::\s*<[^>]+>\s*\(",
|
||||
r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)!\s*\(",
|
||||
]);
|
||||
}
|
||||
Self {
|
||||
class: Regex::new(class).expect("class regex is static"),
|
||||
function: functions
|
||||
|
|
@ -658,10 +687,18 @@ impl Patterns {
|
|||
.map(|pattern| Regex::new(pattern).expect("function regex is static"))
|
||||
.collect(),
|
||||
import: Regex::new(import).expect("import regex is static"),
|
||||
call: Regex::new(r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\(")
|
||||
.expect("call regex is static"),
|
||||
call: calls
|
||||
.into_iter()
|
||||
.map(|pattern| Regex::new(pattern).expect("call regex is static"))
|
||||
.collect(),
|
||||
relationships: relationships
|
||||
.map(|pattern| Regex::new(pattern).expect("relationship regex is static")),
|
||||
enclosing_scope: (language == "rust").then(|| {
|
||||
Regex::new(
|
||||
r"^\s*impl(?:<[^>]*>)?\s+(?:(?:[A-Za-z_]\w*(?:::\w+)*)\s+for\s+)?(?P<name>[A-Za-z_]\w*)",
|
||||
)
|
||||
.expect("scope regex is static")
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -748,6 +785,15 @@ fn clean_target(value: &str) -> String {
|
|||
.into()
|
||||
}
|
||||
|
||||
fn clean_import_target(value: &str, language: &str) -> String {
|
||||
let target = clean_target(value);
|
||||
if language == "rust" {
|
||||
target.trim_end_matches("::").into()
|
||||
} else {
|
||||
target
|
||||
}
|
||||
}
|
||||
|
||||
fn import_keys(name: &str, language: &str) -> Vec<String> {
|
||||
let mut keys = vec![name.to_owned()];
|
||||
match language {
|
||||
|
|
|
|||
14
tests/fixtures/languages/rust/basic.rs
vendored
14
tests/fixtures/languages/rust/basic.rs
vendored
|
|
@ -1,12 +1,18 @@
|
|||
use crate::support::Dependency;
|
||||
mod support;
|
||||
|
||||
use crate::support::{helper, Dependency};
|
||||
|
||||
pub struct Worker;
|
||||
|
||||
pub fn start() {
|
||||
helper();
|
||||
impl Worker {
|
||||
pub fn start() {
|
||||
helper();
|
||||
generic::<usize>();
|
||||
trace!();
|
||||
}
|
||||
}
|
||||
|
||||
fn helper() {}
|
||||
fn generic<T>() {}
|
||||
|
||||
pub const fn constant() -> usize {
|
||||
1
|
||||
|
|
|
|||
3
tests/fixtures/languages/rust/support.rs
vendored
Normal file
3
tests/fixtures/languages/rust/support.rs
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
pub struct Dependency;
|
||||
|
||||
pub fn helper() {}
|
||||
|
|
@ -27,11 +27,18 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
|
|||
FixtureCase {
|
||||
directory: "rust",
|
||||
language: "rust",
|
||||
classes: &["Worker"],
|
||||
functions: &["constant", "ffi_entry", "helper", "start", "unsafe_op"],
|
||||
classes: &["Dependency", "Worker"],
|
||||
functions: &[
|
||||
"constant",
|
||||
"ffi_entry",
|
||||
"generic",
|
||||
"helper",
|
||||
"start",
|
||||
"unsafe_op",
|
||||
],
|
||||
imports: 1,
|
||||
inherits: 0,
|
||||
calls: 1,
|
||||
calls: 3,
|
||||
},
|
||||
FixtureCase {
|
||||
directory: "c",
|
||||
|
|
@ -76,10 +83,37 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
|
|||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rust_fixture_models_impl_ownership_and_local_imports() {
|
||||
let root = fixture_root("rust");
|
||||
let building = scan(&root).unwrap();
|
||||
let worker = building
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.kind == NodeKind::Class && node.name == "Worker")
|
||||
.unwrap();
|
||||
let start = building
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.kind == NodeKind::Function && node.name == "start")
|
||||
.unwrap();
|
||||
let support = building
|
||||
.nodes
|
||||
.iter()
|
||||
.find(|node| node.kind == NodeKind::File && node.path.as_deref() == Some("support.rs"))
|
||||
.unwrap();
|
||||
|
||||
assert_eq!(start.parent.as_deref(), Some(worker.id.as_str()));
|
||||
assert!(
|
||||
building
|
||||
.edges
|
||||
.iter()
|
||||
.any(|edge| { edge.kind == EdgeKind::Imports && edge.target == support.id })
|
||||
);
|
||||
}
|
||||
|
||||
fn assert_fixture(case: FixtureCase) {
|
||||
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/languages")
|
||||
.join(case.directory);
|
||||
let root = fixture_root(case.directory);
|
||||
let building = scan(&root).unwrap();
|
||||
let mut classes: Vec<_> = building
|
||||
.nodes
|
||||
|
|
@ -126,6 +160,12 @@ fn assert_fixture(case: FixtureCase) {
|
|||
);
|
||||
}
|
||||
|
||||
fn fixture_root(directory: &str) -> PathBuf {
|
||||
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/languages")
|
||||
.join(directory)
|
||||
}
|
||||
|
||||
fn edge_count(building: &brick::Building, kind: EdgeKind) -> usize {
|
||||
building
|
||||
.edges
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue