code-gauge 10.4.0 → 10.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -1,34 +1,38 @@
1
- use rustc_hash::{FxHashMap, FxHashSet};
1
+ use rustc_hash::FxHashMap;
2
+ use std::sync::OnceLock;
2
3
  use tree_sitter::Node;
3
4
 
4
- use crate::tree_index::NodeExt;
5
+ use crate::languages::LANGUAGES;
6
+ use crate::tree_index::{KindSet, NodeExt};
5
7
  use crate::types::CognitiveBlock;
6
8
  use crate::util::{all_children, node_text, Source};
7
9
 
8
- /// Node-type lookup sets built once per measurement from the language definition.
10
+ /// Node-type lookup sets of a language.
9
11
  pub struct LanguageSets {
10
- pub function_nodes: FxHashSet<&'static str>,
11
- pub decision_nodes: FxHashSet<&'static str>,
12
- pub nesting_nodes: FxHashSet<&'static str>,
13
- pub ncss_nodes: FxHashSet<&'static str>,
14
- pub ncss_containers: FxHashSet<&'static str>,
15
- pub ncss_bare_body_parents: FxHashSet<&'static str>,
12
+ pub function_nodes: KindSet,
13
+ pub decision_nodes: KindSet,
14
+ pub nesting_nodes: KindSet,
15
+ pub ncss_nodes: KindSet,
16
+ pub ncss_containers: KindSet,
17
+ pub ncss_bare_body_parents: KindSet,
16
18
  }
17
19
 
18
20
  impl LanguageSets {
19
- pub fn new(language: &crate::languages::LanguageDefinition) -> Self {
20
- LanguageSets {
21
- function_nodes: language.function_node_types.iter().copied().collect(),
22
- decision_nodes: language.decision_node_types.iter().copied().collect(),
23
- nesting_nodes: language.nesting_node_types.iter().copied().collect(),
24
- ncss_nodes: language.ncss_node_types.iter().copied().collect(),
25
- ncss_containers: language.ncss_container_node_types.iter().copied().collect(),
26
- ncss_bare_body_parents: language
27
- .ncss_bare_body_parent_node_types
28
- .iter()
29
- .copied()
30
- .collect(),
31
- }
21
+ pub fn of(language: &crate::languages::LanguageDefinition) -> &'static LanguageSets {
22
+ static SETS: [OnceLock<LanguageSets>; LANGUAGES.len()] =
23
+ [const { OnceLock::new() }; LANGUAGES.len()];
24
+ let index = LANGUAGES
25
+ .iter()
26
+ .position(|candidate| candidate.name == language.name)
27
+ .expect("every language is listed in LANGUAGES");
28
+ SETS[index].get_or_init(|| LanguageSets {
29
+ function_nodes: KindSet::new(language.function_node_types),
30
+ decision_nodes: KindSet::new(language.decision_node_types),
31
+ nesting_nodes: KindSet::new(language.nesting_node_types),
32
+ ncss_nodes: KindSet::new(language.ncss_node_types),
33
+ ncss_containers: KindSet::new(language.ncss_container_node_types),
34
+ ncss_bare_body_parents: KindSet::new(language.ncss_bare_body_parent_node_types),
35
+ })
32
36
  }
33
37
  }
34
38
 
