rubocop-ast 0.3.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/lib/rubocop/ast.rb +21 -0
  3. data/lib/rubocop/ast/builder.rb +3 -0
  4. data/lib/rubocop/ast/ext/set.rb +12 -0
  5. data/lib/rubocop/ast/node.rb +96 -127
  6. data/lib/rubocop/ast/node/array_node.rb +1 -0
  7. data/lib/rubocop/ast/node/block_node.rb +2 -1
  8. data/lib/rubocop/ast/node/const_node.rb +65 -0
  9. data/lib/rubocop/ast/node/def_node.rb +5 -0
  10. data/lib/rubocop/ast/node/keyword_splat_node.rb +1 -0
  11. data/lib/rubocop/ast/node/mixin/collection_node.rb +1 -0
  12. data/lib/rubocop/ast/node/mixin/descendence.rb +116 -0
  13. data/lib/rubocop/ast/node/mixin/method_dispatch_node.rb +16 -24
  14. data/lib/rubocop/ast/node/mixin/method_identifier_predicates.rb +9 -0
  15. data/lib/rubocop/ast/node/mixin/numeric_node.rb +1 -0
  16. data/lib/rubocop/ast/node/mixin/parameterized_node.rb +2 -2
  17. data/lib/rubocop/ast/node/mixin/predicate_operator_node.rb +7 -3
  18. data/lib/rubocop/ast/node/pair_node.rb +4 -0
  19. data/lib/rubocop/ast/node/regexp_node.rb +9 -4
  20. data/lib/rubocop/ast/node/resbody_node.rb +21 -0
  21. data/lib/rubocop/ast/node/rescue_node.rb +49 -0
  22. data/lib/rubocop/ast/node_pattern.rb +44 -870
  23. data/lib/rubocop/ast/node_pattern/builder.rb +72 -0
  24. data/lib/rubocop/ast/node_pattern/comment.rb +45 -0
  25. data/lib/rubocop/ast/node_pattern/compiler.rb +104 -0
  26. data/lib/rubocop/ast/node_pattern/compiler/atom_subcompiler.rb +56 -0
  27. data/lib/rubocop/ast/node_pattern/compiler/binding.rb +78 -0
  28. data/lib/rubocop/ast/node_pattern/compiler/debug.rb +168 -0
  29. data/lib/rubocop/ast/node_pattern/compiler/node_pattern_subcompiler.rb +146 -0
  30. data/lib/rubocop/ast/node_pattern/compiler/sequence_subcompiler.rb +420 -0
  31. data/lib/rubocop/ast/node_pattern/compiler/subcompiler.rb +57 -0
  32. data/lib/rubocop/ast/node_pattern/lexer.rb +70 -0
  33. data/lib/rubocop/ast/node_pattern/lexer.rex +39 -0
  34. data/lib/rubocop/ast/node_pattern/lexer.rex.rb +182 -0
  35. data/lib/rubocop/ast/node_pattern/method_definer.rb +143 -0
  36. data/lib/rubocop/ast/node_pattern/node.rb +275 -0
  37. data/lib/rubocop/ast/node_pattern/parser.racc.rb +470 -0
  38. data/lib/rubocop/ast/node_pattern/parser.rb +66 -0
  39. data/lib/rubocop/ast/node_pattern/parser.y +103 -0
  40. data/lib/rubocop/ast/node_pattern/sets.rb +37 -0
  41. data/lib/rubocop/ast/node_pattern/with_meta.rb +111 -0
  42. data/lib/rubocop/ast/processed_source.rb +44 -3
  43. data/lib/rubocop/ast/rubocop_compatibility.rb +31 -0
  44. data/lib/rubocop/ast/traversal.rb +149 -172
  45. data/lib/rubocop/ast/version.rb +1 -1
  46. metadata +28 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 918c6017183db74497fc68166973904312bac7583f7752c2e55d1829574cb248
4
- data.tar.gz: 1d04eb24fcbaec96b4bf0f17694018c31034fce4b3d81a9446ca98b3085e05a4
3
+ metadata.gz: d9e5dc8ddfc947af0653c81c0579c18eba59551567d977dc3bf0a4ca4ea92d59
4
+ data.tar.gz: 4110b86811007a0a28e52c090f45b34449a0e3a6ede4cec97463052c1fb3a8a2
5
5
  SHA512:
