code-gauge 4.7.0 → 4.8.0

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.
Files changed (78) hide show
  1. package/README.md +1 -1
  2. package/THIRD-PARTY-NOTICES.txt +595 -29
  3. package/dist/cli.cjs +1 -1
  4. package/dist/cli.cjs.map +1 -1
  5. package/dist/cli.js +1 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/cliConfig.cjs +1 -1
  8. package/dist/cliConfig.cjs.map +1 -1
  9. package/dist/cliConfig.js +1 -1
  10. package/dist/cliConfig.js.map +1 -1
  11. package/dist/crossFileDuplication.cjs +1 -1
  12. package/dist/crossFileDuplication.cjs.map +1 -1
  13. package/dist/crossFileDuplication.js +1 -1
  14. package/dist/crossFileDuplication.js.map +1 -1
  15. package/dist/crossFileNearMiss.cjs +1 -1
  16. package/dist/crossFileNearMiss.cjs.map +1 -1
  17. package/dist/crossFileNearMiss.js +1 -1
  18. package/dist/crossFileNearMiss.js.map +1 -1
  19. package/dist/diffCommand.cjs +3 -3
  20. package/dist/diffCommand.cjs.map +1 -1
  21. package/dist/diffCommand.js +3 -3
  22. package/dist/diffCommand.js.map +1 -1
  23. package/dist/duplicateSelection.cjs +1 -1
  24. package/dist/duplicateSelection.cjs.map +1 -1
  25. package/dist/duplicateSelection.js +1 -1
  26. package/dist/duplicateSelection.js.map +1 -1
  27. package/dist/duplication.cjs +1 -1
  28. package/dist/duplication.cjs.map +1 -1
  29. package/dist/duplication.js +1 -1
  30. package/dist/duplication.js.map +1 -1
  31. package/dist/git.cjs +1 -1
  32. package/dist/git.cjs.map +1 -1
  33. package/dist/git.js +1 -1
  34. package/dist/git.js.map +1 -1
  35. package/dist/languages.cjs +1 -1
  36. package/dist/languages.cjs.map +1 -1
  37. package/dist/languages.js +1 -1
  38. package/dist/languages.js.map +1 -1
  39. package/dist/metrics.cjs +1 -1
  40. package/dist/metrics.cjs.map +1 -1
  41. package/dist/metrics.d.ts +8 -3
  42. package/dist/metrics.js +1 -1
  43. package/dist/metrics.js.map +1 -1
  44. package/dist/nativeMetrics.cjs +3 -3
  45. package/dist/nativeMetrics.cjs.map +1 -1
  46. package/dist/nativeMetrics.d.ts +19 -3
  47. package/dist/nativeMetrics.js +3 -3
  48. package/dist/nativeMetrics.js.map +1 -1
  49. package/dist/regressionGate.cjs +1 -1
  50. package/dist/regressionGate.cjs.map +1 -1
  51. package/dist/regressionGate.js +1 -1
  52. package/dist/regressionGate.js.map +1 -1
  53. package/dist/scan.cjs +1 -1
  54. package/dist/scan.cjs.map +1 -1
  55. package/dist/scan.d.ts +2 -2
  56. package/dist/scan.js +1 -1
  57. package/dist/scan.js.map +1 -1
  58. package/dist/wasmBinding.cjs +1 -1
  59. package/dist/wasmBinding.cjs.map +1 -1
  60. package/dist/wasmBinding.js +1 -1
  61. package/dist/wasmBinding.js.map +1 -1
  62. package/native/Cargo.lock +48 -15
  63. package/native/Cargo.toml +11 -10
  64. package/native/code-gauge.wasm +0 -0
  65. package/native/src/complexity.rs +152 -255
  66. package/native/src/dep_degree.rs +79 -65
  67. package/native/src/duplication.rs +114 -97
  68. package/native/src/functions.rs +181 -206
  69. package/native/src/languages.rs +48 -7
  70. package/native/src/lib.rs +10 -5
  71. package/native/src/measure.rs +125 -150
  72. package/native/src/napi.rs +61 -2
  73. package/native/src/ncss.rs +108 -104
  74. package/native/src/near_miss.rs +7 -7
  75. package/native/src/tree_index.rs +119 -0
  76. package/native/src/util.rs +15 -55
  77. package/native/src/worker_pool.rs +53 -0
  78. package/package.json +10 -10
@@ -1,6 +1,7 @@
1
- use std::collections::HashSet;
1
+ use rustc_hash::FxHashSet;
2
2
  use tree_sitter::Node;
3
3
 
4
+ use crate::tree_index::NodeExt;
4
5
  use crate::util::{all_children, find_children_by_field_name, named_children, node_text, Source};
5
6
 
6
7
  /// Declarations without a body have no control flow, so they are signatures, not functions: C++
@@ -24,31 +25,34 @@ const BODY_REQUIRED_FUNCTION_TYPES: &[&str] = &[
24
25
  ];
25
26
 
26
27
  pub fn is_implemented_function(node: Node<'_>) -> bool {
27
- if !BODY_REQUIRED_FUNCTION_TYPES.contains(&node.kind())
28
+ if !BODY_REQUIRED_FUNCTION_TYPES.contains(&node.kind_name())
28
29
  || node.child_by_field_name("body").is_some()
29
30
  {
30
31
  return true;
31
32
  }
32
33
 
33
- if node.kind() == "property_declaration" || node.kind() == "indexer_declaration" {
34
+ if node.kind_name() == "property_declaration" || node.kind_name() == "indexer_declaration" {
34
35
  return node
35
36
  .child_by_field_name("value")
36
- .is_some_and(|value| value.kind() == "arrow_expression_clause");
37
+ .is_some_and(|value| value.kind_name() == "arrow_expression_clause");
37
38
  }
38
39
 
39
- // The Kotlin grammar has no fields; an implemented function or accessor holds a
40
- // `function_body` child.
41
- if node.kind() == "getter" || node.kind() == "setter" || node.kind() == "function_declaration" {
40
+ // Kotlin has no `body` field; an implemented function or accessor holds a `function_body`
41
+ // child.
42
+ if node.kind_name() == "getter"
43
+ || node.kind_name() == "setter"
44
+ || node.kind_name() == "function_declaration"
45
+ {
42
46
  return named_children(node)
43
47
  .iter()
44
- .any(|child| child.kind() == "function_body");
48
+ .any(|child| child.kind_name() == "function_body");
45
49
  }
46
50
 
47
51
  // C++ constructor/destructor function-try-blocks carry their `try_statement` outside the
48
52
  // `body` field; they are implementations, unlike `= 0`/`= default`/`= delete` members.
49
53
  named_children(node)
50
54
  .iter()
51
- .any(|child| child.kind() == "try_statement")
55
+ .any(|child| child.kind_name() == "try_statement")
52
56
  }
53
57
 
54
58
  /// Counts declared parameters of a function/method, ignoring punctuation and comments.
@@ -64,29 +68,31 @@ pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
64
68
 
65
69
  // A Java bare lambda parameter (`x -> x + 1`) puts a lone identifier in the `parameters` field;
66
70
  // a C# one (`x => x + 1`) is an `implicit_parameter` leaf.
67
- if parameters_node.kind() == "identifier" || parameters_node.kind() == "implicit_parameter" {
71
+ if parameters_node.kind_name() == "identifier"
72
+ || parameters_node.kind_name() == "implicit_parameter"
73
+ {
68
74
  return 1;
69
75
  }
70
76
  // Kotlin default values (`x: Int = 0`) are siblings of their `parameter`, not children.
71
- if parameters_node.kind() == "function_value_parameters" {
77
+ if parameters_node.kind_name() == "function_value_parameters" {
72
78
  return named_children(parameters_node)
73
79
  .iter()
74
- .filter(|child| child.kind() == "parameter")
80
+ .filter(|child| child.kind_name() == "parameter")
75
81
  .count();
76
82
  }
77
83
  // A Kotlin setter declares its single parameter directly (`set(value) { ... }`).
78
- if parameters_node.kind() == "setter" {
84
+ if parameters_node.kind_name() == "setter" {
79
85
  return 1;
80
86
  }
81
87
  // A C# `params` array is spelled out as `type`/`name` fields of the parameter list itself.
82
- let csharp_params_array_ids: HashSet<usize> = ["type", "name"]
88
+ let csharp_params_array_ids: FxHashSet<usize> = ["type", "name"]
83
89
  .iter()
84
90
  .flat_map(|field| find_children_by_field_name(parameters_node, field))
85
91
  .map(|child| child.id())
86
92
  .collect();
87
93
 
88
94
  // Ruby block-locals after `;` (`{ |x; memo| ... }`) occupy `locals` fields and receive no arguments.
89
- let block_local_ids: HashSet<usize> = find_children_by_field_name(parameters_node, "locals")
95
+ let block_local_ids: FxHashSet<usize> = find_children_by_field_name(parameters_node, "locals")
90
96
  .iter()
91
97
  .map(|child| child.id())
92
98
  .collect();
@@ -95,22 +101,22 @@ pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
95
101
  // C/C++ `f(void)` declares none, and a Ruby block parameter (`&blk`) binds the block, which
96
102
  // call sites pass outside the argument list.
97
103
  for child in named_children(parameters_node) {
98
- if crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind())
99
- || child.kind() == "attribute_list"
104
+ if crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind_name())
105
+ || child.kind_name() == "attribute_list"
100
106
  || csharp_params_array_ids.contains(&child.id())
101
- || child.kind() == "self_parameter"
102
- || child.kind() == "receiver_parameter"
103
- || child.kind() == "block_parameter"
107
+ || child.kind_name() == "self_parameter"
108
+ || child.kind_name() == "receiver_parameter"
109
+ || child.kind_name() == "block_parameter"
104
110
  // Python's PEP 570/3102 markers (`/`, `*`) separate parameter kinds but bind nothing.
105
- || child.kind() == "positional_separator"
106
- || child.kind() == "keyword_separator"
111
+ || child.kind_name() == "positional_separator"
112
+ || child.kind_name() == "keyword_separator"
107
113
  || block_local_ids.contains(&child.id())
108
114
  || is_void_parameter(child, code)
109
115
  {
110
116
  continue;
111
117
  }
112
118
  // Go declares several names per declaration (`a, b int`); each name is a parameter.
113
- count += if child.kind() == "parameter_declaration" {
119
+ count += if child.kind_name() == "parameter_declaration" {
114
120
  find_children_by_field_name(child, "name").len().max(1)
115
121
  } else {
116
122
  1
@@ -126,7 +132,7 @@ pub fn count_parameters(node: Node<'_>, code: &Source<'_>) -> usize {
126
132
 
127
133
  /// C/C++ `int f(void)` has a `parameter_declaration` whose type is a bare `void` with no declarator.
128
134
  fn is_void_parameter(node: Node<'_>, code: &Source<'_>) -> bool {
129
- node.kind() == "parameter_declaration"
135
+ node.kind_name() == "parameter_declaration"
130
136
  && node.child_by_field_name("declarator").is_none()
131
137
  && node
132
138
  .child_by_field_name("type")
@@ -139,20 +145,20 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
139
145
  }
140
146
 
141
147
  // A C# indexer accessor (`this[int i] { get { ... } }`) takes the indexer's parameters.
142
- if node.kind() == "accessor_declaration" {
148
+ if node.kind_name() == "accessor_declaration" {
143
149
  return node
144
- .parent()
145
- .and_then(|list| list.parent())
146
- .filter(|owner| owner.kind() == "indexer_declaration")
150
+ .parent_node()
151
+ .and_then(|list| list.parent_node())
152
+ .filter(|owner| owner.kind_name() == "indexer_declaration")
147
153
  .and_then(|owner| owner.child_by_field_name("parameters"));
148
154
  }
149
155
 
150
156
  // A Java compact constructor implicitly takes the record's components, declared on the
151
157
  // `record_declaration` two levels up (via `class_body`).
152
- if node.kind() == "compact_constructor_declaration" {
158
+ if node.kind_name() == "compact_constructor_declaration" {
153
159
  return node
154
- .parent()
155
- .and_then(|parent| parent.parent())
160
+ .parent_node()
161
+ .and_then(|parent| parent.parent_node())
156
162
  .and_then(|grandparent| grandparent.child_by_field_name("parameters"));
157
163
  }
158
164
 
@@ -166,17 +172,17 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
166
172
  }
167
173
 
168
174
  // A Kotlin setter's parameter (`set(value)`) sits directly under the setter node.
169
- if node.kind() == "setter"
175
+ if node.kind_name() == "setter"
170
176
  && named_children(node)
171
177
  .iter()
172
- .any(|child| child.kind() == "parameter_with_optional_type")
178
+ .any(|child| child.kind_name() == "identifier")
173
179
  {
174
180
  return Some(node);
175
181
  }
176
182
 
177
183
  named_children(node).into_iter().find(|child| {
178
184
  matches!(
179
- child.kind(),
185
+ child.kind_name(),
180
186
  "formal_parameters"
181
187
  | "parameter_list"
182
188
  | "function_value_parameters"
@@ -185,20 +191,20 @@ fn find_parameters_node(node: Node<'_>) -> Option<Node<'_>> {
185
191
  })
186
192
  }
187
193
 
188
- pub fn collect_nodes<'t>(root: Node<'t>, node_types: &HashSet<&'static str>) -> Vec<Node<'t>> {
194
+ pub fn collect_nodes<'t>(root: Node<'t>, matches: impl Fn(&str) -> bool) -> Vec<Node<'t>> {
189
195
  let mut nodes = Vec::new();
190
196
 
191
- fn visit<'t>(node: Node<'t>, node_types: &HashSet<&'static str>, nodes: &mut Vec<Node<'t>>) {
192
- if node_types.contains(node.kind()) {
197
+ fn visit<'t>(node: Node<'t>, matches: &impl Fn(&str) -> bool, nodes: &mut Vec<Node<'t>>) {
198
+ if matches(node.kind_name()) {
193
199
  nodes.push(node);
194
200
  }
195
201
 
196
202
  for child in named_children(node) {
197
- visit(child, node_types, nodes);
203
+ visit(child, matches, nodes);
198
204
  }
199
205
  }
200
206
 
201
- visit(root, node_types, &mut nodes);
207
+ visit(root, &matches, &mut nodes);
202
208
  nodes
203
209
  }
204
210
 
@@ -209,11 +215,11 @@ pub fn collect_nodes<'t>(root: Node<'t>, node_types: &HashSet<&'static str>) ->
209
215
  /// `parenthesized_statements` wraps a lone value only when it holds exactly one statement.
210
216
  fn wrapped_transparent_value(wrapper: Node<'_>) -> Option<Node<'_>> {
211
217
  // A C-style cast (Java, C#, C/C++) names its type first, so its value comes from the field.
212
- if wrapper.kind() == "cast_expression" {
218
+ if wrapper.kind_name() == "cast_expression" {
213
219
  return wrapper.child_by_field_name("value");
214
220
  }
215
221
  if !matches!(
216
- wrapper.kind(),
222
+ wrapper.kind_name(),
217
223
  "type_assertion"
218
224
  | "parenthesized_statements"
219
225
  | "parenthesized_expression"
@@ -230,8 +236,8 @@ fn wrapped_transparent_value(wrapper: Node<'_>) -> Option<Node<'_>> {
230
236
  let children = named_children(wrapper);
231
237
  let mut values = children
232
238
  .into_iter()
233
- .filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()));
234
- match wrapper.kind() {
239
+ .filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind_name()));
240
+ match wrapper.kind_name() {
235
241
  "type_assertion" => values.next_back(),
236
242
  "parenthesized_statements" => {
237
243
  let value = values.next()?;
@@ -246,7 +252,7 @@ fn wrapped_transparent_value(wrapper: Node<'_>) -> Option<Node<'_>> {
246
252
  /// names the value.
247
253
  fn unwrap_transparent_value_wrappers(node: Node<'_>) -> Node<'_> {
248
254
  let mut bound = node;
249
- while let Some(wrapper) = bound.parent().filter(|wrapper| {
255
+ while let Some(wrapper) = bound.parent_node().filter(|wrapper| {
250
256
  wrapped_transparent_value(*wrapper).is_some_and(|value| value.id() == bound.id())
251
257
  }) {
252
258
  bound = wrapper;
@@ -279,13 +285,13 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
279
285
  }
280
286
 
281
287
  let bound = unwrap_transparent_value_wrappers(node);
282
- let parent = bound.parent()?;
288
+ let parent = bound.parent_node()?;
283
289
 
284
290
  // A Rust closure bound to a simple `let` identifier takes that identifier as its name.
285
- if node.kind() == "closure_expression" && parent.kind() == "let_declaration" {
291
+ if node.kind_name() == "closure_expression" && parent.kind_name() == "let_declaration" {
286
292
  let pattern_node = parent.child_by_field_name("pattern");
287
293
  return match pattern_node {
288
- Some(pattern) if pattern.kind() == "identifier" => {
294
+ Some(pattern) if pattern.kind_name() == "identifier" => {
289
295
  Some(node_text(pattern, code).to_string())
290
296
  }
291
297
  _ => None,
@@ -296,17 +302,17 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
296
302
  // as does one that direct-initializes a deduced variable (`auto f{[] {}}`), whose type is the
297
303
  // closure itself. With a written type (`std::thread worker([] {})`) the lambda is a constructor
298
304
  // argument, and the constructor stores whatever it likes, so it names nothing.
299
- if node.kind() == "lambda_expression" {
300
- let declaration = match parent.kind() {
305
+ if node.kind_name() == "lambda_expression" {
306
+ let declaration = match parent.kind_name() {
301
307
  "init_declarator" => Some(parent),
302
308
  "argument_list" | "initializer_list" if binding_children(parent).len() == 1 => parent
303
- .parent()
304
- .filter(|holder| holder.kind() == "init_declarator")
309
+ .parent_node()
310
+ .filter(|holder| holder.kind_name() == "init_declarator")
305
311
  .filter(|holder| declares_deduced_type(*holder))
306
312
  // `auto f = {[] {}}` deduces a list holding the closure, not the closure itself;
307
313
  // only the direct form `auto f{[] {}}` makes the variable the closure.
308
314
  .filter(|holder| {
309
- parent.kind() == "argument_list"
315
+ parent.kind_name() == "argument_list"
310
316
  || !all_children(*holder)
311
317
  .iter()
312
318
  .any(|child| !child.is_named() && node_text(*child, code) == "=")
@@ -320,41 +326,46 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
320
326
 
321
327
  // A Go func literal bound via `add := func...`, `var add = func...`, or `add = func...` takes
322
328
  // the identifier (or selector field) at the same list position.
323
- if node.kind() == "func_literal" && parent.kind() == "expression_list" {
329
+ if node.kind_name() == "func_literal" && parent.kind_name() == "expression_list" {
324
330
  return find_go_func_literal_name(bound, parent, code);
325
331
  }
326
332
 
327
333
  // Ruby and Python lambdas assigned to a name take that name.
328
- if node.kind() == "lambda" && parent.kind() == "assignment" {
334
+ if node.kind_name() == "lambda" && parent.kind_name() == "assignment" {
329
335
  return find_ruby_assignment_name(parent, code);
330
336
  }
331
- if node.kind() == "lambda" && is_value_group(parent) {
337
+ if node.kind_name() == "lambda" && is_value_group(parent) {
332
338
  return find_parallel_assignment_name(bound, code);
333
339
  }
334
340
 
335
341
  // A Kotlin lambda or anonymous function initializing a property (`val f = { ... }`, also through
336
342
  // a label or annotation prefix) takes the property name; one assigned to a variable or member
337
343
  // (`run = { ... }`, `obj.run = { ... }`) takes the assigned name.
338
- if node.kind() == "lambda_literal" || node.kind() == "anonymous_function" {
344
+ if node.kind_name() == "lambda_literal" || node.kind_name() == "anonymous_function" {
339
345
  let mut holder = parent;
340
- while holder.kind() == "prefix_expression" {
341
- holder = holder.parent()?;
346
+ while matches!(
347
+ holder.kind_name(),
348
+ "labeled_expression" | "annotated_expression"
349
+ ) {
350
+ holder = holder.parent_node()?;
342
351
  }
343
- if holder.kind() == "property_declaration" {
352
+ if holder.kind_name() == "property_declaration" {
344
353
  return find_kotlin_property_name(holder, code);
345
354
  }
346
- if holder.kind() == "assignment" {
347
- return find_kotlin_assignment_name(holder, code);
355
+ if holder.kind_name() == "assignment" {
356
+ return find_assignment_target_name(holder, code);
348
357
  }
349
358
  }
350
359
  // A `lambda { }` / `proc { }` block is measured, but the call around it is what gets bound.
351
- if (node.kind() == "block" || node.kind() == "do_block") && is_ruby_lambda_call(parent, code) {
360
+ if (node.kind_name() == "block" || node.kind_name() == "do_block")
361
+ && is_ruby_lambda_call(parent, code)
362
+ {
352
363
  let call = unwrap_transparent_value_wrappers(parent);
353
- return match call.parent() {
354
- Some(holder) if holder.kind() == "assignment" => {
364
+ return match call.parent_node() {
365
+ Some(holder) if holder.kind_name() == "assignment" => {
355
366
  find_ruby_assignment_name(holder, code)
356
367
  }
357
- Some(holder) if holder.kind() == "pair" => find_pair_key_name(holder, code),
368
+ Some(holder) if holder.kind_name() == "pair" => find_pair_key_name(holder, code),
358
369
  Some(holder) if is_value_group(holder) => find_parallel_assignment_name(call, code),
359
370
  _ => None,
360
371
  };
@@ -364,36 +375,36 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
364
375
  // after the key; an assignment
365
376
  // (`obj.run = () => {}`, `run = () => {}`, Rust/C++ `self.cb = |x| x`, C++ `N::run = [] {}`,
366
377
  // C# `this.Run = () => 1`) after its target.
367
- if parent.kind() == "pair" {
378
+ if parent.kind_name() == "pair" {
368
379
  return find_pair_key_name(parent, code);
369
380
  }
370
381
  // A Go keyed composite-literal element (`S{run: func() {}}`) names its value after the key.
371
- if parent.kind() == "literal_element" {
382
+ if parent.kind_name() == "literal_element" {
372
383
  return find_go_keyed_element_name(parent, code);
373
384
  }
374
385
  // A Rust struct-literal field (`S { cb: || 1 }`) names its closure after the field.
375
- if parent.kind() == "field_initializer" {
386
+ if parent.kind_name() == "field_initializer" {
376
387
  return parent
377
388
  .child_by_field_name("field")
378
389
  .map(|field| node_text(field, code).to_string());
379
390
  }
380
391
  // A C++20 designated initializer (`S s{.run = []{}}`) likewise names its value after the field;
381
392
  // an array designator (`{[0] = ...}`) names nothing, like a subscript assignment target.
382
- if parent.kind() == "initializer_pair" {
393
+ if parent.kind_name() == "initializer_pair" {
383
394
  return find_children_by_field_name(parent, "designator")
384
395
  .last()
385
- .filter(|designator| designator.kind() == "field_designator")
396
+ .filter(|designator| designator.kind_name() == "field_designator")
386
397
  .and_then(|designator| designator.named_child(0))
387
398
  .map(|field| node_text(field, code).to_string());
388
399
  }
389
- if parent.kind() == "assignment_expression" {
400
+ if parent.kind_name() == "assignment_expression" {
390
401
  return find_assignment_target_name(parent, code);
391
402
  }
392
403
 
393
404
  // A JavaScript class field (`handle = () => {}`) names its property through the `property`
394
405
  // field; TypeScript's `public_field_definition` exposes the same thing as `name`. A computed
395
406
  // key is as unstable here as in an object literal, and a string key is read the same way.
396
- let field_name = if parent.kind() == "field_definition" {
407
+ let field_name = if parent.kind_name() == "field_definition" {
397
408
  "property"
398
409
  } else {
399
410
  "name"
@@ -405,7 +416,7 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
405
416
  if !is_value_of_parent(bound, parent) {
406
417
  return None;
407
418
  }
408
- match name.kind() {
419
+ match name.kind_name() {
409
420
  "computed_property_name" => None,
410
421
  "string" => find_string_literal_content(name, code),
411
422
  _ => Some(node_text(name, code).to_string()),
@@ -416,7 +427,7 @@ pub fn find_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
416
427
  /// computed key (`[k]: ...`), an interpolated string or symbol, or an empty string names nothing.
417
428
  fn find_pair_key_name(pair: Node<'_>, code: &Source<'_>) -> Option<String> {
418
429
  let key = pair.child_by_field_name("key")?;
419
- match key.kind() {
430
+ match key.kind_name() {
420
431
  // A numeric key (`{ 1: () => {} }`) is as stable a property name as an identifier, signed
421
432
  // (`{ -1: ... }`) or not; any other expression is computed and names nothing.
422
433
  "property_identifier" | "hash_key_symbol" => Some(node_text(key, code).to_string()),
@@ -432,7 +443,7 @@ fn find_pair_key_name(pair: Node<'_>, code: &Source<'_>) -> Option<String> {
432
443
  for part in binding_children(key) {
433
444
  if named_children(part)
434
445
  .iter()
435
- .any(|child| child.kind() == "interpolation")
446
+ .any(|child| child.kind_name() == "interpolation")
436
447
  {
437
448
  return None;
438
449
  }
@@ -450,7 +461,7 @@ fn find_pair_key_name(pair: Node<'_>, code: &Source<'_>) -> Option<String> {
450
461
  fn core_constraint_type(constraint: Node<'_>) -> Node<'_> {
451
462
  let mut current = constraint;
452
463
  while matches!(
453
- current.kind(),
464
+ current.kind_name(),
454
465
  "type_constraint" | "negated_type" | "type_elem" | "interface_type"
455
466
  ) {
456
467
  match named_children(current).as_slice() {
@@ -474,7 +485,7 @@ fn signed_number_name(key: Node<'_>, code: &Source<'_>) -> Option<String> {
474
485
  .find(|child| !child.is_named())
475
486
  .map(|child| node_text(child, code))
476
487
  .unwrap_or_default();
477
- let text = match operand.kind() {
488
+ let text = match operand.kind_name() {
478
489
  "rune_literal" => find_string_literal_content(operand, code)?,
479
490
  _ => node_text(operand, code).to_string(),
480
491
  };
@@ -490,7 +501,7 @@ fn is_signed_number(key: Node<'_>, code: &Source<'_>) -> bool {
490
501
  signed
491
502
  && named_children(key).first().is_some_and(|operand| {
492
503
  matches!(
493
- operand.kind(),
504
+ operand.kind_name(),
494
505
  "number"
495
506
  | "integer"
496
507
  | "float"
@@ -506,14 +517,14 @@ fn is_signed_number(key: Node<'_>, code: &Source<'_>) -> bool {
506
517
  /// value the last. An unkeyed element sits under a `literal_value` instead and names nothing.
507
518
  fn find_go_keyed_element_name(value_element: Node<'_>, code: &Source<'_>) -> Option<String> {
508
519
  let keyed = value_element
509
- .parent()
510
- .filter(|parent| parent.kind() == "keyed_element")?;
520
+ .parent_node()
521
+ .filter(|parent| parent.kind_name() == "keyed_element")?;
511
522
  let elements = named_children(keyed);
512
523
  if elements.len() < 2 || elements.last()?.id() != value_element.id() {
513
524
  return None;
514
525
  }
515
526
  let key = named_children(*elements.first()?).into_iter().next()?;
516
- match key.kind() {
527
+ match key.kind_name() {
517
528
  "identifier" | "field_identifier" if !has_value_keys(keyed, code) => {
518
529
  Some(node_text(key, code).to_string())
519
530
  }
@@ -536,7 +547,7 @@ fn find_go_keyed_element_name(value_element: Node<'_>, code: &Source<'_>) -> Opt
536
547
  /// `key` holds, so it names nothing, exactly like a computed property key.
537
548
  fn has_value_keys(keyed: Node<'_>, code: &Source<'_>) -> bool {
538
549
  keyed
539
- .parent()
550
+ .parent_node()
540
551
  .and_then(|body| key_type_of_literal_body(body, code))
541
552
  .is_some_and(|declared| is_value_keyed_type(declared, code))
542
553
  }
@@ -557,7 +568,7 @@ fn value_keyed_type(declared: Node<'_>, code: &Source<'_>, depth: usize) -> bool
557
568
  return false;
558
569
  }
559
570
  let resolved = resolve_named_type(declared, code);
560
- match resolved.kind() {
571
+ match resolved.kind_name() {
561
572
  "map_type" | "slice_type" | "array_type" | "implicit_length_array_type" => true,
562
573
  "type_constraint" | "interface_type" | "type_elem" | "negated_type" => {
563
574
  // Method requirements restrict what a type does, not what it is, so only the type terms
@@ -575,14 +586,14 @@ fn value_keyed_type(declared: Node<'_>, code: &Source<'_>, depth: usize) -> bool
575
586
  /// Whether a constraint element only requires methods, directly or through an embedded interface
576
587
  /// that does; such an element leaves the underlying type free.
577
588
  fn requires_methods_only(declared: Node<'_>, code: &Source<'_>, depth: usize) -> bool {
578
- if matches!(declared.kind(), "method_elem" | "method_spec") {
589
+ if matches!(declared.kind_name(), "method_elem" | "method_spec") {
579
590
  return true;
580
591
  }
581
592
  if depth >= MAX_CONSTRAINT_DEPTH {
582
593
  return false;
583
594
  }
584
595
  let resolved = resolve_named_type(declared, code);
585
- match resolved.kind() {
596
+ match resolved.kind_name() {
586
597
  "interface_type" | "type_constraint" | "type_elem" => {
587
598
  let terms = binding_children(resolved);
588
599
  !terms.is_empty()
@@ -605,7 +616,7 @@ fn resolve_named_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Node<'t> {
605
616
  let mut visited = Vec::new();
606
617
  while !visited.contains(&current.id()) {
607
618
  visited.push(current.id());
608
- let next = match current.kind() {
619
+ let next = match current.kind_name() {
609
620
  "generic_type" => current.child_by_field_name("type"),
610
621
  // Go allows parentheses around a type; they name the type they hold.
611
622
  // Parentheses name the type they hold, and a nested literal of a pointer element type
@@ -623,7 +634,7 @@ fn resolve_named_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Node<'t> {
623
634
 
624
635
  /// The type a name stands for, resolved through the scopes enclosing it, innermost first.
625
636
  fn lookup_declared_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Option<Node<'t>> {
626
- if declared.kind() != "type_identifier" {
637
+ if declared.kind_name() != "type_identifier" {
627
638
  return None;
628
639
  }
629
640
  let name = node_text(declared, code);
@@ -636,7 +647,7 @@ fn lookup_declared_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Option<Nod
636
647
  // what the literal is written against.
637
648
  if let Some(constraint) = named_children(current)
638
649
  .into_iter()
639
- .filter(|child| child.kind() == "type_parameter_list")
650
+ .filter(|child| child.kind_name() == "type_parameter_list")
640
651
  .flat_map(|list| declared_type_parameters(list))
641
652
  .find(|(parameter_name, _)| node_text(*parameter_name, code) == name)
642
653
  .and_then(|(_, constraint)| constraint)
@@ -645,7 +656,7 @@ fn lookup_declared_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Option<Nod
645
656
  }
646
657
  // A method's receiver carries the parameters of the type it is declared on
647
658
  // (`func (r R[T]) ...`), so the name resolves through that type's declaration.
648
- if current.kind() == "method_declaration" {
659
+ if current.kind_name() == "method_declaration" {
649
660
  if let Some(constraint) = receiver_parameter_constraint(current, name, code) {
650
661
  return Some(core_constraint_type(constraint));
651
662
  }
@@ -653,7 +664,7 @@ fn lookup_declared_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Option<Nod
653
664
  if let Some(found) = find_type_spec(current, name, code) {
654
665
  return found.child_by_field_name("type");
655
666
  }
656
- scope = current.parent();
667
+ scope = current.parent_node();
657
668
  }
658
669
  None
659
670
  }
@@ -662,9 +673,9 @@ fn lookup_declared_type<'t>(declared: Node<'t>, code: &Source<'t>) -> Option<Nod
662
673
  fn find_type_spec<'t>(scope: Node<'t>, name: &str, code: &Source<'t>) -> Option<Node<'t>> {
663
674
  named_children(scope)
664
675
  .into_iter()
665
- .filter(|child| child.kind() == "type_declaration")
676
+ .filter(|child| child.kind_name() == "type_declaration")
666
677
  .flat_map(named_children)
667
- .filter(|spec| spec.kind() == "type_spec" || spec.kind() == "type_alias")
678
+ .filter(|spec| spec.kind_name() == "type_spec" || spec.kind_name() == "type_alias")
668
679
  .find(|spec| {
669
680
  spec.child_by_field_name("name")
670
681
  .is_some_and(|declared_name| node_text(declared_name, code) == name)
@@ -685,7 +696,7 @@ fn receiver_parameter_constraint<'t>(
685
696
  // A pointer or parenthesized receiver (`func (r *R[T])`, `func (r (R[T]))`) wraps the
686
697
  // instantiation, in either order.
687
698
  .map(unwrap_receiver_type)
688
- .find(|declared| declared.kind() == "generic_type")?;
699
+ .find(|declared| declared.kind_name() == "generic_type")?;
689
700
  let arguments = binding_children(instantiation.child_by_field_name("type_arguments")?);
690
701
  let position = arguments.iter().position(|argument| {
691
702
  let argument = named_children(*argument)
@@ -704,7 +715,7 @@ fn receiver_parameter_constraint<'t>(
704
715
  /// The type a receiver names, past the pointer and parenthesis wrappers it may carry.
705
716
  fn unwrap_receiver_type(declared: Node<'_>) -> Node<'_> {
706
717
  let mut current = declared;
707
- while matches!(current.kind(), "pointer_type" | "parenthesized_type") {
718
+ while matches!(current.kind_name(), "pointer_type" | "parenthesized_type") {
708
719
  match named_children(current).into_iter().next() {
709
720
  Some(inner) => current = inner,
710
721
  None => break,
@@ -718,7 +729,7 @@ fn unwrap_receiver_type(declared: Node<'_>) -> Node<'_> {
718
729
  fn declared_type_parameters<'t>(list: Node<'t>) -> Vec<(Node<'t>, Option<Node<'t>>)> {
719
730
  binding_children(list)
720
731
  .into_iter()
721
- .filter(|declaration| declaration.kind() == "type_parameter_declaration")
732
+ .filter(|declaration| declaration.kind_name() == "type_parameter_declaration")
722
733
  .flat_map(|declaration| {
723
734
  let constraint = declaration.child_by_field_name("type");
724
735
  find_children_by_field_name(declaration, "name")
@@ -734,24 +745,24 @@ fn declared_type_parameters<'t>(list: Node<'t>) -> Vec<(Node<'t>, Option<Node<'t
734
745
  /// {k: f}}` nests a map, `[]S{{run: f}}` a struct). A literal nested in a struct field keeps the
735
746
  /// struct reading, since the field's type is not written at the literal.
736
747
  fn key_type_of_literal_body<'t>(body: Node<'t>, code: &Source<'t>) -> Option<Node<'t>> {
737
- let parent = body.parent()?;
738
- if parent.kind() == "composite_literal" {
748
+ let parent = body.parent_node()?;
749
+ if parent.kind_name() == "composite_literal" {
739
750
  return parent.child_by_field_name("type");
740
751
  }
741
- if parent.kind() != "literal_element" {
752
+ if parent.kind_name() != "literal_element" {
742
753
  return None;
743
754
  }
744
- let mut container = parent.parent()?;
745
- if container.kind() == "keyed_element" {
746
- container = container.parent()?;
755
+ let mut container = parent.parent_node()?;
756
+ if container.kind_name() == "keyed_element" {
757
+ container = container.parent_node()?;
747
758
  }
748
- if container.kind() != "literal_value" {
759
+ if container.kind_name() != "literal_value" {
749
760
  return None;
750
761
  }
751
762
  // The container's own type may be a name, which the element type is read through.
752
763
  let declared = key_type_of_literal_body(container, code)?;
753
764
  let holder = resolve_named_type(declared, code);
754
- let element = match holder.kind() {
765
+ let element = match holder.kind_name() {
755
766
  "map_type" => holder.child_by_field_name("value"),
756
767
  "slice_type" | "array_type" | "implicit_length_array_type" => {
757
768
  holder.child_by_field_name("element")
@@ -768,7 +779,7 @@ fn instantiated_type_argument<'t>(
768
779
  declared: Node<'t>,
769
780
  code: &Source<'t>,
770
781
  ) -> Option<Node<'t>> {
771
- if element.kind() != "type_identifier" || declared.kind() != "generic_type" {
782
+ if element.kind_name() != "type_identifier" || declared.kind_name() != "generic_type" {
772
783
  return None;
773
784
  }
774
785
  let base = declared.child_by_field_name("type")?;
@@ -793,7 +804,7 @@ fn find_declared_type_spec<'t>(from: Node<'t>, name: &str, code: &Source<'t>) ->
793
804
  if let Some(spec) = find_type_spec(current, name, code) {
794
805
  return Some(spec);
795
806
  }
796
- scope = current.parent();
807
+ scope = current.parent_node();
797
808
  }
798
809
  None
799
810
  }
@@ -804,7 +815,10 @@ fn find_declared_type_spec<'t>(from: Node<'t>, name: &str, code: &Source<'t>) ->
804
815
  /// `string_content` (Python nests escapes inside it). Interpolation makes the key unstable.
805
816
  fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<String> {
806
817
  let children = named_children(literal);
807
- if children.iter().any(|child| child.kind() == "interpolation") {
818
+ if children
819
+ .iter()
820
+ .any(|child| child.kind_name() == "interpolation")
821
+ {
808
822
  return None;
809
823
  }
810
824
  // Go exposes no content node at all: it names only the escapes, so the concatenation is
@@ -812,7 +826,7 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
812
826
  let mut content = String::new();
813
827
  let mut has_content_node = false;
814
828
  for child in &children {
815
- match child.kind() {
829
+ match child.kind_name() {
816
830
  "string_content" | "string_fragment" => {
817
831
  has_content_node = true;
818
832
  content.push_str(node_text(*child, code));
@@ -841,8 +855,8 @@ fn find_string_literal_content(literal: Node<'_>, code: &Source<'_>) -> Option<S
841
855
  }
842
856
 
843
857
  /// A compound assignment (`x += f`) does not bind the function to its target, so only a plain `=`
844
- /// names it. Grammars with an `operator` field (C/C++, C#, Java) expose it directly; Go and Kotlin
845
- /// have none, so the operator is the assignment's own anonymous token child.
858
+ /// names it. Grammars with an `operator` field (C/C++, C#, Java, Kotlin) expose it directly; Go has
859
+ /// none, so the operator is the assignment's own anonymous token child.
846
860
  fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
847
861
  match assignment.child_by_field_name("operator") {
848
862
  Some(operator) => node_text(operator, code) == "=",
@@ -852,81 +866,63 @@ fn is_plain_assignment(assignment: Node<'_>, code: &Source<'_>) -> bool {
852
866
  }
853
867
  }
854
868
 
855
- /// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member, or Java
856
- /// field access (`a.b.run` names `run`) or a C++ qualified name (`N::run`); subscripts (`o["run"]`)
857
- /// name nothing.
869
+ /// The assigned identifier, or the member name of a JS member, Rust/C++ field, C# member, Java
870
+ /// field access, or Kotlin navigation (`a.b.run` names `run`) or a C++ qualified name (`N::run`);
871
+ /// subscripts (`o["run"]`) name nothing.
858
872
  fn find_assignment_target_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
859
873
  if !is_plain_assignment(assignment, code) {
860
874
  return None;
861
875
  }
862
876
  let target = assignment.child_by_field_name("left")?;
863
- let name = match target.kind() {
877
+ let name = match target.kind_name() {
864
878
  "identifier" => target,
865
879
  "member_expression" => target.child_by_field_name("property")?,
866
880
  "field_expression" => target.child_by_field_name("field")?,
867
881
  "member_access_expression" => target.child_by_field_name("name")?,
868
882
  "field_access" => target.child_by_field_name("field")?,
883
+ "navigation_expression" => target.named_child(target.named_child_count() - 1)?,
869
884
  "qualified_identifier" => return unwrap_declarator_name(Some(target), code),
870
885
  _ => return None,
871
886
  };
872
887
  Some(node_text(name, code).to_string())
873
888
  }
874
889
 
875
- /// The Kotlin assignment target: the variable itself or the member of a trailing navigation suffix
876
- /// (`obj.run` names `run`); a trailing indexing suffix (`arr[0] = { }`) names nothing, like
877
- /// subscripts in the other languages.
878
- fn find_kotlin_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<String> {
879
- if !is_plain_assignment(assignment, code) {
880
- return None;
881
- }
882
- let target = first_named_child_of_kind(assignment, "directly_assignable_expression")?;
883
- let children = named_children(target);
884
- let holder = match children.last()? {
885
- last if last.kind() == "navigation_suffix" => *last,
886
- last if last.kind() == "simple_identifier" && children.len() == 1 => target,
887
- _ => return None,
888
- };
889
- first_named_child_of_kind(holder, "simple_identifier")
890
- .map(|name| node_text(name, code).to_string())
891
- }
892
-
893
890
  /// Names of C# and Kotlin members whose grammars carry no usable `name` field: accessors are
894
891
  /// named after their property (`Count.get`; an expression-bodied property is its own getter),
895
- /// Kotlin functions by their identifier child, Kotlin secondary constructors and C# destructors
896
- /// after their class, and C# operators like C++ ones.
892
+ /// Kotlin secondary constructors and C# destructors after their class, and C# operators like C++
893
+ /// ones.
897
894
  fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
898
- match node.kind() {
895
+ match node.kind_name() {
899
896
  "accessor_declaration" => {
900
897
  let keyword = node_text(node.child_by_field_name("name")?, code);
901
- let owner = node.parent()?.parent()?;
898
+ let owner = node.parent_node()?.parent_node()?;
902
899
  Some(format!("{}.{keyword}", csharp_property_name(owner, code)?))
903
900
  }
904
901
  "property_declaration" | "indexer_declaration" => {
905
902
  Some(format!("{}.get", csharp_property_name(node, code)?))
906
903
  }
907
904
  "getter" | "setter" => {
908
- let keyword = if node.kind() == "getter" {
905
+ let keyword = if node.kind_name() == "getter" {
909
906
  "get"
910
907
  } else {
911
908
  "set"
912
909
  };
913
910
  Some(format!(
914
911
  "{}.{keyword}",
915
- find_kotlin_accessor_owner_name(node, code)?
912
+ find_kotlin_property_name(node.parent_node()?, code)?
916
913
  ))
917
914
  }
918
- "function_declaration" if node.child_by_field_name("name").is_none() => {
919
- first_named_child_of_kind(node, "simple_identifier")
920
- .map(|name| node_text(name, code).to_string())
921
- }
922
915
  "secondary_constructor" => {
923
- let mut ancestor = node.parent();
916
+ let mut ancestor = node.parent_node();
924
917
  while let Some(current) = ancestor {
925
- if current.kind() == "class_declaration" || current.kind() == "object_declaration" {
926
- return first_named_child_of_kind(current, "type_identifier")
918
+ if current.kind_name() == "class_declaration"
919
+ || current.kind_name() == "object_declaration"
920
+ {
921
+ return current
922
+ .child_by_field_name("name")
927
923
  .map(|name| node_text(name, code).to_string());
928
924
  }
929
- ancestor = current.parent();
925
+ ancestor = current.parent_node();
930
926
  }
931
927
  None
932
928
  }
@@ -948,51 +944,26 @@ fn find_member_function_name(node: Node<'_>, code: &Source<'_>) -> Option<String
948
944
 
949
945
  /// A C# property or indexer (`this`) name.
950
946
  fn csharp_property_name(owner: Node<'_>, code: &Source<'_>) -> Option<String> {
951
- match owner.kind() {
947
+ match owner.kind_name() {
952
948
  "indexer_declaration" => Some("this".to_string()),
953
949
  _ => Some(node_text(owner.child_by_field_name("name")?, code).to_string()),
954
950
  }
955
951
  }
956
952
 
957
- /// The property a Kotlin accessor belongs to: its parent when the accessor follows the initializer
958
- /// on the same line, otherwise (accessor on its own line) the grammar emits it as a class-body
959
- /// sibling after the property, any preceding accessor, and any comments between them.
960
- fn find_kotlin_accessor_owner_name(accessor: Node<'_>, code: &Source<'_>) -> Option<String> {
961
- if let Some(name) = accessor
962
- .parent()
963
- .and_then(|parent| find_kotlin_property_name(parent, code))
964
- {
965
- return Some(name);
966
- }
967
- let mut sibling = accessor.prev_named_sibling();
968
- while let Some(current) = sibling {
969
- if current.kind() == "property_declaration" {
970
- return find_kotlin_property_name(current, code);
971
- }
972
- if !matches!(current.kind(), "getter" | "setter")
973
- && !crate::ncss::COMMENT_NODE_TYPES.contains(&current.kind())
974
- {
975
- return None;
976
- }
977
- sibling = current.prev_named_sibling();
978
- }
979
- None
980
- }
981
-
982
953
  /// The declared name of a Kotlin `property_declaration` (`val name: T`), if it declares one.
983
954
  fn find_kotlin_property_name(property: Node<'_>, code: &Source<'_>) -> Option<String> {
984
- if property.kind() != "property_declaration" {
955
+ if property.kind_name() != "property_declaration" {
985
956
  return None;
986
957
  }
987
958
  let declaration = first_named_child_of_kind(property, "variable_declaration")?;
988
- first_named_child_of_kind(declaration, "simple_identifier")
959
+ first_named_child_of_kind(declaration, "identifier")
989
960
  .map(|name| node_text(name, code).to_string())
990
961
  }
991
962
 
992
963
  fn first_named_child_of_kind<'t>(node: Node<'t>, kind: &str) -> Option<Node<'t>> {
993
964
  named_children(node)
994
965
  .into_iter()
995
- .find(|child| child.kind() == kind)
966
+ .find(|child| child.kind_name() == kind)
996
967
  }
997
968
 
998
969
  /// Whether the node occupies its parent's value position: the `value` field, or no field at all
@@ -1017,7 +988,7 @@ fn is_value_of_parent(node: Node<'_>, parent: Node<'_>) -> bool {
1017
988
  /// or a mapping is unordered, so it groups nothing positionally.
1018
989
  fn is_value_group(node: Node<'_>) -> bool {
1019
990
  matches!(
1020
- node.kind(),
991
+ node.kind_name(),
1021
992
  "expression_list" | "right_assignment_list" | "tuple" | "list" | "array"
1022
993
  )
1023
994
  }
@@ -1025,7 +996,7 @@ fn is_value_group(node: Node<'_>) -> bool {
1025
996
  /// The matching target groups, which a nested value group is aligned against level by level.
1026
997
  fn is_target_group(node: Node<'_>) -> bool {
1027
998
  matches!(
1028
- node.kind(),
999
+ node.kind_name(),
1029
1000
  "pattern_list"
1030
1001
  | "left_assignment_list"
1031
1002
  | "tuple_pattern"
@@ -1041,13 +1012,13 @@ fn find_parallel_assignment_name(value: Node<'_>, code: &Source<'_>) -> Option<S
1041
1012
  let mut positions = Vec::new();
1042
1013
  let mut current = value;
1043
1014
  let assignment = loop {
1044
- let parent = current.parent()?;
1015
+ let parent = current.parent_node()?;
1045
1016
  if is_value_group(parent) {
1046
1017
  positions.push((parent, current));
1047
1018
  current = parent;
1048
1019
  continue;
1049
1020
  }
1050
- if parent.kind() == "assignment"
1021
+ if parent.kind_name() == "assignment"
1051
1022
  && parent.child_by_field_name("right")?.id() == current.id()
1052
1023
  {
1053
1024
  break parent;
@@ -1059,9 +1030,9 @@ fn find_parallel_assignment_name(value: Node<'_>, code: &Source<'_>) -> Option<S
1059
1030
  // Ruby spells a fully parenthesized target list as a `left_assignment_list` holding one
1060
1031
  // destructured group (`(a, b) = f, g`), which aligns against that inner one. A Python
1061
1032
  // singleton tuple is a real destructuring level instead, so it is left alone.
1062
- while target.kind() == "left_assignment_list" {
1033
+ while target.kind_name() == "left_assignment_list" {
1063
1034
  match binding_children(target).as_slice() {
1064
- [only] if only.kind() == "destructured_left_assignment" => target = *only,
1035
+ [only] if only.kind_name() == "destructured_left_assignment" => target = *only,
1065
1036
  _ => break,
1066
1037
  }
1067
1038
  }
@@ -1086,7 +1057,7 @@ fn aligned_target<'t>(values: Node<'_>, value: Node<'_>, targets: Node<'t>) -> O
1086
1057
  let splat_after = value_list[index + 1..].iter().any(is_splat);
1087
1058
  let trailing = value_list.len() - 1 - index;
1088
1059
  let unpacks = matches!(
1089
- targets.kind(),
1060
+ targets.kind_name(),
1090
1061
  "pattern_list" | "tuple_pattern" | "list_pattern"
1091
1062
  );
1092
1063
  let targets = binding_children(targets);
@@ -1123,10 +1094,12 @@ fn aligned_target<'t>(values: Node<'_>, value: Node<'_>, targets: Node<'t>) -> O
1123
1094
  // it, by failing the assignment, while Ruby fills the trailing targets from the left
1124
1095
  // when it underflows.
1125
1096
  let reaches_trailing_targets = written_values + 1 >= targets.len()
1126
- || value_list.iter().any(|child| child.kind() == "list_splat");
1097
+ || value_list
1098
+ .iter()
1099
+ .any(|child| child.kind_name() == "list_splat");
1127
1100
  if reaches_trailing_targets {
1128
1101
  targets.get(targets.len() - 1 - trailing).copied()
1129
- } else if !splat_before && targets[splat].kind() == "rest_assignment" {
1102
+ } else if !splat_before && targets[splat].kind_name() == "rest_assignment" {
1130
1103
  // Ruby empties the star and fills the trailing targets from the left when the
1131
1104
  // values run out, so each one binds the next target (`a, *r, c, d = x, f` binds
1132
1105
  // `f` to `c`); Python fails such an assignment instead.
@@ -1142,7 +1115,7 @@ fn aligned_target<'t>(values: Node<'_>, value: Node<'_>, targets: Node<'t>) -> O
1142
1115
  /// A splat on either side of a parallel assignment (`*xs`, `*rest`).
1143
1116
  fn is_splat(node: &Node<'_>) -> bool {
1144
1117
  matches!(
1145
- node.kind(),
1118
+ node.kind_name(),
1146
1119
  "splat_argument" | "rest_assignment" | "list_splat" | "list_splat_pattern"
1147
1120
  )
1148
1121
  }
@@ -1150,7 +1123,7 @@ fn is_splat(node: &Node<'_>) -> bool {
1150
1123
  fn binding_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
1151
1124
  named_children(node)
1152
1125
  .into_iter()
1153
- .filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind()))
1126
+ .filter(|child| !crate::ncss::COMMENT_NODE_TYPES.contains(&child.kind_name()))
1154
1127
  .collect()
1155
1128
  }
1156
1129
 
@@ -1158,7 +1131,7 @@ fn binding_children<'t>(node: Node<'t>) -> Vec<Node<'t>> {
1158
1131
  /// global variable; the method of a Ruby attribute writer (`self.run = ...`); or a Python attribute
1159
1132
  /// (`obj.run = ...`). A subscript or a splat has no stable name and binds none.
1160
1133
  fn find_assignment_target_text(target: Node<'_>, code: &Source<'_>) -> Option<String> {
1161
- match target.kind() {
1134
+ match target.kind_name() {
1162
1135
  "identifier" | "constant" | "instance_variable" | "class_variable" | "global_variable" => {
1163
1136
  Some(node_text(target, code).to_string())
1164
1137
  }
@@ -1179,7 +1152,7 @@ fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<
1179
1152
  let left = assignment.child_by_field_name("left")?;
1180
1153
  let mut target = left;
1181
1154
  if matches!(
1182
- left.kind(),
1155
+ left.kind_name(),
1183
1156
  "left_assignment_list" | "destructured_left_assignment"
1184
1157
  ) {
1185
1158
  while is_target_group(target) {
@@ -1192,11 +1165,11 @@ fn find_ruby_assignment_name(assignment: Node<'_>, code: &Source<'_>) -> Option<
1192
1165
  }
1193
1166
 
1194
1167
  fn is_ruby_lambda_call(node: Node<'_>, code: &Source<'_>) -> bool {
1195
- if node.kind() != "call" || node.child_by_field_name("receiver").is_some() {
1168
+ if node.kind_name() != "call" || node.child_by_field_name("receiver").is_some() {
1196
1169
  return false;
1197
1170
  }
1198
1171
  node.child_by_field_name("method").is_some_and(|method| {
1199
- method.kind() == "identifier"
1172
+ method.kind_name() == "identifier"
1200
1173
  && (node_text(method, code) == "lambda" || node_text(method, code) == "proc")
1201
1174
  })
1202
1175
  }
@@ -1206,22 +1179,23 @@ fn find_go_func_literal_name(
1206
1179
  expression_list: Node<'_>,
1207
1180
  code: &Source<'_>,
1208
1181
  ) -> Option<String> {
1209
- let holder = expression_list.parent()?;
1182
+ let holder = expression_list.parent_node()?;
1210
1183
  // Comments interleave with expressions in the list but have no matching binding target.
1211
1184
  let values: Vec<Node<'_>> = named_children(expression_list)
1212
1185
  .into_iter()
1213
- .filter(|child| child.kind() != "comment")
1186
+ .filter(|child| child.kind_name() != "comment")
1214
1187
  .collect();
1215
1188
  let value_index = values.iter().position(|child| child.id() == node.id())?;
1216
1189
 
1217
- if holder.kind() == "assignment_statement" && !is_plain_assignment(holder, code) {
1190
+ if holder.kind_name() == "assignment_statement" && !is_plain_assignment(holder, code) {
1218
1191
  return None;
1219
1192
  }
1220
- if holder.kind() == "short_var_declaration" || holder.kind() == "assignment_statement" {
1193
+ if holder.kind_name() == "short_var_declaration" || holder.kind_name() == "assignment_statement"
1194
+ {
1221
1195
  let targets = holder.child_by_field_name("left").map(|left| {
1222
1196
  named_children(left)
1223
1197
  .into_iter()
1224
- .filter(|child| child.kind() != "comment")
1198
+ .filter(|child| child.kind_name() != "comment")
1225
1199
  .collect::<Vec<_>>()
1226
1200
  });
1227
1201
  return as_go_binding_name(
@@ -1233,7 +1207,7 @@ fn find_go_func_literal_name(
1233
1207
  );
1234
1208
  }
1235
1209
 
1236
- if holder.kind() == "var_spec" {
1210
+ if holder.kind_name() == "var_spec" {
1237
1211
  let target = find_children_by_field_name(holder, "name")
1238
1212
  .get(value_index)
1239
1213
  .copied();
@@ -1247,10 +1221,10 @@ fn find_go_func_literal_name(
1247
1221
  /// (`m.run = func...`) names its field.
1248
1222
  fn as_go_binding_name(target: Option<Node<'_>>, code: &Source<'_>) -> Option<String> {
1249
1223
  match target {
1250
- Some(target) if target.kind() == "identifier" && node_text(target, code) != "_" => {
1224
+ Some(target) if target.kind_name() == "identifier" && node_text(target, code) != "_" => {
1251
1225
  Some(node_text(target, code).to_string())
1252
1226
  }
1253
- Some(target) if target.kind() == "selector_expression" => target
1227
+ Some(target) if target.kind_name() == "selector_expression" => target
1254
1228
  .child_by_field_name("field")
1255
1229
  .map(|field| node_text(field, code).to_string()),
1256
1230
  _ => None,
@@ -1260,12 +1234,12 @@ fn as_go_binding_name(target: Option<Node<'_>>, code: &Source<'_>) -> Option<Str
1260
1234
  fn find_wrapped_component_name(node: Node<'_>, code: &Source<'_>) -> Option<String> {
1261
1235
  let mut current = node;
1262
1236
  loop {
1263
- let arguments_node = current.parent();
1264
- let call_node = arguments_node.and_then(|arguments| arguments.parent());
1237
+ let arguments_node = current.parent_node();
1238
+ let call_node = arguments_node.and_then(|arguments| arguments.parent_node());
1265
1239
  let (Some(arguments_node), Some(call_node)) = (arguments_node, call_node) else {
1266
1240
  return None;
1267
1241
  };
1268
- if arguments_node.kind() != "arguments" || call_node.kind() != "call_expression" {
1242
+ if arguments_node.kind_name() != "arguments" || call_node.kind_name() != "call_expression" {
1269
1243
  return None;
1270
1244
  }
1271
1245
 
@@ -1273,8 +1247,8 @@ fn find_wrapped_component_name(node: Node<'_>, code: &Source<'_>) -> Option<Stri
1273
1247
  return None;
1274
1248
  }
1275
1249
 
1276
- if let Some(declarator_node) = call_node.parent() {
1277
- if declarator_node.kind() == "variable_declarator" {
1250
+ if let Some(declarator_node) = call_node.parent_node() {
1251
+ if declarator_node.kind_name() == "variable_declarator" {
1278
1252
  return declarator_node
1279
1253
  .child_by_field_name("name")
1280
1254
  .map(|name| node_text(name, code).to_string());
@@ -1299,16 +1273,16 @@ fn is_react_component_wrapper_call(node: Node<'_>, code: &Source<'_>) -> bool {
1299
1273
  /// variable the closure itself rather than something constructed from it.
1300
1274
  fn declares_deduced_type(declarator: Node<'_>) -> bool {
1301
1275
  declarator
1302
- .parent()
1276
+ .parent_node()
1303
1277
  .and_then(|declaration| declaration.child_by_field_name("type"))
1304
- .is_some_and(|declared| declared.kind() == "placeholder_type_specifier")
1278
+ .is_some_and(|declared| declared.kind_name() == "placeholder_type_specifier")
1305
1279
  }
1306
1280
 
1307
1281
  /// Unwraps a C/C++ declarator chain to the declared name.
1308
1282
  fn unwrap_declarator_name(declarator: Option<Node<'_>>, code: &Source<'_>) -> Option<String> {
1309
1283
  let mut current = declarator;
1310
1284
  while let Some(node) = current {
1311
- match node.kind() {
1285
+ match node.kind_name() {
1312
1286
  "identifier" | "field_identifier" | "type_identifier" | "destructor_name"
1313
1287
  | "operator_name" => {
1314
1288
  return Some(node_text(node, code).to_string());
@@ -1339,7 +1313,8 @@ fn next_declarator(node: Node<'_>) -> Option<Node<'_>> {
1339
1313
  if let Some(direct) = node.child_by_field_name("declarator") {
1340
1314
  return Some(direct);
1341
1315
  }
1342
- if node.kind() == "reference_declarator" || node.kind() == "parenthesized_declarator" {
1316
+ if node.kind_name() == "reference_declarator" || node.kind_name() == "parenthesized_declarator"
1317
+ {
1343
1318
  return node.named_child(0);
1344
1319
  }
1345
1320
  None