@@ -54,15 +58,15 @@ pub fn is_lambda_body_block(node: Node<'_>) -> bool {
54
58
  /// A function node with a body of its own: bodyless declarations (abstract methods, auto-property
55
59
  /// accessors, accessor-list properties) open no nesting frame, so members inside them are not
56
60
  /// charged as nested functions.
57
- pub fn is_function_boundary(node: Node<'_>, function_nodes: &FxHashSet<&'static str>) -> bool {
58
- function_nodes.contains(node.kind_name())
61
+ pub fn is_function_boundary(node: Node<'_>, function_nodes: &KindSet) -> bool {
62
+ function_nodes.contains(node)
59
63
  && !is_lambda_body_block(node)
60
64
  && crate::functions::is_implemented_function(node)
61
65
  }
62
66
 
63
67
  // Sonar cognitive complexity charges a switch/match once as a whole, not per case label. Only
64
68
  // named nodes are consulted, so anonymous keyword tokens never match.
65
- const SWITCH_LIKE_NODE_TYPES: &[&str] = &[
69
+ static SWITCH_LIKE_KINDS: KindSet = KindSet::new(&[
66
70
  "switch_statement",
67
71
  "switch_expression",
68
72
  "expression_switch_statement",
@@ -73,10 +77,10 @@ const SWITCH_LIKE_NODE_TYPES: &[&str] = &[
73
77
  "when_expression",
74
78
  "case",
75
79
  "case_match",
76
- ];
80
+ ]);
77
81
 
78
82
  // Per-case decision nodes: cyclomatic-only, because the switch itself carries the cognitive cost.
79
- const CASE_CLAUSE_NODE_TYPES: &[&str] = &[
83
+ static CASE_CLAUSE_KINDS: KindSet = KindSet::new(&[
80
84
  "case_clause",
81
85
  "switch_case",
82
86
  "switch_block_statement_group",
@@ -91,7 +95,7 @@ const CASE_CLAUSE_NODE_TYPES: &[&str] = &[
91
95
  "when_entry",
92
96
  "when",
93
97
  "in_clause",
94
- ];
98
+ ]);
95
99
 
96
100
  const IF_LIKE_NODE_TYPES: &[&str] = &["if_statement", "if_expression", "if", "unless"];
97
101
 
@@ -303,13 +307,12 @@ impl FunctionBodyPass<'_, '_, '_> {
303
307
  let counts_for_own_body = !inside_nested_region && !opens_frame;
304
308
 
305
309
  let is_decision = self.is_decision(current);
306
- let is_case_clause =
307
- current.is_named() && CASE_CLAUSE_NODE_TYPES.contains(&current.kind_name());
310
+ let is_case_clause = current.is_named() && CASE_CLAUSE_KINDS.contains(current);
308
311
  // Ruby's `case ... else` arm is an `else` node; like every other language's default branch
309
312
  // it nests its contents inside the switch (it cannot go in the Ruby nesting set because
310
313
  // `if`/`begin` else branches would then double-nest under their already-nesting parent).
311
314
  let is_nesting = current.is_named()
312
- && (self.sets.nesting_nodes.contains(current.kind_name())
315
+ && (self.sets.nesting_nodes.contains(current)
313
316
  || (current.kind_name() == "else" && is_case_else_parent(parent)));
314
317
  // `elsif`/`elif`/`else if` continue a flat chain: they add a decision without a nesting
315
318
  // surcharge (Sonar cognitive-complexity semantics).
@@ -317,8 +320,7 @@ impl FunctionBodyPass<'_, '_, '_> {
317
320
  let is_boolean_operator = is_boolean_operator(current, parent, self.code);
318
321
  let is_pattern_guard = is_pattern_guard(current, parent);
319
322
 
320
- let is_switch_like =
321
- current.is_named() && SWITCH_LIKE_NODE_TYPES.contains(&current.kind_name());
323
+ let is_switch_like = current.is_named() && SWITCH_LIKE_KINDS.contains(current);
322
324
  // Opened before the node's own increments so that the block includes them. The sentinel
323
325
  // frame's blocks would be those of top-level code, which no function reports.
324
326
  if self.frames.len() > 1
@@ -513,8 +515,7 @@ impl FunctionBodyPass<'_, '_, '_> {
513
515
  // Anonymous keyword tokens can share a type with named nodes (Ruby's `if` node contains an
514
516
  // `if` keyword token), so only named nodes count as decisions.
515
517
  node.is_named()
516
- && (self.sets.decision_nodes.contains(node.kind_name())
517
- || crate::util::is_else_if_clause(node))
518
+ && (self.sets.decision_nodes.contains(node) || crate::util::is_else_if_clause(node))
518
519
  && !is_pathless_switch_branch(node)
519
520
  }
520
521
 
@@ -1,4 +1,3 @@
1
- use rustc_hash::FxHashSet;
2
1
  use tree_sitter::Node;
3
2
 
4
3
  use crate::functions::{
@@ -6,7 +5,7 @@ use crate::functions::{
6
5
  find_string_literal_content, find_value_binding, is_value_group, next_declarator,
7
6
  unwrap_transparent_value_wrappers,
8
7
  };
9
- use crate::tree_index::NodeExt;
8
+ use crate::tree_index::{KindSet, NodeExt};
10
9
  use crate::util::{named_children, node_text, Source};
11
10
 
12
11
  const CONTAINER_NODE_TYPES: &[&str] = &[
@@ -58,7 +57,7 @@ const CPP_SCOPE_NODE_TYPES: &[&str] = &[
58
57
  /// enclosing it.
59
58
  fn enclosing_cpp_scopes<'s>(
60
59
  node: Node<'_>,
61
- function_nodes: &FxHashSet<&'static str>,
60
+ function_nodes: &KindSet,
62
61
  code: &Source<'s>,
63
62
  namespaces_only: bool,
64
63
  ) -> Vec<&'s str> {
@@ -98,7 +97,7 @@ fn explicit_ruby_owner(object: Option<Node<'_>>, code: &Source<'_>) -> Option<St
98
97
  /// method); or the path a JavaScript object literal holding it is bound to.
99
98
  pub fn find_container_name(
100
99
  node: Node<'_>,
101
- function_nodes: &FxHashSet<&'static str>,
100
+ function_nodes: &KindSet,
102
101
  code: &Source<'_>,
103
102
  inline_namespaces: &InlineNamespaces<'_>,
104
103
  ) -> Option<String> {
@@ -201,7 +200,7 @@ fn assigns_attribute(assignment: Node<'_>, target: Node<'_>) -> bool {
201
200
  /// to nothing the syntax names.
202
201
  fn object_owner(
203
202
  object: Node<'_>,
204
- function_nodes: &FxHashSet<&'static str>,
203
+ function_nodes: &KindSet,
205
204
  code: &Source<'_>,
206
205
  inline_namespaces: &InlineNamespaces<'_>,
207
206
  ) -> Option<String> {
@@ -293,7 +292,7 @@ fn find_qualified_declarator(node: Node<'_>) -> Option<Node<'_>> {
293
292
  fn cpp_spelled_owner(
294
293
  node: Node<'_>,
295
294
  qualified: Node<'_>,
296
- function_nodes: &FxHashSet<&'static str>,
295
+ function_nodes: &KindSet,
297
296
  code: &Source<'_>,
298
297
  inline_namespaces: &InlineNamespaces<'_>,
299
298
  ) -> Option<String> {
@@ -431,7 +430,7 @@ fn holds_namespaces_without_scope(node: Node<'_>) -> bool {
431
430
  /// the function no member of it.
432
431
  fn enclosing_owner(
433
432
  node: Node<'_>,
434
- function_nodes: &FxHashSet<&'static str>,
433
+ function_nodes: &KindSet,
435
434
  code: &Source<'_>,
436
435
  inline_namespaces: &InlineNamespaces<'_>,
437
436
  ) -> Option<String> {
@@ -463,7 +462,7 @@ fn enclosing_owner(
463
462
  fn spell_owner(
464
463
  container: Node<'_>,
465
464
  is_friend: bool,
466
- function_nodes: &FxHashSet<&'static str>,
465
+ function_nodes: &KindSet,
467
466
  code: &Source<'_>,
468
467
  inline_namespaces: &InlineNamespaces<'_>,
469
468
  ) -> Option<String> {
@@ -2,7 +2,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
2
2
  use tree_sitter::Node;
3
3
 
4
4
  use crate::complexity::is_function_boundary;
5
- use crate::tree_index::NodeExt;
5
+ use crate::tree_index::{KindSet, NodeExt};
6
6
  use crate::util::{node_text, Source};
7
7
 
8
8
  /// Leaf node types treated as variable references by the def-use approximation.
@@ -89,65 +89,153 @@ const DEFINITION_LIST_HOLDER_TYPES: &[&str] = &[
89
89
  /// Parameter-position fields that annotate or initialize rather than bind (`x: T`, `x = default`).
90
90
  const NON_BINDING_PARAMETER_FIELDS: &[&str] = &["type", "value"];
91
91
 
92
- /// One leaf of the def-use walk: its field in the parent and its function-scope chain.
92
+ /// One leaf of the def-use walk: its field in the parent and its function scope.
93
93
  struct DepDegreeLeaf<'t> {
94
94
  node: Node<'t>,
95
95
  field_name: Option<&'t str>,
96
- /// Chain of nested-function scope ids below the measured function: "" for its own body,
97
- /// then "/0", "/0/1", ...
98
- scope: String,
96
+ /// Preorder number of the innermost function scope holding the leaf (see LeafWalk).
97
+ scope: usize,
99
98
  }
100
99
 
101
- /// Approximate def-use pairs of the function's subtree.
102
- pub fn measure_dep_degree(
103
- function_node: Node<'_>,
100
+ /// The non-comment leaves of a subtree in walk order, with the function scopes they lie in.
101
+ struct LeafWalk<'t> {
102
+ leaves: Vec<DepDegreeLeaf<'t>>,
103
+ /// The last scope nested in each scope, by preorder number; scope 0 is the walked subtree's
104
+ /// own, and scope `outer` encloses scope `inner` exactly when
105
+ /// `outer <= inner && inner <= last_nested_scopes[outer]`.
106
+ last_nested_scopes: Vec<usize>,
107
+ /// The leaf range and body scope of each function, by node id, whose subtree the walk entered.
108
+ function_spans: FxHashMap<usize, (std::ops::Range<usize>, usize)>,
109
+ }
110
+
111
+ /// What a variable leaf does in the def-use walk, which depends only on the leaf and the leaf
112
+ /// after it.
113
+ #[derive(Clone, Copy)]
114
+ struct VariableUse<'a> {
115
+ name: &'a str,
116
+ next_operator: Option<AssignmentOperator>,
117
+ /// Whether the leaf is a definition even when no assignment operator follows it.
118
+ is_binding: bool,
119
+ }
120
+
121
+ #[derive(Clone, Copy, PartialEq)]
122
+ enum AssignmentOperator {
123
+ Pure,
124
+ Compound,
125
+ }
126
+
127
+ /// Approximate def-use pairs of the subtree of each function in `functions`, by node id: one walk
128
+ /// of the file gathers every function's leaves, since a function's leaves and the scopes they lie
129
+ /// in are the same wherever the walk starts.
130
+ pub fn measure_dep_degrees(
131
+ root: Node<'_>,
132
+ functions: &[Node<'_>],
104
133
  code: &Source<'_>,
105
- function_nodes: &FxHashSet<&'static str>,
106
- ) -> u64 {
107
- let mut leaves = Vec::new();
108
- let mut next_scope_id = 0usize;
109
- collect_dep_degree_leaves(
110
- function_node,
111
- None,
112
- "",
113
- &mut next_scope_id,
134
+ function_nodes: &KindSet,
135
+ ) -> FxHashMap<usize, u64> {
136
+ let walk = LeafWalk::new(
137
+ root,
114
138
  function_nodes,
115
- &mut leaves,
116
- true,
139
+ &functions.iter().map(Node::id).collect(),
117
140
  );
118
- let mut definition_scopes_by_name: FxHashMap<&str, Vec<String>> = FxHashMap::default();
141
+ let mut variable_uses = vec![None; walk.leaves.len()];
142
+ functions
143
+ .iter()
144
+ .map(|&function_node| {
145
+ let (leaf_range, body_scope) = walk
146
+ .function_spans
147
+ .get(&function_node.id())
148
+ .expect("the walk enters every function boundary");
149
+ let pairs = count_pairs(
150
+ &walk,
151
+ leaf_range.clone(),
152
+ *body_scope,
153
+ function_node,
154
+ code,
155
+ &mut variable_uses,
156
+ );
157
+ (function_node.id(), pairs)
158
+ })
159
+ .collect()
160
+ }
161
+
162
+ /// The def-use pairs among the leaves in `leaf_range` of the function whose own body is
163
+ /// `body_scope`; `variable_uses` caches what each leaf of the walk does, as `Some(None)` for a
164
+ /// leaf that is no variable.
165
+ fn count_pairs<'a>(
166
+ walk: &LeafWalk<'_>,
167
+ leaf_range: std::ops::Range<usize>,
168
+ body_scope: usize,
169
+ function_node: Node<'_>,
170
+ code: &Source<'a>,
171
+ variable_uses: &mut [Option<Option<VariableUse<'a>>>],
172
+ ) -> u64 {
173
+ let is_visible = |definition_scopes: Option<&Vec<usize>>, scope: usize| {
174
+ definition_scopes.is_some_and(|scopes| {
175
+ scopes
176
+ .iter()
177
+ .any(|&outer| outer <= scope && scope <= walk.last_nested_scopes[outer])
178
+ })
179
+ };
180
+ let mut definition_scopes_by_name: FxHashMap<&str, Vec<usize>> = FxHashMap::default();
119
181
  for name in implicit_accessor_definitions(function_node, code) {
120
- add_definition(&mut definition_scopes_by_name, name, "");
182
+ add_definition(&mut definition_scopes_by_name, name, body_scope);
121
183
  }
184
+ let leaf_end = leaf_range.end;
122
185
  let mut pairs = 0u64;
123
- for index in 0..leaves.len() {
124
- let leaf = &leaves[index];
125
- if !is_variable_leaf(leaf) {
186
+ for index in leaf_range {
187
+ let leaf = &walk.leaves[index];
188
+ let Some(variable_use) =
189
+ *variable_uses[index].get_or_insert_with(|| variable_use(&walk.leaves, index, code))
190
+ else {
126
191
  continue;
127
- }
128
- let name = node_text(leaf.node, code);
129
- let next_text = leaves.get(index + 1).map(|next| node_text(next.node, code));
130
- if next_text.is_some_and(|next| COMPOUND_ASSIGNMENT_OPERATORS.contains(&next)) {
131
- if is_definition_visible(definition_scopes_by_name.get(name), &leaf.scope) {
192
+ };
193
+ let name = variable_use.name;
194
+ // The function's last leaf has no next leaf within the function.
195
+ let next_operator = variable_use.next_operator.filter(|_| index + 1 != leaf_end);
196
+ if next_operator == Some(AssignmentOperator::Compound) {
197
+ if is_visible(definition_scopes_by_name.get(name), leaf.scope) {
132
198
  pairs += 1;
133
199
  }
134
- add_definition(&mut definition_scopes_by_name, name, &leaf.scope);
200
+ add_definition(&mut definition_scopes_by_name, name, leaf.scope);
135
201
  continue;
136
202
  }
137
- if next_text.is_some_and(|next| PURE_ASSIGNMENT_OPERATORS.contains(&next))
138
- || is_structural_definition(leaf)
139
- || is_parameter_definition(leaf)
140
- {
141
- add_definition(&mut definition_scopes_by_name, name, &leaf.scope);
203
+ if next_operator == Some(AssignmentOperator::Pure) || variable_use.is_binding {
204
+ add_definition(&mut definition_scopes_by_name, name, leaf.scope);
142
205
  continue;
143
206
  }
144
- if is_definition_visible(definition_scopes_by_name.get(name), &leaf.scope) {
207
+ if is_visible(definition_scopes_by_name.get(name), leaf.scope) {
145
208
  pairs += 1;
146
209
  }
147
210
  }
148
211
  pairs
149
212
  }
150
213
 
214
+ fn variable_use<'a>(
215
+ leaves: &[DepDegreeLeaf<'_>],
216
+ index: usize,
217
+ code: &Source<'a>,
218
+ ) -> Option<VariableUse<'a>> {
219
+ let leaf = &leaves[index];
220
+ if !is_variable_leaf(leaf) {
221
+ return None;
222
+ }
223
+ let next_text = leaves.get(index + 1).map(|next| node_text(next.node, code));
224
+ Some(VariableUse {
225
+ name: node_text(leaf.node, code),
226
+ next_operator: next_text.and_then(|next| {
227
+ if COMPOUND_ASSIGNMENT_OPERATORS.contains(&next) {
228
+ Some(AssignmentOperator::Compound)
229
+ } else if PURE_ASSIGNMENT_OPERATORS.contains(&next) {
230
+ Some(AssignmentOperator::Pure)
231
+ } else {
232
+ None
233
+ }
234
+ }),
235
+ is_binding: is_structural_definition(leaf) || is_parameter_definition(leaf),
236
+ })
237
+ }
238
+
151
239
  /// Kotlin spells type names as identifiers: in a `user_type`, or as a type parameter's fieldless
152
240
  /// name (C# puts its type parameter in a `name` field).
153
241
  fn is_variable_leaf(leaf: &DepDegreeLeaf<'_>) -> bool {
@@ -215,81 +303,86 @@ fn implicit_accessor_definitions<'a>(function_node: Node<'_>, code: &Source<'a>)
215
303
  names
216
304
  }
217
305
 
218
- /// Collects non-comment leaves with their parent field (one cursor pass, so children of
219
- /// high-arity nodes cost O(1)) and function-scope chain; mirrors collectDepDegreeLeaves.
220
- #[allow(clippy::too_many_arguments)]
221
- fn collect_dep_degree_leaves<'t>(
222
- node: Node<'t>,
223
- field_name: Option<&'t str>,
224
- scope: &str,
225
- next_scope_id: &mut usize,
226
- function_nodes: &FxHashSet<&'static str>,
227
- leaves: &mut Vec<DepDegreeLeaf<'t>>,
228
- is_measured_root: bool,
229
- ) {
230
- if matches!(
231
- node.kind_name(),
232
- "comment" | "line_comment" | "block_comment"
233
- ) {
234
- return;
235
- }
236
- if crate::util::is_token(node) {
237
- leaves.push(DepDegreeLeaf {
238
- node,
239
- field_name,
240
- scope: scope.to_string(),
241
- });
242
- return;
306
+ impl<'t> LeafWalk<'t> {
307
+ /// Walks the subtree of `root`, recording the span of each function in `function_ids` it
308
+ /// enters.
309
+ fn new(
310
+ root: Node<'t>,
311
+ function_nodes: &KindSet,
312
+ function_ids: &FxHashSet<usize>,
313
+ ) -> LeafWalk<'t> {
314
+ let mut walk = LeafWalk {
315
+ leaves: Vec::new(),
316
+ last_nested_scopes: vec![0],
317
+ function_spans: FxHashMap::default(),
318
+ };
319
+ walk.visit(root, None, 0, function_nodes, function_ids);
320
+ // The walked subtree's own scope encloses every scope opened inside it.
321
+ walk.last_nested_scopes[0] = walk.last_nested_scopes.len() - 1;
322
+ walk
243
323
  }
244
- let child_scope = if !is_measured_root && is_function_boundary(node, function_nodes) {
245
- let scope = format!("{scope}/{next_scope_id}");
246
- *next_scope_id += 1;
247
- scope
248
- } else {
249
- scope.to_string()
250
- };
251
- let mut cursor = node.walk();
252
- if cursor.goto_first_child() {
253
- loop {
254
- collect_dep_degree_leaves(
255
- cursor.node(),
256
- cursor.field_name(),
257
- &child_scope,
258
- next_scope_id,
324
+
325
+ fn visit(
326
+ &mut self,
327
+ node: Node<'t>,
328
+ field_name: Option<&'t str>,
329
+ scope: usize,
330
+ function_nodes: &KindSet,
331
+ function_ids: &FxHashSet<usize>,
332
+ ) {
333
+ if matches!(
334
+ node.kind_name(),
335
+ "comment" | "line_comment" | "block_comment"
336
+ ) {
337
+ return;
338
+ }
339
+ if crate::util::is_token(node) {
340
+ self.leaves.push(DepDegreeLeaf {
341
+ node,
342
+ field_name,
343
+ scope,
344
+ });
345
+ return;
346
+ }
347
+ let opens_scope = is_function_boundary(node, function_nodes);
348
+ let child_scope = if opens_scope {
349
+ self.last_nested_scopes.push(0);
350
+ self.last_nested_scopes.len() - 1
351
+ } else {
352
+ scope
353
+ };
354
+ let first_leaf = self.leaves.len();
355
+ for (child, child_field_name) in crate::util::children_with_field_names(node) {
356
+ self.visit(
357
+ child,
358
+ child_field_name,
359
+ child_scope,
259
360
  function_nodes,
260
- leaves,
261
- false,
361
+ function_ids,
262
362
  );
263
- if !cursor.goto_next_sibling() {
264
- break;
265
- }
363
+ }
364
+ if opens_scope {
365
+ self.last_nested_scopes[child_scope] = self.last_nested_scopes.len() - 1;
366
+ }
367
+ // Every measured function is a function boundary.
368
+ if opens_scope && function_ids.contains(&node.id()) {
369
+ self.function_spans
370
+ .insert(node.id(), (first_leaf..self.leaves.len(), child_scope));
266
371
  }
267
372
  }
268
373
  }
269
374
 
270
375
  fn add_definition<'a>(
271
- definition_scopes_by_name: &mut FxHashMap<&'a str, Vec<String>>,
376
+ definition_scopes_by_name: &mut FxHashMap<&'a str, Vec<usize>>,
272
377
  name: &'a str,
273
- scope: &str,
378
+ scope: usize,
274
379
  ) {
275
380
  let scopes = definition_scopes_by_name.entry(name).or_default();
276
- if !scopes.iter().any(|existing| existing == scope) {
277
- scopes.push(scope.to_string());
381
+ if !scopes.contains(&scope) {
382
+ scopes.push(scope);
278
383
  }
279
384
  }
280
385
 
281
- /// A definition reaches a read only from the read's own or an enclosing function scope.
282
- fn is_definition_visible(definition_scopes: Option<&Vec<String>>, scope: &str) -> bool {
283
- definition_scopes.is_some_and(|scopes| {
284
- scopes.iter().any(|definition_scope| {
285
- scope == definition_scope
286
- || (scope.len() > definition_scope.len()
287
- && scope.starts_with(definition_scope.as_str())
288
- && scope.as_bytes()[definition_scope.len()] == b'/')
289
- })
290
- })
291
- }
292
-
293
386
  fn is_structural_definition(leaf: &DepDegreeLeaf<'_>) -> bool {
294
387
  let Some(parent) = leaf.node.parent_node() else {
295
388
  return false;
@@ -4,6 +4,8 @@ use tree_sitter::Node;
4
4
  use crate::tree_index::NodeExt;
5
5
  use crate::util::{all_children, find_children_by_field_name, named_children, node_text, Source};
6
6
 
7
+ // code-gauge-allow-file fileNcss 648 -- holds the function-recognition and naming rules of every language
8
+
7
9
  /// Declarations without a body have no control flow, so they are signatures, not functions: C++
8
10
  /// pure-virtual/`= default`/`= delete` members, abstract/interface/extern methods (Java, C#,
9
11
  /// Kotlin), Go assembly-backed function declarations, and Rust trait method signatures, matching
@@ -194,11 +196,11 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
194
196
  })
195
197
  }
196
198
 
197
- pub fn collect_nodes<'t>(root: Node<'t>, matches: impl Fn(&str) -> bool) -> Vec<Node<'t>> {
199
+ pub fn collect_nodes<'t>(root: Node<'t>, matches: impl Fn(Node<'t>) -> bool) -> Vec<Node<'t>> {
198
200
  let mut nodes = Vec::new();
199
201
 
200
- fn visit<'t>(node: Node<'t>, matches: &impl Fn(&str) -> bool, nodes: &mut Vec<Node<'t>>) {
201
- if matches(node.kind_name()) {
202
+ fn visit<'t>(node: Node<'t>, matches: &impl Fn(Node<'t>) -> bool, nodes: &mut Vec<Node<'t>>) {
203
+ if matches(node) {
202
204
  nodes.push(node);
203
205
  }
204
206
 
@@ -36,7 +36,7 @@ enum GrammarId {
36
36
  Tsx,
37
37
  }
38
38
 
39
- const GRAMMAR_COUNT: usize = GrammarId::Tsx as usize + 1;
39
+ pub const GRAMMAR_COUNT: usize = GrammarId::Tsx as usize + 1;
40
40
 
41
41
  impl LanguageDefinition {
42
42
  pub fn grammar(&self) -> Language {
@@ -56,6 +56,11 @@ impl LanguageDefinition {
56
56
  }
57
57
  }
58
58
 
59
+ /// Distinct for each grammar, below GRAMMAR_COUNT.
60
+ pub fn grammar_index(&self) -> usize {
61
+ self.grammar_id as usize
62
+ }
63
+
59
64
  /// The grammar's node kind names, indexed by kind id.
60
65
  pub fn kind_names(&self) -> &'static [String] {
61
66
  static KIND_NAMES: [OnceLock<Vec<String>>; GRAMMAR_COUNT] =