6
- metadata.gz: 54f6882ebf057c76a9c97212a62b295d03baea85ec7c44e606b412e74edce01b6c4bba93dfafa341756009a8bfa2352c4b2b3b22bdbf689dac08ddd9395a9aff
7
- data.tar.gz: '08e60b802e83dba688bf1a31be417f9880b640194cf3b966849dd2c211de5a1f3797c8a37be3f2d3aa466d6c994b10c31277b0ed3dc708b12fe1c2f7a0f15552'
6
+ metadata.gz: 827480d8986177910eee3a5d7bebafd9eb169818d755d79c16f9d4a3f89ebcd486494f35f07fbf3e0f6ffcc2a82da74a86b0475504c79b4fb1de3b3ad732a78f
7
+ data.tar.gz: 6daa124ee67a95fbc6e91cf5cc0a1179e9aad6718fd8a3f9d54b48c5209b45ede07875e1becbdae84ee2106458bb23538fc144a8aefcc35e922204fcb4c30619
data/lib/rubocop/ast.rb CHANGED
@@ -5,7 +5,22 @@ require 'forwardable'
5
5
  require 'set'
6
6
 
7
7
  require_relative 'ast/ext/range'
8
+ require_relative 'ast/ext/set'
9
+ require_relative 'ast/node_pattern/method_definer'
8
10
  require_relative 'ast/node_pattern'
11
+ require_relative 'ast/node/mixin/descendence'
12
+ require_relative 'ast/node_pattern/builder'
13
+ require_relative 'ast/node_pattern/comment'
14
+ require_relative 'ast/node_pattern/compiler'
15
+ require_relative 'ast/node_pattern/compiler/subcompiler'
16
+ require_relative 'ast/node_pattern/compiler/atom_subcompiler'
17
+ require_relative 'ast/node_pattern/compiler/binding'
18
+ require_relative 'ast/node_pattern/compiler/node_pattern_subcompiler'
19
+ require_relative 'ast/node_pattern/compiler/sequence_subcompiler'
20
+ require_relative 'ast/node_pattern/lexer'
21
+ require_relative 'ast/node_pattern/node'
22
+ require_relative 'ast/node_pattern/parser'
23
+ require_relative 'ast/node_pattern/sets'
9
24
  require_relative 'ast/sexp'
10
25
  require_relative 'ast/node'
11
26
  require_relative 'ast/node/mixin/method_identifier_predicates'
@@ -28,6 +43,7 @@ require_relative 'ast/node/break_node'
28
43
  require_relative 'ast/node/case_match_node'
29
44
  require_relative 'ast/node/case_node'
30
45
  require_relative 'ast/node/class_node'
46
+ require_relative 'ast/node/const_node'
31
47
  require_relative 'ast/node/def_node'
32
48
  require_relative 'ast/node/defined_node'
33
49
  require_relative 'ast/node/ensure_node'
@@ -47,6 +63,7 @@ require_relative 'ast/node/or_node'
47
63
  require_relative 'ast/node/pair_node'
48
64
  require_relative 'ast/node/range_node'
49
65
  require_relative 'ast/node/regexp_node'
66
+ require_relative 'ast/node/rescue_node'
50
67
  require_relative 'ast/node/resbody_node'
51
68
  require_relative 'ast/node/return_node'
52
69
  require_relative 'ast/node/self_class_node'
@@ -60,6 +77,10 @@ require_relative 'ast/node/while_node'
60
77
  require_relative 'ast/node/yield_node'
61
78
  require_relative 'ast/builder'
62
79
  require_relative 'ast/processed_source'
80
+ require_relative 'ast/rubocop_compatibility'
63
81
  require_relative 'ast/token'
64
82
  require_relative 'ast/traversal'
65
83
  require_relative 'ast/version'
84
+
85
+ ::RuboCop::AST::NodePattern::Parser.autoload :WithMeta, "#{__dir__}/ast/node_pattern/with_meta"
86
+ ::RuboCop::AST::NodePattern::Compiler.autoload :Debug, "#{__dir__}/ast/node_pattern/compiler/debug"
@@ -16,6 +16,7 @@ module RuboCop
16
16
  class Builder < Parser::Builders::Default
