rspec-core 2.11.1 → 3.11.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (222) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +3 -1
  5. data/Changelog.md +1814 -29
  6. data/{License.txt → LICENSE.md} +6 -4
  7. data/README.md +197 -48
  8. data/exe/rspec +2 -23
  9. data/lib/rspec/autorun.rb +1 -0
  10. data/lib/rspec/core/backtrace_formatter.rb +65 -0
  11. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  12. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  13. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  14. data/lib/rspec/core/bisect/server.rb +61 -0
  15. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  16. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  17. data/lib/rspec/core/bisect/utilities.rb +69 -0
  18. data/lib/rspec/core/configuration.rb +1846 -407
  19. data/lib/rspec/core/configuration_options.rb +154 -50
  20. data/lib/rspec/core/did_you_mean.rb +46 -0
  21. data/lib/rspec/core/drb.rb +120 -0
  22. data/lib/rspec/core/dsl.rb +90 -18
  23. data/lib/rspec/core/example.rb +488 -152
  24. data/lib/rspec/core/example_group.rb +733 -294
  25. data/lib/rspec/core/example_status_persister.rb +235 -0
  26. data/lib/rspec/core/filter_manager.rb +175 -147
  27. data/lib/rspec/core/flat_map.rb +20 -0
  28. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  29. data/lib/rspec/core/formatters/base_formatter.rb +32 -130
  30. data/lib/rspec/core/formatters/base_text_formatter.rb +62 -190
  31. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  32. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  33. data/lib/rspec/core/formatters/console_codes.rb +76 -0
  34. data/lib/rspec/core/formatters/deprecation_formatter.rb +223 -0
  35. data/lib/rspec/core/formatters/documentation_formatter.rb +62 -27
  36. data/lib/rspec/core/formatters/exception_presenter.rb +521 -0
  37. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  38. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  39. data/lib/rspec/core/formatters/helpers.rb +93 -14
  40. data/lib/rspec/core/formatters/html_formatter.rb +104 -415
  41. data/lib/rspec/core/formatters/html_printer.rb +414 -0
  42. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  43. data/lib/rspec/core/formatters/json_formatter.rb +102 -0
  44. data/lib/rspec/core/formatters/profile_formatter.rb +68 -0
  45. data/lib/rspec/core/formatters/progress_formatter.rb +12 -15
  46. data/lib/rspec/core/formatters/protocol.rb +182 -0
  47. data/lib/rspec/core/formatters/snippet_extractor.rb +115 -39
  48. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  49. data/lib/rspec/core/formatters.rb +279 -0
  50. data/lib/rspec/core/hooks.rb +451 -300
  51. data/lib/rspec/core/invocations.rb +87 -0
  52. data/lib/rspec/core/memoized_helpers.rb +580 -0
  53. data/lib/rspec/core/metadata.rb +395 -173
  54. data/lib/rspec/core/metadata_filter.rb +255 -0
  55. data/lib/rspec/core/minitest_assertions_adapter.rb +31 -0
  56. data/lib/rspec/core/mocking_adapters/flexmock.rb +31 -0
  57. data/lib/rspec/core/mocking_adapters/mocha.rb +57 -0
  58. data/lib/rspec/core/mocking_adapters/null.rb +14 -0
  59. data/lib/rspec/core/mocking_adapters/rr.rb +31 -0
  60. data/lib/rspec/core/mocking_adapters/rspec.rb +32 -0
  61. data/lib/rspec/core/notifications.rb +521 -0
  62. data/lib/rspec/core/option_parser.rb +208 -64
  63. data/lib/rspec/core/ordering.rb +169 -0
  64. data/lib/rspec/core/output_wrapper.rb +29 -0
  65. data/lib/rspec/core/pending.rb +115 -59
  66. data/lib/rspec/core/profiler.rb +34 -0
  67. data/lib/rspec/core/project_initializer/.rspec +1 -0
  68. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +98 -0
  69. data/lib/rspec/core/project_initializer.rb +26 -65
  70. data/lib/rspec/core/rake_task.rb +140 -131
  71. data/lib/rspec/core/reporter.rb +207 -44
  72. data/lib/rspec/core/ruby_project.rb +15 -6
  73. data/lib/rspec/core/runner.rb +180 -44
  74. data/lib/rspec/core/sandbox.rb +37 -0
  75. data/lib/rspec/core/set.rb +54 -0
  76. data/lib/rspec/core/shared_context.rb +25 -19
  77. data/lib/rspec/core/shared_example_group.rb +229 -54
  78. data/lib/rspec/core/shell_escape.rb +49 -0
  79. data/lib/rspec/core/test_unit_assertions_adapter.rb +30 -0
  80. data/lib/rspec/core/version.rb +3 -1
  81. data/lib/rspec/core/warnings.rb +40 -0
  82. data/lib/rspec/core/world.rb +208 -49
  83. data/lib/rspec/core.rb +166 -80
  84. data.tar.gz.sig +0 -0
  85. metadata +230 -445
  86. metadata.gz.sig +0 -0
  87. data/exe/autospec +0 -13
  88. data/features/Autotest.md +0 -38
  89. data/features/README.md +0 -17
  90. data/features/Upgrade.md +0 -364
  91. data/features/command_line/README.md +0 -28
  92. data/features/command_line/example_name_option.feature +0 -101
  93. data/features/command_line/exit_status.feature +0 -83
  94. data/features/command_line/format_option.feature +0 -81
  95. data/features/command_line/init.feature +0 -18
  96. data/features/command_line/line_number_appended_to_path.feature +0 -140
  97. data/features/command_line/line_number_option.feature +0 -58
  98. data/features/command_line/order.feature +0 -29
  99. data/features/command_line/pattern_option.feature +0 -31
  100. data/features/command_line/rake_task.feature +0 -68
  101. data/features/command_line/ruby.feature +0 -22
  102. data/features/command_line/tag.feature +0 -91
  103. data/features/configuration/alias_example_to.feature +0 -48
  104. data/features/configuration/custom_settings.feature +0 -84
  105. data/features/configuration/default_path.feature +0 -38
  106. data/features/configuration/fail_fast.feature +0 -77
  107. data/features/configuration/read_options_from_file.feature +0 -87
  108. data/features/example_groups/basic_structure.feature +0 -55
  109. data/features/example_groups/shared_context.feature +0 -74
  110. data/features/example_groups/shared_examples.feature +0 -204
  111. data/features/expectation_framework_integration/configure_expectation_framework.feature +0 -102
  112. data/features/filtering/exclusion_filters.feature +0 -139
  113. data/features/filtering/if_and_unless.feature +0 -168
  114. data/features/filtering/inclusion_filters.feature +0 -105
  115. data/features/filtering/run_all_when_everything_filtered.feature +0 -46
  116. data/features/formatters/custom_formatter.feature +0 -36
  117. data/features/formatters/text_formatter.feature +0 -46
  118. data/features/helper_methods/arbitrary_methods.feature +0 -40
  119. data/features/helper_methods/let.feature +0 -50
  120. data/features/helper_methods/modules.feature +0 -149
  121. data/features/hooks/around_hooks.feature +0 -343
  122. data/features/hooks/before_and_after_hooks.feature +0 -423
  123. data/features/hooks/filtering.feature +0 -234
  124. data/features/metadata/current_example.feature +0 -17
  125. data/features/metadata/described_class.feature +0 -17
  126. data/features/metadata/user_defined.feature +0 -113
  127. data/features/mock_framework_integration/use_any_framework.feature +0 -106
  128. data/features/mock_framework_integration/use_flexmock.feature +0 -96
  129. data/features/mock_framework_integration/use_mocha.feature +0 -97
  130. data/features/mock_framework_integration/use_rr.feature +0 -98
  131. data/features/mock_framework_integration/use_rspec.feature +0 -97
  132. data/features/pending/pending_examples.feature +0 -229
  133. data/features/spec_files/arbitrary_file_suffix.feature +0 -13
  134. data/features/step_definitions/additional_cli_steps.rb +0 -30
  135. data/features/subject/attribute_of_subject.feature +0 -124
  136. data/features/subject/explicit_subject.feature +0 -82
  137. data/features/subject/implicit_receiver.feature +0 -29
  138. data/features/subject/implicit_subject.feature +0 -63
  139. data/features/support/env.rb +0 -12
  140. data/lib/autotest/discover.rb +0 -1
  141. data/lib/autotest/rspec2.rb +0 -73
  142. data/lib/rspec/core/backward_compatibility.rb +0 -65
  143. data/lib/rspec/core/command_line.rb +0 -36
  144. data/lib/rspec/core/deprecation.rb +0 -36
  145. data/lib/rspec/core/drb_command_line.rb +0 -26
  146. data/lib/rspec/core/drb_options.rb +0 -87
  147. data/lib/rspec/core/extensions/instance_eval_with_args.rb +0 -44
  148. data/lib/rspec/core/extensions/kernel.rb +0 -9
  149. data/lib/rspec/core/extensions/module_eval_with_args.rb +0 -38
  150. data/lib/rspec/core/extensions/ordered.rb +0 -21
  151. data/lib/rspec/core/extensions.rb +0 -4
  152. data/lib/rspec/core/formatters/text_mate_formatter.rb +0 -34
  153. data/lib/rspec/core/let.rb +0 -110
  154. data/lib/rspec/core/load_path.rb +0 -3
  155. data/lib/rspec/core/metadata_hash_builder.rb +0 -97
  156. data/lib/rspec/core/mocking/with_absolutely_nothing.rb +0 -11
  157. data/lib/rspec/core/mocking/with_flexmock.rb +0 -27
  158. data/lib/rspec/core/mocking/with_mocha.rb +0 -29
  159. data/lib/rspec/core/mocking/with_rr.rb +0 -27
  160. data/lib/rspec/core/mocking/with_rspec.rb +0 -23
  161. data/lib/rspec/core/subject.rb +0 -219
  162. data/spec/autotest/discover_spec.rb +0 -19
  163. data/spec/autotest/failed_results_re_spec.rb +0 -45
  164. data/spec/autotest/rspec_spec.rb +0 -123
  165. data/spec/command_line/order_spec.rb +0 -137
  166. data/spec/rspec/core/command_line_spec.rb +0 -108
  167. data/spec/rspec/core/command_line_spec_output.txt +0 -0
  168. data/spec/rspec/core/configuration_options_spec.rb +0 -377
  169. data/spec/rspec/core/configuration_spec.rb +0 -1196
  170. data/spec/rspec/core/deprecations_spec.rb +0 -66
  171. data/spec/rspec/core/drb_command_line_spec.rb +0 -108
  172. data/spec/rspec/core/drb_options_spec.rb +0 -180
  173. data/spec/rspec/core/dsl_spec.rb +0 -17
  174. data/spec/rspec/core/example_group_spec.rb +0 -1098
  175. data/spec/rspec/core/example_spec.rb +0 -370
  176. data/spec/rspec/core/filter_manager_spec.rb +0 -256
  177. data/spec/rspec/core/formatters/base_formatter_spec.rb +0 -80
  178. data/spec/rspec/core/formatters/base_text_formatter_spec.rb +0 -363
  179. data/spec/rspec/core/formatters/documentation_formatter_spec.rb +0 -88
  180. data/spec/rspec/core/formatters/helpers_spec.rb +0 -66
  181. data/spec/rspec/core/formatters/html_formatted-1.8.7-jruby.html +0 -410
  182. data/spec/rspec/core/formatters/html_formatted-1.8.7.html +0 -409
  183. data/spec/rspec/core/formatters/html_formatted-1.9.2.html +0 -416
  184. data/spec/rspec/core/formatters/html_formatted-1.9.3.html +0 -416
  185. data/spec/rspec/core/formatters/html_formatter_spec.rb +0 -82
  186. data/spec/rspec/core/formatters/progress_formatter_spec.rb +0 -30
  187. data/spec/rspec/core/formatters/snippet_extractor_spec.rb +0 -18
  188. data/spec/rspec/core/formatters/text_mate_formatted-1.8.7-jruby.html +0 -410
  189. data/spec/rspec/core/formatters/text_mate_formatted-1.8.7.html +0 -409
  190. data/spec/rspec/core/formatters/text_mate_formatted-1.9.2.html +0 -416
  191. data/spec/rspec/core/formatters/text_mate_formatted-1.9.3.html +0 -416
  192. data/spec/rspec/core/formatters/text_mate_formatter_spec.rb +0 -83
  193. data/spec/rspec/core/hooks_filtering_spec.rb +0 -227
  194. data/spec/rspec/core/hooks_spec.rb +0 -250
  195. data/spec/rspec/core/kernel_extensions_spec.rb +0 -9
  196. data/spec/rspec/core/let_spec.rb +0 -55
  197. data/spec/rspec/core/metadata_spec.rb +0 -447
  198. data/spec/rspec/core/option_parser_spec.rb +0 -166
  199. data/spec/rspec/core/pending_example_spec.rb +0 -220
  200. data/spec/rspec/core/project_initializer_spec.rb +0 -130
  201. data/spec/rspec/core/rake_task_spec.rb +0 -138
  202. data/spec/rspec/core/reporter_spec.rb +0 -103
  203. data/spec/rspec/core/resources/a_bar.rb +0 -0
  204. data/spec/rspec/core/resources/a_foo.rb +0 -0
  205. data/spec/rspec/core/resources/a_spec.rb +0 -1
  206. data/spec/rspec/core/resources/custom_example_group_runner.rb +0 -14
  207. data/spec/rspec/core/resources/formatter_specs.rb +0 -60
  208. data/spec/rspec/core/resources/utf8_encoded.rb +0 -8
  209. data/spec/rspec/core/rspec_matchers_spec.rb +0 -45
  210. data/spec/rspec/core/ruby_project_spec.rb +0 -24
  211. data/spec/rspec/core/runner_spec.rb +0 -81
  212. data/spec/rspec/core/shared_context_spec.rb +0 -67
  213. data/spec/rspec/core/shared_example_group_spec.rb +0 -84
  214. data/spec/rspec/core/subject_spec.rb +0 -244
  215. data/spec/rspec/core/world_spec.rb +0 -144
  216. data/spec/rspec/core_spec.rb +0 -35
  217. data/spec/spec_helper.rb +0 -98
  218. data/spec/support/config_options_helper.rb +0 -24
  219. data/spec/support/helper_methods.rb +0 -5
  220. data/spec/support/matchers.rb +0 -65
  221. data/spec/support/shared_example_groups.rb +0 -41
  222. data/spec/support/spec_files.rb +0 -44
