ultra-pro-pkg 0.0.1

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 (117) hide show
  1. checksums.yaml +7 -0
  2. data/simplecov-1.0.0/LICENSE +20 -0
  3. data/simplecov-1.0.0/README.md +1524 -0
  4. data/simplecov-1.0.0/doc/alternate-formatters.md +66 -0
  5. data/simplecov-1.0.0/doc/commercial-services.md +25 -0
  6. data/simplecov-1.0.0/doc/editor-integration.md +18 -0
  7. data/simplecov-1.0.0/exe/simplecov +11 -0
  8. data/simplecov-1.0.0/lib/minitest/simplecov_plugin.rb +171 -0
  9. data/simplecov-1.0.0/lib/simplecov/autostart.rb +11 -0
  10. data/simplecov-1.0.0/lib/simplecov/cli/clean.rb +47 -0
  11. data/simplecov-1.0.0/lib/simplecov/cli/coverage.rb +91 -0
  12. data/simplecov-1.0.0/lib/simplecov/cli/diff.rb +151 -0
  13. data/simplecov-1.0.0/lib/simplecov/cli/dotfile.rb +100 -0
  14. data/simplecov-1.0.0/lib/simplecov/cli/merge.rb +116 -0
  15. data/simplecov-1.0.0/lib/simplecov/cli/open.rb +50 -0
  16. data/simplecov-1.0.0/lib/simplecov/cli/report.rb +84 -0
  17. data/simplecov-1.0.0/lib/simplecov/cli/run.rb +36 -0
  18. data/simplecov-1.0.0/lib/simplecov/cli/serve.rb +139 -0
  19. data/simplecov-1.0.0/lib/simplecov/cli/uncovered.rb +107 -0
  20. data/simplecov-1.0.0/lib/simplecov/cli.rb +150 -0
  21. data/simplecov-1.0.0/lib/simplecov/color.rb +74 -0
  22. data/simplecov-1.0.0/lib/simplecov/combine/branches_combiner.rb +54 -0
  23. data/simplecov-1.0.0/lib/simplecov/combine/files_combiner.rb +30 -0
  24. data/simplecov-1.0.0/lib/simplecov/combine/lines_combiner.rb +45 -0
  25. data/simplecov-1.0.0/lib/simplecov/combine/methods_combiner.rb +26 -0
  26. data/simplecov-1.0.0/lib/simplecov/combine/results_combiner.rb +61 -0
  27. data/simplecov-1.0.0/lib/simplecov/combine.rb +30 -0
  28. data/simplecov-1.0.0/lib/simplecov/command_guesser.rb +78 -0
  29. data/simplecov-1.0.0/lib/simplecov/configuration/coverage.rb +171 -0
  30. data/simplecov-1.0.0/lib/simplecov/configuration/coverage_criteria.rb +156 -0
  31. data/simplecov-1.0.0/lib/simplecov/configuration/filters.rb +197 -0
  32. data/simplecov-1.0.0/lib/simplecov/configuration/formatting.rb +119 -0
  33. data/simplecov-1.0.0/lib/simplecov/configuration/ignored_entries.rb +63 -0
  34. data/simplecov-1.0.0/lib/simplecov/configuration/merging.rb +164 -0
  35. data/simplecov-1.0.0/lib/simplecov/configuration/thresholds.rb +174 -0
  36. data/simplecov-1.0.0/lib/simplecov/configuration.rb +184 -0
  37. data/simplecov-1.0.0/lib/simplecov/coverage_statistics.rb +59 -0
  38. data/simplecov-1.0.0/lib/simplecov/coverage_violations.rb +148 -0
  39. data/simplecov-1.0.0/lib/simplecov/defaults.rb +60 -0
  40. data/simplecov-1.0.0/lib/simplecov/deprecation.rb +45 -0
  41. data/simplecov-1.0.0/lib/simplecov/directive.rb +162 -0
  42. data/simplecov-1.0.0/lib/simplecov/exit_codes/exit_code_handling.rb +35 -0
  43. data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_coverage_drop_check.rb +45 -0
  44. data/simplecov-1.0.0/lib/simplecov/exit_codes/maximum_overall_coverage_check.rb +45 -0
  45. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_file_check.rb +44 -0
  46. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_coverage_by_group_check.rb +41 -0
  47. data/simplecov-1.0.0/lib/simplecov/exit_codes/minimum_overall_coverage_check.rb +70 -0
  48. data/simplecov-1.0.0/lib/simplecov/exit_codes.rb +18 -0
  49. data/simplecov-1.0.0/lib/simplecov/exit_handling.rb +158 -0
  50. data/simplecov-1.0.0/lib/simplecov/file_list.rb +164 -0
  51. data/simplecov-1.0.0/lib/simplecov/filter.rb +139 -0
  52. data/simplecov-1.0.0/lib/simplecov/formatter/base.rb +101 -0
  53. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.css +1 -0
  54. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/application.js +18 -0
  55. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_green.png +0 -0
  56. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_red.png +0 -0
  57. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/favicon_yellow.png +0 -0
  58. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter/public/index.html +56 -0
  59. data/simplecov-1.0.0/lib/simplecov/formatter/html_formatter.rb +79 -0
  60. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/errors_formatter.rb +84 -0
  61. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/result_hash_formatter.rb +128 -0
  62. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter/source_file_formatter.rb +99 -0
  63. data/simplecov-1.0.0/lib/simplecov/formatter/json_formatter.rb +87 -0
  64. data/simplecov-1.0.0/lib/simplecov/formatter/multi_formatter.rb +31 -0
  65. data/simplecov-1.0.0/lib/simplecov/formatter/simple_formatter.rb +23 -0
  66. data/simplecov-1.0.0/lib/simplecov/formatter.rb +14 -0
  67. data/simplecov-1.0.0/lib/simplecov/last_run.rb +35 -0
  68. data/simplecov-1.0.0/lib/simplecov/lines_classifier.rb +60 -0
  69. data/simplecov-1.0.0/lib/simplecov/load_global_config.rb +13 -0
  70. data/simplecov-1.0.0/lib/simplecov/no_defaults.rb +4 -0
  71. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/base.rb +61 -0
  72. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/generic.rb +44 -0
  73. data/simplecov-1.0.0/lib/simplecov/parallel_adapters/parallel_tests.rb +93 -0
  74. data/simplecov-1.0.0/lib/simplecov/parallel_adapters.rb +83 -0
  75. data/simplecov-1.0.0/lib/simplecov/parallel_coordination.rb +129 -0
  76. data/simplecov-1.0.0/lib/simplecov/process.rb +37 -0
  77. data/simplecov-1.0.0/lib/simplecov/profiles/bundler_filter.rb +5 -0
  78. data/simplecov-1.0.0/lib/simplecov/profiles/hidden_filter.rb +5 -0
  79. data/simplecov-1.0.0/lib/simplecov/profiles/rails.rb +32 -0
  80. data/simplecov-1.0.0/lib/simplecov/profiles/root_filter.rb +11 -0
  81. data/simplecov-1.0.0/lib/simplecov/profiles/strict.rb +32 -0
  82. data/simplecov-1.0.0/lib/simplecov/profiles/test_frameworks.rb +5 -0
  83. data/simplecov-1.0.0/lib/simplecov/profiles.rb +64 -0
  84. data/simplecov-1.0.0/lib/simplecov/result/missing_source_files_reporter.rb +49 -0
  85. data/simplecov-1.0.0/lib/simplecov/result/source_file_builder.rb +51 -0
  86. data/simplecov-1.0.0/lib/simplecov/result.rb +183 -0
  87. data/simplecov-1.0.0/lib/simplecov/result_adapter.rb +92 -0
  88. data/simplecov-1.0.0/lib/simplecov/result_merger/legacy_format_adapter.rb +28 -0
  89. data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_file.rb +38 -0
  90. data/simplecov-1.0.0/lib/simplecov/result_merger/resultset_store.rb +50 -0
  91. data/simplecov-1.0.0/lib/simplecov/result_merger.rb +162 -0
  92. data/simplecov-1.0.0/lib/simplecov/result_processing.rb +179 -0
  93. data/simplecov-1.0.0/lib/simplecov/simulate_coverage.rb +75 -0
  94. data/simplecov-1.0.0/lib/simplecov/source_file/branch.rb +82 -0
  95. data/simplecov-1.0.0/lib/simplecov/source_file/branch_builder.rb +114 -0
  96. data/simplecov-1.0.0/lib/simplecov/source_file/builder_context.rb +28 -0
  97. data/simplecov-1.0.0/lib/simplecov/source_file/line.rb +77 -0
  98. data/simplecov-1.0.0/lib/simplecov/source_file/line_builder.rb +43 -0
  99. data/simplecov-1.0.0/lib/simplecov/source_file/method.rb +52 -0
  100. data/simplecov-1.0.0/lib/simplecov/source_file/method_builder.rb +58 -0
  101. data/simplecov-1.0.0/lib/simplecov/source_file/ruby_data_parser.rb +88 -0
  102. data/simplecov-1.0.0/lib/simplecov/source_file/skip_chunks.rb +75 -0
  103. data/simplecov-1.0.0/lib/simplecov/source_file/source_loader.rb +63 -0
  104. data/simplecov-1.0.0/lib/simplecov/source_file/statistics.rb +57 -0
  105. data/simplecov-1.0.0/lib/simplecov/source_file.rb +189 -0
  106. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/method_collector.rb +55 -0
  107. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor/visitor.rb +171 -0
  108. data/simplecov-1.0.0/lib/simplecov/static_coverage_extractor.rb +110 -0
  109. data/simplecov-1.0.0/lib/simplecov/useless_results_remover.rb +27 -0
  110. data/simplecov-1.0.0/lib/simplecov/version.rb +5 -0
  111. data/simplecov-1.0.0/lib/simplecov-html.rb +4 -0
  112. data/simplecov-1.0.0/lib/simplecov.rb +255 -0
  113. data/simplecov-1.0.0/lib/simplecov_json_formatter.rb +4 -0
  114. data/simplecov-1.0.0/schemas/coverage-v1.0.schema.json +306 -0
  115. data/simplecov-1.0.0/schemas/coverage.schema.json +306 -0
  116. data/ultra-pro-pkg.gemspec +12 -0
  117. metadata +156 -0
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ #
6
+ # Representing single branch that has been detected in coverage report.
7
+ # Give us support methods that handle needed calculations.
8
+ class Branch
9
+ attr_reader :start_line, :end_line, :coverage, :type
10
+
11
+ def initialize(start_line:, end_line:, coverage:, inline:, type:)
12
+ @start_line = start_line
13
+ @end_line = end_line
14
+ @coverage = coverage
15
+ @inline = inline
16
+ @type = type
17
+ @skipped = false
18
+ end
19
+
20
+ def inline?
21
+ @inline
22
+ end
23
+
24
+ #
25
+ # Return true if there is relevant count defined > 0
26
+ #
27
+ # @return [Boolean]
28
+ #
29
+ def covered?
30
+ !skipped? && coverage.positive?
31
+ end
32
+
33
+ #
34
+ # Check if branch missed or not
35
+ #
36
+ # @return [Boolean]
37
+ #
38
+ def missed?
39
+ !skipped? && coverage.zero?
40
+ end
41
+
42
+ # The line on which we want to report the coverage
43
+ #
44
+ # Usually we choose the line above the start of the branch (so that it shows up
45
+ # at if/else) because that
46
+ # * highlights the condition
47
+ # * makes it distinguishable if the first line of the branch is an inline branch
48
+ # (see the nested_branches fixture)
49
+ #
50
+ def report_line
51
+ if inline?
52
+ start_line
53
+ else
54
+ start_line - 1
55
+ end
56
+ end
57
+
58
+ # Flags the branch as skipped
59
+ def skipped!
60
+ @skipped = true
61
+ end
62
+
63
+ # Returns true if the branch was marked skipped by virtue of nocov comments.
64
+ def skipped?
65
+ @skipped
66
+ end
67
+
68
+ def overlaps_with?(line_range)
69
+ start_line <= line_range.end && end_line >= line_range.begin
70
+ end
71
+
72
+ #
73
+ # Return array with coverage count and badge
74
+ #
75
+ # @return [Array]
76
+ #
77
+ def report
78
+ [type, coverage]
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,114 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Builds the `SourceFile::Branch` objects for a source file from
6
+ # the raw branch data Ruby's Coverage library reports. Applies the
7
+ # `ignore_branches :eval_generated` / `:implicit_else` filters and
8
+ # marks branches inside `# simplecov:disable` / `# :nocov:` chunks
9
+ # as skipped.
10
+ class BranchBuilder
11
+ def initialize(source_file)
12
+ @source_file = source_file
13
+ end
14
+
15
+ def call
16
+ coverage_branch_data = @source_file.coverage_data["branches"] || {}
17
+ branches = coverage_branch_data.flat_map do |condition, coverage_branches|
18
+ next [] if eval_generated_condition_to_ignore?(condition)
19
+
20
+ build_branches_from(condition, coverage_branches)
21
+ end
22
+
23
+ process_skipped(branches)
24
+ end
25
+
26
+ private
27
+
28
+ # Detect a Coverage-reported branch condition that originates from
29
+ # `eval`/`module_eval`/`class_eval`/`instance_eval` rather than from
30
+ # the file's literal source. Coverage attributes such branches to the
31
+ # caller's `__FILE__`/`__LINE__`, so a Rails `delegate :foo, to: :bar`
32
+ # call surfaces inside the source file as if there were branches at
33
+ # the `delegate` line. Prism never sees those branches in the static
34
+ # source, so a condition whose start_line isn't in the real-source
35
+ # branch set must be eval-generated. Only consulted when the user has
36
+ # opted in via `SimpleCov.ignore_branches :eval_generated`. See #1046.
37
+ def eval_generated_condition_to_ignore?(condition)
38
+ return false unless SimpleCov.ignored_branch?(:eval_generated)
39
+
40
+ positions = @source_file.real_source_positions
41
+ # simplecov:disable branch — nil branch fires only when Prism is unavailable
42
+ return false unless positions
43
+
44
+ # simplecov:enable branch
45
+
46
+ _type, _id, start_line, * = RubyDataParser.call(condition)
47
+ !positions[:branches].include?(start_line)
48
+ end
49
+
50
+ def build_branches_from(condition, branches)
51
+ # the format handed in from the coverage data is like this:
52
+ #
53
+ # [:then, 4, 6, 6, 6, 10]
54
+ #
55
+ # which is [type, id, start_line, start_col, end_line, end_col]
56
+ _condition_type, _condition_id, *condition_range = RubyDataParser.call(condition)
57
+
58
+ branches.filter_map do |branch_data, hit_count|
59
+ build_branch(RubyDataParser.call(branch_data), hit_count, condition_range)
60
+ end
61
+ end
62
+
63
+ def build_branch(branch_data, hit_count, condition_range)
64
+ type, _id, start_line, start_col, end_line, end_col = branch_data
65
+ return nil if implicit_else_to_ignore?(type, [start_line, start_col, end_line, end_col], condition_range)
66
+
67
+ SourceFile::Branch.new(
68
+ start_line: start_line,
69
+ end_line: end_line,
70
+ coverage: hit_count,
71
+ inline: start_line == condition_range.first,
72
+ type: type
73
+ )
74
+ end
75
+
76
+ # Detect synthetic `:else` branches that Ruby's Coverage library reports
77
+ # for constructs with no literal `else` keyword in source (`case/in` /
78
+ # `case/when` without else, `||=`, `&&=`, `if`/`unless` without else,
79
+ # and the postfix `return if cond` shape). The signal is structural:
80
+ # a synthetic else reuses its parent condition's *full source range*
81
+ # (start_line, start_col, end_line, end_col all identical), while an
82
+ # explicit `else` arm carries a narrower range — its own keyword/body
83
+ # position rather than the whole conditional. Comparing the full range
84
+ # (not just `start_line`) is what distinguishes a ternary's explicit
85
+ # else on the same line as the condition — `arg == 42 ? :yes : :no`,
86
+ # where the else's columns differ from the parent's — from a postfix
87
+ # `return if cond` where the synthetic else inherits the full range.
88
+ # Only consulted when the user has opted in via
89
+ # `SimpleCov.ignore_branches :implicit_else`. See #1033.
90
+ def implicit_else_to_ignore?(type, branch_range, condition_range)
91
+ return false unless type == :else
92
+ return false unless SimpleCov.ignored_branch?(:implicit_else)
93
+
94
+ branch_range == condition_range
95
+ end
96
+
97
+ def process_skipped(branches)
98
+ chunks = @source_file.skip_chunks_for(:branch)
99
+ return branches if chunks.empty?
100
+
101
+ # A non-inline branch's source range starts on its arm body (e.g. the
102
+ # `:yes` line of `if cond / :yes / else / :no / end`), but `report_line`
103
+ # is the condition line above it — that's where the user sees the
104
+ # branch in the report and where they would naturally place an inline
105
+ # `# simplecov:disable branch` directive. Honour both.
106
+ branches.each do |branch|
107
+ branch.skipped! if chunks.any? { |chunk| branch.overlaps_with?(chunk) || chunk.include?(branch.report_line) }
108
+ end
109
+
110
+ branches
111
+ end
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Helpers shared by the per-criterion builders (LineBuilder /
6
+ # BranchBuilder / MethodBuilder). Mixed into SourceFile so each
7
+ # builder can ask the file for its skip-chunk ranges and Prism-derived
8
+ # real source positions without duplicating the memoization.
9
+ module BuilderContext
10
+ # Skip-chunk lookup for the named criterion (`:line`, `:branch`,
11
+ # `:method`).
12
+ def skip_chunks_for(criterion)
13
+ (@skip_chunks ||= SkipChunks.new(filename, src)).for(criterion)
14
+ end
15
+
16
+ # Memoized set of real source positions (branch start lines, method
17
+ # name+line pairs) extracted via Prism. Returns nil when Prism is
18
+ # unavailable or parsing fails, signaling callers to keep every
19
+ # Coverage entry (no false drops). The `defined?` guard preserves a
20
+ # nil memoization across calls.
21
+ def real_source_positions
22
+ return @real_source_positions if defined?(@real_source_positions)
23
+
24
+ @real_source_positions = StaticCoverageExtractor.real_source_positions(src.join)
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Representation of a single line in a source file including
6
+ # this specific line's source code, line_number and code coverage,
7
+ # with the coverage being either nil (coverage not applicable, e.g. comment
8
+ # line), 0 (line not covered) or >1 (the amount of times the line was
9
+ # executed)
10
+ class Line
11
+ # The source code for this line. Aliased as :source
12
+ attr_reader :src
13
+ # The line number in the source file. Aliased as :line, :number
14
+ attr_reader :line_number
15
+ # The coverage data for this line: either nil (never), 0 (missed) or >=1 (times covered)
16
+ attr_reader :coverage
17
+ # Whether this line was skipped
18
+ attr_reader :skipped
19
+
20
+ # Lets grab some fancy aliases, shall we?
21
+ alias source src
22
+ alias line line_number
23
+ alias number line_number
24
+
25
+ def initialize(src, line_number, coverage)
26
+ raise ArgumentError, "Only String accepted for source" unless src.is_a?(String)
27
+ raise ArgumentError, "Only Integer accepted for line_number" unless line_number.is_a?(Integer)
28
+ unless coverage.is_a?(Integer) || coverage.nil?
29
+ raise ArgumentError, "Only Integer and nil accepted for coverage"
30
+ end
31
+
32
+ @src = src
33
+ @line_number = line_number
34
+ @coverage = coverage
35
+ @skipped = false
36
+ end
37
+
38
+ # Returns true if this is a line that should have been covered, but was not
39
+ def missed?
40
+ !never? && !skipped? && coverage.zero?
41
+ end
42
+
43
+ # Returns true if this is a line that has been covered
44
+ def covered?
45
+ !never? && !skipped? && coverage.positive?
46
+ end
47
+
48
+ # Returns true if this line is not relevant for coverage
49
+ def never?
50
+ !skipped? && coverage.nil?
51
+ end
52
+
53
+ # Flags this line as skipped
54
+ def skipped!
55
+ @skipped = true
56
+ end
57
+
58
+ # Returns true if this line was skipped, false otherwise. Lines are skipped if they are wrapped with
59
+ # # :nocov: comment lines.
60
+ def skipped?
61
+ skipped
62
+ end
63
+
64
+ # The status of this line - either covered, missed, skipped or never. Useful i.e. for direct use
65
+ # as a css class in report generation
66
+ def status
67
+ return "skipped" if skipped?
68
+ return "never" if never?
69
+ return "missed" if missed?
70
+
71
+ # simplecov:disable — defensive: covered? is the only state left after the three above
72
+ "covered" if covered?
73
+ # simplecov:enable
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Builds the `SourceFile::Line` objects for a source file from the
6
+ # raw line-coverage array. Each line carries its source text, its
7
+ # 1-based line number, and the Coverage hit count (or nil for
8
+ # never-counted lines). Applies `# simplecov:disable` /
9
+ # `# :nocov:` block ranges via `skipped!`.
10
+ class LineBuilder
11
+ def initialize(source_file)
12
+ @source_file = source_file
13
+ end
14
+
15
+ def call
16
+ lines = build_lines
17
+ mark_skipped(lines, @source_file.skip_chunks_for(:line))
18
+ lines
19
+ end
20
+
21
+ private
22
+
23
+ # When `:line` coverage is disabled, the Ruby Coverage module
24
+ # doesn't emit "lines" data, so look up `nil` (never-counted) for
25
+ # every position. The source rows are still useful — e.g. for the
26
+ # HTML report's source view — even without per-line hits.
27
+ def build_lines
28
+ line_coverage = @source_file.coverage_data["lines"] || []
29
+ @source_file.src.map.with_index(1) do |src, i|
30
+ SourceFile::Line.new(src, i, line_coverage[i - 1])
31
+ end
32
+ end
33
+
34
+ # The array the lines are kept in is 0-based whereas the line
35
+ # numbers in the chunks are 1-based (more understandable elsewhere),
36
+ # so each range needs to be shifted down by one to slice into the
37
+ # `lines` array.
38
+ def mark_skipped(lines, chunks)
39
+ chunks.each { |chunk| lines[(chunk.begin - 1)..(chunk.end - 1)].each(&:skipped!) }
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Represents a single method detected in coverage data.
6
+ # Provides helpers similar to Branch for coverage status.
7
+ class Method
8
+ attr_reader :source_file, :coverage, :class_name, :method_name,
9
+ :start_line, :start_col, :end_line, :end_col
10
+
11
+ def initialize(source_file, info, coverage)
12
+ @source_file = source_file
13
+ @class_name, @method_name, @start_line, @start_col, @end_line, @end_col = info
14
+ @coverage = coverage
15
+ end
16
+
17
+ def covered?
18
+ !skipped? && coverage.positive?
19
+ end
20
+
21
+ def skipped?
22
+ return @skipped if defined?(@skipped)
23
+
24
+ @skipped = lines.all?(&:skipped?)
25
+ end
26
+
27
+ # Flag the method as skipped directly, without going through its lines.
28
+ def skipped!
29
+ @skipped = true
30
+ end
31
+
32
+ def missed?
33
+ !skipped? && coverage.zero?
34
+ end
35
+
36
+ def lines
37
+ @lines ||= start_line && end_line ? source_file.lines[(start_line - 1)..(end_line - 1)] : []
38
+ end
39
+
40
+ # Whether this method's source range intersects the given inclusive line range.
41
+ def overlaps_with?(line_range)
42
+ return false unless start_line && end_line
43
+
44
+ start_line <= line_range.end && end_line >= line_range.begin
45
+ end
46
+
47
+ def to_s
48
+ "#{class_name}##{method_name}"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,58 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Builds the `SourceFile::Method` objects for a source file from
6
+ # the raw method-coverage data. Applies the
7
+ # `ignore_methods :eval_generated` filter and marks methods inside
8
+ # `# simplecov:disable` / `# :nocov:` chunks as skipped.
9
+ class MethodBuilder
10
+ def initialize(source_file)
11
+ @source_file = source_file
12
+ end
13
+
14
+ def call
15
+ methods = @source_file.coverage_data.fetch("methods", {}).filter_map do |info, hit_count|
16
+ info = RubyDataParser.call(info)
17
+ next if eval_generated_to_ignore?(info)
18
+
19
+ SourceFile::Method.new(@source_file, info, hit_count)
20
+ end
21
+
22
+ process_skipped(methods)
23
+ end
24
+
25
+ private
26
+
27
+ # See `BranchBuilder#eval_generated_condition_to_ignore?` for the
28
+ # rationale. Coverage reports an eval'd `def` at the eval caller's
29
+ # line and name, so a method whose `(name, start_line)` is absent
30
+ # from the real-source `def` set is eval-generated. Only consulted
31
+ # when the user has opted in via
32
+ # `SimpleCov.ignore_methods :eval_generated`. See #1046.
33
+ def eval_generated_to_ignore?(info)
34
+ return false unless SimpleCov.ignored_method?(:eval_generated)
35
+
36
+ positions = @source_file.real_source_positions
37
+ # simplecov:disable branch — nil branch fires only when Prism is unavailable
38
+ return false unless positions
39
+
40
+ # simplecov:enable branch
41
+
42
+ _class_name, name, start_line, * = info
43
+ !positions[:methods].include?([name, start_line])
44
+ end
45
+
46
+ def process_skipped(methods)
47
+ chunks = @source_file.skip_chunks_for(:method)
48
+ return methods if chunks.empty?
49
+
50
+ methods.each do |method|
51
+ method.skipped! if chunks.any? { |chunk| method.overlaps_with?(chunk) }
52
+ end
53
+
54
+ methods
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "ripper"
4
+
5
+ module SimpleCov
6
+ class SourceFile
7
+ # `Coverage.result` reports condition and method keys as Ruby
8
+ # arrays. When the resultset is round-tripped through JSON those
9
+ # array keys become their stringified inspect form, so this parser
10
+ # walks the literal back into a real Array without using `eval` (see
11
+ # #801). The grammar covers symbols, strings, integers, unary minus,
12
+ # and constant paths — every shape Coverage ever emits.
13
+ module RubyDataParser
14
+ module_function
15
+
16
+ # Tests use the real data structures (except for integration tests)
17
+ # so no need to put them through here.
18
+ def call(structure)
19
+ return structure if structure.is_a?(Array)
20
+
21
+ parse_array_string(structure.to_s)
22
+ end
23
+
24
+ # Parse a string like '[:if, 0, 3, 4, 3, 21]' or
25
+ # '["ClassName", :method1, 2, 2, 5, 5]' back into a Ruby array.
26
+ def parse_array_string(str)
27
+ # Try plain Ripper first; only pre-quote `#<...>` inspect segments
28
+ # if the input isn't already valid Ruby (otherwise we corrupt
29
+ # `"#<Class:Foo>"` strings that *are* valid Ruby literals — exactly
30
+ # the shape simplecov-on-simplecov method-coverage keys take).
31
+ sexp = Ripper.sexp(str) || Ripper.sexp(quote_inspected_class_segments(str))
32
+ # simplecov:disable — defensive: Ripper.sexp returning nil from both passes requires malformed input
33
+ array_node = sexp&.dig(1, 0)
34
+ # simplecov:enable
35
+ raise ArgumentError, "expected array literal: #{str.inspect}" unless array_node && array_node[0] == :array
36
+
37
+ Array(array_node[1]).map { |element| parse_element(element) }
38
+ end
39
+
40
+ def parse_element(node)
41
+ case node[0]
42
+ when :@int, :unary then parse_integer_node(node)
43
+ when :symbol_literal, :dyna_symbol then parse_symbol_node(node)
44
+ when :string_literal then unescape_ruby(string_literal_text(node[1]))
45
+ when :var_ref then node.dig(1, 1) # `Foo`
46
+ when :const_path_ref then "#{parse_element(node[1])}::#{node[2][1]}" # `Foo::Bar`
47
+ else
48
+ # simplecov:disable — defensive fallback for unexpected Ripper node shapes
49
+ raise ArgumentError, "unexpected element: #{node.inspect}"
50
+ # simplecov:enable
51
+ end
52
+ end
53
+
54
+ def parse_integer_node(node)
55
+ node[0] == :@int ? node[1].to_i : -node[2][1].to_i
56
+ end
57
+
58
+ def parse_symbol_node(node)
59
+ if node[0] == :symbol_literal
60
+ node.dig(1, 1, 1).to_sym
61
+ else
62
+ unescape_ruby(string_literal_text(node[1])).to_sym
63
+ end
64
+ end
65
+
66
+ # Concatenate the text fragments of a `:string_content` node. Ripper
67
+ # may emit zero, one, or many `:@tstring_content` children depending
68
+ # on the literal.
69
+ def string_literal_text(string_content)
70
+ Array(string_content[1..]).map { |child| child[1] }.join
71
+ end
72
+
73
+ # Undo the same backslash-prefix escapes the previous hand-rolled
74
+ # parser undid: `\X` → `X` for any X.
75
+ def unescape_ruby(raw)
76
+ raw.gsub(/\\(.)/) { ::Regexp.last_match(1) }
77
+ end
78
+
79
+ # Method coverage keys can contain inspect-format class references
80
+ # like `#<Class:Foo>` or `#<Class:0x...>`, which aren't valid Ruby
81
+ # syntax. Wrap them in quotes so Ripper can parse the surrounding
82
+ # array literal; downstream we treat them as opaque strings.
83
+ def quote_inspected_class_segments(str)
84
+ str.gsub(/#<[^>]*>/) { |segment| %("#{segment.gsub('"', '\\"')}") }
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Computes the set of line ranges that should be excluded from a
6
+ # SourceFile's coverage for each criterion. Two sources contribute:
7
+ #
8
+ # * The deprecated `# :nocov:` block toggle (lines wrapped between
9
+ # even-numbered pairs of nocov markers are excluded from line and
10
+ # branch coverage).
11
+ # * `# simplecov:disable` / `# simplecov:enable` block directives,
12
+ # which can be scoped per-criterion (`# simplecov:disable branch`,
13
+ # etc.) — see `SimpleCov::Directive`.
14
+ class SkipChunks
15
+ @nocov_warned = Set.new
16
+ class << self
17
+ attr_reader :nocov_warned
18
+ end
19
+
20
+ def initialize(filename, src)
21
+ @filename = filename
22
+ @src = src
23
+ end
24
+
25
+ # `:method` ignores nocov chunks (Ruby's Coverage doesn't tie
26
+ # method entries to line ranges); `:line` / `:branch` honor both
27
+ # the nocov chunks and the per-criterion directive ranges.
28
+ def for(criterion)
29
+ if criterion == :method
30
+ directive_chunks.fetch(:method)
31
+ else
32
+ nocov_chunks + directive_chunks.fetch(criterion)
33
+ end
34
+ end
35
+
36
+ # no_cov_chunks is zero indexed to work directly with the array
37
+ # holding the lines.
38
+ def nocov_chunks
39
+ @nocov_chunks ||= build_nocov_chunks
40
+ end
41
+
42
+ def directive_chunks
43
+ @directive_chunks ||= Directive.disabled_ranges(@src)
44
+ end
45
+
46
+ private
47
+
48
+ def build_nocov_chunks
49
+ no_cov_lines = @src.map.with_index(1).select { |line_src, _index| LinesClassifier.no_cov_line?(line_src) }
50
+
51
+ warn_nocov_deprecation(no_cov_lines.first.last) if no_cov_lines.any?
52
+
53
+ # If we have an uneven number of nocovs we assume they go to the
54
+ # end of the file, the source doesn't really matter. Can't deal
55
+ # with this within the each_slice due to differing behavior in
56
+ # JRuby: jruby/jruby#6048
57
+ no_cov_lines << ["", @src.size] if no_cov_lines.size.odd?
58
+
59
+ no_cov_lines.each_slice(2).map do |(_line_src_start, index_start), (_line_src_end, index_end)|
60
+ index_start..index_end
61
+ end
62
+ end
63
+
64
+ # Emit a one-time-per-file deprecation warning pointing the user
65
+ # at the `# simplecov:disable` / `# simplecov:enable` replacement.
66
+ def warn_nocov_deprecation(first_line_number)
67
+ return unless self.class.nocov_warned.add?(@filename)
68
+
69
+ token = SimpleCov.current_nocov_token
70
+ warn "#{@filename}:#{first_line_number}: [DEPRECATION] `# :#{token}:` is deprecated and will be removed " \
71
+ "in a future release. Replace with `# simplecov:disable` / `# simplecov:enable` block comments."
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SimpleCov
4
+ class SourceFile
5
+ # Reads a source file into an array of lines, honoring the source's
6
+ # shebang and `coding:` magic comment when present. Always
7
+ # transcodes to UTF-8 with invalid/undef bytes replaced — both for
8
+ # JRuby compatibility and to keep encoding shenanigans in one place
9
+ # (see #866).
10
+ module SourceLoader
11
+ SHEBANG_REGEX = /\A#!/
12
+ RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX = /\A#\s*(?:-\*-)?\s*(?:en)?coding:\s*(\S+)\s*(?:-\*-)?\s*\z/
13
+
14
+ module_function
15
+
16
+ def call(filename)
17
+ lines = []
18
+ # The default encoding is UTF-8
19
+ File.open(filename, "rb:UTF-8") do |file|
20
+ current_line = file.gets
21
+
22
+ if shebang?(current_line)
23
+ lines << current_line
24
+ current_line = file.gets
25
+ end
26
+
27
+ read_lines(file, lines, current_line)
28
+ end
29
+ end
30
+
31
+ def shebang?(line)
32
+ SHEBANG_REGEX.match?(line)
33
+ end
34
+
35
+ def read_lines(file, lines, current_line)
36
+ return lines unless current_line
37
+
38
+ set_encoding_based_on_magic_comment(file, current_line)
39
+ lines.concat([current_line], ensure_remove_undefs(file.readlines))
40
+ end
41
+
42
+ # Encoding magic comment must be placed at first line except for
43
+ # shebang.
44
+ def set_encoding_based_on_magic_comment(file, line)
45
+ if (match = RUBY_FILE_ENCODING_MAGIC_COMMENT_REGEX.match(line))
46
+ file.set_encoding(match[1], "UTF-8")
47
+ end
48
+ end
49
+
50
+ # invalid/undef replace are technically not really necessary but
51
+ # nice to have and work around a JRuby incompatibility. Setting
52
+ # these options on `file.set_encoding` doesn't seem to work
53
+ # properly, so it has to be done here.
54
+ def ensure_remove_undefs(file_lines)
55
+ file_lines.each do |line|
56
+ # simplecov:disable — defensive: only fires for non-UTF-8 source files
57
+ line.encode!("UTF-8", invalid: :replace, undef: :replace) unless line.encoding == Encoding::UTF_8
58
+ # simplecov:enable
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end