17
17
  self.emit_forward_arg = true
18
18
 
19
+ # @api private
19
20
  NODE_MAP = {
20
21
  and: AndNode,
21
22
  alias: AliasNode,
@@ -27,6 +28,7 @@ module RuboCop
27
28
  case_match: CaseMatchNode,
28
29
  case: CaseNode,
29
30
  class: ClassNode,
31
+ const: ConstNode,
30
32
  def: DefNode,
31
33
  defined?: DefinedNode,
32
34
  defs: DefNode,
@@ -48,6 +50,7 @@ module RuboCop
48
50
  or: OrNode,
49
51
  pair: PairNode,
50
52
  regexp: RegexpNode,
53
+ rescue: RescueNode,
51
54
  resbody: ResbodyNode,
52
55
  return: ReturnNode,
53
56
  csend: SendNode,
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ test = :foo
4
+ case test
5
+ when Set[:foo]
6
+ # ok, RUBY_VERSION > 2.4
7
+ else
8
+ # Harmonize `Set#===`
9
+ class Set
10
+ alias === include?
11
+ end
12
+ end
@@ -21,41 +21,67 @@ module RuboCop
21
21
  class Node < Parser::AST::Node # rubocop:disable Metrics/ClassLength
22
22
  include RuboCop::AST::Sexp
23
23
  extend NodePattern::Macros
24
+ include RuboCop::AST::Descendence
24
25
 
26
+ # @api private
25
27
  # <=> isn't included here, because it doesn't return a boolean.
26
- COMPARISON_OPERATORS = %i[== === != <= >= > <].freeze
28
+ COMPARISON_OPERATORS = %i[== === != <= >= > <].to_set.freeze
27
29
 
30
+ # @api private
28
31
  TRUTHY_LITERALS = %i[str dstr xstr int float sym dsym array
29
32
  hash regexp true irange erange complex
30
- rational regopt].freeze
31
- FALSEY_LITERALS = %i[false nil].freeze
33
+ rational regopt].to_set.freeze
34
+ # @api private
35
+ FALSEY_LITERALS = %i[false nil].to_set.freeze
36
+ # @api private
32
37
  LITERALS = (TRUTHY_LITERALS + FALSEY_LITERALS).freeze
38
+ # @api private
33
39
  COMPOSITE_LITERALS = %i[dstr xstr dsym array hash irange
34
- erange regexp].freeze
40
+ erange regexp].to_set.freeze
41
+ # @api private
35
42
  BASIC_LITERALS = (LITERALS - COMPOSITE_LITERALS).freeze
43
+ # @api private
36
44
  MUTABLE_LITERALS = %i[str dstr xstr array hash
37
- regexp irange erange].freeze
45
+ regexp irange erange].to_set.freeze
46
+ # @api private
38
47
  IMMUTABLE_LITERALS = (LITERALS - MUTABLE_LITERALS).freeze
39
48
 
49
+ # @api private
40
50
  EQUALS_ASSIGNMENTS = %i[lvasgn ivasgn cvasgn gvasgn
41
- casgn masgn].freeze
42
- SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].freeze
51
+ casgn masgn rasgn mrasgn].to_set.freeze
52
+ # @api private
53
+ SHORTHAND_ASSIGNMENTS = %i[op_asgn or_asgn and_asgn].to_set.freeze
54
+ # @api private
43
55
  ASSIGNMENTS = (EQUALS_ASSIGNMENTS + SHORTHAND_ASSIGNMENTS).freeze
44
56
 
45
- BASIC_CONDITIONALS = %i[if while until].freeze
46
- CONDITIONALS = [*BASIC_CONDITIONALS, :case].freeze
47
- POST_CONDITION_LOOP_TYPES = %i[while_post until_post].freeze
57
+ # @api private
58
+ BASIC_CONDITIONALS = %i[if while until].to_set.freeze
59
+ # @api private
60
+ CONDITIONALS = (BASIC_CONDITIONALS + [:case]).freeze
61
+ # @api private
62
+ POST_CONDITION_LOOP_TYPES = %i[while_post until_post].to_set.freeze
63
+ # @api private
48
64
  LOOP_TYPES = (POST_CONDITION_LOOP_TYPES + %i[while until for]).freeze
