shirobai 2026.0709.0000
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.
- checksums.yaml +7 -0
- data/Cargo.lock +420 -0
- data/Cargo.toml +13 -0
- data/LICENSE.txt +21 -0
- data/crates/shirobai-core/Cargo.toml +20 -0
- data/crates/shirobai-core/src/bin/analysis_profile.rs +447 -0
- data/crates/shirobai-core/src/bin/bundle_profile.rs +656 -0
- data/crates/shirobai-core/src/bin/hotspot_profile.rs +596 -0
- data/crates/shirobai-core/src/bin/walk_profile.rs +191 -0
- data/crates/shirobai-core/src/lib.rs +1 -0
- data/crates/shirobai-core/src/rules/abc_size.rs +1475 -0
- data/crates/shirobai-core/src/rules/access_modifier_indentation.rs +450 -0
- data/crates/shirobai-core/src/rules/ambiguous_block_association.rs +528 -0
- data/crates/shirobai-core/src/rules/argument_alignment.rs +300 -0
- data/crates/shirobai-core/src/rules/arguments_forwarding.rs +1125 -0
- data/crates/shirobai-core/src/rules/array_alignment.rs +398 -0
- data/crates/shirobai-core/src/rules/assignment_indentation.rs +502 -0
- data/crates/shirobai-core/src/rules/block_alignment.rs +869 -0
- data/crates/shirobai-core/src/rules/block_delimiters.rs +1876 -0
- data/crates/shirobai-core/src/rules/block_length.rs +631 -0
- data/crates/shirobai-core/src/rules/block_nesting.rs +390 -0
- data/crates/shirobai-core/src/rules/bundle.rs +4433 -0
- data/crates/shirobai-core/src/rules/class_length.rs +531 -0
- data/crates/shirobai-core/src/rules/closing_parenthesis_indentation.rs +425 -0
- data/crates/shirobai-core/src/rules/code_length.rs +801 -0
- data/crates/shirobai-core/src/rules/colon_method_call.rs +285 -0
- data/crates/shirobai-core/src/rules/complexity.rs +585 -0
- data/crates/shirobai-core/src/rules/debugger.rs +438 -0
- data/crates/shirobai-core/src/rules/def_end_alignment.rs +485 -0
- data/crates/shirobai-core/src/rules/dispatch.rs +276 -0
- data/crates/shirobai-core/src/rules/dot_position.rs +276 -0
- data/crates/shirobai-core/src/rules/duplicate_magic_comment.rs +1001 -0
- data/crates/shirobai-core/src/rules/duplicate_methods.rs +1725 -0
- data/crates/shirobai-core/src/rules/else_alignment.rs +837 -0
- data/crates/shirobai-core/src/rules/empty_comment.rs +452 -0
- data/crates/shirobai-core/src/rules/empty_line_after_guard_clause.rs +1210 -0
- data/crates/shirobai-core/src/rules/empty_line_after_magic_comment.rs +166 -0
- data/crates/shirobai-core/src/rules/empty_line_between_defs.rs +731 -0
- data/crates/shirobai-core/src/rules/empty_lines.rs +432 -0
- data/crates/shirobai-core/src/rules/empty_lines_around_arguments.rs +298 -0
- data/crates/shirobai-core/src/rules/empty_lines_around_body.rs +1320 -0
- data/crates/shirobai-core/src/rules/end_alignment.rs +798 -0
- data/crates/shirobai-core/src/rules/file_null.rs +320 -0
- data/crates/shirobai-core/src/rules/first_argument_indentation.rs +643 -0
- data/crates/shirobai-core/src/rules/first_array_element_indentation.rs +644 -0
- data/crates/shirobai-core/src/rules/first_hash_element_indentation.rs +699 -0
- data/crates/shirobai-core/src/rules/frozen_string_literal_comment.rs +414 -0
- data/crates/shirobai-core/src/rules/hash_alignment.rs +1030 -0
- data/crates/shirobai-core/src/rules/hash_each_methods.rs +744 -0
- data/crates/shirobai-core/src/rules/hash_syntax.rs +1304 -0
- data/crates/shirobai-core/src/rules/hash_transform_keys.rs +909 -0
- data/crates/shirobai-core/src/rules/if_unless_modifier.rs +1494 -0
- data/crates/shirobai-core/src/rules/indentation_consistency.rs +585 -0
- data/crates/shirobai-core/src/rules/indentation_width.rs +1231 -0
- data/crates/shirobai-core/src/rules/leading_empty_lines.rs +365 -0
- data/crates/shirobai-core/src/rules/line_end_concatenation.rs +330 -0
- data/crates/shirobai-core/src/rules/line_index.rs +227 -0
- data/crates/shirobai-core/src/rules/line_length.rs +434 -0
- data/crates/shirobai-core/src/rules/line_length_breakable.rs +1454 -0
- data/crates/shirobai-core/src/rules/method_length.rs +438 -0
- data/crates/shirobai-core/src/rules/method_name.rs +565 -0
- data/crates/shirobai-core/src/rules/mod.rs +104 -0
- data/crates/shirobai-core/src/rules/module_length.rs +283 -0
- data/crates/shirobai-core/src/rules/multiline_method_call_brace_layout.rs +594 -0
- data/crates/shirobai-core/src/rules/multiline_method_call_indentation.rs +1444 -0
- data/crates/shirobai-core/src/rules/multiline_operation_indentation.rs +720 -0
- data/crates/shirobai-core/src/rules/nested_parenthesized_calls.rs +486 -0
- data/crates/shirobai-core/src/rules/opaque_mask.rs +164 -0
- data/crates/shirobai-core/src/rules/parentheses_as_grouped_expression.rs +410 -0
- data/crates/shirobai-core/src/rules/parse_cache.rs +116 -0
- data/crates/shirobai-core/src/rules/percent_literal_delimiters.rs +835 -0
- data/crates/shirobai-core/src/rules/perf_detect.rs +524 -0
- data/crates/shirobai-core/src/rules/perf_end_with.rs +368 -0
- data/crates/shirobai-core/src/rules/perf_start_with.rs +333 -0
- data/crates/shirobai-core/src/rules/perf_string_include.rs +399 -0
- data/crates/shirobai-core/src/rules/perf_times_map.rs +570 -0
- data/crates/shirobai-core/src/rules/predicate_prefix.rs +397 -0
- data/crates/shirobai-core/src/rules/punctuation_spacing.rs +725 -0
- data/crates/shirobai-core/src/rules/rails_app.rs +765 -0
- data/crates/shirobai-core/src/rules/rails_config.rs +80 -0
- data/crates/shirobai-core/src/rules/rails_dynamic_find_by.rs +447 -0
- data/crates/shirobai-core/src/rules/rails_pluck.rs +470 -0
- data/crates/shirobai-core/src/rules/rails_unknown_env.rs +362 -0
- data/crates/shirobai-core/src/rules/redundant_freeze.rs +550 -0
- data/crates/shirobai-core/src/rules/redundant_self.rs +691 -0
- data/crates/shirobai-core/src/rules/redundant_self_assignment.rs +530 -0
- data/crates/shirobai-core/src/rules/require_parentheses.rs +237 -0
- data/crates/shirobai-core/src/rules/rspec_dispatcher.rs +2010 -0
- data/crates/shirobai-core/src/rules/rspec_empty_line.rs +791 -0
- data/crates/shirobai-core/src/rules/rspec_language.rs +233 -0
- data/crates/shirobai-core/src/rules/safe_navigation_chain.rs +376 -0
- data/crates/shirobai-core/src/rules/self_assignment.rs +976 -0
- data/crates/shirobai-core/src/rules/semicolon.rs +492 -0
- data/crates/shirobai-core/src/rules/space_around_keyword.rs +881 -0
- data/crates/shirobai-core/src/rules/space_around_method_call_operator.rs +398 -0
- data/crates/shirobai-core/src/rules/space_before_block_braces.rs +380 -0
- data/crates/shirobai-core/src/rules/space_before_first_arg.rs +627 -0
- data/crates/shirobai-core/src/rules/space_inside_array_literal_brackets.rs +977 -0
- data/crates/shirobai-core/src/rules/space_inside_block_braces.rs +669 -0
- data/crates/shirobai-core/src/rules/space_inside_hash_literal_braces.rs +498 -0
- data/crates/shirobai-core/src/rules/space_inside_parens.rs +538 -0
- data/crates/shirobai-core/src/rules/space_inside_reference_brackets.rs +513 -0
- data/crates/shirobai-core/src/rules/space_scan.rs +136 -0
- data/crates/shirobai-core/src/rules/stabby_lambda_parentheses.rs +326 -0
- data/crates/shirobai-core/src/rules/string_literals.rs +611 -0
- data/crates/shirobai-core/src/rules/string_literals_in_interpolation.rs +244 -0
- data/crates/shirobai-core/src/rules/trailing_comma.rs +428 -0
- data/crates/shirobai-core/src/rules/trailing_comma_in_arguments.rs +502 -0
- data/crates/shirobai-core/src/rules/trailing_comma_in_array_literal.rs +210 -0
- data/crates/shirobai-core/src/rules/trailing_comma_in_hash_literal.rs +174 -0
- data/crates/shirobai-core/src/rules/trailing_empty_lines.rs +307 -0
- data/crates/shirobai-core/src/rules/unreachable_code.rs +719 -0
- data/crates/shirobai-core/src/rules/useless_access_modifier.rs +1087 -0
- data/crates/shirobai-core/src/rules/variable_number.rs +318 -0
- data/crates/shirobai-core/src/rules/void.rs +1328 -0
- data/ext/shirobai/Cargo.toml +16 -0
- data/ext/shirobai/src/lib.rs +4501 -0
- data/lib/shirobai/cop/base.rb +45 -0
- data/lib/shirobai/cop/layout/access_modifier_indentation.rb +117 -0
- data/lib/shirobai/cop/layout/argument_alignment.rb +86 -0
- data/lib/shirobai/cop/layout/array_alignment.rb +109 -0
- data/lib/shirobai/cop/layout/assignment_indentation.rb +73 -0
- data/lib/shirobai/cop/layout/block_alignment.rb +79 -0
- data/lib/shirobai/cop/layout/closing_parenthesis_indentation.rb +51 -0
- data/lib/shirobai/cop/layout/def_end_alignment.rb +114 -0
- data/lib/shirobai/cop/layout/dot_position.rb +63 -0
- data/lib/shirobai/cop/layout/else_alignment.rb +86 -0
- data/lib/shirobai/cop/layout/empty_comment.rb +75 -0
- data/lib/shirobai/cop/layout/empty_line_after_guard_clause.rb +116 -0
- data/lib/shirobai/cop/layout/empty_line_after_magic_comment.rb +82 -0
- data/lib/shirobai/cop/layout/empty_line_between_defs.rb +78 -0
- data/lib/shirobai/cop/layout/empty_lines.rb +70 -0
- data/lib/shirobai/cop/layout/empty_lines_around_arguments.rb +55 -0
- data/lib/shirobai/cop/layout/empty_lines_around_begin_body.rb +22 -0
- data/lib/shirobai/cop/layout/empty_lines_around_block_body.rb +28 -0
- data/lib/shirobai/cop/layout/empty_lines_around_body_shared.rb +58 -0
- data/lib/shirobai/cop/layout/empty_lines_around_class_body.rb +27 -0
- data/lib/shirobai/cop/layout/empty_lines_around_exception_handling_keywords.rb +24 -0
- data/lib/shirobai/cop/layout/empty_lines_around_method_body.rb +22 -0
- data/lib/shirobai/cop/layout/empty_lines_around_module_body.rb +26 -0
- data/lib/shirobai/cop/layout/end_alignment.rb +101 -0
- data/lib/shirobai/cop/layout/first_argument_indentation.rb +76 -0
- data/lib/shirobai/cop/layout/first_array_element_indentation.rb +89 -0
- data/lib/shirobai/cop/layout/first_hash_element_indentation.rb +104 -0
- data/lib/shirobai/cop/layout/hash_alignment.rb +135 -0
- data/lib/shirobai/cop/layout/indentation_consistency.rb +95 -0
- data/lib/shirobai/cop/layout/indentation_width.rb +141 -0
- data/lib/shirobai/cop/layout/leading_empty_lines.rb +72 -0
- data/lib/shirobai/cop/layout/line_length.rb +227 -0
- data/lib/shirobai/cop/layout/multiline_method_call_brace_layout.rb +126 -0
- data/lib/shirobai/cop/layout/multiline_method_call_indentation.rb +87 -0
- data/lib/shirobai/cop/layout/multiline_operation_indentation.rb +61 -0
- data/lib/shirobai/cop/layout/space_after_colon.rb +58 -0
- data/lib/shirobai/cop/layout/space_after_comma.rb +64 -0
- data/lib/shirobai/cop/layout/space_after_semicolon.rb +57 -0
- data/lib/shirobai/cop/layout/space_around_keyword.rb +69 -0
- data/lib/shirobai/cop/layout/space_around_method_call_operator.rb +55 -0
- data/lib/shirobai/cop/layout/space_before_block_braces.rb +150 -0
- data/lib/shirobai/cop/layout/space_before_comma.rb +63 -0
- data/lib/shirobai/cop/layout/space_before_comment.rb +56 -0
- data/lib/shirobai/cop/layout/space_before_first_arg.rb +65 -0
- data/lib/shirobai/cop/layout/space_before_semicolon.rb +57 -0
- data/lib/shirobai/cop/layout/space_inside_array_literal_brackets.rb +109 -0
- data/lib/shirobai/cop/layout/space_inside_block_braces.rb +116 -0
- data/lib/shirobai/cop/layout/space_inside_hash_literal_braces.rb +93 -0
- data/lib/shirobai/cop/layout/space_inside_parens.rb +67 -0
- data/lib/shirobai/cop/layout/space_inside_reference_brackets.rb +104 -0
- data/lib/shirobai/cop/layout/trailing_empty_lines.rb +104 -0
- data/lib/shirobai/cop/lint/ambiguous_block_association.rb +117 -0
- data/lib/shirobai/cop/lint/debugger.rb +44 -0
- data/lib/shirobai/cop/lint/duplicate_magic_comment.rb +67 -0
- data/lib/shirobai/cop/lint/duplicate_methods.rb +109 -0
- data/lib/shirobai/cop/lint/parentheses_as_grouped_expression.rb +54 -0
- data/lib/shirobai/cop/lint/require_parentheses.rb +35 -0
- data/lib/shirobai/cop/lint/safe_navigation_chain.rb +52 -0
- data/lib/shirobai/cop/lint/self_assignment.rb +105 -0
- data/lib/shirobai/cop/lint/unreachable_code.rb +48 -0
- data/lib/shirobai/cop/lint/useless_access_modifier.rb +61 -0
- data/lib/shirobai/cop/lint/void.rb +60 -0
- data/lib/shirobai/cop/metrics/abc_size.rb +61 -0
- data/lib/shirobai/cop/metrics/block_length.rb +139 -0
- data/lib/shirobai/cop/metrics/block_nesting.rb +62 -0
- data/lib/shirobai/cop/metrics/class_length.rb +88 -0
- data/lib/shirobai/cop/metrics/complexity_base.rb +35 -0
- data/lib/shirobai/cop/metrics/complexity_shared.rb +45 -0
- data/lib/shirobai/cop/metrics/cyclomatic_complexity.rb +30 -0
- data/lib/shirobai/cop/metrics/method_length.rb +70 -0
- data/lib/shirobai/cop/metrics/module_length.rb +83 -0
- data/lib/shirobai/cop/metrics/perceived_complexity.rb +30 -0
- data/lib/shirobai/cop/naming/method_name.rb +100 -0
- data/lib/shirobai/cop/naming/predicate_prefix.rb +107 -0
- data/lib/shirobai/cop/naming/variable_number.rb +72 -0
- data/lib/shirobai/cop/style/arguments_forwarding.rb +108 -0
- data/lib/shirobai/cop/style/block_delimiters.rb +164 -0
- data/lib/shirobai/cop/style/colon_method_call.rb +60 -0
- data/lib/shirobai/cop/style/file_null.rb +55 -0
- data/lib/shirobai/cop/style/frozen_string_literal_comment.rb +118 -0
- data/lib/shirobai/cop/style/hash_each_methods.rb +50 -0
- data/lib/shirobai/cop/style/hash_syntax.rb +147 -0
- data/lib/shirobai/cop/style/hash_transform_keys.rb +46 -0
- data/lib/shirobai/cop/style/if_unless_modifier.rb +207 -0
- data/lib/shirobai/cop/style/line_end_concatenation.rb +42 -0
- data/lib/shirobai/cop/style/nested_parenthesized_calls.rb +88 -0
- data/lib/shirobai/cop/style/percent_literal_delimiters.rb +113 -0
- data/lib/shirobai/cop/style/redundant_freeze.rb +75 -0
- data/lib/shirobai/cop/style/redundant_self.rb +50 -0
- data/lib/shirobai/cop/style/redundant_self_assignment.rb +62 -0
- data/lib/shirobai/cop/style/semicolon.rb +190 -0
- data/lib/shirobai/cop/style/stabby_lambda_parentheses.rb +75 -0
- data/lib/shirobai/cop/style/string_literals.rb +135 -0
- data/lib/shirobai/cop/style/string_literals_in_interpolation.rb +130 -0
- data/lib/shirobai/cop/style/trailing_comma_in_arguments.rb +127 -0
- data/lib/shirobai/cop/style/trailing_comma_in_array_literal.rb +127 -0
- data/lib/shirobai/cop/style/trailing_comma_in_hash_literal.rb +127 -0
- data/lib/shirobai/dispatch.rb +481 -0
- data/lib/shirobai/inject.rb +123 -0
- data/lib/shirobai/node_locator.rb +124 -0
- data/lib/shirobai/source_offsets.rb +43 -0
- data/lib/shirobai/version.rb +5 -0
- data/lib/shirobai.rb +5 -0
- metadata +271 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
//! `Layout/FirstArrayElementIndentation`.
|
|
2
|
+
//!
|
|
3
|
+
//! Checks the indentation of the first element of an array literal whose
|
|
4
|
+
//! opening bracket and first element are on separate lines, and of a hanging
|
|
5
|
+
//! right bracket. Same `AlignmentCorrector` division of labour as the other
|
|
6
|
+
//! alignment cops: Rust computes the offense range (the first element, or the
|
|
7
|
+
//! `]` token), the `column_delta` and the message; Ruby applies the
|
|
8
|
+
//! realignment via `AlignmentCorrector`.
|
|
9
|
+
//!
|
|
10
|
+
//! Columns are parser-gem columns (character counts from the line start), not
|
|
11
|
+
//! display width: the `MultilineElementIndentation` mixin reads
|
|
12
|
+
//! `source_range.column` / `=~ /\S/` directly instead of `display_column`.
|
|
13
|
+
//!
|
|
14
|
+
//! The trickiest part is replicating which left parenthesis "claims" an array
|
|
15
|
+
//! (stock's `each_argument_node` + `ignore_node` dance): `on_send` fires in
|
|
16
|
+
//! pre-order and scans the call's arguments with `on_node(:array, arg, :send)`,
|
|
17
|
+
//! which descends into everything *except* nested plain `send`s — parser's
|
|
18
|
+
//! `:block` node keeps a call's block body scannable, `:csend` is never
|
|
19
|
+
//! excluded, and an index call (`foo[...]`) is a `send` with `loc.begin` `nil`
|
|
20
|
+
//! so it blocks but can never claim. The ancestor-stack walk reproduces this:
|
|
21
|
+
//! the deepest plain-call ancestor (path not entering through its block) is a
|
|
22
|
+
//! blocking boundary, and the outermost candidate call at or below it whose
|
|
23
|
+
//! `(` is on the array's opening line claims the array.
|
|
24
|
+
|
|
25
|
+
use std::rc::Rc;
|
|
26
|
+
|
|
27
|
+
use ruby_prism::{Location, Node};
|
|
28
|
+
|
|
29
|
+
use super::line_index::LineIndex;
|
|
30
|
+
|
|
31
|
+
/// One misindented first element or right bracket. `[start_offset,
|
|
32
|
+
/// end_offset)` is the offense range, which Ruby both reports and realigns by
|
|
33
|
+
/// `column_delta` via `AlignmentCorrector` (resolving the element's AST node
|
|
34
|
+
/// for the string-taboo handling; the `]` range stays a range, like stock).
|
|
35
|
+
pub struct FirstArrayElemIndentOffense {
|
|
36
|
+
pub start_offset: usize,
|
|
37
|
+
pub end_offset: usize,
|
|
38
|
+
pub column_delta: isize,
|
|
39
|
+
pub message: String,
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
#[derive(Clone, Copy, PartialEq, Eq)]
|
|
43
|
+
enum Style {
|
|
44
|
+
/// `special_inside_parentheses` (default).
|
|
45
|
+
SpecialInsideParens,
|
|
46
|
+
Consistent,
|
|
47
|
+
AlignBrackets,
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
impl Style {
|
|
51
|
+
fn from_u8(v: u8) -> Self {
|
|
52
|
+
match v {
|
|
53
|
+
1 => Style::Consistent,
|
|
54
|
+
2 => Style::AlignBrackets,
|
|
55
|
+
_ => Style::SpecialInsideParens,
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// `indent_base`'s second return value: what the expected column is based on.
|
|
61
|
+
#[derive(Clone, Copy)]
|
|
62
|
+
enum BaseType {
|
|
63
|
+
/// `:left_brace_or_bracket` (`align_brackets` style).
|
|
64
|
+
LeftBracket,
|
|
65
|
+
/// `:first_column_after_left_parenthesis`.
|
|
66
|
+
AfterParen,
|
|
67
|
+
/// `:parent_hash_key`.
|
|
68
|
+
ParentHashKey,
|
|
69
|
+
/// `:start_of_line`.
|
|
70
|
+
StartOfLine,
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
pub fn check_first_array_element_indentation(
|
|
74
|
+
source: &[u8],
|
|
75
|
+
style: u8,
|
|
76
|
+
indent_width: usize,
|
|
77
|
+
enforce_fixed_indentation: bool,
|
|
78
|
+
) -> Vec<FirstArrayElemIndentOffense> {
|
|
79
|
+
let Some(mut rule) = build_rule(source, style, indent_width, enforce_fixed_indentation) else {
|
|
80
|
+
return Vec::new();
|
|
81
|
+
};
|
|
82
|
+
super::dispatch::run(source, &mut [&mut rule]);
|
|
83
|
+
rule.offenses
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/// Build the rule for use standalone or in a shared-walk bundle. `None` when
|
|
87
|
+
/// the cop is disabled outright: `Layout/ArrayAlignment` enforcing
|
|
88
|
+
/// `with_fixed_indentation` gates both `on_array` and `on_send` unless the
|
|
89
|
+
/// style is `consistent`.
|
|
90
|
+
pub(crate) fn build_rule(
|
|
91
|
+
source: &[u8],
|
|
92
|
+
style: u8,
|
|
93
|
+
indent_width: usize,
|
|
94
|
+
enforce_fixed_indentation: bool,
|
|
95
|
+
) -> Option<Visitor<'_>> {
|
|
96
|
+
let style = Style::from_u8(style);
|
|
97
|
+
if enforce_fixed_indentation && style != Style::Consistent {
|
|
98
|
+
return None;
|
|
99
|
+
}
|
|
100
|
+
Some(Visitor {
|
|
101
|
+
source,
|
|
102
|
+
line_index: super::line_index::with_line_index(source, |li| li.clone()),
|
|
103
|
+
style,
|
|
104
|
+
indent: indent_width,
|
|
105
|
+
stack: Vec::new(),
|
|
106
|
+
offenses: Vec::new(),
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/// Lightweight ancestor frame kind, carrying exactly what the claiming logic
|
|
111
|
+
/// and the parent-hash-key base need.
|
|
112
|
+
///
|
|
113
|
+
/// NOTE: `ArgumentsNode` (like `StatementsNode`) is reached through its
|
|
114
|
+
/// parent's concretely-typed field (`visit_arguments_node`), which bypasses
|
|
115
|
+
/// the generic branch hooks — it never appears on the stack. "The path enters
|
|
116
|
+
/// through the call's arguments" is therefore an offset test against the
|
|
117
|
+
/// `ArgumentsNode`'s range stored on the call's own frame; skipped transparent
|
|
118
|
+
/// levels cannot change a containment test because node ranges nest.
|
|
119
|
+
enum FrameKind {
|
|
120
|
+
/// A parser `send` / `csend`. `paren_start` is the begin offset of a
|
|
121
|
+
/// literal `(` (an index call's `[` is `loc.begin == nil` in parser and
|
|
122
|
+
/// can never claim). `args_range` spans the `ArgumentsNode`, which in
|
|
123
|
+
/// parser is the send's own extent minus receiver/selector/block.
|
|
124
|
+
Call {
|
|
125
|
+
csend: bool,
|
|
126
|
+
paren_start: Option<usize>,
|
|
127
|
+
args_range: Option<(usize, usize)>,
|
|
128
|
+
},
|
|
129
|
+
/// A `&blk` block-pass (part of `node.arguments` in parser, so a path
|
|
130
|
+
/// through it counts as entering the call's arguments).
|
|
131
|
+
BlockArgument,
|
|
132
|
+
/// A `{}`/`do` block attached to a call: parser materialises a separate
|
|
133
|
+
/// `:block` node *containing* the send, so a path through the block is not
|
|
134
|
+
/// excluded by the send.
|
|
135
|
+
Block,
|
|
136
|
+
/// A hash `pair`, for `pair.loc.column` and `pair.last_line` (the frame's
|
|
137
|
+
/// own `start`/`end` span the pair).
|
|
138
|
+
Assoc {
|
|
139
|
+
key_start: usize,
|
|
140
|
+
value_start: usize,
|
|
141
|
+
},
|
|
142
|
+
/// A hash (braced or keyword). `elements` are the children's ranges, for
|
|
143
|
+
/// `pair.right_sibling`.
|
|
144
|
+
Hash {
|
|
145
|
+
elements: Vec<(usize, usize)>,
|
|
146
|
+
},
|
|
147
|
+
/// An attribute / index op-assign (`a.b += x`, `a[i] ||= x`): parser nests
|
|
148
|
+
/// a plain `send` covering everything up to `block_end` (`a.b` / `a[i]`)
|
|
149
|
+
/// inside the assign node, blocking `on_node` scans into that range, while
|
|
150
|
+
/// the assigned value sits outside it and stays scannable.
|
|
151
|
+
AsgnTarget {
|
|
152
|
+
block_end: usize,
|
|
153
|
+
},
|
|
154
|
+
Other,
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/// Ancestor frame: the node's range plus its classified kind.
|
|
158
|
+
struct Frame {
|
|
159
|
+
start: usize,
|
|
160
|
+
end: usize,
|
|
161
|
+
kind: FrameKind,
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
pub(crate) struct Visitor<'a> {
|
|
165
|
+
source: &'a [u8],
|
|
166
|
+
line_index: Rc<LineIndex>,
|
|
167
|
+
style: Style,
|
|
168
|
+
indent: usize,
|
|
169
|
+
stack: Vec<Frame>,
|
|
170
|
+
pub(crate) offenses: Vec<FirstArrayElemIndentOffense>,
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
fn loc(l: &Location<'_>) -> (usize, usize) {
|
|
174
|
+
(l.start_offset(), l.end_offset())
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/// Ruby regex `\s` (the line-local subset; `\n` cannot occur inside a line).
|
|
178
|
+
fn is_ws(b: u8) -> bool {
|
|
179
|
+
matches!(b, b' ' | b'\t' | b'\x0b' | b'\x0c' | b'\r')
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
impl super::dispatch::Rule for Visitor<'_> {
|
|
183
|
+
fn enter(&mut self, node: &Node<'_>) {
|
|
184
|
+
if let Some(a) = node.as_array_node() {
|
|
185
|
+
self.process_array(&a);
|
|
186
|
+
}
|
|
187
|
+
self.stack.push(self.make_frame(node));
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
fn leave(&mut self) {
|
|
191
|
+
self.stack.pop();
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
impl<'a> Visitor<'a> {
|
|
196
|
+
fn make_frame(&self, node: &Node<'_>) -> Frame {
|
|
197
|
+
let (start, end) = loc(&node.location());
|
|
198
|
+
let kind = if let Some(c) = node.as_call_node() {
|
|
199
|
+
let csend = c
|
|
200
|
+
.call_operator_loc()
|
|
201
|
+
.map(|l| l.as_slice() == b"&.")
|
|
202
|
+
.unwrap_or(false);
|
|
203
|
+
let paren_start = c
|
|
204
|
+
.opening_loc()
|
|
205
|
+
.filter(|o| o.as_slice() == b"(")
|
|
206
|
+
.map(|o| o.start_offset());
|
|
207
|
+
FrameKind::Call {
|
|
208
|
+
csend,
|
|
209
|
+
paren_start,
|
|
210
|
+
args_range: c.arguments().map(|a| loc(&a.as_node().location())),
|
|
211
|
+
}
|
|
212
|
+
} else if node.as_block_argument_node().is_some() {
|
|
213
|
+
FrameKind::BlockArgument
|
|
214
|
+
} else if node.as_block_node().is_some() {
|
|
215
|
+
FrameKind::Block
|
|
216
|
+
} else if let Some(a) = node.as_assoc_node() {
|
|
217
|
+
FrameKind::Assoc {
|
|
218
|
+
key_start: a.key().location().start_offset(),
|
|
219
|
+
value_start: a.value().location().start_offset(),
|
|
220
|
+
}
|
|
221
|
+
} else if let Some(h) = node.as_hash_node() {
|
|
222
|
+
FrameKind::Hash {
|
|
223
|
+
elements: h.elements().iter().map(|e| loc(&e.location())).collect(),
|
|
224
|
+
}
|
|
225
|
+
} else if let Some(h) = node.as_keyword_hash_node() {
|
|
226
|
+
FrameKind::Hash {
|
|
227
|
+
elements: h.elements().iter().map(|e| loc(&e.location())).collect(),
|
|
228
|
+
}
|
|
229
|
+
} else if let Some(k) = asgn_target_kind(node) {
|
|
230
|
+
k
|
|
231
|
+
} else {
|
|
232
|
+
FrameKind::Other
|
|
233
|
+
};
|
|
234
|
+
Frame { start, end, kind }
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/// 1-based line number of `off`.
|
|
238
|
+
fn line_of(&self, off: usize) -> usize {
|
|
239
|
+
self.line_index.line_of(off)
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/// parser-gem `Source::Range#column`: character column within the line.
|
|
243
|
+
fn column(&self, off: usize) -> usize {
|
|
244
|
+
self.line_index.column(self.source, off)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/// `left_brace.source_line =~ /\S/`: the column of the first non-blank
|
|
248
|
+
/// character of `off`'s line (the leading run is ASCII, so bytes == chars;
|
|
249
|
+
/// the line cannot be all-blank — `off` points at a token on it).
|
|
250
|
+
fn line_first_nonws_column(&self, off: usize) -> usize {
|
|
251
|
+
let ls = self.line_index.line_start(off);
|
|
252
|
+
self.source[ls..].iter().take_while(|&&b| is_ws(b)).count()
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
/// The `(` that claims this array via stock's `on_send` path, if any: the
|
|
256
|
+
/// outermost eligible ancestor call not separated from the array by a
|
|
257
|
+
/// plain `send` (`on_node(:array, arg, :send)` stops at nested sends), with
|
|
258
|
+
/// parentheses on the same line as the array's opening bracket. Pre-order
|
|
259
|
+
/// `on_send` + `ignore_node` make the outermost match win.
|
|
260
|
+
fn claimed_paren(&self, array_open_start: usize) -> Option<usize> {
|
|
261
|
+
let array_line = self.line_of(array_open_start);
|
|
262
|
+
let n = self.stack.len();
|
|
263
|
+
// Start offset of the next node on the path from frame `i` towards the
|
|
264
|
+
// array (the array itself when `i` is the direct parent).
|
|
265
|
+
let next_start =
|
|
266
|
+
|i: usize| -> usize { self.stack.get(i + 1).map_or(array_open_start, |f| f.start) };
|
|
267
|
+
// The deepest blocking ancestor: a plain (non-`&.`) call whose path
|
|
268
|
+
// towards the array does not enter through its block, or an op-assign
|
|
269
|
+
// target whose blocked range (parser's nested plain `send`) the path
|
|
270
|
+
// enters. Calls *below* the boundary can still claim.
|
|
271
|
+
let mut start = 0;
|
|
272
|
+
for i in 0..n {
|
|
273
|
+
match self.stack[i].kind {
|
|
274
|
+
FrameKind::Call { csend: false, .. } => {
|
|
275
|
+
let child_is_block =
|
|
276
|
+
i + 1 < n && matches!(self.stack[i + 1].kind, FrameKind::Block);
|
|
277
|
+
if !child_is_block {
|
|
278
|
+
start = i;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
FrameKind::AsgnTarget { block_end } if next_start(i) < block_end => {
|
|
282
|
+
start = i;
|
|
283
|
+
}
|
|
284
|
+
_ => {}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
for i in start..n {
|
|
288
|
+
let FrameKind::Call {
|
|
289
|
+
paren_start: Some(p),
|
|
290
|
+
args_range,
|
|
291
|
+
..
|
|
292
|
+
} = self.stack[i].kind
|
|
293
|
+
else {
|
|
294
|
+
continue;
|
|
295
|
+
};
|
|
296
|
+
// `each_argument_node` scans only the call's own arguments (which
|
|
297
|
+
// in parser include a `&blk` block-pass — Prism's separate
|
|
298
|
+
// `BlockArgumentNode` field), never its receiver.
|
|
299
|
+
let ns = next_start(i);
|
|
300
|
+
let via_arguments = args_range.is_some_and(|(s, e)| s <= ns && ns < e)
|
|
301
|
+
|| (i + 1 < n && matches!(self.stack[i + 1].kind, FrameKind::BlockArgument));
|
|
302
|
+
if via_arguments && self.line_of(p) == array_line {
|
|
303
|
+
return Some(p);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
None
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/// `check(array_node, left_parenthesis)`.
|
|
310
|
+
fn process_array(&mut self, a: &ruby_prism::ArrayNode<'_>) {
|
|
311
|
+
let Some(open) = a.opening_loc() else { return };
|
|
312
|
+
let open_start = open.start_offset();
|
|
313
|
+
let paren = self.claimed_paren(open_start);
|
|
314
|
+
let first = a.elements().iter().next().map(|e| loc(&e.location()));
|
|
315
|
+
|
|
316
|
+
if let Some(f) = first {
|
|
317
|
+
if self.line_of(f.0) == self.line_of(open_start) {
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
self.check_first(f, open_start, paren);
|
|
321
|
+
}
|
|
322
|
+
if let Some(close) = a.closing_loc() {
|
|
323
|
+
self.check_right_bracket(loc(&close), first, open_start, paren);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/// `check_first(first, left_bracket, left_parenthesis, 0)`.
|
|
328
|
+
fn check_first(&mut self, first: (usize, usize), open_start: usize, paren: Option<usize>) {
|
|
329
|
+
let actual_column = self.column(first.0);
|
|
330
|
+
let (base_column, base_type) = self.indent_base(open_start, Some(first), paren);
|
|
331
|
+
let expected_column = base_column + self.indent;
|
|
332
|
+
let column_delta = expected_column as isize - actual_column as isize;
|
|
333
|
+
if column_delta == 0 {
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
let message = format!(
|
|
337
|
+
"Use {} spaces for indentation in an array, relative to {}.",
|
|
338
|
+
self.indent,
|
|
339
|
+
base_description(base_type)
|
|
340
|
+
);
|
|
341
|
+
self.offenses.push(FirstArrayElemIndentOffense {
|
|
342
|
+
start_offset: first.0,
|
|
343
|
+
end_offset: first.1,
|
|
344
|
+
column_delta,
|
|
345
|
+
message,
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/// `check_right_bracket(right_bracket, first_elem, left_bracket,
|
|
350
|
+
/// left_parenthesis)`.
|
|
351
|
+
fn check_right_bracket(
|
|
352
|
+
&mut self,
|
|
353
|
+
close: (usize, usize),
|
|
354
|
+
first: Option<(usize, usize)>,
|
|
355
|
+
open_start: usize,
|
|
356
|
+
paren: Option<usize>,
|
|
357
|
+
) {
|
|
358
|
+
// Accept a right bracket that does not begin its line.
|
|
359
|
+
let ls = self.line_index.line_start(close.0);
|
|
360
|
+
if self.source[ls..close.0].iter().any(|&b| !is_ws(b)) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
let (expected_column, base_type) = self.indent_base(open_start, first, paren);
|
|
364
|
+
let column_delta = expected_column as isize - self.column(close.0) as isize;
|
|
365
|
+
if column_delta == 0 {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
let message = match base_type {
|
|
369
|
+
BaseType::LeftBracket => "Indent the right bracket the same as the left bracket.",
|
|
370
|
+
BaseType::AfterParen => {
|
|
371
|
+
"Indent the right bracket the same as the first position \
|
|
372
|
+
after the preceding left parenthesis."
|
|
373
|
+
}
|
|
374
|
+
BaseType::ParentHashKey => "Indent the right bracket the same as the parent hash key.",
|
|
375
|
+
BaseType::StartOfLine => {
|
|
376
|
+
"Indent the right bracket the same as the start of the line \
|
|
377
|
+
where the left bracket is."
|
|
378
|
+
}
|
|
379
|
+
};
|
|
380
|
+
self.offenses.push(FirstArrayElemIndentOffense {
|
|
381
|
+
start_offset: close.0,
|
|
382
|
+
end_offset: close.1,
|
|
383
|
+
column_delta,
|
|
384
|
+
message: message.to_string(),
|
|
385
|
+
});
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/// `indent_base(left_brace, first, left_parenthesis)`.
|
|
389
|
+
fn indent_base(
|
|
390
|
+
&self,
|
|
391
|
+
open_start: usize,
|
|
392
|
+
first: Option<(usize, usize)>,
|
|
393
|
+
paren: Option<usize>,
|
|
394
|
+
) -> (usize, BaseType) {
|
|
395
|
+
if self.style == Style::AlignBrackets {
|
|
396
|
+
return (self.column(open_start), BaseType::LeftBracket);
|
|
397
|
+
}
|
|
398
|
+
if first.is_some()
|
|
399
|
+
&& let Some(col) = self.parent_hash_key_column()
|
|
400
|
+
{
|
|
401
|
+
return (col, BaseType::ParentHashKey);
|
|
402
|
+
}
|
|
403
|
+
if let Some(p) = paren
|
|
404
|
+
&& self.style == Style::SpecialInsideParens
|
|
405
|
+
{
|
|
406
|
+
return (self.column(p) + 1, BaseType::AfterParen);
|
|
407
|
+
}
|
|
408
|
+
(
|
|
409
|
+
self.line_first_nonws_column(open_start),
|
|
410
|
+
BaseType::StartOfLine,
|
|
411
|
+
)
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/// `hash_pair_where_value_beginning_with` + the two pair conditions: the
|
|
415
|
+
/// array's direct parent is a hash pair whose key and value begin on the
|
|
416
|
+
/// same line and whose right sibling begins on a subsequent line. Returns
|
|
417
|
+
/// `pair.loc.column`.
|
|
418
|
+
fn parent_hash_key_column(&self) -> Option<usize> {
|
|
419
|
+
let n = self.stack.len();
|
|
420
|
+
let pair = self.stack.last()?;
|
|
421
|
+
let FrameKind::Assoc {
|
|
422
|
+
key_start,
|
|
423
|
+
value_start,
|
|
424
|
+
} = pair.kind
|
|
425
|
+
else {
|
|
426
|
+
return None;
|
|
427
|
+
};
|
|
428
|
+
let (start, end) = (pair.start, pair.end);
|
|
429
|
+
// `key_and_value_begin_on_same_line?(pair)`.
|
|
430
|
+
if self.line_of(key_start) != self.line_of(value_start) {
|
|
431
|
+
return None;
|
|
432
|
+
}
|
|
433
|
+
// `right_sibling_begins_on_subsequent_line?(pair)`.
|
|
434
|
+
let FrameKind::Hash { elements } = &self.stack[n.checked_sub(2)?].kind else {
|
|
435
|
+
return None;
|
|
436
|
+
};
|
|
437
|
+
let idx = elements.iter().position(|&(s, _)| s == start)?;
|
|
438
|
+
let sibling = elements.get(idx + 1)?;
|
|
439
|
+
if self.line_of(end - 1) >= self.line_of(sibling.0) {
|
|
440
|
+
return None;
|
|
441
|
+
}
|
|
442
|
+
Some(self.column(start))
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/// Classifies `a.b op= x` / `a[i] op= x` as an [`FrameKind::AsgnTarget`]:
|
|
447
|
+
/// parser nests a plain `send` spanning everything before the assignment
|
|
448
|
+
/// operator (`a.b` / `a[i]`), which blocks `on_node` scans into that range.
|
|
449
|
+
/// Safe-navigation forms nest a `csend` instead, which `on_node` does not
|
|
450
|
+
/// exclude, so they stay transparent (`None`).
|
|
451
|
+
fn asgn_target_kind(node: &Node<'_>) -> Option<FrameKind> {
|
|
452
|
+
let (safe, op_start) = if let Some(n) = node.as_call_operator_write_node() {
|
|
453
|
+
(
|
|
454
|
+
n.is_safe_navigation(),
|
|
455
|
+
n.binary_operator_loc().start_offset(),
|
|
456
|
+
)
|
|
457
|
+
} else if let Some(n) = node.as_call_and_write_node() {
|
|
458
|
+
(n.is_safe_navigation(), n.operator_loc().start_offset())
|
|
459
|
+
} else if let Some(n) = node.as_call_or_write_node() {
|
|
460
|
+
(n.is_safe_navigation(), n.operator_loc().start_offset())
|
|
461
|
+
} else if let Some(n) = node.as_index_operator_write_node() {
|
|
462
|
+
(
|
|
463
|
+
n.is_safe_navigation(),
|
|
464
|
+
n.binary_operator_loc().start_offset(),
|
|
465
|
+
)
|
|
466
|
+
} else if let Some(n) = node.as_index_and_write_node() {
|
|
467
|
+
(n.is_safe_navigation(), n.operator_loc().start_offset())
|
|
468
|
+
} else if let Some(n) = node.as_index_or_write_node() {
|
|
469
|
+
(n.is_safe_navigation(), n.operator_loc().start_offset())
|
|
470
|
+
} else {
|
|
471
|
+
return None;
|
|
472
|
+
};
|
|
473
|
+
(!safe).then_some(FrameKind::AsgnTarget {
|
|
474
|
+
block_end: op_start,
|
|
475
|
+
})
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
/// `base_description(indent_base_type)`.
|
|
479
|
+
fn base_description(base_type: BaseType) -> &'static str {
|
|
480
|
+
match base_type {
|
|
481
|
+
BaseType::LeftBracket => "the position of the opening bracket",
|
|
482
|
+
BaseType::AfterParen => "the first position after the preceding left parenthesis",
|
|
483
|
+
BaseType::ParentHashKey => "the parent hash key",
|
|
484
|
+
BaseType::StartOfLine => "the start of the line where the left square bracket is",
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#[cfg(test)]
|
|
489
|
+
mod tests {
|
|
490
|
+
use super::*;
|
|
491
|
+
|
|
492
|
+
fn run(source: &str, style: u8) -> Vec<(usize, usize, isize, String)> {
|
|
493
|
+
check_first_array_element_indentation(source.as_bytes(), style, 2, false)
|
|
494
|
+
.into_iter()
|
|
495
|
+
.map(|o| (o.start_offset, o.end_offset, o.column_delta, o.message))
|
|
496
|
+
.collect()
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
#[test]
|
|
500
|
+
fn operand_array_first_element_and_right_bracket() {
|
|
501
|
+
let got = run("a << [\n 1\n ]\n", 0);
|
|
502
|
+
assert_eq!(got.len(), 2);
|
|
503
|
+
assert_eq!(
|
|
504
|
+
(got[0].2, got[0].3.as_str()),
|
|
505
|
+
(
|
|
506
|
+
1,
|
|
507
|
+
"Use 2 spaces for indentation in an array, relative to the \
|
|
508
|
+
start of the line where the left square bracket is."
|
|
509
|
+
)
|
|
510
|
+
);
|
|
511
|
+
assert_eq!(
|
|
512
|
+
(got[1].2, got[1].3.as_str()),
|
|
513
|
+
(
|
|
514
|
+
-2,
|
|
515
|
+
"Indent the right bracket the same as the start of the line \
|
|
516
|
+
where the left bracket is."
|
|
517
|
+
)
|
|
518
|
+
);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
#[test]
|
|
522
|
+
fn accepts_correct_indentation_and_same_line_first_element() {
|
|
523
|
+
assert!(run("a = [\n 1\n]\n", 0).is_empty());
|
|
524
|
+
assert!(run("a = [1,\n 2]\n", 0).is_empty());
|
|
525
|
+
assert!(run("a = [1, 2]\n", 0).is_empty());
|
|
526
|
+
assert!(run("a = []\n", 0).is_empty());
|
|
527
|
+
assert!(run("a, b = b, a\n", 0).is_empty());
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
#[test]
|
|
531
|
+
fn special_inside_parentheses_claims_via_send() {
|
|
532
|
+
let got = run("func([\n 1\n])\n", 0);
|
|
533
|
+
assert_eq!(got.len(), 2);
|
|
534
|
+
assert!(
|
|
535
|
+
got[0]
|
|
536
|
+
.3
|
|
537
|
+
.contains("the first position after the preceding left parenthesis"),
|
|
538
|
+
"{}",
|
|
539
|
+
got[0].3
|
|
540
|
+
);
|
|
541
|
+
assert_eq!(got[0].2, 5);
|
|
542
|
+
assert_eq!(got[1].2, 5);
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
#[test]
|
|
546
|
+
fn consistent_style_ignores_parentheses() {
|
|
547
|
+
let got = run("func([\n 1\n ])\n", 1);
|
|
548
|
+
assert_eq!(got.len(), 2);
|
|
549
|
+
assert!(got[0].3.contains("the start of the line"), "{}", got[0].3);
|
|
550
|
+
assert_eq!(got[0].2, -5);
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
#[test]
|
|
554
|
+
fn align_brackets_uses_bracket_column() {
|
|
555
|
+
let got = run("var = [\n 1\n]\n", 2);
|
|
556
|
+
assert_eq!(got.len(), 2);
|
|
557
|
+
assert!(
|
|
558
|
+
got[0].3.contains("the position of the opening bracket"),
|
|
559
|
+
"{}",
|
|
560
|
+
got[0].3
|
|
561
|
+
);
|
|
562
|
+
assert_eq!(got[0].2, 6); // expected 6 + 2 = 8, actual 2
|
|
563
|
+
assert_eq!(got[1].2, 6);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
#[test]
|
|
567
|
+
fn parent_hash_key_base_for_multi_pair_hash() {
|
|
568
|
+
let got = run(
|
|
569
|
+
"func(x: [\n :a,\n :b\n],\n y: [\n :c\n ])\n",
|
|
570
|
+
0,
|
|
571
|
+
);
|
|
572
|
+
assert_eq!(got.len(), 2);
|
|
573
|
+
assert!(got[0].3.contains("the parent hash key"), "{}", got[0].3);
|
|
574
|
+
assert_eq!(got[0].2, 5); // pair col 5 + 2 = 7, actual 2
|
|
575
|
+
assert!(got[1].3.contains("parent hash key"), "{}", got[1].3);
|
|
576
|
+
assert_eq!(got[1].2, 5);
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
#[test]
|
|
580
|
+
fn index_send_never_claims() {
|
|
581
|
+
// `foo[...]` has no `(`; the inner array falls back to start-of-line.
|
|
582
|
+
assert!(run("foo[[\n 1]]\n", 0).is_empty());
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
#[test]
|
|
586
|
+
fn block_body_is_scannable_through_the_send() {
|
|
587
|
+
// parser's `:block` node contains the send, so `func`'s scan reaches
|
|
588
|
+
// the array inside the block body.
|
|
589
|
+
let got = run("func(foo { [\n 1\n] })\n", 0);
|
|
590
|
+
assert_eq!(got.len(), 2);
|
|
591
|
+
assert!(
|
|
592
|
+
got[0].3.contains("preceding left parenthesis"),
|
|
593
|
+
"{}",
|
|
594
|
+
got[0].3
|
|
595
|
+
);
|
|
596
|
+
assert_eq!(got[0].2, 5);
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
#[test]
|
|
600
|
+
fn nested_plain_send_blocks_outer_claim() {
|
|
601
|
+
// `inner` (a plain send) stops `outer`'s scan; `inner` itself claims:
|
|
602
|
+
// base is inner's `(` col 11 + 1, expected 14, not outer's 5 + 1 + 2.
|
|
603
|
+
let got = run("outer(inner([\n 1\n]))\n", 0);
|
|
604
|
+
assert_eq!(got.len(), 2);
|
|
605
|
+
assert_eq!(got[0].2, 12); // 14 - actual col 2
|
|
606
|
+
assert_eq!(got[1].2, 12); // `]` at col 0
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
#[test]
|
|
610
|
+
fn op_assign_value_is_claimable_but_target_is_not() {
|
|
611
|
+
// The assigned value sits outside parser's nested `a.b` send, so the
|
|
612
|
+
// outer `(` claims it (after-paren base, col 5 + 1 + 2 = 8).
|
|
613
|
+
let got = run("func(a.b += [\n 1\n])\n", 0);
|
|
614
|
+
assert_eq!(got.len(), 2);
|
|
615
|
+
assert!(
|
|
616
|
+
got[0].3.contains("preceding left parenthesis"),
|
|
617
|
+
"{}",
|
|
618
|
+
got[0].3
|
|
619
|
+
);
|
|
620
|
+
assert_eq!(got[0].2, 5);
|
|
621
|
+
assert_eq!(got[1].2, 5);
|
|
622
|
+
// An array inside the target (the receiver) is blocked from the outer
|
|
623
|
+
// `(` and falls back to start-of-line (col 0 + 2 = 2: no offense).
|
|
624
|
+
assert!(run("func([\n 1\n].b += x)\n", 0).is_empty());
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
#[test]
|
|
628
|
+
fn disabled_when_array_alignment_enforces_fixed_indentation() {
|
|
629
|
+
assert!(
|
|
630
|
+
check_first_array_element_indentation("func([\n 1\n])\n".as_bytes(), 0, 2, true)
|
|
631
|
+
.is_empty()
|
|
632
|
+
);
|
|
633
|
+
// ...unless the style is `consistent`.
|
|
634
|
+
assert!(
|
|
635
|
+
!check_first_array_element_indentation(
|
|
636
|
+
"func([\n 1\n ])\n".as_bytes(),
|
|
637
|
+
1,
|
|
638
|
+
2,
|
|
639
|
+
true
|
|
640
|
+
)
|
|
641
|
+
.is_empty()
|
|
642
|
+
);
|
|
643
|
+
}
|
|
644
|
+
}
|