@@ -1,32 +1,29 @@
1
- require 'rspec/core/formatters/base_text_formatter'
1
+ RSpec::Support.require_rspec_core "formatters/base_text_formatter"
2
+ RSpec::Support.require_rspec_core "formatters/console_codes"
3
+
2
4
  module RSpec
3
5
  module Core
4
6
  module Formatters
5
-
7
+ # @private
6
8
  class ProgressFormatter < BaseTextFormatter
9
+ Formatters.register self, :example_passed, :example_pending, :example_failed, :start_dump
7
10
 
8
- def example_passed(example)
9
- super(example)
10
- output.print green('.')
11
+ def example_passed(_notification)
12
+ output.print ConsoleCodes.wrap('.', :success)
11
13
  end
12
14
 
13
- def example_pending(example)
14
- super(example)
15
- output.print yellow('*')
15
+ def example_pending(_notification)
16
+ output.print ConsoleCodes.wrap('*', :pending)
16
17
  end
17
18
 
18
- def example_failed(example)
19
- super(example)
20
- output.print red('F')
19
+ def example_failed(_notification)
20
+ output.print ConsoleCodes.wrap('F', :failure)
21
21
  end
22
22
 
23
- def start_dump
24
- super()
23
+ def start_dump(_notification)
25
24
  output.puts