49
- VARIABLES = %i[ivar gvar cvar lvar].freeze
50
- REFERENCES = %i[nth_ref back_ref].freeze
65
+ # @api private
66
+ VARIABLES = %i[ivar gvar cvar lvar].to_set.freeze
67
+ # @api private
68
+ REFERENCES = %i[nth_ref back_ref].to_set.freeze
69
+ # @api private
51
70
  KEYWORDS = %i[alias and break case class def defs defined?
52
71
  kwbegin do else ensure for if module next
53
72
  not or postexe redo rescue retry return self
54
73
  super zsuper then undef until when while
55
- yield].freeze
56
- OPERATOR_KEYWORDS = %i[and or].freeze
57
- SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].freeze
58
- ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].freeze
74
+ yield].to_set.freeze
75
+ # @api private
76
+ OPERATOR_KEYWORDS = %i[and or].to_set.freeze
77
+ # @api private
78
+ SPECIAL_KEYWORDS = %w[__FILE__ __LINE__ __ENCODING__].to_set.freeze
79
+ # @api private
80
+ ARGUMENT_TYPES = %i[arg optarg restarg kwarg kwoptarg kwrestarg blockarg].to_set.freeze
81
+
82
+ LITERAL_RECURSIVE_METHODS = (COMPARISON_OPERATORS + %i[* ! <=>]).freeze
83
+ LITERAL_RECURSIVE_TYPES = (OPERATOR_KEYWORDS + COMPOSITE_LITERALS + %i[begin pair]).freeze
84
+ private_constant :LITERAL_RECURSIVE_METHODS, :LITERAL_RECURSIVE_TYPES
59
85
 
60
86
  # @see https://www.rubydoc.info/gems/ast/AST/Node:initialize
61
87
  def initialize(type, children = [], properties = {})
@@ -92,6 +118,16 @@ module RuboCop
92
118
  @mutable_attributes[:parent] = node
93
119
  end
94
120
 
121
+ # @return [Boolean]
122
+ def parent?
123
+ !!parent
124
+ end
125
+
126
+ # @return [Boolean]
127
+ def root?
128
+ !parent
129
+ end
130
+
95
131
  def complete!
96
132
  @mutable_attributes.freeze
97
133
  each_child_node(&:complete!)
@@ -116,12 +152,50 @@ module RuboCop
116
152
 
117
153
  # Returns the index of the receiver node in its siblings. (Sibling index
118
154
  # uses zero based numbering.)
155
+ # Use is discouraged, this is a potentially slow method.
119
156
  #
120
- # @return [Integer] the index of the receiver node in its siblings
157
+ # @return [Integer, nil] the index of the receiver node in its siblings
121
158
  def sibling_index
122
159
  parent&.children&.index { |sibling| sibling.equal?(self) }
123
160
  end
124
161
 
162
+ # Use is discouraged, this is a potentially slow method and can lead
163
+ # to even slower algorithms
164
+ # @return [Node, nil] the right (aka next) sibling
165
+ def right_sibling
166
+ return unless parent
167
+
168
+ parent.children[sibling_index + 1].freeze
169
+ end
170
+
171
+ # Use is discouraged, this is a potentially slow method and can lead
172
+ # to even slower algorithms
173
+ # @return [Node, nil] the left (aka previous) sibling
174
+ def left_sibling
175
+ i = sibling_index
176
+ return if i.nil? || i.zero?
177
+
178
+ parent.children[i - 1].freeze
179
+ end
180
+
181
+ # Use is discouraged, this is a potentially slow method and can lead
182
+ # to even slower algorithms
183
+ # @return [Array<Node>] the left (aka previous) siblings
184
+ def left_siblings
185
+ return [].freeze unless parent
186
+
187
+ parent.children[0...sibling_index].freeze
188
+ end
189
+
190
+ # Use is discouraged, this is a potentially slow method and can lead
191
+ # to even slower algorithms
192
+ # @return [Array<Node>] the right (aka next) siblings
193
+ def right_siblings
194
+ return [].freeze unless parent
195
+
196
+ parent.children[sibling_index + 1..-1].freeze
197
+ end
198
+
125
199
  # Common destructuring method. This can be used to normalize
