rubocop-nathank 1.0.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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +30 -0
- data/lib/rubocop/nathank/begin_block_assignment.rb +40 -0
- data/lib/rubocop/nathank/comment_structure.rb +305 -0
- data/lib/rubocop/nathank/config.yaml +52 -0
- data/lib/rubocop/nathank/no_conditional_assignment.rb +256 -0
- data/lib/rubocop/nathank/no_modifier_if.rb +100 -0
- data/lib/rubocop/nathank/no_return.rb +208 -0
- data/lib/rubocop/nathank/plugin.rb +43 -0
- data/lib/rubocop/nathank/single_line_methods_for_events.rb +168 -0
- data/lib/rubocop/nathank/support/prism_source.rb +234 -0
- data/lib/rubocop/nathank/use_tap.rb +100 -0
- data/lib/rubocop/nathank/version.rb +10 -0
- data/lib/rubocop/nathank.rb +23 -0
- data/lib/rubocop-nathank.rb +6 -0
- data/license.md +21 -0
- data/readme.md +54 -0
- data/releases.md +6 -0
- metadata +114 -0
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the Apache License, Version 2.0.
|
|
4
|
+
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
+
|
|
6
|
+
# Bans the modifier `if` / `unless` form.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# return true if task.state_task_completed? # NO
|
|
10
|
+
#
|
|
11
|
+
# @example
|
|
12
|
+
# if task.state_task_completed? # YES
|
|
13
|
+
# return true
|
|
14
|
+
# end
|
|
15
|
+
|
|
16
|
+
require_relative "support/prism_source"
|
|
17
|
+
|
|
18
|
+
module RuboCop
|
|
19
|
+
module Nathank
|
|
20
|
+
class NoModifierIf < ::RuboCop::Cop::Base
|
|
21
|
+
include PrismSource
|
|
22
|
+
extend ::RuboCop::Cop::AutoCorrector
|
|
23
|
+
|
|
24
|
+
MSG = "Do not use modifier `%<keyword>s`; use the full `%<keyword>s ... end` block form."
|
|
25
|
+
|
|
26
|
+
def on_new_investigation
|
|
27
|
+
self.prism_each(::Prism::IfNode, ::Prism::UnlessNode).each do |node|
|
|
28
|
+
if self.modifier_form?(node)
|
|
29
|
+
range = self.prism_node_range(node)
|
|
30
|
+
|
|
31
|
+
add_offense(range, message: format(MSG, keyword: self.keyword(node))) do |corrector|
|
|
32
|
+
if self.safe_to_unwrap?(node)
|
|
33
|
+
corrector.replace(range, self.block_form(node))
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# A modifier has its keyword written after what it guards, and so has no `end` to close.
|
|
43
|
+
# A ternary has no keyword at all, and is not this cop's business.
|
|
44
|
+
def modifier_form?(node)
|
|
45
|
+
if node.is_a?(::Prism::UnlessNode)
|
|
46
|
+
keyword = node.keyword_loc
|
|
47
|
+
else
|
|
48
|
+
keyword = node.if_keyword_loc
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
!keyword.nil? && node.end_keyword_loc.nil?
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def keyword(node)
|
|
55
|
+
if node.is_a?(::Prism::UnlessNode)
|
|
56
|
+
"unless"
|
|
57
|
+
else
|
|
58
|
+
"if"
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Two shapes are left for a human.
|
|
63
|
+
def safe_to_unwrap?(node)
|
|
64
|
+
self.own_line?(node) && !self.prism_heredoc?(node)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def own_line?(node)
|
|
68
|
+
line = processed_source.lines[node.location.start_line - 1].to_s
|
|
69
|
+
|
|
70
|
+
line[0...self.prism_column(node)].strip.empty?
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def block_form(node)
|
|
74
|
+
indent = " " * self.prism_column(node)
|
|
75
|
+
body = node.statements
|
|
76
|
+
|
|
77
|
+
"#{self.keyword(node)} #{self.prism_source(node.predicate)}\n" \
|
|
78
|
+
"#{self.reindent(body, indent)}\n" \
|
|
79
|
+
"#{indent}end"
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Move the body to a new indent.
|
|
83
|
+
def reindent(node, indent)
|
|
84
|
+
target = indent.length + 2
|
|
85
|
+
delta = target - self.prism_column(node)
|
|
86
|
+
lines = self.prism_source(node).lines
|
|
87
|
+
|
|
88
|
+
rest = lines[1..].to_a.map{|line|
|
|
89
|
+
if delta.negative?
|
|
90
|
+
line.chomp.sub(/\A {0,#{-delta}}/, "")
|
|
91
|
+
else
|
|
92
|
+
"#{" " * delta}#{line.chomp}"
|
|
93
|
+
end
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[("#{" " * target}#{lines.first.chomp}")].concat(rest).join("\n")
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the Apache License, Version 2.0.
|
|
4
|
+
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
+
|
|
6
|
+
# Bans the `return` keyword.
|
|
7
|
+
#
|
|
8
|
+
# @example
|
|
9
|
+
# if cond → if cond
|
|
10
|
+
# return X X
|
|
11
|
+
# end else
|
|
12
|
+
# rest rest
|
|
13
|
+
# end
|
|
14
|
+
|
|
15
|
+
require_relative "support/prism_source"
|
|
16
|
+
|
|
17
|
+
module RuboCop
|
|
18
|
+
module Nathank
|
|
19
|
+
class NoReturn < ::RuboCop::Cop::Base
|
|
20
|
+
include PrismSource
|
|
21
|
+
extend ::RuboCop::Cop::AutoCorrector
|
|
22
|
+
|
|
23
|
+
MSG = "Do not use `return`; let the method's last expression be the return value."
|
|
24
|
+
|
|
25
|
+
def on_new_investigation
|
|
26
|
+
# One guard per body per pass — two corrections inside the same body would overlap and clobber each other. `rubocop -a` re-runs the cop until it stops changing anything, so chained guards still unwind, one per pass.
|
|
27
|
+
@corrected_bodies = {}
|
|
28
|
+
|
|
29
|
+
self.prism_each(::Prism::ReturnNode).each do |node|
|
|
30
|
+
self.report(node)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def report(node)
|
|
37
|
+
add_offense(self.prism_node_range(node)) do |corrector|
|
|
38
|
+
guard = self.correctable_guard(node)
|
|
39
|
+
|
|
40
|
+
if guard
|
|
41
|
+
body, index = guard
|
|
42
|
+
|
|
43
|
+
if @corrected_bodies[body.object_id].nil?
|
|
44
|
+
@corrected_bodies[body.object_id] = true
|
|
45
|
+
corrector.replace(self.replaced_range(body, index), self.rewritten(body, index))
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# The guard's body and index, when the node is one.
|
|
52
|
+
#
|
|
53
|
+
# Prism writes a `StatementsNode` between a conditional and its body, so the guard is the `return`'s grandparent.
|
|
54
|
+
def correctable_guard(node)
|
|
55
|
+
statements = self.prism_parent(node)
|
|
56
|
+
|
|
57
|
+
if !statements.is_a?(::Prism::StatementsNode)
|
|
58
|
+
nil
|
|
59
|
+
else
|
|
60
|
+
if_node = self.prism_parent(statements)
|
|
61
|
+
|
|
62
|
+
if self.unguarded?(if_node, statements, node)
|
|
63
|
+
nil
|
|
64
|
+
else
|
|
65
|
+
body = self.prism_parent(if_node)
|
|
66
|
+
|
|
67
|
+
if !body.is_a?(::Prism::StatementsNode) || !self.tail_position?(body)
|
|
68
|
+
nil
|
|
69
|
+
else
|
|
70
|
+
index = body.body.index(if_node)
|
|
71
|
+
|
|
72
|
+
if index.nil? || index == body.body.size - 1
|
|
73
|
+
nil
|
|
74
|
+
else
|
|
75
|
+
[body, index]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Every shape which is not a plain `if cond / return X / end` guard.
|
|
83
|
+
def unguarded?(if_node, statements, node)
|
|
84
|
+
!self.conditional?(if_node) ||
|
|
85
|
+
self.modifier_form?(if_node) ||
|
|
86
|
+
self.ternary?(if_node) ||
|
|
87
|
+
self.elsif?(if_node) ||
|
|
88
|
+
!if_node.statements.equal?(statements) ||
|
|
89
|
+
statements.body != [node] ||
|
|
90
|
+
!self.else_branch(if_node).nil?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def conditional?(node)
|
|
94
|
+
node.is_a?(::Prism::IfNode) || node.is_a?(::Prism::UnlessNode)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# A modifier has its keyword written after what it guards, and so has no `end` to close.
|
|
98
|
+
def modifier_form?(node)
|
|
99
|
+
!self.keyword_location(node).nil? && node.end_keyword_loc.nil?
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# A ternary has no keyword at all.
|
|
103
|
+
def ternary?(node)
|
|
104
|
+
self.keyword_location(node).nil?
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# An `elsif` is a conditional whose keyword says so.
|
|
108
|
+
def elsif?(node)
|
|
109
|
+
location = self.keyword_location(node)
|
|
110
|
+
|
|
111
|
+
!location.nil? && self.prism_range(location).source == "elsif"
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def keyword_location(node)
|
|
115
|
+
if node.is_a?(::Prism::UnlessNode)
|
|
116
|
+
node.keyword_loc
|
|
117
|
+
else
|
|
118
|
+
node.if_keyword_loc
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
# What follows the then-branch: an `else`, or a chained `elsif`.
|
|
123
|
+
def else_branch(node)
|
|
124
|
+
if node.is_a?(::Prism::UnlessNode)
|
|
125
|
+
node.else_clause
|
|
126
|
+
else
|
|
127
|
+
node.subsequent
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def keyword(node)
|
|
132
|
+
if node.is_a?(::Prism::UnlessNode)
|
|
133
|
+
"unless"
|
|
134
|
+
else
|
|
135
|
+
"if"
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Whether this node is the last thing its enclosing method evaluates.
|
|
140
|
+
def tail_position?(node)
|
|
141
|
+
parent = self.prism_parent(node)
|
|
142
|
+
|
|
143
|
+
if parent.nil?
|
|
144
|
+
false
|
|
145
|
+
elsif parent.is_a?(::Prism::DefNode)
|
|
146
|
+
parent.body.equal?(node)
|
|
147
|
+
elsif parent.is_a?(::Prism::StatementsNode)
|
|
148
|
+
parent.body.last.equal?(node) && self.tail_position?(parent)
|
|
149
|
+
elsif parent.is_a?(::Prism::ElseNode)
|
|
150
|
+
parent.statements.equal?(node) && self.tail_position?(parent)
|
|
151
|
+
elsif self.conditional?(parent) && !self.ternary?(parent)
|
|
152
|
+
parent.statements.equal?(node) && self.tail_position?(parent)
|
|
153
|
+
else
|
|
154
|
+
false
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def replaced_range(body, index)
|
|
159
|
+
self.prism_span(body.body[index].location, body.body.last.location)
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
def rewritten(body, index)
|
|
163
|
+
if_node = body.body[index]
|
|
164
|
+
indent = " " * self.prism_column(if_node)
|
|
165
|
+
guarded = self.returned_value(if_node)
|
|
166
|
+
rest = body.body[(index + 1)..]
|
|
167
|
+
|
|
168
|
+
if guarded
|
|
169
|
+
guard_value = self.reindent(guarded, indent)
|
|
170
|
+
else
|
|
171
|
+
guard_value = "#{indent} nil"
|
|
172
|
+
end
|
|
173
|
+
rest_source = rest.map{|statement| self.reindent(statement, indent)}.join("\n")
|
|
174
|
+
|
|
175
|
+
# `unless cond / return X / end` means "X unless cond", so the branches swap rather than the condition being negated.
|
|
176
|
+
if self.keyword(if_node) == "unless"
|
|
177
|
+
first, second = rest_source, guard_value
|
|
178
|
+
else
|
|
179
|
+
first, second = guard_value, rest_source
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
"if #{self.prism_source(if_node.predicate)}\n#{first}\n#{indent}else\n#{second}\n#{indent}end"
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
# The value the guard returns, or nil for a bare `return`.
|
|
186
|
+
def returned_value(if_node)
|
|
187
|
+
if_node.statements.body.first.arguments&.arguments&.first
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
# Move a statement to a new indent.
|
|
191
|
+
def reindent(node, indent)
|
|
192
|
+
target = indent.length + 2
|
|
193
|
+
delta = target - self.prism_column(node)
|
|
194
|
+
lines = self.prism_source(node).lines
|
|
195
|
+
|
|
196
|
+
rest = lines[1..].to_a.map{|line|
|
|
197
|
+
if delta.negative?
|
|
198
|
+
line.chomp.sub(/\A {0,#{-delta}}/, "")
|
|
199
|
+
else
|
|
200
|
+
"#{" " * delta}#{line.chomp}"
|
|
201
|
+
end
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[("#{" " * target}#{lines.first.chomp}")].concat(rest).join("\n")
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the MIT License.
|
|
4
|
+
# Copyright, 2026, by Nathan Kidd.
|
|
5
|
+
|
|
6
|
+
require "lint_roller"
|
|
7
|
+
require_relative "version"
|
|
8
|
+
|
|
9
|
+
module RuboCop
|
|
10
|
+
module Nathank
|
|
11
|
+
# Represents a LintRoller plugin that provides RuboCop rules for Nathank projects.
|
|
12
|
+
# This plugin integrates custom RuboCop cops and configuration into the LintRoller system.
|
|
13
|
+
class Plugin < LintRoller::Plugin
|
|
14
|
+
# Initialize the plugin with version information.
|
|
15
|
+
def initialize(...)
|
|
16
|
+
super
|
|
17
|
+
@version = RuboCop::Nathank::VERSION
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Get information about this plugin for the LintRoller system.
|
|
21
|
+
# @returns [LintRoller::About] Plugin metadata including name, version, and description.
|
|
22
|
+
def about
|
|
23
|
+
LintRoller::About.new(
|
|
24
|
+
name: "rubocop-nathank",
|
|
25
|
+
version: @version,
|
|
26
|
+
homepage: "https://github.com/n-at-han-k/rubocop-nathank",
|
|
27
|
+
description: "RuboCop rules for Nathank projects."
|
|
28
|
+
)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Define the rules configuration for this plugin.
|
|
32
|
+
# @parameter context [Object] The LintRoller context object.
|
|
33
|
+
# @returns [LintRoller::Rules] Rules configuration specifying the path to the default RuboCop config.
|
|
34
|
+
def rules(context)
|
|
35
|
+
LintRoller::Rules.new(
|
|
36
|
+
type: :path,
|
|
37
|
+
config_format: :rubocop,
|
|
38
|
+
value: File.expand_path("config.yaml", __dir__),
|
|
39
|
+
)
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# Released under the Apache License, Version 2.0.
|
|
4
|
+
# Copyright, 2026, by General Intelligence Systems.
|
|
5
|
+
|
|
6
|
+
require_relative "support/prism_source"
|
|
7
|
+
|
|
8
|
+
module RuboCop
|
|
9
|
+
module Nathank
|
|
10
|
+
# Requires a method whose body is a single expression to use the endless method form.
|
|
11
|
+
#
|
|
12
|
+
# @example
|
|
13
|
+
# # bad
|
|
14
|
+
# def event_type
|
|
15
|
+
# "m.room.message"
|
|
16
|
+
# end
|
|
17
|
+
# # good
|
|
18
|
+
# def event_type = "m.room.message"
|
|
19
|
+
class SingleLineMethodsForEvents < ::RuboCop::Cop::Base
|
|
20
|
+
include PrismSource
|
|
21
|
+
extend ::RuboCop::Cop::AutoCorrector
|
|
22
|
+
|
|
23
|
+
MSG = "A method whose body is one expression must use the endless form: `def name = expression`."
|
|
24
|
+
|
|
25
|
+
# Expressions which can follow ` = ` unchanged. Anything with its own `end`, or with lower precedence than the assignment, is left as a block: what it would parse as is not what it says as a body.
|
|
26
|
+
ACCEPTED_BODIES = [
|
|
27
|
+
::Prism::ArrayNode,
|
|
28
|
+
::Prism::CallNode,
|
|
29
|
+
::Prism::ClassVariableReadNode,
|
|
30
|
+
::Prism::ConstantPathNode,
|
|
31
|
+
::Prism::ConstantReadNode,
|
|
32
|
+
::Prism::FalseNode,
|
|
33
|
+
::Prism::FloatNode,
|
|
34
|
+
::Prism::GlobalVariableReadNode,
|
|
35
|
+
::Prism::HashNode,
|
|
36
|
+
::Prism::ImaginaryNode,
|
|
37
|
+
::Prism::InstanceVariableReadNode,
|
|
38
|
+
::Prism::IntegerNode,
|
|
39
|
+
::Prism::InterpolatedStringNode,
|
|
40
|
+
::Prism::InterpolatedSymbolNode,
|
|
41
|
+
::Prism::InterpolatedXStringNode,
|
|
42
|
+
::Prism::LocalVariableReadNode,
|
|
43
|
+
::Prism::NilNode,
|
|
44
|
+
::Prism::RangeNode,
|
|
45
|
+
::Prism::RationalNode,
|
|
46
|
+
::Prism::RegularExpressionNode,
|
|
47
|
+
::Prism::SelfNode,
|
|
48
|
+
::Prism::StringNode,
|
|
49
|
+
::Prism::SymbolNode,
|
|
50
|
+
::Prism::TrueNode,
|
|
51
|
+
::Prism::XStringNode,
|
|
52
|
+
].freeze
|
|
53
|
+
|
|
54
|
+
def on_new_investigation
|
|
55
|
+
self.prism_each(::Prism::DefNode).each do |node|
|
|
56
|
+
if self.rewriteable?(node)
|
|
57
|
+
self.report(node)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
private
|
|
63
|
+
|
|
64
|
+
# The one expression a method's body is, when that is what it is.
|
|
65
|
+
def sole_expression(node)
|
|
66
|
+
statements = self.prism_statements(node.body)
|
|
67
|
+
|
|
68
|
+
if statements.size == 1
|
|
69
|
+
statements.first
|
|
70
|
+
else
|
|
71
|
+
nil
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# A method is rewritten only when it is not endless, not a setter, and has one non-heredoc body expression on one line.
|
|
76
|
+
def rewriteable?(node)
|
|
77
|
+
body = self.sole_expression(node)
|
|
78
|
+
|
|
79
|
+
!self.endless?(node) &&
|
|
80
|
+
!self.setter?(node.name) &&
|
|
81
|
+
!body.nil? &&
|
|
82
|
+
ACCEPTED_BODIES.any?{|klass| body.is_a?(klass)} &&
|
|
83
|
+
self.single_line?(node, body) &&
|
|
84
|
+
!self.prism_heredoc?(body)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# An endless method is written with `=` rather than a body closed by `end`.
|
|
88
|
+
def endless?(node)
|
|
89
|
+
!node.equal_loc.nil?
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The offense is reported either way; the correction is withheld when a comment sits inside the method, since replacing the method would take the comment with it.
|
|
93
|
+
def report(node)
|
|
94
|
+
range = self.prism_node_range(node)
|
|
95
|
+
|
|
96
|
+
if self.comment?(node)
|
|
97
|
+
add_offense(range)
|
|
98
|
+
else
|
|
99
|
+
add_offense(range) do |corrector|
|
|
100
|
+
corrector.replace(range, self.endless_form(node))
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
# `def foo=(value)` and `def []=(key, value)` have no endless form.
|
|
106
|
+
def setter?(name)
|
|
107
|
+
name == :[]= || /\A[a-zA-Z_]\w*=\z/.match?(name.to_s)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# The body on one line, and the signature on one line too, so that the rewritten method is still a single line.
|
|
111
|
+
def single_line?(node, body)
|
|
112
|
+
body.location.start_line == body.location.end_line &&
|
|
113
|
+
!self.signature_source(node).include?("\n")
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# The source of the signature, e.g. `def build(**fields)`, without the gap before the body.
|
|
117
|
+
def signature_source(node)
|
|
118
|
+
source = processed_source.buffer.source
|
|
119
|
+
|
|
120
|
+
# byteslice, because every offset here is one of Prism's and those count bytes, where `String#[]` counts characters.
|
|
121
|
+
source.byteslice(node.location.start_offset...self.signature_end(node))
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def signature_end(node)
|
|
125
|
+
ending = node.name_loc.end_offset
|
|
126
|
+
|
|
127
|
+
if parameters = node.parameters
|
|
128
|
+
[ending, parameters.location.end_offset].max
|
|
129
|
+
else
|
|
130
|
+
ending
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def comment?(node)
|
|
135
|
+
self.prism_comments.any? do |comment|
|
|
136
|
+
(node.location.start_line..node.location.end_line).cover?(comment.location.start_line)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def endless_form(node)
|
|
141
|
+
"#{self.signature(node)} = #{self.prism_source(self.sole_expression(node))}"
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def signature(node)
|
|
145
|
+
"#{self.receiver_prefix(node)}#{node.name}#{self.arguments_suffix(node)}"
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def receiver_prefix(node)
|
|
149
|
+
if node.receiver
|
|
150
|
+
"def #{self.prism_source(node.receiver)}."
|
|
151
|
+
else
|
|
152
|
+
"def "
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Bare arguments are ambiguous before ` = `; parenthesize them.
|
|
157
|
+
def arguments_suffix(node)
|
|
158
|
+
parameters = node.parameters
|
|
159
|
+
|
|
160
|
+
if parameters.nil?
|
|
161
|
+
""
|
|
162
|
+
else
|
|
163
|
+
"(#{self.prism_source(parameters)})"
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|