rubocop-airbnb 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 +2 -0
- data/Gemfile +7 -0
- data/LICENSE.md +9 -0
- data/README.md +68 -0
- data/config/default.yml +39 -0
- data/config/rubocop-airbnb.yml +96 -0
- data/config/rubocop-bundler.yml +8 -0
- data/config/rubocop-gemspec.yml +9 -0
- data/config/rubocop-layout.yml +514 -0
- data/config/rubocop-lint.yml +315 -0
- data/config/rubocop-metrics.yml +47 -0
- data/config/rubocop-naming.yml +68 -0
- data/config/rubocop-performance.yml +143 -0
- data/config/rubocop-rails.yml +193 -0
- data/config/rubocop-rspec.yml +281 -0
- data/config/rubocop-security.yml +13 -0
- data/config/rubocop-style.yml +953 -0
- data/lib/rubocop-airbnb.rb +11 -0
- data/lib/rubocop/airbnb.rb +16 -0
- data/lib/rubocop/airbnb/inflections.rb +14 -0
- data/lib/rubocop/airbnb/inject.rb +20 -0
- data/lib/rubocop/airbnb/rails_autoloading.rb +55 -0
- data/lib/rubocop/airbnb/version.rb +8 -0
- data/lib/rubocop/cop/airbnb/class_name.rb +47 -0
- data/lib/rubocop/cop/airbnb/class_or_module_declared_in_wrong_file.rb +138 -0
- data/lib/rubocop/cop/airbnb/const_assigned_in_wrong_file.rb +74 -0
- data/lib/rubocop/cop/airbnb/continuation_slash.rb +25 -0
- data/lib/rubocop/cop/airbnb/default_scope.rb +20 -0
- data/lib/rubocop/cop/airbnb/factory_attr_references_class.rb +74 -0
- data/lib/rubocop/cop/airbnb/factory_class_use_string.rb +39 -0
- data/lib/rubocop/cop/airbnb/mass_assignment_accessible_modifier.rb +18 -0
- data/lib/rubocop/cop/airbnb/module_method_in_wrong_file.rb +104 -0
- data/lib/rubocop/cop/airbnb/no_timeout.rb +19 -0
- data/lib/rubocop/cop/airbnb/opt_arg_parameters.rb +38 -0
- data/lib/rubocop/cop/airbnb/phrase_bundle_keys.rb +67 -0
- data/lib/rubocop/cop/airbnb/risky_activerecord_invocation.rb +63 -0
- data/lib/rubocop/cop/airbnb/rspec_describe_or_context_under_namespace.rb +114 -0
- data/lib/rubocop/cop/airbnb/rspec_environment_modification.rb +58 -0
- data/lib/rubocop/cop/airbnb/simple_modifier_conditional.rb +23 -0
- data/lib/rubocop/cop/airbnb/spec_constant_assignment.rb +55 -0
- data/lib/rubocop/cop/airbnb/unsafe_yaml_marshal.rb +47 -0
- data/rubocop-airbnb.gemspec +32 -0
- data/spec/rubocop/cop/airbnb/class_name_spec.rb +78 -0
- data/spec/rubocop/cop/airbnb/class_or_module_declared_in_wrong_file_spec.rb +174 -0
- data/spec/rubocop/cop/airbnb/const_assigned_in_wrong_file_spec.rb +178 -0
- data/spec/rubocop/cop/airbnb/continuation_slash_spec.rb +162 -0
- data/spec/rubocop/cop/airbnb/default_scope_spec.rb +38 -0
- data/spec/rubocop/cop/airbnb/factory_attr_references_class_spec.rb +160 -0
- data/spec/rubocop/cop/airbnb/factory_class_use_string_spec.rb +26 -0
- data/spec/rubocop/cop/airbnb/mass_assignment_accessible_modifier_spec.rb +28 -0
- data/spec/rubocop/cop/airbnb/module_method_in_wrong_file_spec.rb +181 -0
- data/spec/rubocop/cop/airbnb/no_timeout_spec.rb +30 -0
- data/spec/rubocop/cop/airbnb/opt_arg_parameter_spec.rb +103 -0
- data/spec/rubocop/cop/airbnb/phrase_bundle_keys_spec.rb +74 -0
- data/spec/rubocop/cop/airbnb/risky_activerecord_invocation_spec.rb +54 -0
- data/spec/rubocop/cop/airbnb/rspec_describe_or_context_under_namespace_spec.rb +284 -0
- data/spec/rubocop/cop/airbnb/rspec_environment_modification_spec.rb +64 -0
- data/spec/rubocop/cop/airbnb/simple_modifier_conditional_spec.rb +122 -0
- data/spec/rubocop/cop/airbnb/spec_constant_assignment_spec.rb +80 -0
- data/spec/rubocop/cop/airbnb/unsafe_yaml_marshal_spec.rb +50 -0
- data/spec/spec_helper.rb +35 -0
- metadata +150 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
# Cop to tell developers to use :class => "MyClass" instead of :class => MyClass,
|
5
|
+
# because the latter slows down reloading zeus.
|
6
|
+
class FactoryClassUseString < Cop
|
7
|
+
MSG = 'Instead of :class => MyClass, use :class => "MyClass". ' \
|
8
|
+
"This enables faster spec startup time and faster Zeus reload time.".freeze
|
9
|
+
|
10
|
+
def on_send(node)
|
11
|
+
return unless node.command?(:factory)
|
12
|
+
|
13
|
+
class_pair = class_node(node)
|
14
|
+
|
15
|
+
if class_pair && !string_class_name?(class_pair)
|
16
|
+
add_offense(class_pair)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
# Return the descendant node that is a hash pair (:key => value) whose key
|
23
|
+
# is :class.
|
24
|
+
def class_node(node)
|
25
|
+
node.descendants.detect do |e|
|
26
|
+
e.is_a?(Parser::AST::Node) &&
|
27
|
+
e.pair_type? &&
|
28
|
+
e.children[0].children[0] == :class
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
# Given a hash pair :class_name => value, is the value a hardcoded string?
|
33
|
+
def string_class_name?(class_pair)
|
34
|
+
class_pair.children[1].str_type?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
# Modifying Mass assignment restrictions eliminates the entire point of disabling
|
5
|
+
# mass assignment. It's a lazy, potentially dangerous approach that should be discouraged.
|
6
|
+
class MassAssignmentAccessibleModifier < Cop
|
7
|
+
MSG = 'Do no override and objects mass assignment restrictions.'.freeze
|
8
|
+
|
9
|
+
def on_send(node)
|
10
|
+
_receiver, method_name, *_args = *node
|
11
|
+
|
12
|
+
return unless method_name == :accessible=
|
13
|
+
add_offense(node, message: MSG)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,104 @@
|
|
1
|
+
require_relative '../../airbnb/inflections'
|
2
|
+
require_relative '../../airbnb/rails_autoloading'
|
3
|
+
|
4
|
+
module RuboCop
|
5
|
+
module Cop
|
6
|
+
module Airbnb
|
7
|
+
# This cop checks for methods defined in a module declaration, in a file that doesn't
|
8
|
+
# match the module name. The Rails autoloader can't find such a method, but sometimes
|
9
|
+
# people "get lucky" if the file happened to be loaded before the method was defined.
|
10
|
+
#
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
#
|
14
|
+
# # foo/bar.rb
|
15
|
+
# module Foo
|
16
|
+
# class Bar
|
17
|
+
# end
|
18
|
+
#
|
19
|
+
# def baz
|
20
|
+
# 42
|
21
|
+
# end
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# # good
|
25
|
+
#
|
26
|
+
# # foo/bar.rb
|
27
|
+
# module Foo
|
28
|
+
# class Bar
|
29
|
+
# end
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# # foo.rb
|
33
|
+
# module Foo
|
34
|
+
# def baz
|
35
|
+
# 42
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
#
|
39
|
+
# Note that autoloading works fine if classes are defined in the file that defines
|
40
|
+
# the module. This is common usage for things like error classes, so we'll allow it:
|
41
|
+
#
|
42
|
+
# @example
|
43
|
+
# # good
|
44
|
+
#
|
45
|
+
# # foo.rb
|
46
|
+
# module Foo
|
47
|
+
# class Bar < StandardError
|
48
|
+
# def baz
|
49
|
+
# end
|
50
|
+
# end
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# # good
|
54
|
+
#
|
55
|
+
# # foo.rb
|
56
|
+
# class Foo
|
57
|
+
# class Bar # nested class
|
58
|
+
# def baz
|
59
|
+
# end
|
60
|
+
# end
|
61
|
+
# end
|
62
|
+
class ModuleMethodInWrongFile < Cop
|
63
|
+
include Inflections
|
64
|
+
include RailsAutoloading
|
65
|
+
|
66
|
+
MSG_TEMPLATE =
|
67
|
+
"In order for Rails autoloading to be able to find and load this file when " \
|
68
|
+
"someone calls this method, move the method definition to a file that defines " \
|
69
|
+
"the module. This file just uses the module as a namespace for another class " \
|
70
|
+
"or module. Method %s should be defined in %s.".freeze
|
71
|
+
|
72
|
+
def on_def(node)
|
73
|
+
method_name, args, body = *node
|
74
|
+
on_method_def(node, method_name, args, body)
|
75
|
+
end
|
76
|
+
|
77
|
+
alias on_defs on_def
|
78
|
+
|
79
|
+
private
|
80
|
+
|
81
|
+
def on_method_def(node, method_name, args, body)
|
82
|
+
path = node.source_range.source_buffer.name
|
83
|
+
return unless run_rails_autoloading_cops?(path)
|
84
|
+
return unless node.parent_module_name
|
85
|
+
# "#<Class:>" is the parent module name of a method being defined in an if/unless.
|
86
|
+
return if node.parent_module_name == "#<Class:>"
|
87
|
+
|
88
|
+
expected_dir = underscore(normalize_module_name(node.parent_module_name))
|
89
|
+
allowable_filenames = expected_dir.split("/").map { |file| "#{file}.rb" }
|
90
|
+
basename = File.basename(path)
|
91
|
+
if !allowable_filenames.include?(basename)
|
92
|
+
parent_module_names = split_modules(node.parent_module_name)
|
93
|
+
expected_parent_module_file =
|
94
|
+
"#{parent_module_names.map { |name| underscore(name) }.join("/")}.rb"
|
95
|
+
add_offense(
|
96
|
+
node,
|
97
|
+
message: MSG_TEMPLATE % [method_name, expected_parent_module_file]
|
98
|
+
)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
class NoTimeout < Cop
|
5
|
+
MSG =
|
6
|
+
'Do not use Timeout.timeout. In combination with Rails autoloading, ' \
|
7
|
+
'timeout can cause Segmentation Faults in version of Ruby we use. ' \
|
8
|
+
'It can also cause logic errors since it can raise in ' \
|
9
|
+
'any callee scope. Use client library timeouts and monitoring to ' \
|
10
|
+
'ensure proper timing behavior for web requests.'.freeze
|
11
|
+
|
12
|
+
def on_send(node)
|
13
|
+
return unless node.source.start_with?('Timeout.timeout')
|
14
|
+
add_offense(node, message: MSG)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
# Cop to enforce use of options hash over default arguments
|
5
|
+
# https://github.com/airbnb/ruby#no-default-args
|
6
|
+
class OptArgParameters < Cop
|
7
|
+
MSG =
|
8
|
+
'Do not use default positional arguments. '\
|
9
|
+
'Use keyword arguments or an options hash instead.'.freeze
|
10
|
+
|
11
|
+
def on_args(node)
|
12
|
+
*but_last, last_arg = *node
|
13
|
+
|
14
|
+
if last_arg && last_arg.blockarg_type?
|
15
|
+
last_arg = but_last.pop
|
16
|
+
end
|
17
|
+
|
18
|
+
but_last.each do |arg|
|
19
|
+
next unless arg.optarg_type?
|
20
|
+
add_offense(arg, message: MSG)
|
21
|
+
end
|
22
|
+
return if last_arg.nil?
|
23
|
+
|
24
|
+
return unless last_arg.optarg_type?
|
25
|
+
|
26
|
+
_arg_name, default_value = *last_arg
|
27
|
+
if default_value.hash_type?
|
28
|
+
# asserting default value is empty hash
|
29
|
+
*key_value_pairs = *default_value
|
30
|
+
return if key_value_pairs.empty?
|
31
|
+
end
|
32
|
+
|
33
|
+
add_offense(last_arg, message: MSG)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
# Prefer matching Phrase Bundle and t call keys inside of
|
5
|
+
# PhraseBundleClasses.
|
6
|
+
#
|
7
|
+
# @example
|
8
|
+
# # bad
|
9
|
+
# def phrases
|
10
|
+
# {
|
11
|
+
# "shortened_key" => t(
|
12
|
+
# "my_real_translation_key",
|
13
|
+
# default: 'Does not matter',
|
14
|
+
# ),
|
15
|
+
# }
|
16
|
+
# end
|
17
|
+
#
|
18
|
+
# # good
|
19
|
+
# def phrases
|
20
|
+
# {
|
21
|
+
# "my_real_translation_key" => t(
|
22
|
+
# "my_real_translation_key",
|
23
|
+
# default: 'Does not matter',
|
24
|
+
# ),
|
25
|
+
# }
|
26
|
+
# end
|
27
|
+
class PhraseBundleKeys < Cop
|
28
|
+
MESSAGE =
|
29
|
+
'Phrase bundle keys should match their translation keys.'.freeze
|
30
|
+
|
31
|
+
def on_send(node)
|
32
|
+
parent = node.parent
|
33
|
+
if t_call?(node) && in_phrase_bundle_class?(node) && parent.pair_type?
|
34
|
+
hash_key = parent.children[0]
|
35
|
+
unless hash_key.children[0] == node.children[2].children[0]
|
36
|
+
add_offense(hash_key, message: MESSAGE)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def in_phrase_bundle_class?(node)
|
44
|
+
if node.class_type? && !const_phrase_bundle_children(node).empty?
|
45
|
+
true
|
46
|
+
elsif node.parent
|
47
|
+
in_phrase_bundle_class?(node.parent)
|
48
|
+
else
|
49
|
+
false
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def const_phrase_bundle_children(node)
|
54
|
+
node.children.select do |e|
|
55
|
+
e.is_a?(Parser::AST::Node) &&
|
56
|
+
e.const_type? &&
|
57
|
+
e.children[1] == :PhraseBundle
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def t_call?(node)
|
62
|
+
node.children[1] == :t
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
# Disallow ActiveRecord calls that pass interpolated or added strings as an argument.
|
5
|
+
class RiskyActiverecordInvocation < Cop
|
6
|
+
VULNERABLE_AR_METHODS = [
|
7
|
+
:delete_all,
|
8
|
+
:destroy_all,
|
9
|
+
:exists?,
|
10
|
+
:execute,
|
11
|
+
:find_by_sql,
|
12
|
+
:group,
|
13
|
+
:having,
|
14
|
+
:insert,
|
15
|
+
:order,
|
16
|
+
:pluck,
|
17
|
+
:reorder,
|
18
|
+
:select,
|
19
|
+
:select_rows,
|
20
|
+
:select_values,
|
21
|
+
:select_all,
|
22
|
+
:update_all,
|
23
|
+
:where,
|
24
|
+
].freeze
|
25
|
+
MSG = 'Passing a string computed by interpolation or addition to an ActiveRecord ' \
|
26
|
+
'method is likely to lead to SQL injection. Use hash or parameterized syntax. For ' \
|
27
|
+
'more information, see ' \
|
28
|
+
'http://guides.rubyonrails.org/security.html#sql-injection-countermeasures and ' \
|
29
|
+
'https://rails-sqli.org/rails3. If you have confirmed with Security that this is a ' \
|
30
|
+
'safe usage of this style, disable this alert with ' \
|
31
|
+
'`# rubocop:disable Airbnb/RiskyActiverecordInvocation`.'.freeze
|
32
|
+
def on_send(node)
|
33
|
+
receiver, method_name, *_args = *node
|
34
|
+
|
35
|
+
return if receiver.nil?
|
36
|
+
return unless vulnerable_ar_method?(method_name)
|
37
|
+
if !includes_interpolation?(_args) && !includes_sum?(_args)
|
38
|
+
return
|
39
|
+
end
|
40
|
+
|
41
|
+
add_offense(node)
|
42
|
+
end
|
43
|
+
|
44
|
+
def vulnerable_ar_method?(method)
|
45
|
+
VULNERABLE_AR_METHODS.include?(method)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Return true if the first arg is a :dstr that has non-:str components
|
49
|
+
def includes_interpolation?(args)
|
50
|
+
!args.first.nil? &&
|
51
|
+
args.first.type == :dstr &&
|
52
|
+
args.first.each_child_node.any? { |child| child.type != :str }
|
53
|
+
end
|
54
|
+
|
55
|
+
def includes_sum?(args)
|
56
|
+
!args.first.nil? &&
|
57
|
+
args.first.type == :send &&
|
58
|
+
args.first.method_name == :+
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,114 @@
|
|
1
|
+
module RuboCop
|
2
|
+
module Cop
|
3
|
+
module Airbnb
|
4
|
+
# This cop checks for Rspec describe or context method calls under a namespace.
|
5
|
+
# It can potentially cause autoloading to occur in a different order than it
|
6
|
+
# would have in development or production. This could cause flaky tests.
|
7
|
+
#
|
8
|
+
# @example
|
9
|
+
# # bad
|
10
|
+
#
|
11
|
+
# # spec/foo/bar_spec.rb
|
12
|
+
# module Foo
|
13
|
+
# describe Bar do
|
14
|
+
# end
|
15
|
+
# end
|
16
|
+
#
|
17
|
+
# # good
|
18
|
+
#
|
19
|
+
# # spec/foo/bar_spec.rb do
|
20
|
+
#
|
21
|
+
# describe Foo::Bar
|
22
|
+
# end
|
23
|
+
class RspecDescribeOrContextUnderNamespace < Cop
|
24
|
+
DESCRIBE_OR_CONTEXT_UNDER_NAMESPACE_MSG =
|
25
|
+
'Declaring a `module` in a spec can break autoloading because subsequent references ' \
|
26
|
+
'to it will not cause it to be loaded from the app. This could cause flaky tests.'.freeze
|
27
|
+
|
28
|
+
FIX_DESCRIBE_OR_CONTEXT_HELP_MSG =
|
29
|
+
'Change `%{describe} %{klass} do` to `%{describe} %{module_name}::%{klass} do`.'.freeze
|
30
|
+
|
31
|
+
FIX_CODE_HELP_MSG =
|
32
|
+
'Remove `module %{module_name}` and fix `%{module_name}::CONST` and ' \
|
33
|
+
'`%{module_name}.method` calls accordingly.'.freeze
|
34
|
+
|
35
|
+
def_node_matcher :describe_or_context?,
|
36
|
+
'(send {(const nil? :RSpec) nil?} {:describe :context} ...)'.freeze
|
37
|
+
|
38
|
+
def on_module(node)
|
39
|
+
path = node.source_range.source_buffer.name
|
40
|
+
return unless is_spec_file?(path)
|
41
|
+
|
42
|
+
matched_node = search_children_for_describe_or_context(node.children)
|
43
|
+
return unless matched_node
|
44
|
+
|
45
|
+
method_name = matched_node.method_name
|
46
|
+
module_name = get_module_name(node)
|
47
|
+
message = [DESCRIBE_OR_CONTEXT_UNDER_NAMESPACE_MSG]
|
48
|
+
|
49
|
+
described_class = get_described_class(matched_node)
|
50
|
+
method_parent = get_method_parent(matched_node)
|
51
|
+
parent_dot_method = method_parent ? "#{method_parent}.#{method_name}" : method_name
|
52
|
+
if described_class
|
53
|
+
message << FIX_DESCRIBE_OR_CONTEXT_HELP_MSG % {
|
54
|
+
describe: parent_dot_method,
|
55
|
+
klass: described_class,
|
56
|
+
module_name: module_name,
|
57
|
+
}
|
58
|
+
end
|
59
|
+
|
60
|
+
message << FIX_CODE_HELP_MSG % { module_name: module_name }
|
61
|
+
add_offense(node, message: message.join(' '))
|
62
|
+
end
|
63
|
+
|
64
|
+
def search_children_for_describe_or_context(nodes)
|
65
|
+
blocks = []
|
66
|
+
# match nodes for send describe or context
|
67
|
+
nodes.detect do |node|
|
68
|
+
next unless node
|
69
|
+
|
70
|
+
if is_block?(node)
|
71
|
+
blocks << node
|
72
|
+
next
|
73
|
+
end
|
74
|
+
return node if describe_or_context?(node)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Process child nodes of block
|
78
|
+
blocks.each do |node|
|
79
|
+
matched_node = search_children_for_describe_or_context(node.children)
|
80
|
+
return matched_node if matched_node
|
81
|
+
end
|
82
|
+
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
|
86
|
+
def is_spec_file?(path)
|
87
|
+
path.end_with?('_spec.rb')
|
88
|
+
end
|
89
|
+
|
90
|
+
def get_module_name(node)
|
91
|
+
const_node = node.children[0]
|
92
|
+
return unless const_node
|
93
|
+
const_node.const_name
|
94
|
+
end
|
95
|
+
|
96
|
+
def get_described_class(node)
|
97
|
+
const_node = node.children[2]
|
98
|
+
return unless const_node
|
99
|
+
const_node.const_name
|
100
|
+
end
|
101
|
+
|
102
|
+
def get_method_parent(node)
|
103
|
+
const_node = node.children[0]
|
104
|
+
return unless const_node
|
105
|
+
const_node.const_name
|
106
|
+
end
|
107
|
+
|
108
|
+
def is_block?(node)
|
109
|
+
node && [:block, :begin].include?(node.type)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|