126
200
  # destructuring for different variations of the node.
127
201
  # Some node types override this with their own custom
@@ -163,106 +237,10 @@ module RuboCop
163
237
  each_ancestor.to_a
164
238
  end
165
239
 
166
- # Calls the given block for each child node.
167
- # If no block is given, an `Enumerator` is returned.
168
- #
169
- # Note that this is different from `node.children.each { |child| ... }`
170
- # which yields all children including non-node elements.
171
- #
172
- # @overload each_child_node
173
- # Yield all nodes.
174
- # @overload each_child_node(type)
175
- # Yield only nodes matching the type.
176
- # @param [Symbol] type a node type
177
- # @overload each_child_node(type_a, type_b, ...)
178
- # Yield only nodes matching any of the types.
179
- # @param [Symbol] type_a a node type
180
- # @param [Symbol] type_b a node type
181
- # @yieldparam [Node] node each child node
182
- # @return [self] if a block is given
183
- # @return [Enumerator] if no block is given
184
- def each_child_node(*types)
185
- return to_enum(__method__, *types) unless block_given?
186
-
187
- children.each do |child|
188
- next unless child.is_a?(Node)
189
-
190
- yield child if types.empty? || types.include?(child.type)
191
- end
192
-
193
- self
194
- end
195
-
196
- # Returns an array of child nodes.
197
- # This is a shorthand for `node.each_child_node.to_a`.
198
- #
199
- # @return [Array<Node>] an array of child nodes
200
- def child_nodes
201
- each_child_node.to_a
202
- end
203
-
204
- # Calls the given block for each descendant node with depth first order.
205
- # If no block is given, an `Enumerator` is returned.
206
- #
207
- # @overload each_descendant
208
- # Yield all nodes.
209
- # @overload each_descendant(type)
210
- # Yield only nodes matching the type.
211
- # @param [Symbol] type a node type
212
- # @overload each_descendant(type_a, type_b, ...)
213
- # Yield only nodes matching any of the types.
214
- # @param [Symbol] type_a a node type
215
- # @param [Symbol] type_b a node type
216
- # @yieldparam [Node] node each descendant node
217
- # @return [self] if a block is given
218
- # @return [Enumerator] if no block is given
219
- def each_descendant(*types, &block)
220
- return to_enum(__method__, *types) unless block_given?
221
-
222
- visit_descendants(types, &block)
223
-
224
- self
225
- end
226
-
227
- # Returns an array of descendant nodes.
228
- # This is a shorthand for `node.each_descendant.to_a`.
229
- #
230
- # @return [Array<Node>] an array of descendant nodes
231
- def descendants
232
- each_descendant.to_a
233
- end
234
-
235
- # Calls the given block for the receiver and each descendant node in
236
- # depth-first order.
237
- # If no block is given, an `Enumerator` is returned.
238
- #
239
- # This method would be useful when you treat the receiver node as the root
240
- # of a tree and want to iterate over all nodes in the tree.
241
- #
242
- # @overload each_node
243
- # Yield all nodes.
244
- # @overload each_node(type)
245
- # Yield only nodes matching the type.
246
- # @param [Symbol] type a node type
247
- # @overload each_node(type_a, type_b, ...)
248
- # Yield only nodes matching any of the types.
249
- # @param [Symbol] type_a a node type
250
- # @param [Symbol] type_b a node type
251
- # @yieldparam [Node] node each node
252
- # @return [self] if a block is given
253
- # @return [Enumerator] if no block is given
254
- def each_node(*types, &block)
255
- return to_enum(__method__, *types) unless block_given?
256
-
257
- yield self if types.empty? || types.include?(type)
258
-
259
- visit_descendants(types, &block)
260
-
261
- self
262
- end
263
-
240
+ # Note: Some rare nodes may have no source, like `s(:args)` in `foo {}`
241
+ # @return [String, nil]
264
242
  def source