26
25
  end
27
-
28
26
  end
29
-
30
27
  end
31
28
  end
32
29
  end
@@ -0,0 +1,182 @@
1
+ module RSpec
2
+ module Core
3
+ module Formatters
4
+ # This class isn't loaded at runtime but serves to document all of the
5
+ # notifications implemented as part of the standard interface. The
6
+ # reporter will issue these during a normal test suite run, but a
7
+ # formatter will only receive those notifications it has registered
8
+ # itself to receive. To register a formatter call:
9
+ #
10
+ # `::RSpec::Core::Formatters.register class, :list, :of, :notifications`
11
+ #
12
+ # e.g.
13
+ #
14
+ # `::RSpec::Core::Formatters.register self, :start, :example_started`
15
+ #
16
+ # @see RSpec::Core::Formatters::BaseFormatter
17
+ # @see RSpec::Core::Formatters::BaseTextFormatter
18
+ # @see RSpec::Core::Reporter
19
+ class Protocol
20
+ # @method initialize(output)
21
+ # @api public
22
+ #
23
+ # @param output [IO] the formatter output
24
+
25
+ # @method start(notification)
26
+ # @api public
27
+ # @group Suite Notifications
28
+ #
29
+ # This method is invoked before any examples are run, right after
30
+ # they have all been collected. This can be useful for special
31
+ # formatters that need to provide progress on feedback (graphical ones).
32
+ #
33
+ # This will only be invoked once, and the next one to be invoked
34
+ # is {#example_group_started}.
35
+ #
36
+ # @param notification [Notifications::StartNotification]
37
+
38
+ # @method example_group_started(notification)
39
+ # @api public
40
+ # @group Group Notifications
41
+ #
42
+ # This method is invoked at the beginning of the execution of each
43
+ # example group.
44
+ #
45
+ # The next method to be invoked after this is {#example_passed},
46
+ # {#example_pending}, or {#example_group_finished}.
47
+ #
48
+ # @param notification [Notifications::GroupNotification] containing example_group
49
+ # subclass of {ExampleGroup}
50
+
51
+ # @method example_group_finished(notification)
52
+ # @api public
53
+ # @group Group Notifications
54
+ #
55
+ # Invoked at the end of the execution of each example group.
56
+ #
57
+ # @param notification [Notifications::GroupNotification] containing example_group
58
+ # subclass of {ExampleGroup}
59
+
60
+ # @method example_started(notification)
61
+ # @api public
62
+ # @group Example Notifications
63
+ #
64
+ # Invoked at the beginning of the execution of each example.
65
+ #
66
+ # @param notification [Notifications::ExampleNotification] containing example subclass
67
+ # of {Example}
68
+
69
+ # @method example_finished(notification)
70
+ # @api public
71
+ # @group Example Notifications
72
+ #
73
+ # Invoked at the end of the execution of each example.
74
+ #
75
+ # @param notification [Notifications::ExampleNotification] containing example subclass
76
+ # of {Example}
77
+
78
+ # @method example_passed(notification)
79
+ # @api public
80
+ # @group Example Notifications
81
+ #
82
+ # Invoked when an example passes.
83
+ #
84
+ # @param notification [Notifications::ExampleNotification] containing example subclass
85
+ # of {Example}
86
+
87
+ # @method example_pending(notification)
88
+ # @api public
89
+ # @group Example Notifications
90
+ #
91
+ # Invoked when an example is pending.
92
+ #
93
+ # @param notification [Notifications::ExampleNotification] containing example subclass
94
+ # of {Example}
95
+
96
+ # @method example_failed(notification)
97
+ # @api public
98
+ # @group Example Notifications
99
+ #
100
+ # Invoked when an example fails.
101
+ #
102
+ # @param notification [Notifications::ExampleNotification] containing example subclass
103
+ # of {Example}
104
+
105
+ # @method message(notification)
106
+ # @api public
107
+ # @group Suite Notifications
108
+ #
109
+ # Used by the reporter to send messages to the output stream.
110
+ #
111
+ # @param notification [Notifications::MessageNotification] containing message
112
+
113
+ # @method stop(notification)
114
+ # @api public
115
+ # @group Suite Notifications
116
+ #
117
+ # Invoked after all examples have executed, before dumping post-run
118
+ # reports.
119
+ #
120
+ # @param notification [Notifications::NullNotification]
121
+
122
+ # @method start_dump(notification)
123
+ # @api public
124
+ # @group Suite Notifications
125
+ #
126
+ # This method is invoked after all of the examples have executed. The
127
+ # next method to be invoked after this one is {#dump_failures}
128
+ # (BaseTextFormatter then calls {#dump_failures} once for each failed
129
+ # example).
130
+ #
131
+ # @param notification [Notifications::NullNotification]
132
+
133
+ # @method dump_failures(notification)
134
+ # @api public
135
+ # @group Suite Notifications
136
+ #
137
+ # Dumps detailed information about each example failure.
138
+ #
139
+ # @param notification [Notifications::NullNotification]
140
+
141
+ # @method dump_summary(summary)
142
+ # @api public
143
+ # @group Suite Notifications
144
+ #
145
+ # This method is invoked after the dumping of examples and failures.
146
+ # Each parameter is assigned to a corresponding attribute.
147
+ #
148
+ # @param summary [Notifications::SummaryNotification] containing duration,
149
+ # example_count, failure_count and pending_count
150
+
151
+ # @method dump_profile(profile)
152
+ # @api public
153
+ # @group Suite Notifications
154
+ #
155
+ # This method is invoked after the dumping the summary if profiling is
156
+ # enabled.
157
+ #
158
+ # @param profile [Notifications::ProfileNotification] containing duration,
159
+ # slowest_examples and slowest_example_groups
160
+
161
+ # @method dump_pending(notification)
162
+ # @api public
163
+ # @group Suite Notifications
164
+ #
165
+ # Outputs a report of pending examples. This gets invoked
166
+ # after the summary if option is set to do so.
167
+ #
168
+ # @param notification [Notifications::NullNotification]
169
+
170
+ # @method close(notification)
171
+ # @api public
172
+ # @group Suite Notifications
173
+ #
174
+ # Invoked at the end of a suite run. Allows the formatter to do any
175
+ # tidying up, but be aware that formatter output streams may be used
176
+ # elsewhere so don't actually close them.
177
+ #
178
+ # @param notification [Notifications::NullNotification]
179
+ end
180
+ end
181
+ end
182
+ end
@@ -1,57 +1,133 @@
1
1
  module RSpec
2
2
  module Core
3
3
  module Formatters
4
- # This class extracts code snippets by looking at the backtrace of the passed error
4
+ # @private
5
5
  class SnippetExtractor
6
- class NullConverter; def convert(code, pre); code; end; end
7
-
8
- begin
9
- require 'syntax/convertors/html'
10
- @@converter = Syntax::Convertors::HTML.for_syntax "ruby"
11
- rescue LoadError
12
- @@converter = NullConverter.new
6
+ NoSuchFileError = Class.new(StandardError)
7
+ NoSuchLineError = Class.new(StandardError)
8
+
9
+ def self.extract_line_at(file_path, line_number)
10
+ source = source_from_file(file_path)
11
+ line = source.lines[line_number - 1]
12
+ raise NoSuchLineError unless line
13
+ line
13
14
  end
14
15
 
15
- def snippet(backtrace)
16
- raw_code, line = snippet_for(backtrace[0])
17
- highlighted = @@converter.convert(raw_code, false)
18
- highlighted << "\n<span class=\"comment\"># gem install syntax to get syntax highlighting</span>" if @@converter.is_a?(NullConverter)
19
- post_process(highlighted, line)
16
+ def self.source_from_file(path)
17
+ raise NoSuchFileError unless File.exist?(path)
18
+ RSpec.world.source_from_file(path)
20
19
  end
21
20
 
22
- def snippet_for(error_line)
23
- if error_line =~ /(.*):(\d+)/
24
- file = $1
25
- line = $2.to_i
26
- [lines_around(file, line), line]
27
- else
28
- ["# Couldn't get snippet for #{error_line}", 1]
21
+ if RSpec::Support::RubyFeatures.ripper_supported?
22
+ NoExpressionAtLineError = Class.new(StandardError)
23
+
24
+ attr_reader :source, :beginning_line_number, :max_line_count
25
+
26
+ def self.extract_expression_lines_at(file_path, beginning_line_number, max_line_count=nil)
27
+ if max_line_count == 1
28
+ [extract_line_at(file_path, beginning_line_number)]
29
+ else
30
+ source = source_from_file(file_path)
31
+ new(source, beginning_line_number, max_line_count).expression_lines
32
+ end
29
33
  end
30
- end
31
34
 
32
- def lines_around(file, line)
33
- if File.file?(file)
34
- lines = File.read(file).split("\n")
35
- min = [0, line-3].max
36
- max = [line+1, lines.length-1].min
37
- selected_lines = []
38
- selected_lines.join("\n")
39
- lines[min..max].join("\n")
40
- else
41
- "# Couldn't get snippet for #{file}"
35
+ def initialize(source, beginning_line_number, max_line_count=nil)
36
+ @source = source
37
+ @beginning_line_number = beginning_line_number
38
+ @max_line_count = max_line_count
39
+ end
40
+
41
+ def expression_lines
42
+ line_range = line_range_of_expression
43
+
44
+ if max_line_count && line_range.count > max_line_count
45
+ line_range = (line_range.begin)..(line_range.begin + max_line_count - 1)
46
+ end
47
+
48
+ source.lines[(line_range.begin - 1)..(line_range.end - 1)]
49
+ rescue SyntaxError, NoExpressionAtLineError
50
+ [self.class.extract_line_at(source.path, beginning_line_number)]
51
+ end
52
+
53
+ private
54
+
55
+ def line_range_of_expression
56
+ @line_range_of_expression ||= begin
57
+ line_range = line_range_of_location_nodes_in_expression
58
+ initial_unclosed_tokens = unclosed_tokens_in_line_range(line_range)
59
+ unclosed_tokens = initial_unclosed_tokens
60
+
61
+ until (initial_unclosed_tokens & unclosed_tokens).empty?
62
+ line_range = (line_range.begin)..(line_range.end + 1)
63
+ unclosed_tokens = unclosed_tokens_in_line_range(line_range)
64
+ end
65
+
66
+ line_range
67
+ end
42
68
  end
43
- end
44
69
 
45
- def post_process(highlighted, offending_line)
46
- new_lines = []
47
- highlighted.split("\n").each_with_index do |line, i|
48
- new_line = "<span class=\"linenum\">#{offending_line+i-2}</span>#{line}"
49
- new_line = "<span class=\"offending\">#{new_line}</span>" if i == 2
50
- new_lines << new_line
70
+ def unclosed_tokens_in_line_range(line_range)
71
+ tokens = FlatMap.flat_map(line_range) do |line_number|
72
+ source.tokens_by_line_number[line_number]
73
+ end
74
+
75
+ tokens.each_with_object([]) do |token, unclosed_tokens|
76
+ if token.opening?
77
+ unclosed_tokens << token
78
+ else
79
+ index = unclosed_tokens.rindex do |unclosed_token|
80
+ unclosed_token.closed_by?(token)
81
+ end
82
+ unclosed_tokens.delete_at(index) if index
83
+ end
84
+ end
85
+ end
86
+
87
+ def line_range_of_location_nodes_in_expression
88
+ line_numbers = expression_node.each_with_object(Set.new) do |node, set|
89
+ set << node.location.line if node.location
90
+ end
91
+
92
+ line_numbers.min..line_numbers.max
93
+ end
94
+
95
+ def expression_node
96
+ raise NoExpressionAtLineError if location_nodes_at_beginning_line.empty?
97
+
98
+ @expression_node ||= begin
99
+ common_ancestor_nodes = location_nodes_at_beginning_line.map do |node|
100
+ node.each_ancestor.to_a
101
+ end.reduce(:&)
102
+
103
+ common_ancestor_nodes.find { |node| expression_outmost_node?(node) }
104
+ end
105
+ end
106
+
107
+ def expression_outmost_node?(node)
108
+ return true unless node.parent
109
+ return false if node.type.to_s.start_with?('@')
110
+ ![node, node.parent].all? do |n|
111
+ # See `Ripper::PARSER_EVENTS` for the complete list of sexp types.
112
+ type = n.type.to_s
113
+ type.end_with?('call') || type.start_with?('method_add_')
114
+ end
51
115
  end
52
- new_lines.join("\n")
116
+
117
+ def location_nodes_at_beginning_line
118
+ source.nodes_by_line_number[beginning_line_number]
119
+ end
120
+ else
121
+ # :nocov:
122
+ def self.extract_expression_lines_at(file_path, beginning_line_number, *)
123
+ [extract_line_at(file_path, beginning_line_number)]
124
+ end
125
+ # :nocov:
53
126
  end
54
127
 
128
+ def self.least_indentation_from(lines)
129
+ lines.map { |line| line[/^[ \t]*/] }.min
130
+ end
55
131
  end
56
132
  end
57
133
  end
@@ -0,0 +1,91 @@
1
+ module RSpec
2
+ module Core
3
+ module Formatters
4
+ # @private
5
+ # Provides terminal syntax highlighting of code snippets
6
+ # when coderay is available.
7
+ class SyntaxHighlighter
8
+ def initialize(configuration)
9
+ @configuration = configuration
10
+ end
11
+
12
+ def highlight(lines)
13
+ implementation.highlight_syntax(lines)
14
+ end
15
+
16
+ # rubocop:disable Lint/RescueException
17
+ # rubocop:disable Lint/HandleExceptions
18
+ def self.attempt_to_add_rspec_terms_to_coderay_keywords
19
+ CodeRay::Scanners::Ruby::Patterns::IDENT_KIND.add(%w[
20
+ describe context
21
+ it specify
22
+ before after around
23
+ let subject
24
+ expect allow
25
+ ], :keyword)
26
+ rescue Exception
27
+ # Mutating CodeRay's contants like this is not a public API
28
+ # and might not always work. If we cannot add our keywords
29
+ # to CodeRay it is not a big deal and not worth raising an
30
+ # error over, so we ignore it.
31
+ end
32
+ # rubocop:enable Lint/HandleExceptions
33
+ # rubocop:enable Lint/RescueException
34
+
35
+ private
36
+
37
+ if RSpec::Support::OS.windows?
38
+ # :nocov:
39
+ def implementation
40
+ WindowsImplementation
41
+ end
42
+ # :nocov:
43
+ else
44
+ def implementation
45
+ return color_enabled_implementation if @configuration.color_enabled?
46
+ NoSyntaxHighlightingImplementation
47
+ end
48
+ end
49
+
50
+ def color_enabled_implementation
51
+ @color_enabled_implementation ||= begin
52
+ require 'coderay'
53
+ self.class.attempt_to_add_rspec_terms_to_coderay_keywords
54
+ CodeRayImplementation
55
+ rescue LoadError
56
+ NoSyntaxHighlightingImplementation
57
+ end
58
+ end
59
+
60
+ # @private
61
+ module CodeRayImplementation
62
+ RESET_CODE = "\e[0m"
63
+
64
+ def self.highlight_syntax(lines)
65
+ highlighted = begin
66
+ CodeRay.encode(lines.join("\n"), :ruby, :terminal)
67
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue
68
+ return lines
69
+ end
70
+
71
+ highlighted.split("\n").map do |line|
72
+ line.sub(/\S/) { |char| char.insert(0, RESET_CODE) }
73
+ end
74
+ end
75
+ end
76
+
77
+ # @private
78
+ module NoSyntaxHighlightingImplementation
79
+ def self.highlight_syntax(lines)
80
+ lines
81
+ end
82
+ end
83
+
84
+ # @private
85
+ # Not sure why, but our code above (and/or coderay itself) does not work
86
+ # on Windows, so we disable the feature on Windows.
87
+ WindowsImplementation = NoSyntaxHighlightingImplementation
88
+ end
89
+ end
90
+ end
91
+ end