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 |
|
||||
| --- | --- | --- | --- |
|
||||
| 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 |
|
||||
| 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 |
|
||||
|
|
|
|||
77
src/scan.rs
77
src/scan.rs
|
|
@ -290,10 +290,26 @@ impl ScanState {
|
|||
NodeKind::Class,
|
||||
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 {
|
||||
source: id.clone(),
|
||||
target_name: clean_target(base.as_str()),
|
||||
target_name: base,
|
||||
kind: EdgeKind::Inherits,
|
||||
language: language.into(),
|
||||
member_call: false,
|
||||
|
|
@ -310,11 +326,13 @@ impl ScanState {
|
|||
}
|
||||
|
||||
let mut definition_name = None;
|
||||
if let Some(captures) = patterns.function.captures(line)
|
||||
&& let Some(name) = captures.name("name")
|
||||
&& !is_keyword(name.as_str())
|
||||
if let Some(name) = patterns.function.iter().find_map(|pattern| {
|
||||
pattern
|
||||
.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
|
||||
.iter()
|
||||
.rev()
|
||||
|
|
@ -325,7 +343,7 @@ impl ScanState {
|
|||
parent_id,
|
||||
path,
|
||||
language,
|
||||
name.as_str(),
|
||||
&name,
|
||||
NodeKind::Function,
|
||||
line_number,
|
||||
);
|
||||
|
|
@ -365,7 +383,10 @@ impl ScanState {
|
|||
let target = target_match.as_str();
|
||||
let prefix = call_line[..target_match.start()].trim_end();
|
||||
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 {
|
||||
source: source.clone(),
|
||||
target_name: clean_target(target),
|
||||
|
|
@ -579,46 +600,68 @@ struct Scope {
|
|||
|
||||
struct Patterns {
|
||||
class: Regex,
|
||||
function: Regex,
|
||||
function: Vec<Regex>,
|
||||
import: Regex,
|
||||
call: Regex,
|
||||
relationships: Option<Regex>,
|
||||
}
|
||||
|
||||
impl Patterns {
|
||||
fn new(language: &str) -> Self {
|
||||
let (class, function, import) = match language {
|
||||
let (class, functions, import, relationships) = match language {
|
||||
"python" => (
|
||||
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.]*))",
|
||||
None,
|
||||
),
|
||||
"rust" => (
|
||||
r"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:struct|enum|trait)\s+(?P<name>[A-Za-z_]\w*)",
|
||||
r#"^\s*(?:pub(?:\([^)]*\))?\s+)?(?:(?:const|async|unsafe)\s+)*(?:extern(?:\s+"[^"]+")?\s+)?fn\s+(?P<name>[A-Za-z_]\w*)\s*(?:<[^>]*>)?\s*\("#,
|
||||
vec![
|
||||
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:]*)",
|
||||
None,
|
||||
),
|
||||
"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+)?(?: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>[^'\"]+)['\"]"#,
|
||||
None,
|
||||
),
|
||||
"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*(?:(?: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>[^'\"]+)['\"]"#,
|
||||
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*(?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*(?:[\w:<>,*&]+\s+)+(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\([^;{}]*\)\s*(?:const\s*)?(?:noexcept\s*)?\{?\s*$",
|
||||
vec![
|
||||
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>[^>\"]+)[>\"]"#,
|
||||
None,
|
||||
),
|
||||
};
|
||||
Self {
|
||||
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"),
|
||||
call: Regex::new(r"(?P<name>[A-Za-z_]\w*(?:::\w+)*)\s*\(")
|
||||
.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';
|
||||
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() {
|
||||
helper();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,10 +18,10 @@ fn first_class_language_fixtures_have_stable_baseline_evidence() {
|
|||
FixtureCase {
|
||||
directory: "dart",
|
||||
language: "dart",
|
||||
classes: &["Controller"],
|
||||
functions: &["helper", "start"],
|
||||
imports: 1,
|
||||
inherits: 1,
|
||||
classes: &["Controller", "Logging"],
|
||||
functions: &["Controller.named", "helper", "label", "label", "start"],
|
||||
imports: 3,
|
||||
inherits: 3,
|
||||
calls: 1,
|
||||
},
|
||||
FixtureCase {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue