rubocop-rspec 2.0.0 → 2.4.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.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +39 -0
  3. data/README.md +3 -3
  4. data/config/default.yml +141 -125
  5. data/lib/rubocop-rspec.rb +1 -0
  6. data/lib/rubocop/cop/rspec/any_instance.rb +6 -10
  7. data/lib/rubocop/cop/rspec/around_block.rb +3 -1
  8. data/lib/rubocop/cop/rspec/be.rb +2 -1
  9. data/lib/rubocop/cop/rspec/be_eql.rb +2 -0
  10. data/lib/rubocop/cop/rspec/before_after_all.rb +6 -3
  11. data/lib/rubocop/cop/rspec/capybara/current_path_expectation.rb +5 -0
  12. data/lib/rubocop/cop/rspec/capybara/feature_methods.rb +3 -0
  13. data/lib/rubocop/cop/rspec/capybara/visibility_matcher.rb +4 -0
  14. data/lib/rubocop/cop/rspec/context_method.rb +1 -0
  15. data/lib/rubocop/cop/rspec/context_wording.rb +7 -1
  16. data/lib/rubocop/cop/rspec/describe_class.rb +4 -1
  17. data/lib/rubocop/cop/rspec/describe_method.rb +2 -1
  18. data/lib/rubocop/cop/rspec/describe_symbol.rb +2 -0
  19. data/lib/rubocop/cop/rspec/described_class.rb +6 -1
  20. data/lib/rubocop/cop/rspec/described_class_module_wrapping.rb +2 -1
  21. data/lib/rubocop/cop/rspec/dialect.rb +1 -0
  22. data/lib/rubocop/cop/rspec/empty_hook.rb +1 -0
  23. data/lib/rubocop/cop/rspec/example_length.rb +26 -12
  24. data/lib/rubocop/cop/rspec/example_without_description.rb +1 -0
  25. data/lib/rubocop/cop/rspec/example_wording.rb +1 -0
  26. data/lib/rubocop/cop/rspec/expect_actual.rb +2 -0
  27. data/lib/rubocop/cop/rspec/expect_change.rb +6 -3
  28. data/lib/rubocop/cop/rspec/expect_in_hook.rb +1 -0
  29. data/lib/rubocop/cop/rspec/expect_output.rb +1 -1
  30. data/lib/rubocop/cop/rspec/factory_bot/attribute_defined_statically.rb +3 -0
  31. data/lib/rubocop/cop/rspec/factory_bot/create_list.rb +4 -0
  32. data/lib/rubocop/cop/rspec/factory_bot/factory_class_name.rb +2 -0
  33. data/lib/rubocop/cop/rspec/file_path.rb +31 -20
  34. data/lib/rubocop/cop/rspec/focus.rb +33 -1
  35. data/lib/rubocop/cop/rspec/hook_argument.rb +2 -0
  36. data/lib/rubocop/cop/rspec/hooks_before_examples.rb +1 -0
  37. data/lib/rubocop/cop/rspec/identical_equality_assertion.rb +38 -0
  38. data/lib/rubocop/cop/rspec/implicit_block_expectation.rb +4 -0
  39. data/lib/rubocop/cop/rspec/implicit_expect.rb +1 -0
  40. data/lib/rubocop/cop/rspec/implicit_subject.rb +19 -1
  41. data/lib/rubocop/cop/rspec/instance_spy.rb +3 -1
  42. data/lib/rubocop/cop/rspec/instance_variable.rb +4 -0
  43. data/lib/rubocop/cop/rspec/it_behaves_like.rb +3 -1
  44. data/lib/rubocop/cop/rspec/iterated_expectation.rb +3 -1
  45. data/lib/rubocop/cop/rspec/let_before_examples.rb +1 -0
  46. data/lib/rubocop/cop/rspec/let_setup.rb +3 -0
  47. data/lib/rubocop/cop/rspec/message_chain.rb +4 -10
  48. data/lib/rubocop/cop/rspec/message_expectation.rb +3 -0
  49. data/lib/rubocop/cop/rspec/message_spies.rb +4 -2
  50. data/lib/rubocop/cop/rspec/mixin/comments_help.rb +38 -0
  51. data/lib/rubocop/cop/rspec/mixin/variable.rb +1 -0
  52. data/lib/rubocop/cop/rspec/multiple_describes.rb +1 -2
  53. data/lib/rubocop/cop/rspec/multiple_expectations.rb +3 -0
  54. data/lib/rubocop/cop/rspec/named_subject.rb +3 -0
  55. data/lib/rubocop/cop/rspec/not_to_not.rb +2 -0
  56. data/lib/rubocop/cop/rspec/overwriting_setup.rb +2 -0
  57. data/lib/rubocop/cop/rspec/pending.rb +4 -0
  58. data/lib/rubocop/cop/rspec/predicate_matcher.rb +5 -0
  59. data/lib/rubocop/cop/rspec/rails/avoid_setup_hook.rb +44 -0
  60. data/lib/rubocop/cop/rspec/rails/http_status.rb +2 -0
  61. data/lib/rubocop/cop/rspec/receive_counts.rb +4 -0
  62. data/lib/rubocop/cop/rspec/receive_never.rb +2 -0
  63. data/lib/rubocop/cop/rspec/repeated_example_group_body.rb +8 -1
  64. data/lib/rubocop/cop/rspec/repeated_example_group_description.rb +4 -0
  65. data/lib/rubocop/cop/rspec/repeated_include_example.rb +3 -0
  66. data/lib/rubocop/cop/rspec/return_from_stub.rb +6 -0
  67. data/lib/rubocop/cop/rspec/scattered_setup.rb +1 -1
  68. data/lib/rubocop/cop/rspec/shared_context.rb +6 -5
  69. data/lib/rubocop/cop/rspec/shared_examples.rb +1 -0
  70. data/lib/rubocop/cop/rspec/single_argument_message_chain.rb +4 -1
  71. data/lib/rubocop/cop/rspec/stubbed_mock.rb +1 -0
  72. data/lib/rubocop/cop/rspec/subject_stub.rb +15 -4
  73. data/lib/rubocop/cop/rspec/unspecified_exception.rb +2 -0
  74. data/lib/rubocop/cop/rspec/verified_doubles.rb +2 -0
  75. data/lib/rubocop/cop/rspec/void_expect.rb +3 -0
  76. data/lib/rubocop/cop/rspec/yield.rb +3 -0
  77. data/lib/rubocop/cop/rspec_cops.rb +2 -0
  78. data/lib/rubocop/rspec/config_formatter.rb +3 -1
  79. data/lib/rubocop/rspec/corrector/move_node.rb +6 -9
  80. data/lib/rubocop/rspec/example.rb +5 -0
  81. data/lib/rubocop/rspec/hook.rb +1 -0
  82. data/lib/rubocop/rspec/language.rb +10 -0
  83. data/lib/rubocop/rspec/node.rb +1 -1
  84. data/lib/rubocop/rspec/version.rb +1 -1
  85. metadata +9 -6
@@ -15,18 +15,12 @@ module RuboCop
15
15
  #
16
16
  class MessageChain < Base
17
17
  MSG = 'Avoid stubbing using `%<method>s`.'
18
-
19
- def_node_matcher :message_chain, <<-PATTERN
20
- (send _ {:receive_message_chain :stub_chain} ...)
21
- PATTERN
18
+ RESTRICT_ON_SEND = %i[receive_message_chain stub_chain].freeze
22
19
 
23
20
  def on_send(node)
24
- message_chain(node) do
25
- add_offense(
26
- node.loc.selector,
27
- message: format(MSG, method: node.method_name)
28
- )
29
- end
21
+ add_offense(
22
+ node.loc.selector, message: format(MSG, method: node.method_name)
23
+ )
30
24
  end
31
25
  end
32
26
  end
@@ -30,11 +30,14 @@ module RuboCop
30
30
  MSG = 'Prefer `%<style>s` for setting message expectations.'
31
31
 
32
32
  SUPPORTED_STYLES = %w[allow expect].freeze
33
+ RESTRICT_ON_SEND = %i[to].freeze
33
34
 
35
+ # @!method message_expectation(node)
34
36
  def_node_matcher :message_expectation, <<-PATTERN
35
37
  (send $(send nil? {:expect :allow} ...) :to #receive_message?)
36
38
  PATTERN
37
39
 
40
+ # @!method receive_message?(node)
38
41
  def_node_search :receive_message?, '(send nil? :receive ...)'
39
42
 
40
43
  def on_send(node)
@@ -29,16 +29,18 @@ module RuboCop
29
29
 
30
30
  MSG_RECEIVE = 'Prefer `receive` for setting message expectations.'
31
31
 
32
- MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message '\
33
- 'expectations. Setup `%<source>s` as a spy using '\
32
+ MSG_HAVE_RECEIVED = 'Prefer `have_received` for setting message ' \
33
+ 'expectations. Setup `%<source>s` as a spy using ' \
34
34
  '`allow` or `instance_spy`.'
35
35
 
36
36
  SUPPORTED_STYLES = %w[have_received receive].freeze
37
37
 
38
+ # @!method message_expectation(node)
38
39
  def_node_matcher :message_expectation, %(
39
40
  (send (send nil? :expect $_) #Runners.all ...)
40
41
  )
41
42
 
43
+ # @!method receive_message(node)
42
44
  def_node_search :receive_message, %(
43
45
  $(send nil? {:receive :have_received} ...)
44
46
  )
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ # Help methods for working with nodes containing comments.
7
+ module CommentsHelp
8
+ include FinalEndLocation
9
+
10
+ def source_range_with_comment(node)
11
+ begin_pos = begin_pos_with_comment(node).begin_pos
12
+ end_pos = end_line_position(node).end_pos
13
+
14
+ Parser::Source::Range.new(buffer, begin_pos, end_pos)
15
+ end
16
+
17
+ def begin_pos_with_comment(node)
18
+ first_comment = processed_source.ast_with_comments[node].first
19
+
20
+ start_line_position(first_comment || node)
21
+ end
22
+
23
+ def start_line_position(node)
24
+ buffer.line_range(node.loc.line)
25
+ end
26
+
27
+ def end_line_position(node)
28
+ end_line = buffer.line_for_position(final_end_location(node).end_pos)
29
+ buffer.line_range(end_line)
30
+ end
31
+
32
+ def buffer
33
+ processed_source.buffer
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -10,6 +10,7 @@ module RuboCop
10
10
  Subjects = RuboCop::RSpec::Language::Subjects
11
11
  Helpers = RuboCop::RSpec::Language::Helpers
12
12
 
13
+ # @!method variable_definition?(node)
13
14
  def_node_matcher :variable_definition?, <<~PATTERN
14
15
  (send nil? {#Subjects.all #Helpers.all}
15
16
  $({sym str dsym dstr} ...) ...)
@@ -25,8 +25,7 @@ module RuboCop
25
25
  class MultipleDescribes < Base
26
26
  include TopLevelGroup
27
27
 
28
- MSG = 'Do not use multiple top-level example groups - '\
29
- 'try to nest them.'
28
+ MSG = 'Do not use multiple top-level example groups - try to nest them.'
30
29
 
31
30
  def on_top_level_group(node)
32
31
  top_level_example_groups =
@@ -53,6 +53,7 @@ module RuboCop
53
53
  ANYTHING = ->(_node) { true }
54
54
  TRUE = ->(node) { node.true_type? }
55
55
 
56
+ # @!method aggregate_failures?(node)
56
57
  def_node_matcher :aggregate_failures?, <<-PATTERN
57
58
  (block {
58
59
  (send _ _ <(sym :aggregate_failures) ...>)
@@ -60,7 +61,9 @@ module RuboCop
60
61
  } ...)
61
62
  PATTERN
62
63
 
64
+ # @!method expect?(node)
63
65
  def_node_matcher :expect?, send_pattern('#Expectations.all')
66
+ # @!method aggregate_failures_block?(node)
64
67
  def_node_matcher :aggregate_failures_block?, <<-PATTERN
65
68
  (block (send nil? :aggregate_failures ...) ...)
66
69
  PATTERN
@@ -44,12 +44,15 @@ module RuboCop
44
44
  class NamedSubject < Base
45
45
  MSG = 'Name your test subject if you need to reference it explicitly.'
46
46
 
47
+ # @!method example_or_hook_block?(node)
47
48
  def_node_matcher :example_or_hook_block?,
48
49
  block_pattern('{#Examples.all #Hooks.all}')
49
50
 
51
+ # @!method shared_example?(node)
50
52
  def_node_matcher :shared_example?,
51
53
  block_pattern('#SharedGroups.examples')
52
54
 
55
+ # @!method subject_usage(node)
53
56
  def_node_search :subject_usage, '$(send nil? :subject)'
54
57
 
55
58
  def on_block(node)
@@ -20,7 +20,9 @@ module RuboCop
20
20
  include ConfigurableEnforcedStyle
21
21
 
22
22
  MSG = 'Prefer `%<replacement>s` over `%<original>s`.'
23
+ RESTRICT_ON_SEND = %i[not_to to_not].freeze
23
24
 
25
+ # @!method not_to_not_offense(node)
24
26
  def_node_matcher :not_to_not_offense, '(send _ % ...)'
25
27
 
26
28
  def on_send(node)
@@ -24,8 +24,10 @@ module RuboCop
24
24
  class OverwritingSetup < Base
25
25
  MSG = '`%<name>s` is already defined.'
26
26
 
27
+ # @!method setup?(node)
27
28
  def_node_matcher :setup?, block_pattern('{#Helpers.all #Subjects.all}')
28
29
 
30
+ # @!method first_argument_name(node)
29
31
  def_node_matcher :first_argument_name, '(send _ _ ({str sym} $_))'
30
32
 
31
33
  def on_block(node)
@@ -34,11 +34,13 @@ module RuboCop
34
34
  class Pending < Base
35
35
  MSG = 'Pending spec found.'
36
36
 
37
+ # @!method skippable?(node)
37
38
  def_node_matcher :skippable?,
38
39
  send_pattern(<<~PATTERN)
39
40
  {#ExampleGroups.regular #Examples.regular}
40
41
  PATTERN
41
42
 
43
+ # @!method skipped_in_metadata?(node)
42
44
  def_node_matcher :skipped_in_metadata?, <<-PATTERN
43
45
  {
44
46
  (send _ _ <#skip_or_pending? ...>)
@@ -46,8 +48,10 @@ module RuboCop
46
48
  }
47
49
  PATTERN
48
50
 
51
+ # @!method skip_or_pending?(node)
49
52
  def_node_matcher :skip_or_pending?, '{(sym :skip) (sym :pending)}'
50
53
 
54
+ # @!method pending_block?(node)
51
55
  def_node_matcher :pending_block?,
52
56
  send_pattern(<<~PATTERN)
53
57
  {
@@ -25,6 +25,7 @@ module RuboCop
25
25
  end
26
26
  end
27
27
 
28
+ # @!method predicate_in_actual?(node)
28
29
  def_node_matcher :predicate_in_actual?, <<-PATTERN
29
30
  (send
30
31
  (send nil? :expect {
@@ -34,10 +35,12 @@ module RuboCop
34
35
  $#boolean_matcher?)
35
36
  PATTERN
36
37
 
38
+ # @!method be_bool?(node)
37
39
  def_node_matcher :be_bool?, <<-PATTERN
38
40
  (send nil? {:be :eq :eql :equal} {true false})
39
41
  PATTERN
40
42
 
43
+ # @!method be_boolthy?(node)
41
44
  def_node_matcher :be_boolthy?, <<-PATTERN
42
45
  (send nil? {:be_truthy :be_falsey :be_falsy :a_truthy_value :a_falsey_value :a_falsy_value})
43
46
  PATTERN
@@ -152,6 +155,7 @@ module RuboCop
152
155
  end
153
156
  end
154
157
 
158
+ # @!method predicate_matcher?(node)
155
159
  def_node_matcher :predicate_matcher?, <<-PATTERN
156
160
  (send
157
161
  (send nil? :expect $!nil?)
@@ -160,6 +164,7 @@ module RuboCop
160
164
  (block $(send nil? #predicate_matcher_name? ...) ...)})
161
165
  PATTERN
162
166
 
167
+ # @!method predicate_matcher_block?(node)
163
168
  def_node_matcher :predicate_matcher_block?, <<-PATTERN
164
169
  (block
165
170
  (send
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Cop
5
+ module RSpec
6
+ module Rails
7
+ # Checks that tests use RSpec `before` hook over Rails `setup` method.
8
+ #
9
+ # @example
10
+ #
11
+ # # bad
12
+ # setup do
13
+ # allow(foo).to receive(:bar)
14
+ # end
15
+ #
16
+ # # good
17
+ # before do
18
+ # allow(foo).to receive(:bar)
19
+ # end
20
+ #
21
+ class AvoidSetupHook < Base
22
+ extend AutoCorrector
23
+
24
+ MSG = 'Use `before` instead of `setup`.'
25
+
26
+ # @!method setup_call(node)
27
+ def_node_matcher :setup_call, <<-PATTERN
28
+ (block
29
+ $(send nil? :setup)
30
+ (args) _)
31
+ PATTERN
32
+
33
+ def on_block(node)
34
+ setup_call(node) do |setup|
35
+ add_offense(node) do |corrector|
36
+ corrector.replace setup, 'before'
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -33,7 +33,9 @@ module RuboCop
33
33
  class HttpStatus < Base
34
34
  extend AutoCorrector
35
35
  include ConfigurableEnforcedStyle
36
+ RESTRICT_ON_SEND = %i[have_http_status].freeze
36
37
 
38
+ # @!method http_status(node)
37
39
  def_node_matcher :http_status, <<-PATTERN
38
40
  (send nil? :have_http_status ${int sym})
39
41
  PATTERN
@@ -28,10 +28,14 @@ module RuboCop
28
28
 
29
29
  MSG = 'Use `%<alternative>s` instead of `%<original>s`.'
30
30
 
31
+ RESTRICT_ON_SEND = %i[times].freeze
32
+
33
+ # @!method receive_counts(node)
31
34
  def_node_matcher :receive_counts, <<-PATTERN
32
35
  (send $(send _ {:exactly :at_least :at_most} (int {1 2})) :times)
33
36
  PATTERN
34
37
 
38
+ # @!method stub?(node)
35
39
  def_node_search :stub?, '(send nil? :receive ...)'
36
40
 
37
41
  def on_send(node)
@@ -16,7 +16,9 @@ module RuboCop
16
16
  class ReceiveNever < Base
17
17
  extend AutoCorrector
18
18
  MSG = 'Use `not_to receive` instead of `never`.'
19
+ RESTRICT_ON_SEND = %i[never].freeze
19
20
 
21
+ # @!method method_on_stub?(node)
20
22
  def_node_search :method_on_stub?, '(send nil? :receive ...)'
21
23
 
22
24
  def on_send(node)
@@ -46,16 +46,23 @@ module RuboCop
46
46
  class RepeatedExampleGroupBody < Base
47
47
  MSG = 'Repeated %<group>s block body on line(s) %<loc>s'
48
48
 
49
+ # @!method several_example_groups?(node)
49
50
  def_node_matcher :several_example_groups?, <<-PATTERN
50
51
  (begin <#example_group_with_body? #example_group_with_body? ...>)
51
52
  PATTERN
52
53
 
54
+ # @!method metadata(node)
53
55
  def_node_matcher :metadata, '(block (send _ _ _ $...) ...)'
56
+
57
+ # @!method body(node)
54
58
  def_node_matcher :body, '(block _ args $...)'
59
+
60
+ # @!method const_arg(node)
55
61
  def_node_matcher :const_arg, '(block (send _ _ $const ...) ...)'
56
62
 
63
+ # @!method skip_or_pending?(node)
57
64
  def_node_matcher :skip_or_pending?, <<-PATTERN
58
- (block <(send nil? {:skip :pending}) ...>)
65
+ (block <(send nil? {:skip :pending} ...) ...>)
59
66
  PATTERN
60
67
 
61
68
  def on_begin(node)
@@ -46,18 +46,22 @@ module RuboCop
46
46
  class RepeatedExampleGroupDescription < Base
47
47
  MSG = 'Repeated %<group>s block description on line(s) %<loc>s'
48
48
 
49
+ # @!method several_example_groups?(node)
49
50
  def_node_matcher :several_example_groups?, <<-PATTERN
50
51
  (begin <#example_group? #example_group? ...>)
51
52
  PATTERN
52
53
 
54
+ # @!method doc_string_and_metadata(node)
53
55
  def_node_matcher :doc_string_and_metadata, <<-PATTERN
54
56
  (block (send _ _ $_ $...) ...)
55
57
  PATTERN
56
58
 
59
+ # @!method skip_or_pending?(node)
57
60
  def_node_matcher :skip_or_pending?, <<-PATTERN
58
61
  (block <(send nil? {:skip :pending}) ...>)
59
62
  PATTERN
60
63
 
64
+ # @!method empty_description?(node)
61
65
  def_node_matcher :empty_description?, '(block (send _ _) ...)'
62
66
 
63
67
  def on_begin(node)
@@ -50,13 +50,16 @@ module RuboCop
50
50
  MSG = 'Repeated include of shared_examples %<name>s ' \
51
51
  'on line(s) %<repeat>s'
52
52
 
53
+ # @!method several_include_examples?(node)
53
54
  def_node_matcher :several_include_examples?, <<-PATTERN
54
55
  (begin <#include_examples? #include_examples? ...>)
55
56
  PATTERN
56
57
 
58
+ # @!method include_examples?(node)
57
59
  def_node_matcher :include_examples?,
58
60
  send_pattern('#Includes.examples')
59
61
 
62
+ # @!method shared_examples_name(node)
60
63
  def_node_matcher :shared_examples_name, <<-PATTERN
61
64
  (send _ #Includes.examples $_ ...)
62
65
  PATTERN
@@ -39,9 +39,15 @@ module RuboCop
39
39
 
40
40
  MSG_AND_RETURN = 'Use `and_return` for static values.'
41
41
  MSG_BLOCK = 'Use block for static values.'
42
+ RESTRICT_ON_SEND = %i[and_return].freeze
42
43
 
44
+ # @!method contains_stub?(node)
43
45
  def_node_search :contains_stub?, '(send nil? :receive (...))'
46
+
47
+ # @!method stub_with_block?(node)
44
48
  def_node_matcher :stub_with_block?, '(block #contains_stub? ...)'
49
+
50
+ # @!method and_return_value(node)
45
51
  def_node_search :and_return_value, <<-PATTERN
46
52
  $(send _ :and_return $(...))
47
53
  PATTERN
@@ -23,7 +23,7 @@ module RuboCop
23
23
  # end
24
24
  #
25
25
  class ScatteredSetup < Base
26
- MSG = 'Do not define multiple `%<hook_name>s` hooks in the same '\
26
+ MSG = 'Do not define multiple `%<hook_name>s` hooks in the same ' \
27
27
  'example group (also defined on %<lines>s).'
28
28
 
29
29
  def on_block(node)
@@ -53,15 +53,14 @@ module RuboCop
53
53
  class SharedContext < Base
54
54
  extend AutoCorrector
55
55
 
56
- MSG_EXAMPLES = "Use `shared_examples` when you don't "\
57
- 'define context.'
58
-
59
- MSG_CONTEXT = "Use `shared_context` when you don't "\
60
- 'define examples.'
56
+ MSG_EXAMPLES = "Use `shared_examples` when you don't define context."
57
+ MSG_CONTEXT = "Use `shared_context` when you don't define examples."
61
58
 
59
+ # @!method examples?(node)
62
60
  def_node_search :examples?,
63
61
  send_pattern('{#Includes.examples #Examples.all}')
64
62
 
63
+ # @!method context?(node)
65
64
  def_node_search :context?, <<-PATTERN
66
65
  (
67
66
  send #rspec? {
@@ -73,8 +72,10 @@ module RuboCop
73
72
  )
74
73
  PATTERN
75
74
 
75
+ # @!method shared_context(node)
76
76
  def_node_matcher :shared_context,
77
77
  block_pattern('#SharedGroups.context')
78
+ # @!method shared_example(node)
78
79
  def_node_matcher :shared_example,
79
80
  block_pattern('#SharedGroups.examples')
80
81