265
- loc.expression.source
243
+ loc.expression&.source
266
244
  end
267
245
 
268
246
  def source_range
@@ -389,10 +367,10 @@ module RuboCop
389
367
  define_method(recursive_kind) do
390
368
  case type
391
369
  when :send
392
- [*COMPARISON_OPERATORS, :!, :<=>].include?(method_name) &&
370
+ LITERAL_RECURSIVE_METHODS.include?(method_name) &&
393
371
  receiver.send(recursive_kind) &&
394
372
  arguments.all?(&recursive_kind)
395
- when :begin, :pair, *OPERATOR_KEYWORDS, *COMPOSITE_LITERALS
373
+ when LITERAL_RECURSIVE_TYPES
396
374
  children.compact.all?(&recursive_kind)
397
375
  else
398
376
  send(kind_filter)
@@ -581,15 +559,6 @@ module RuboCop
581
559
  end
582
560
  end
583
561
 
584
- protected
585
-
586
- def visit_descendants(types, &block)
587
- each_child_node do |child|
588
- yield child if types.empty? || types.include?(child.type)
589
- child.visit_descendants(types, &block)
590
- end
591
- end
592
-
593
562
  private
594
563
 
595
564
  def visit_ancestors(types)
@@ -10,6 +10,7 @@ module RuboCop
10
10
  string: /^%[wW]/,
11
11
  symbol: /^%[iI]/
12
12
  }.freeze
13
+ private_constant :PERCENT_LITERAL_TYPES
13
14
 
14
15
  # Returns an array of all value nodes in the `array` literal.
15
16
  #
@@ -12,6 +12,7 @@ module RuboCop
12
12
  include MethodIdentifierPredicates
13
13
 
14
14
  VOID_CONTEXT_METHODS = %i[each tap].freeze
15
+ private_constant :VOID_CONTEXT_METHODS
15
16
 
16
17
  # The `send` node associated with this block.
17
18
  #
@@ -25,7 +26,7 @@ module RuboCop
25
26
  # @return [Array<Node>]
26
27
  def arguments
27
28
  if numblock_type?
28
- [] # Numbered parameters have no block arguments.
29
+ [].freeze # Numbered parameters have no block arguments.
29
30
  else
30
31
  node_parts[1]
31
32
  end
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ # A node extension for `const` nodes.
6
+ class ConstNode < Node
7
+ # The `send` node associated with this block.
8
+ #
9
+ # @return [Node, nil] the node associated with the scope (e.g. cbase, const, ...)
10
+ def namespace
11
+ children[0]
12
+ end
13
+
14
+ # @return [Symbol] the demodulized name of the constant: "::Foo::Bar" => :Bar
15
+ def short_name
16
+ children[1]
17
+ end
18
+
19
+ # The body of this block.
20
+ #
21
+ # @return [Boolean] if the constant is a Module / Class, according to the standard convention.
22
+ # Note: some classes might have uppercase in which case this method
23
+ # returns false
24
+ def module_name?
25
+ short_name.match?(/[[:lower:]]/)
26
+ end
27
+ alias class_name? module_name?
28
+
29
+ # @return [Boolean] if the constant starts with `::` (aka s(:cbase))
30
+ def absolute?
31
+ return false unless namespace
32
+
33
+ each_path.first.cbase_type?
34
+ end
35
+
36
+ # @return [Boolean] if the constant does not start with `::` (aka s(:cbase))
37
+ def relative?
38
+ !absolute?
39
+ end
40
+
41
+ # Yield nodes for the namespace
42
+ #
43
+ # For `::Foo::Bar::BAZ` => yields:
44
+ # s(:cbase), then
45
+ # s(:const, :Foo), then
46
+ # s(:const, s(:const, :Foo), :Bar)
47
+ def each_path(&block)
48
+ return to_enum(__method__) unless block_given?
49
+
50
+ descendants = []
51
+ last = self
52
+ loop do
53
+ last = last.children.first
54
+ break if last.nil?
55
+
56
+ descendants << last
57
+ break unless last.const_type?
58
+ end
59
+ descendants.reverse_each(&block)
60
+
61
+ self
62
+ end
63
+ end
64
+ end
65
+ end