feat: establish first-class lexical language parity #2
4 changed files with 76 additions and 23 deletions
feat(scan): expand Dart architecture relationships
commit
42bda779f3
|
|
@ -13,7 +13,7 @@ contracts and fixtures reach the same standard.
|
||||||
|
|
||||||
| Language | Useful current coverage | Important gaps | Tier |
|
| Language | Useful current coverage | Important gaps | Tier |
|
||||||
| --- | --- | --- | --- |
|
| --- | --- | --- | --- |
|
||||||
| Dart | Classes, mixins, enums, extensions, many methods, imports, `extends`, and ordinary calls | `with`/`implements`, export/part, named constructors, accessors/operators, and multiline declarations | first-class target |
|
| 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 `use`, calls, and raw-string masking | impl/module ownership, unions/type aliases, macros, turbofish calls, and braced/glob imports | 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 | 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 |
|
| 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 |
|
||||||
|
|
|
||||||
71
src/scan.rs
71
src/scan.rs
|
|
@ -290,10 +290,26 @@ impl ScanState {
|
||||||
NodeKind::Class,
|
NodeKind::Class,
|
||||||
line_number,
|
line_number,
|
||||||
);
|
);
|
||||||
if let Some(base) = captures.name("base") {
|
let mut bases: Vec<String> = patterns
|
||||||
|
.relationships
|
||||||
|
.as_ref()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|pattern| pattern.captures_iter(line))
|
||||||
|
.filter_map(|captures| captures.name("names"))
|
||||||
|
.flat_map(|names| names.as_str().split(','))
|
||||||
|
.map(str::trim)
|
||||||
|
.filter(|name| !name.is_empty())
|
||||||
|
.map(clean_target)
|
||||||
|
.collect();
|
||||||
|
if bases.is_empty()
|
||||||
|
&& let Some(base) = captures.name("base")
|
||||||
|
{
|
||||||
|
bases.push(clean_target(base.as_str()));
|
||||||
|
}
|
||||||
|
for base in bases {
|
||||||
self.pending.push(PendingRelationship {
|
self.pending.push(PendingRelationship {
|
||||||
source: id.clone(),
|
source: id.clone(),
|
||||||
target_name: clean_target(base.as_str()),
|
target_name: base,
|
||||||
kind: EdgeKind::Inherits,
|
kind: EdgeKind::Inherits,
|
||||||
language: language.into(),
|
language: language.into(),
|
||||||
member_call: false,
|
member_call: false,
|
||||||
|
|
@ -310,11 +326,13 @@ impl ScanState {
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut definition_name = None;
|
let mut definition_name = None;
|
||||||
if let Some(captures) = patterns.function.captures(line)
|
if let Some(name) = patterns.function.iter().find_map(|pattern| {
|
||||||
&& let Some(name) = captures.name("name")
|
pattern
|
||||||
&& !is_keyword(name.as_str())
|
.captures(line)
|
||||||
|
.and_then(|captures| captures.name("name").map(|name| name.as_str().to_owned()))
|
||||||
|
}) && !is_keyword(&name)
|
||||||
{
|
{
|
||||||
definition_name = Some(name.as_str().to_owned());
|
definition_name = Some(name.clone());
|
||||||
let parent_id = scopes
|
let parent_id = scopes
|
||||||
.iter()
|
.iter()
|
||||||
.rev()
|
.rev()
|
||||||
|
|
@ -325,7 +343,7 @@ impl ScanState {
|
||||||
parent_id,
|
parent_id,
|
||||||
path,
|
path,
|
||||||
language,
|
language,
|
||||||
name.as_str(),
|
&name,
|
||||||
NodeKind::Function,
|
NodeKind::Function,
|
||||||
line_number,
|
line_number,
|
||||||
);
|
);
|
||||||
|
|
@ -365,7 +383,10 @@ impl ScanState {
|
||||||
let target = target_match.as_str();
|
let target = target_match.as_str();
|
||||||
let prefix = call_line[..target_match.start()].trim_end();
|
let prefix = call_line[..target_match.start()].trim_end();
|
||||||
let member_call = prefix.ends_with('.') || prefix.ends_with("->");
|
let member_call = prefix.ends_with('.') || prefix.ends_with("->");
|
||||||
if definition_name.as_deref() != Some(target) && !is_keyword(target) {
|
let definition_call_name = definition_name
|
||||||
|
.as_deref()
|
||||||
|
.and_then(|name| name.rsplit('.').next());
|
||||||
|
if definition_call_name != Some(target) && !is_keyword(target) {
|
||||||
self.pending.push(PendingRelationship {
|
self.pending.push(PendingRelationship {
|
||||||
source: source.clone(),
|
source: source.clone(),
|
||||||
target_name: clean_target(target),
|
target_name: clean_target(target),
|
||||||
|
|
@ -579,46 +600,68 @@ struct Scope {
|
||||||
|
|
||||||
struct Patterns {
|
struct Patterns {
|
||||||
class: Regex,
|
class: Regex,
|
||||||
function: Regex,
|
function: Vec<Regex>,
|
||||||
import: Regex,
|
import: Regex,
|
||||||
call: Regex,
|
call: Regex,
|
||||||
|
relationships: Option<Regex>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Patterns {
|
impl Patterns {
|
||||||
fn new(language: &str) -> Self {
|
fn new(language: &str) -> Self {
|
||||||
let (class, function, import) = match language {
|
let (class, functions, import, relationships) = match language {
|
||||||
"python" => (
|
"python" => (
|
||||||
r"^\s*class\s+(?P<name>[A-Za-z_]\w*)(?:\((?P<base>[A-Za-z_][\w.]*)\))?",
|
r"^\s*class\s+(?P<name>[A-Za-z_]\w*)(?:\((?P<base>[A-Za-z_][\w.]*)\))?",
|
||||||
r"^\s*(?:async\s+)?def\s+(?P<name>[A-Za-z_]\w*)\s*\(",
|
vec![r"^\s*(?:async\s+)?def\s+(?P<name>[A-Za-z_]\w*)\s*\("],
|
||||||
r"^\s*(?:from\s+(?P<name>[A-Za-z_][\w.]*)\s+import|import\s+(?P<name2>[A-Za-z_][\w.]*))",
|
r"^\s*(?:from\s+(?P<name>[A-Za-z_][\w.]*)\s+import|import\s+(?P<name2>[A-Za-z_][\w.]*))",
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
"rust" => (
|
"rust" => (
|
||||||
r"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:struct|enum|trait)\s+(?P<name>[A-Za-z_]\w*)",
|
r"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:struct|enum|trait)\s+(?P<name>[A-Za-z_]\w*)",
|
||||||
|
vec![
|
||||||
r#"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:(?:const|async|unsafe)\s+)*(?:extern(?:\s+"[^"]+")?\s+)?fn\s+(?P<name>[A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\("#,
|
r#"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:(?:const|async|unsafe)\s+)*(?:extern(?:\s+"[^"]+")?\s+)?fn\s+(?P<name>[A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\("#,
|
||||||
|
],
|
||||||
r"^\s*use\s+(?P<name>[A-Za-z_][\w:]*)",
|
r"^\s*use\s+(?P<name>[A-Za-z_][\w:]*)",
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
"javascript" | "typescript" => (
|
"javascript" | "typescript" => (
|
||||||
r"^\s*(?:export\s+)?class\s+(?P<name>[A-Za-z_$][\w$]*)(?:\s+extends\s+(?P<base>[A-Za-z_$][\w$]*))?",
|
r"^\s*(?:export\s+)?class\s+(?P<name>[A-Za-z_$][\w$]*)(?:\s+extends\s+(?P<base>[A-Za-z_$][\w$]*))?",
|
||||||
r"^\s*(?:export\s+)?(?:async\s+)?function\s+(?P<name>[A-Za-z_$][\w$]*)\s*\(",
|
vec![r"^\s*(?:export\s+)?(?:async\s+)?function\s+(?P<name>[A-Za-z_$][\w$]*)\s*\("],
|
||||||
r#"^\s*import(?:[^'\"]*from\s*)?['\"](?P<name>[^'\"]+)['\"]"#,
|
r#"^\s*import(?:[^'\"]*from\s*)?['\"](?P<name>[^'\"]+)['\"]"#,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
"dart" => (
|
"dart" => (
|
||||||
r"^\s*(?:(?:abstract|base|final|interface|sealed)\s+)?(?:class|mixin|enum|extension(?:\s+type)?)\s+(?P<name>[A-Za-z_]\w*)(?:\s+extends\s+(?P<base>[A-Za-z_]\w*))?",
|
r"^\s*(?:(?:abstract|base|final|interface|sealed)\s+)?(?:class|mixin|enum|extension(?:\s+type)?)\s+(?P<name>[A-Za-z_]\w*)(?:\s+extends\s+(?P<base>[A-Za-z_]\w*))?",
|
||||||
|
vec![
|
||||||
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*(?:(?:[A-Za-z_]\w*(?:<[^>{};()]+>)?[?!]?)\s+)?(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:(?:async|sync)\*?\s*)?(?:\{|=>)",
|
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*(?:(?:[A-Za-z_]\w*(?:<[^>{};()]+>)?[?!]?)\s+)?(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:(?:async|sync)\*?\s*)?(?:\{|=>)",
|
||||||
r#"^\s*import\s+['\"](?P<name>[^'\"]+)['\"]"#,
|
r"^\s*(?P<name>[A-Za-z_]\w*\.[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:(?:async|sync)\*?\s*)?(?:\{|=>|;)",
|
||||||
|
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*(?:(?:[A-Za-z_]\w*(?:<[^>{};()]+>)?[?!]?)\s+)?get\s+(?P<name>[A-Za-z_]\w*)\s*(?:=>|\{)",
|
||||||
|
r"^\s*(?:(?:external|static|abstract|covariant|late)\s+)*set\s+(?P<name>[A-Za-z_]\w*)\s*\([^;{}]*\)\s*(?:=>|\{|;)",
|
||||||
|
],
|
||||||
|
r#"^\s*(?:import|export|part)\s+['\"](?P<name>[^'\"]+)['\"]"#,
|
||||||
|
Some(
|
||||||
|
r"\b(?:extends|with|implements|on)\s+(?P<names>[A-Za-z_]\w*(?:\s*,\s*[A-Za-z_]\w*)*)",
|
||||||
|
),
|
||||||
),
|
),
|
||||||
_ => (
|
_ => (
|
||||||
r"^\s*(?:class|struct)\s+(?P<name>[A-Za-z_]\w*)(?:\s*:\s*(?:public\s+|protected\s+|private\s+)?(?P<base>[A-Za-z_:]\w*(?:::\w+)*))?",
|
r"^\s*(?:class|struct)\s+(?P<name>[A-Za-z_]\w*)(?:\s*:\s*(?:public\s+|protected\s+|private\s+)?(?P<base>[A-Za-z_:]\w*(?:::\w+)*))?",
|
||||||
|
vec![
|
||||||
r"^\s*(?:[\w:<>,*&]+\s+)+(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{?\s*$",
|
r"^\s*(?:[\w:<>,*&]+\s+)+(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{?\s*$",
|
||||||
|
],
|
||||||
r#"^\s*#\s*include\s*[<\"](?P<name>[^>\"]+)[>\"]"#,
|
r#"^\s*#\s*include\s*[<\"](?P<name>[^>\"]+)[>\"]"#,
|
||||||
|
None,
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
Self {
|
Self {
|
||||||
class: Regex::new(class).expect("class regex is static"),
|
class: Regex::new(class).expect("class regex is static"),
|
||||||
function: Regex::new(function).expect("function regex is static"),
|
function: functions
|
||||||
|
.into_iter()
|
||||||
|
.map(|pattern| Regex::new(pattern).expect("function regex is static"))
|
||||||
|
.collect(),
|
||||||
import: Regex::new(import).expect("import regex is static"),
|
import: Regex::new(import).expect("import regex is static"),
|
||||||
call: Regex::new(r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\(")
|
call: Regex::new(r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\(")
|
||||||
.expect("call regex is static"),
|
.expect("call regex is static"),
|
||||||
|
relationships: relationships
|
||||||
|
.map(|pattern| Regex::new(pattern).expect("relationship regex is static")),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
12
tests/fixtures/languages/dart/basic.dart
vendored
12
tests/fixtures/languages/dart/basic.dart
vendored
|
|
@ -1,6 +1,16 @@
|
||||||
import 'package:example/base.dart';
|
import 'package:example/base.dart';
|
||||||
|
export 'package:example/public.dart';
|
||||||
|
part 'generated.dart';
|
||||||
|
|
||||||
|
mixin Logging {}
|
||||||
|
|
||||||
|
abstract class Controller extends Base with Logging implements Runnable {
|
||||||
|
Controller.named();
|
||||||
|
|
||||||
|
String get label => 'controller';
|
||||||
|
|
||||||
|
set label(String value) {}
|
||||||
|
|
||||||
class Controller extends Base {
|
|
||||||
void start() {
|
void start() {
|
||||||
helper();
|
helper();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,10 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
|
||||||
FixtureCase {
|
FixtureCase {
|
||||||
directory: "dart",
|
directory: "dart",
|
||||||
language: "dart",
|
language: "dart",
|
||||||
classes: &["Controller"],
|
classes: &["Controller", "Logging"],
|
||||||
functions: &["helper", "start"],
|
functions: &["Controller.named", "helper", "label", "label", "start"],
|
||||||
imports: 1,
|
imports: 3,
|
||||||
inherits: 1,
|
inherits: 3,
|
||||||
calls: 1,
|
calls: 1,
|
||||||
},
|
},
|
||||||
FixtureCase {
|
FixtureCase {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue