shirobai 2026.0820.0300 → 2026.0905.1300

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 (31) hide show
  1. checksums.yaml +4 -4
  2. data/Cargo.lock +2 -2
  3. data/Cargo.toml +1 -1
  4. data/crates/shirobai-core/src/bin/analysis_profile.rs +1 -1
  5. data/crates/shirobai-core/src/bin/bundle_profile.rs +1 -1
  6. data/crates/shirobai-core/src/bin/hotspot_profile.rs +1 -1
  7. data/crates/shirobai-core/src/rules/aligner.rs +19 -6
  8. data/crates/shirobai-core/src/rules/bundle.rs +24 -10
  9. data/crates/shirobai-core/src/rules/duplicate_methods.rs +57 -14
  10. data/crates/shirobai-core/src/rules/extra_spacing.rs +352 -20
  11. data/crates/shirobai-core/src/rules/first_argument_indentation.rs +135 -12
  12. data/crates/shirobai-core/src/rules/if_unless_modifier.rs +7 -25
  13. data/crates/shirobai-core/src/rules/method_name.rs +29 -1
  14. data/crates/shirobai-core/src/rules/multiline_method_call_indentation.rs +24 -8
  15. data/crates/shirobai-core/src/rules/space_around_operators.rs +62 -21
  16. data/crates/shirobai-core/src/rules/space_before_first_arg.rs +23 -4
  17. data/crates/shirobai-core/src/rules/tokens.rs +28 -0
  18. data/ext/shirobai/src/lib.rs +11 -18
  19. data/lib/shirobai/cop/layout/empty_line_after_magic_comment.rb +41 -13
  20. data/lib/shirobai/cop/layout/end_of_line.rb +18 -7
  21. data/lib/shirobai/cop/layout/extra_spacing.rb +5 -3
  22. data/lib/shirobai/cop/layout/first_argument_indentation.rb +12 -7
  23. data/lib/shirobai/cop/layout/line_continuation_spacing.rb +8 -1
  24. data/lib/shirobai/cop/layout/line_length.rb +7 -1
  25. data/lib/shirobai/cop/lint/duplicate_methods.rb +55 -16
  26. data/lib/shirobai/cop/naming/method_name.rb +14 -4
  27. data/lib/shirobai/cop/style/if_unless_modifier.rb +13 -12
  28. data/lib/shirobai/cop/style/magic_comment_format.rb +10 -4
  29. data/lib/shirobai/dispatch.rb +2 -2
  30. data/lib/shirobai/version.rb +1 -1
  31. metadata +3 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: dba7704fcfe48ea2b368a6eae1afadb52dbfaaa926c4388f9d74a038f64c8bdf
4
- data.tar.gz: 17e02721945c202d5cd39487dc155b66c42180f21e6692e58c3c0a8818b2d246
3
+ metadata.gz: 1f4f98851b8d60acd4a275498959e3e6a0fca00455715bcdfa5b16a653a90015
4
+ data.tar.gz: 9bb15c3004bfa6415d01d2e6038dfa430cd930f260f988faf22133f445869597
5
5
  SHA512:
6
- metadata.gz: de5ec9277179462e47e99b30b209e261a6bfc3468ef63f62e49bcf3d1e88ff92ff3fa5aa20d1c1dd67b52934a5197d2b99f334f944c9e5cf9da074d38271ad97
7
- data.tar.gz: 8dd4eae8d0a5f3fa820fed008736b4c0381445eab74b6ee2f665958727281e658bba766c405883f6fa4e1fac9f9ba8d443df04310435ace19c5bb779ff2de374
6
+ metadata.gz: 7effd8214232c64b9fee9ecbe046bf102e2b24936aef4c991039bd8028acc79d936b4c6d0d42ed184a247e1e1ac0b1ea0ca268f57c6001a3d16e9913af9106a5
7
+ data.tar.gz: 186a7b1f6e790e78c73f85ed0000bacf40ed19dbb8c0ab426b52b31b124f81f7eb907392400732b242b8fd92026907bef51f637f117c0957e92c14f117f1f3ae
data/Cargo.lock CHANGED
@@ -356,7 +356,7 @@ checksum = "dc6fe69c597f9c37bfeeeeeb33da3530379845f10be461a66d16d03eca2ded77"
356
356
 
357
357
  [[package]]
358
358
  name = "shirobai"
359
- version = "2026.820.300"
359
+ version = "2026.905.1300"
360
360
  dependencies = [
361
361
  "magnus",
362
362
  "shirobai-core",
@@ -364,7 +364,7 @@ dependencies = [
364
364
 
365
365
  [[package]]
366
366
  name = "shirobai-core"
367
- version = "2026.820.300"
367
+ version = "2026.905.1300"
368
368
  dependencies = [
369
369
  "libc",
370
370
  "ruby-prism",
data/Cargo.toml CHANGED
@@ -7,7 +7,7 @@ resolver = "3"
7
7
  # is crate 2026.709.0 -- cargo rejects leading zeros in numeric segments, and
8
8
  # Gem::Version normalizes to the same integers, so the two stay value-equal).
9
9
  [workspace.package]
10
- version = "2026.820.300"
10
+ version = "2026.905.1300"
11
11
 
12
12
  # Release tuning. The hot path crosses crate boundaries (ext cdylib -> core
13
13
  # check_* -> ruby-prism); without LTO the cross-crate calls are not inlined and
@@ -288,7 +288,7 @@ fn run_cop(cop: &str, sources: &[Vec<u8>]) -> usize {
288
288
  for s in sources {
289
289
  sink = sink.wrapping_add(
290
290
  rules::first_argument_indentation::check_first_argument_indentation(
291
- s, 0, 2, false,
291
+ s, 0, 2, 0,
292
292
  )
293
293
  .len(),
294
294
  );
@@ -221,7 +221,7 @@ fn run_perrule(sources: &[Vec<u8>], cfg: &BundleConfig, iterations: usize) {
221
221
  s,
222
222
  cfg.first_argument_style,
223
223
  cfg.first_argument_indent,
224
- cfg.first_argument_enforce_fixed_no_line_break,
224
+ cfg.first_argument_fixed_mode,
225
225
  ));
226
226
  }),
227
227
  e!("redundant_self", |s: &[u8]| {
@@ -411,7 +411,7 @@ fn run_full_firstarg(sources: &[Vec<u8>]) -> usize {
411
411
  let mut sink = 0usize;
412
412
  for s in sources {
413
413
  sink = sink.wrapping_add(
414
- rules::first_argument_indentation::check_first_argument_indentation(s, 0, 2, false)
414
+ rules::first_argument_indentation::check_first_argument_indentation(s, 0, 2, 0)
415
415
  .len(),
416
416
  );
417
417
  }
@@ -1,12 +1,15 @@
1
1
  //! Shared alignment helper for the `AllowForAlignment` family of cops.
2
2
  //!
3
- //! `Layout/SpaceAroundOperators` and `Layout/SpaceBeforeFirstArg` both honour an
3
+ //! `Layout/SpaceAroundOperators` and `Layout/ExtraSpacing` both honour an
4
4
  //! `AllowForAlignment` option that permits extra spacing used to vertically
5
5
  //! align a token with something on a preceding or following line. The logic is
6
6
  //! the `PrecedingFollowingAlignment` mixin in stock rubocop
7
7
  //! (`lib/rubocop/cop/mixin/preceding_following_alignment.rb`). This module hosts
8
- //! the single implementation both cops drive (copy-divergence is forbidden;
9
- //! equivalence is held by each cop's cargo tests).
8
+ //! the single implementation both token-stream cops drive (copy-divergence is
9
+ //! forbidden; equivalence is held by each cop's cargo tests).
10
+ //! `Layout/SpaceBeforeFirstArg` runs the same search over a masked byte scan
11
+ //! (it has no token stream); a change to the search rules here must be
12
+ //! mirrored in `space_before_first_arg.rs`.
10
13
  //!
11
14
  //! [`Aligner`] is bound to one source + its parser-gem token list. It needs the
12
15
  //! token list, so callers build it in the walk-outer phase (the token cache
@@ -198,8 +201,18 @@ impl<'a> Aligner<'a> {
198
201
  let Some(index) = first_non_space_index(line) else {
199
202
  continue;
200
203
  };
201
- if matches!(indent, Some(ind) if ind != index) {
202
- continue;
204
+ if let Some(ind) = indent {
205
+ // When searching for the nearest line with the same
206
+ // indentation, more deeply indented lines are nested content
207
+ // of the current group and are skipped, but a less deeply
208
+ // indented line ends the enclosing block: an alignment anchor
209
+ // beyond it would be coincidental (1.90).
210
+ if index < ind {
211
+ break;
212
+ }
213
+ if index > ind {
214
+ continue;
215
+ }
203
216
  }
204
217
  // The first line with a non-space (and matching indent, if given)
205
218
  // decides the result; stock returns the predicate value here.
@@ -220,7 +233,7 @@ impl<'a> Aligner<'a> {
220
233
 
221
234
  /// `aligned_comment_lines`: lines of full-line comments (a comment whose
222
235
  /// expression begins its line).
223
- fn aligned_comment_line(&self, line: usize) -> bool {
236
+ pub(crate) fn aligned_comment_line(&self, line: usize) -> bool {
224
237
  self.tokens.iter().any(|t| {
225
238
  t.comment() && self.line(t.begin_pos) == line && self.begins_its_line(t.begin_pos)
226
239
  })
@@ -113,7 +113,7 @@ pub fn check_multiline_bundle(
113
113
  /// | 15-17 | multiline_operation style / indent / base |
114
114
  /// | 18-20 | multiline_method_call style / indent / base |
115
115
  /// | 21-23 | argument_alignment style / indent / incompatible |
116
- /// | 24-26 | first_argument_indentation style / indent / enforce_fixed_no_line_break |
116
+ /// | 24-26 | first_argument_indentation style / indent / fixed_mode (0 off / 1 `with_fixed_indentation` with FirstMethodArgumentLineBreak off: defer all / 2 with it on: defer conflicts) |
117
117
  /// | 27-33 | indentation_width width / relative_to_receiver / access_modifier_outdent / indented_internal_methods / end_align / def_end_align_def / use_tabs |
118
118
  /// | 34 | closing_paren_indent |
119
119
  /// | 35-37 | first_array_element style / indent / enforce_fixed_indentation |
@@ -231,6 +231,7 @@ pub fn check_multiline_bundle(
231
231
  /// | 26 | arguments_forwarding RedundantKeywordRestArgumentNames |
232
232
  /// | 27 | arguments_forwarding RedundantBlockArgumentNames |
233
233
  /// | 28 | duplicate_methods_delegating (`Lint/DuplicateMethods` `DelegatingMethods`, 1.89) |
234
+ /// | 29 | method_name_forbidden (`Naming/MethodName` `ForbiddenIdentifiers`: valid-style names the filtered flavor keeps) |
234
235
  ///
235
236
  /// Performance segment `nums[1]` (the shirobai-performance plugin origin):
236
237
  ///
@@ -319,6 +320,9 @@ pub struct BundleConfig {
319
320
  pub variable_number_flags: u8,
320
321
  pub variable_number_allowed_identifiers: Vec<String>,
321
322
  pub method_name_style: u8,
323
+ /// `Naming/MethodName` `ForbiddenIdentifiers`: names the filtered flavor
324
+ /// returns even when their style is fine (list 29).
325
+ pub method_name_forbidden: Vec<String>,
322
326
  pub safe_navigation_nil_methods: Vec<String>,
323
327
  pub dot_position_style: u8,
324
328
  pub line_length_max: usize,
@@ -333,7 +337,7 @@ pub struct BundleConfig {
333
337
  pub array_alignment_indent: usize,
334
338
  pub first_argument_style: u8,
335
339
  pub first_argument_indent: usize,
336
- pub first_argument_enforce_fixed_no_line_break: bool,
340
+ pub first_argument_fixed_mode: u8,
337
341
  pub indentation_width: indentation_width::Config,
338
342
  pub closing_paren_indent: usize,
339
343
  pub first_array_element_style: u8,
@@ -486,7 +490,7 @@ pub const ORIGIN_RAILS: usize = 3;
486
490
  pub const N_ORIGINS: usize = 4;
487
491
 
488
492
  const CORE_NUMS_LEN: usize = 134;
489
- const CORE_LISTS_LEN: usize = 29;
493
+ const CORE_LISTS_LEN: usize = 30;
490
494
  const PERF_NUMS_LEN: usize = 3;
491
495
  const PERF_LISTS_LEN: usize = 1;
492
496
 
@@ -564,6 +568,8 @@ impl BundleConfig {
564
568
  variable_number_flags: nums[9] as u8,
565
569
  variable_number_allowed_identifiers: next_list(),
566
570
  method_name_style: nums[10] as u8,
571
+ // Filled after the literal: list 29 follows the DuplicateMethods list.
572
+ method_name_forbidden: Vec::new(),
567
573
  safe_navigation_nil_methods: next_list(),
568
574
  dot_position_style: nums[11] as u8,
569
575
  line_length_max: nums[12] as usize,
@@ -579,7 +585,7 @@ impl BundleConfig {
579
585
  frozen_string_literal_comment_style: nums[116] as u8,
580
586
  first_argument_style: nums[24] as u8,
581
587
  first_argument_indent: nums[25] as usize,
582
- first_argument_enforce_fixed_no_line_break: nums[26] != 0,
588
+ first_argument_fixed_mode: nums[26] as u8,
583
589
  indentation_width: indentation_width::Config {
584
590
  width: nums[27] as usize,
585
591
  relative_to_receiver: nums[28] != 0,
@@ -862,6 +868,7 @@ impl BundleConfig {
862
868
  rails: rails_config::RailsConfig::from_segment(rails_nums, &rails_lists)?,
863
869
  };
864
870
  bundle.duplicate_methods.delegating_methods = next_list();
871
+ bundle.method_name_forbidden = next_list();
865
872
  Ok(bundle)
866
873
  }
867
874
  }
@@ -1216,7 +1223,7 @@ pub fn check_all_bundle(source: &[u8], cfg: &BundleConfig) -> BundleResult {
1216
1223
  source,
1217
1224
  cfg.first_argument_style,
1218
1225
  cfg.first_argument_indent,
1219
- cfg.first_argument_enforce_fixed_no_line_break,
1226
+ cfg.first_argument_fixed_mode,
1220
1227
  );
1221
1228
  let mut snc_rule = safe_navigation_chain::build_rule(source, &cfg.safe_navigation_nil_methods);
1222
1229
  let mut iw_rule = indentation_width::build_rule(source, cfg.indentation_width, &[], &[]);
@@ -1588,7 +1595,7 @@ pub fn check_all_bundle(source: &[u8], cfg: &BundleConfig) -> BundleResult {
1588
1595
  };
1589
1596
  // `Layout/ExtraSpacing` is a token-scan cop with an AST side input: it walks
1590
1597
  // the token stream collected up front as adjacent pairs, and its own
1591
- // `with_parsed` (`collect_def_equals`, sharing the cached parse) supplies the
1598
+ // `with_parsed` (`collect_def_info`, sharing the cached parse) supplies the
1592
1599
  // `remove_equals_in_def` positions the alignment / assignment logic needs.
1593
1600
  let extra_spacing = match &bundle_tokens {
1594
1601
  Some(tokens) if cfg.extra_spacing_enabled => {
@@ -1720,9 +1727,15 @@ pub fn check_all_bundle(source: &[u8], cfg: &BundleConfig) -> BundleResult {
1720
1727
  );
1721
1728
 
1722
1729
  // --- Cops off the shared walk (see the doc comment above). ---
1723
- // The bundle always computes the filtered flavor; a `MethodName` whose
1724
- // config needs the unfiltered one takes the fallback path on the Ruby side.
1725
- let method_name = method_name::check_method_name_filtered(source, cfg.method_name_style, true);
1730
+ // The bundle always computes the filtered flavor (keeping the forbidden
1731
+ // identifiers); a `MethodName` whose config needs the unfiltered one
1732
+ // (pattern lists) takes the fallback path on the Ruby side.
1733
+ let method_name = method_name::check_method_name_filtered_keeping(
1734
+ source,
1735
+ cfg.method_name_style,
1736
+ true,
1737
+ &cfg.method_name_forbidden,
1738
+ );
1726
1739
  let line_length = line_length::check_line_length_with_heredocs(
1727
1740
  source,
1728
1741
  cfg.line_length_max,
@@ -2028,6 +2041,7 @@ mod tests {
2028
2041
  ["kwargs", "options", "opts"].map(String::from).to_vec(), // af: RedundantKeywordRestArgumentNames
2029
2042
  ["blk", "block", "proc"].map(String::from).to_vec(), // af: RedundantBlockArgumentNames
2030
2043
  vec!["delegate".to_string()], // duplicate_methods: DelegatingMethods
2044
+ vec![], // method_name: ForbiddenIdentifiers
2031
2045
  ];
2032
2046
  // Performance segment (origin 1): enabled, with the SafeMultiline
2033
2047
  // defaults and RuboCop's default preferred method for Detect
@@ -2585,7 +2599,7 @@ mod tests {
2585
2599
  src.as_bytes(),
2586
2600
  0,
2587
2601
  2,
2588
- false,
2602
+ 0,
2589
2603
  );
2590
2604
  assert!(!fa_alone.is_empty());
2591
2605
  assert_eq!(bundle.first_argument_indentation.len(), fa_alone.len());
@@ -105,9 +105,10 @@ pub struct DupMethodEvent {
105
105
  /// 1.89 the suffix is `"@#{path}:#{line}:#{begin_pos}"`
106
106
  /// (`anon_block_identity`), distinguishing blocks that share a line.
107
107
  pub scope_begin: i64,
108
- /// A `track_self_alias` event (1.89): `name` carries the humanized
109
- /// method name; the wrapper records it and skips the bookkeeping.
110
- pub self_alias: bool,
108
+ /// A `track_intentional_redefinition` event (1.89 self-alias trick, 1.90
109
+ /// Active Support markers): `name` carries the humanized method name;
110
+ /// the wrapper records it and skips the bookkeeping.
111
+ pub intentional_redefinition: bool,
111
112
  /// The key carries a scope id (`scope_id` non-nil in stock) — gates the
112
113
  /// project-index cross-file check in the wrapper.
113
114
  pub scoped: bool,
@@ -116,6 +117,8 @@ pub struct DupMethodEvent {
116
117
  }
117
118
 
118
119
  const RESTRICT_ON_SEND: &[&[u8]] = &[
120
+ b"silence_redefinition_of_method",
121
+ b"redefine_method",
119
122
  b"alias_method",
120
123
  b"attr_reader",
121
124
  b"attr_writer",
@@ -727,17 +730,19 @@ impl<'s> DuplicateMethodsRule<'s> {
727
730
  line,
728
731
  rescue_scope,
729
732
  scope_begin,
730
- self_alias: false,
733
+ intentional_redefinition: false,
731
734
  scoped,
732
735
  inside_def,
733
736
  });
734
737
  }
735
738
 
736
- /// Stock `track_self_alias` (1.89): `alias foo foo` /
737
- /// `alias_method :foo, :foo` marks an intentional redefinition of a
738
- /// method defined in another file. Only tracked when
739
+ /// Stock `track_intentional_redefinition`: `alias foo foo` /
740
+ /// `alias_method :foo, :foo` (1.89) and, with
741
+ /// `ActiveSupportExtensionsEnabled`, `silence_redefinition_of_method
742
+ /// :foo` / `redefine_method(:foo)` (1.90) mark an intentional
743
+ /// redefinition of a method defined in another file. Only tracked when
739
744
  /// `parent_module_name` resolves, like stock.
740
- fn track_self_alias(&mut self, name: &str) {
745
+ fn track_intentional_redefinition(&mut self, name: &str) {
741
746
  let Some(scope) = self.pmn(self.frames.len()) else {
742
747
  return;
743
748
  };
@@ -753,7 +758,7 @@ impl<'s> DuplicateMethodsRule<'s> {
753
758
  line: 0,
754
759
  rescue_scope: 0,
755
760
  scope_begin: -1,
756
- self_alias: true,
761
+ intentional_redefinition: true,
757
762
  scoped: false,
758
763
  inside_def: false,
759
764
  });
@@ -930,7 +935,7 @@ impl<'s> DuplicateMethodsRule<'s> {
930
935
  return;
931
936
  };
932
937
  if new == old {
933
- self.track_self_alias(&new);
938
+ self.track_intentional_redefinition(&new);
934
939
  return;
935
940
  }
936
941
  if self.if_depth > 0 {
@@ -1016,7 +1021,7 @@ impl<'s> DuplicateMethodsRule<'s> {
1016
1021
  return;
1017
1022
  };
1018
1023
  if new == old {
1019
- self.track_self_alias(&new);
1024
+ self.track_intentional_redefinition(&new);
1020
1025
  return;
1021
1026
  }
1022
1027
  if self.if_depth > 0 {
@@ -1078,6 +1083,20 @@ impl<'s> DuplicateMethodsRule<'s> {
1078
1083
  let defined = values.last().unwrap().clone().unwrap();
1079
1084
  self.found_instance_method(&defined, off, node_start);
1080
1085
  }
1086
+ // `active_support_redefinition_marker` (1.90): `(send nil?
1087
+ // {:silence_redefinition_of_method :redefine_method} ({sym str}
1088
+ // $_) ...)`. Scope resolution starts from the call node itself —
1089
+ // stock passes `node.block_node || node` because parser hangs
1090
+ // the block above the send, prism below it.
1091
+ b"silence_redefinition_of_method" | b"redefine_method" => {
1092
+ if !self.cfg.active_support_extensions_enabled {
1093
+ return;
1094
+ }
1095
+ let Some(marked) = args.first().and_then(sym_or_str_value) else {
1096
+ return;
1097
+ };
1098
+ self.track_intentional_redefinition(&marked);
1099
+ }
1081
1100
  b"def_delegators" | b"def_instance_delegators" => {
1082
1101
  if args.len() < 2 {
1083
1102
  return;
@@ -1450,7 +1469,7 @@ mod tests {
1450
1469
  fn events(src: &str) -> Vec<(String, String, i64, i64, u8)> {
1451
1470
  all_events(src)
1452
1471
  .into_iter()
1453
- .filter(|e| !e.self_alias)
1472
+ .filter(|e| !e.intentional_redefinition)
1454
1473
  .map(|e| (e.key, e.name, e.scope_line, e.sexp_start, e.rescue_scope))
1455
1474
  .collect()
1456
1475
  }
@@ -1667,11 +1686,11 @@ mod tests {
1667
1686
  fn alias_forms() {
1668
1687
  assert_eq!(keys("class A\n alias foo bar\nend\n"), vec!["A#foo"]);
1669
1688
  assert_eq!(keys("class A\n alias foo foo\nend\n"), Vec::<String>::new());
1670
- // ...but 1.89 tracks it as a self-alias event (the intentional
1689
+ // ...but 1.89 tracks it as an intentional-redefinition event (the
1671
1690
  // cross-file redefinition marker).
1672
1691
  let sa: Vec<DupMethodEvent> = all_events("class A\n alias foo foo\nend\n")
1673
1692
  .into_iter()
1674
- .filter(|e| e.self_alias)
1693
+ .filter(|e| e.intentional_redefinition)
1675
1694
  .collect();
1676
1695
  assert_eq!(sa.len(), 1);
1677
1696
  assert_eq!(sa[0].name, "A#foo");
@@ -1721,6 +1740,30 @@ mod tests {
1721
1740
  assert_eq!(evts[0].key, "A#foo");
1722
1741
  }
1723
1742
 
1743
+ // Active Support redefinition markers (1.90): tracked like the
1744
+ // self-alias trick, only with `ActiveSupportExtensionsEnabled`. The
1745
+ // `redefine_method` block hangs off the call in prism, so the scope
1746
+ // still resolves to the enclosing class.
1747
+ #[test]
1748
+ fn active_support_redefinition_markers() {
1749
+ let src = "class A\n silence_redefinition_of_method :foo\n redefine_method('bar') do\n 1\n end\nend\n";
1750
+ let marked = |enabled: bool| {
1751
+ check_duplicate_methods(
1752
+ src.as_bytes(),
1753
+ &Config {
1754
+ active_support_extensions_enabled: enabled,
1755
+ delegating_methods: vec!["delegate".to_string()],
1756
+ },
1757
+ )
1758
+ .into_iter()
1759
+ .filter(|e| e.intentional_redefinition)
1760
+ .map(|e| e.name)
1761
+ .collect::<Vec<_>>()
1762
+ };
1763
+ assert_eq!(marked(true), vec!["A#foo", "A#bar"]);
1764
+ assert!(marked(false).is_empty());
1765
+ }
1766
+
1724
1767
  // delegate prefix handling.
1725
1768
  #[test]
1726
1769
  fn delegate_prefix() {