countless 1.4.2 → 1.5.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 11bb83e635d33305cf87f38392fddd956a719cd1e241cd200dbbd7b9d2fbca96
4
- data.tar.gz: 7dfaed1f487f596788b37b0278ab1c479fbfecf1b8af8eb5edbcf750cec57d99
3
+ metadata.gz: 2d9664ed4e59ece63018a7442d0efe49a91cde8c1de0d6558eabf81926a96b8e
4
+ data.tar.gz: e397a9899834faa31a0ed2b0d0bacaa3541393b999c74d5945365cff1d7afab5
5
5
  SHA512:
6
- metadata.gz: 52cedd63e5d65ebe00c46508a03a4c5507aa1cf755bcf5c725e8fa95d0e1e189c33ee32ba578ecff7c06f3fc35fee6c995b7842063483df7f0252b87c516f8ef
7
- data.tar.gz: 5095cd9e25e0e1e519913a5f5deadd9fd8cfafb426a43e45321108908443374caf1cef7489612373042455cf0f5ffcfb814294a2432a7d00f98c4165c958f747
6
+ metadata.gz: eae663a728f9aae15aec38152aebeed2a700116d7cefccf5b2160d5cc810a373c5d544b674860960b5d599ca3e9bf568f39e7a05b2203c067765c750a1b7475d
7
+ data.tar.gz: 65aeae0be6f5f18ea7e1f958eed1ab0581087d9d908bbe778a0f6b07c4df777b6bcf73b9126f27b7824f848e9557f6680275f3c8860e2338f7ba60d8458e1b7d
@@ -18,7 +18,7 @@ jobs:
18
18
  strategy:
19
19
  fail-fast: false
20
20
  matrix:
21
- ruby: ['2.7', '3.0', '3.1']
21
+ ruby: ['2.7', '3.0', '3.1', '3.2', '3.3', '3.4']
22
22
  rails: ['6.1', '7.1']
23
23
  env:
24
24
  BUNDLE_GEMFILE: 'gemfiles/rails_${{ matrix.rails }}.gemfile'
data/.rubocop.yml CHANGED
@@ -1,5 +1,6 @@
1
- require:
1
+ plugins:
2
2
  - rubocop-rspec
3
+ - rubocop-rails
3
4
 
4
5
  Style/Documentation:
5
6
  Enabled: true
@@ -55,10 +56,15 @@ RSpec/NestedGroups:
55
56
  Max: 4
56
57
 
57
58
  # Disable regular Rails spec paths.
58
- RSpec/FilePath:
59
+ Rails/FilePath:
59
60
  Enabled: false
60
61
 
61
62
  # Because +enqueued_jobs+ is a method not a memoized variable,
62
63
  # so when first evaluated it won't change.
63
64
  RSpec/ExpectChange:
64
65
  Enabled: false
66
+
67
+ # Because we support Rails applications as well as gems and non-Rails
68
+ # applications, therefore the +:environment+ task is not always present
69
+ Rails/RakeEnvironment:
70
+ Enabled: false
data/CHANGELOG.md CHANGED
@@ -2,17 +2,26 @@
2
2
 
3
3
  * TODO: Replace this bullet point with an actual description of a change.
4
4
 
5
+ ### 1.5.1 (21 May 2025)
6
+
7
+ * Corrected some RuboCop glitches ([#9](https://github.com/hausgold/countless/pull/9))
8
+ * Upgraded the rubocop dependencies ([#10](https://github.com/hausgold/countless/pull/10))
9
+
10
+ ### 1.5.0 (30 January 2025)
11
+
12
+ * Added all versions up to Ruby 3.4 to the CI matrix ([#8](https://github.com/hausgold/countless/pull/8))
13
+
5
14
  ### 1.4.2 (17 January 2025)
6
15
 
7
- * Added the logger dependency, see: https://bit.ly/3E8Zqg0 (#7)
16
+ * Added the logger dependency, see: https://bit.ly/3E8Zqg0 ([#7](https://github.com/hausgold/countless/pull/7))
8
17
 
9
18
  ### 1.4.1 (13 January 2025)
10
19
 
11
- * Do not eager load the configuration (#6)
20
+ * Do not eager load the configuration ([#6](https://github.com/hausgold/countless/pull/6))
12
21
 
13
22
  ### 1.4.0 (3 January 2025)
14
23
 
15
- * Raised minimum supported Ruby/Rails version to 2.7/6.1 (#5)
24
+ * Raised minimum supported Ruby/Rails version to 2.7/6.1 ([#5](https://github.com/hausgold/countless/pull/5))
16
25
 
17
26
  ### 1.3.4 (15 August 2024)
18
27
 
@@ -28,12 +37,12 @@
28
37
 
29
38
  ### 1.3.1 (9 August 2024)
30
39
 
31
- * Added API docs building to continuous integration (#4)
40
+ * Added API docs building to continuous integration ([#4](https://github.com/hausgold/countless/pull/4))
32
41
 
33
42
  ### 1.3.0 (8 July 2024)
34
43
 
35
- * Moved the development dependencies from the gemspec to the Gemfile (#2)
36
- * Dropped support for Ruby <2.7 (#3)
44
+ * Moved the development dependencies from the gemspec to the Gemfile ([#2](https://github.com/hausgold/countless/pull/2))
45
+ * Dropped support for Ruby <2.7 ([#3](https://github.com/hausgold/countless/pull/3))
37
46
 
38
47
  ### 1.2.0 (24 February 2023)
39
48
 
@@ -41,11 +50,11 @@
41
50
 
42
51
  ### 1.1.0 (18 January 2023)
43
52
 
44
- * Bundler >= 2.3 is from now on required as minimal version (#1)
45
- * Dropped support for Ruby < 2.5 (#1)
46
- * Dropped support for Rails < 5.2 (#1)
53
+ * Bundler >= 2.3 is from now on required as minimal version ([#1](https://github.com/hausgold/countless/pull/1))
54
+ * Dropped support for Ruby < 2.5 ([#1](https://github.com/hausgold/countless/pull/1))
55
+ * Dropped support for Rails < 5.2 ([#1](https://github.com/hausgold/countless/pull/1))
47
56
  * Updated all development/runtime gems to their latest
48
- Ruby 2.5 compatible version (#1)
57
+ Ruby 2.5 compatible version ([#1](https://github.com/hausgold/countless/pull/1))
49
58
 
50
59
  ### 1.0.0 (11 January 2022)
51
60
 
data/Dockerfile CHANGED
@@ -1,5 +1,5 @@
1
1
  FROM hausgold/ruby:2.7
2
- MAINTAINER Hermann Mayer <hermann.mayer@hausgold.de>
2
+ LABEL org.opencontainers.image.authors="containers@hausgold.de"
3
3
 
4
4
  # Update system gem
5
5
  RUN gem update --system '3.4.22'
data/Gemfile CHANGED
@@ -14,9 +14,9 @@ gem 'bundler', '~> 2.3'
14
14
  gem 'guard-rspec', '~> 4.7'
15
15
  gem 'irb', '~> 1.2'
16
16
  gem 'rspec', '~> 3.12'
17
- gem 'rubocop', '~> 1.28'
18
- gem 'rubocop-rails', '~> 2.14'
19
- gem 'rubocop-rspec', '~> 2.10'
17
+ gem 'rubocop'
18
+ gem 'rubocop-rails'
19
+ gem 'rubocop-rspec'
20
20
  gem 'simplecov', '>= 0.22'
21
21
  gem 'yard', '>= 0.9.28'
22
22
  gem 'yard-activesupport-concern', '>= 0.0.1'
@@ -1,68 +1,7 @@
1
- <?xml version="1.0" encoding="UTF-8" standalone="no"?>
2
- <svg
3
- xmlns:dc="http://purl.org/dc/elements/1.1/"
4
- xmlns:cc="http://creativecommons.org/ns#"
5
- xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
6
- xmlns:svg="http://www.w3.org/2000/svg"
7
- xmlns="http://www.w3.org/2000/svg"
8
- version="1.1"
9
- id="Ebene_1"
10
- x="0px"
11
- y="0px"
12
- viewBox="0 0 800 200"
13
- xml:space="preserve"
14
- width="800"
15
- height="200"><metadata
16
- id="metadata33"><rdf:RDF><cc:Work
17
- rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
18
- rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
19
- id="defs31" />
20
- <style
21
- type="text/css"
22
- id="style2">
23
- .st0{fill-rule:evenodd;clip-rule:evenodd;fill:#E73E11;}
24
- .st1{fill-rule:evenodd;clip-rule:evenodd;fill:#0371B9;}
25
- .st2{fill:#132E48;}
26
- .st3{font-family:'OpenSans-Bold';}
27
- .st4{font-size:29.5168px;}
28
- .st5{fill-rule:evenodd;clip-rule:evenodd;fill:none;}
29
- .st6{opacity:0.5;fill:#132E48;}
30
- .st7{font-family:'OpenSans';}
31
- .st8{font-size:12px;}
32
- </style>
33
- <g
34
- transform="translate(0,1.53584)"
35
- id="g828"><g
36
- transform="translate(35.93985,35.66416)"
37
- id="g8">
38
- <path
39
- style="clip-rule:evenodd;fill:#e73e11;fill-rule:evenodd"
40
- id="path4"
41
- d="m -0.1,124.4 c 0,0 33.7,-123.2 66.7,-123.2 12.8,0 26.9,21.9 38.8,47.2 -23.6,27.9 -66.6,59.7 -94,76 -7.1,0 -11.5,0 -11.5,0 z"
42
- class="st0" />
43
- <path
44
- style="clip-rule:evenodd;fill:#0371b9;fill-rule:evenodd"
45
- id="path6"
46
- d="m 88.1,101.8 c 13.5,-10.4 18.4,-16.2 27.1,-25.4 10,25.7 16.7,48 16.7,48 0,0 -41.4,0 -78,0 14.6,-7.9 18.7,-10.7 34.2,-22.6 z"
47
- class="st1" />
48
- </g><text
49
- y="106.40316"
50
- x="192.43155"
51
- style="font-style:normal;font-variant:normal;font-weight:bold;font-stretch:normal;font-size:29.51733398px;font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start;writing-mode:lr-tb;text-anchor:start;fill:#132e48"
52
- id="text10"
53
- class="st2 st3 st4">Countless</text>
54
- <rect
55
- style="clip-rule:evenodd;fill:none;fill-rule:evenodd"
56
- id="rect12"
57
- height="24"
58
- width="314.5"
59
- class="st5"
60
- y="118.06416"
61
- x="194.23985" /><text
62
- y="127.22146"
63
- x="194.21715"
64
- style="font-size:12px;font-family:'Open Sans', sans-serif;opacity:0.5;fill:#132e48;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-weight:normal;font-style:normal;font-stretch:normal;font-variant:normal;text-anchor:start;text-align:start;writing-mode:lr;font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;"
65
- id="text14"
66
- class="st6 st7 st8">Code statistics/annotations helpers</text>
67
- </g>
68
- </svg>
1
+ <svg id="hg-readme-banner-v2" viewBox="0 0 800 200" width="800" height="200" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="a" x1="112.46" y1="309.13" x2="314.5" y2="309.13" gradientUnits="userSpaceOnUse" gradientTransform="translate(-33.985 -122.816)"><stop offset="0" stop-color="#1992f4" /><stop offset="1" stop-color="#2b8af6" /></linearGradient></defs><style>.st3{font-family:&apos;OpenSans-Bold&apos;}.st4{font-size:29.5168px}.st5{fill-rule:evenodd;clip-rule:evenodd;fill:none}.st7{font-family:&apos;OpenSans&apos;}.st8{font-size:12px}@media (prefers-color-scheme:dark){text{fill:#f0f6fc!important}}</style>
2
+ <text y="107.939" x="192.432" style="font-family:'Open Sans', sans-serif;-inkscape-font-specification:'OpenSans-Bold, Bold';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" class="st3 st4" font-weight="700" font-size="29.517" font-family="'Open Sans',sans-serif" fill="#3f3e4b"
3
+ >Countless</text>
4
+ <path class="st5" clip-rule="evenodd" fill="none" fill-rule="evenodd" d="M194.24 119.6h314.5v24h-314.5z"/>
5
+ <text y="128.757" x="194.217" style="font-family:'Open Sans', sans-serif;-inkscape-font-specification:'Open Sans, sans-serif, Normal';font-variant-ligatures:normal;font-variant-caps:normal;font-variant-numeric:normal;font-feature-settings:normal;text-align:start" class="st7 st8" font-weight="400" font-size="12" font-family="'Open Sans',sans-serif" opacity=".75" fill="#3f3e4b"
6
+ >Code statistics/annotations helpers</text>
7
+ <g transform="translate(33.985 122.816)"><path d="M129.427 32.4q-7-20.71-14.09-41.44c-.33-1-.52-1.18-1.34-.25-3.1 3.48-6.25 6.92-9.49 10.26a180.23 180.23 0 0 1-18.54 16.15 193.7 193.7 0 0 1-21.95 15.43c-2.53 1.47-5.08 2.91-7.62 4.36l.11.27h73.05c1 .1 1.4-.13 1-1.22-.4-1.09-.73-2.38-1.13-3.56z" fill="url(#a)" /><path d="M89.077-63.87c-3.57-5.58-7.42-10.95-12.62-15.17-3.93-3.19-8.28-5-13.42-3.32a23 23 0 0 0-8.76 5.6 66.77 66.77 0 0 0-10.34 13.09c-8 12.68-14.06 26.28-19.28 40.25-4.6 12.33-8.82 24.8-12.95 37.29-2.45 7.41-4.39 15-6.61 22.49-.26.88 0 1 .79 1h7.4c1.23 0 2.44.14 3.6-.68 9.17-6.45 18.51-12.67 27.55-19.31a396.21 396.21 0 0 0 37.36-31.3 245.79 245.79 0 0 0 20.74-22 1.6 1.6 0 0 0 .26-2 224.45 224.45 0 0 0-13.72-25.94z" fill="#f2686d" /></g></svg>
@@ -8,9 +8,9 @@ gem "bundler", "~> 2.3"
8
8
  gem "guard-rspec", "~> 4.7"
9
9
  gem "irb", "~> 1.2"
10
10
  gem "rspec", "~> 3.12"
11
- gem "rubocop", "~> 1.28"
12
- gem "rubocop-rails", "~> 2.14"
13
- gem "rubocop-rspec", "~> 2.10"
11
+ gem "rubocop"
12
+ gem "rubocop-rails"
13
+ gem "rubocop-rspec"
14
14
  gem "simplecov", ">= 0.22"
15
15
  gem "yard", ">= 0.9.28"
16
16
  gem "yard-activesupport-concern", ">= 0.0.1"
@@ -8,9 +8,9 @@ gem "bundler", "~> 2.3"
8
8
  gem "guard-rspec", "~> 4.7"
9
9
  gem "irb", "~> 1.2"
10
10
  gem "rspec", "~> 3.12"
11
- gem "rubocop", "~> 1.28"
12
- gem "rubocop-rails", "~> 2.14"
13
- gem "rubocop-rspec", "~> 2.10"
11
+ gem "rubocop"
12
+ gem "rubocop-rails"
13
+ gem "rubocop-rspec"
14
14
  gem "simplecov", ">= 0.22"
15
15
  gem "yard", ">= 0.9.28"
16
16
  gem "yard-activesupport-concern", ">= 0.0.1"
@@ -11,7 +11,8 @@ module Countless
11
11
  #
12
12
  # Heavily stolen from: https://bit.ly/3nBS0aj
13
13
  #
14
- # rubocop:disable Metrics/ClassLength because of the nested Annotation class
14
+ # rubocop:disable Metrics/ClassLength -- because of the nested Annotation
15
+ # class
15
16
  class Annotations
16
17
  attr_reader :tag, :options, :dirs, :files, :annotations
17
18
 
@@ -124,7 +125,7 @@ module Countless
124
125
  #
125
126
  # @return [String] the formatted annotations
126
127
  #
127
- # rubocop:disable Metrics/AbcSize because of the indentation logic
128
+ # rubocop:disable Metrics/AbcSize -- because of the indentation logic
128
129
  def to_s
129
130
  buf = []
130
131
  options[:indent] = annotations.flat_map do |_f, a|
@@ -34,8 +34,8 @@ module Countless
34
34
  # @return [Hash{String => Hash{String => Mixed}] the raw CLOC
35
35
  # YAML output
36
36
  #
37
- # rubocop:disable Metrics/MethodLength because of the system
38
- # command preparation
37
+ # rubocop:disable Metrics/MethodLength -- because of the system command
38
+ # preparation
39
39
  def raw_stats(*paths)
40
40
  cmd = [
41
41
  Countless.cloc_path,
@@ -3,8 +3,8 @@
3
3
  module Countless
4
4
  # The configuration for the countless gem.
5
5
  #
6
- # rubocop:disable Metrics/ClassLength because of the various defaults
7
- # rubocop:disable Metrics/BlockLength ditoditodito
6
+ # rubocop:disable Metrics/ClassLength -- because of the various defaults
7
+ # rubocop:disable Metrics/BlockLength -- ditto
8
8
  class Configuration
9
9
  include ActiveSupport::Configurable
10
10
 
@@ -4,8 +4,8 @@ module Countless
4
4
  module Extensions
5
5
  # A top-level gem-module extension to handle configuration needs.
6
6
  #
7
- # rubocop:disable Metrics/BlockLength because this is how
8
- # an +ActiveSupport::Concern+ looks like
7
+ # rubocop:disable Metrics/BlockLength -- because this is how an
8
+ # +ActiveSupport::Concern+ looks like
9
9
  module ConfigurationHandling
10
10
  extend ActiveSupport::Concern
11
11
 
@@ -40,10 +40,10 @@ module Countless
40
40
  #
41
41
  # @return [Array<Hash{Symbol => Mixed}>] the statistics directories
42
42
  #
43
- # rubocop:disable Metrics/MethodLength because of the
44
- # configuration assembling
45
- # rubocop:disable Metrics/AbcSize dito
46
- # rubocop:disable Metrics/CyclomaticComplexity dito
43
+ # rubocop:disable Metrics/MethodLength -- because of the configuration
44
+ # assembling
45
+ # rubocop:disable Metrics/AbcSize -- ditto
46
+ # rubocop:disable Metrics/CyclomaticComplexity -- ditto
47
47
  def statistic_directories
48
48
  conf = configuration
49
49
  pattern_suffix = "/**/*.{#{conf.stats_file_extensions.join(',')}}"
@@ -5,8 +5,8 @@ module Countless
5
5
  #
6
6
  # Heavily stolen from: https://bit.ly/3qpvgfu
7
7
  #
8
- # rubocop:disable Metrics/ClassLength because of the calculation
9
- # and formatting logic
8
+ # rubocop:disable Metrics/ClassLength -- because of the calculation and
9
+ # formatting logic
10
10
  class Statistics
11
11
  # Make the extracted information accessible
12
12
  attr_reader :dirs, :statistics, :total
@@ -18,11 +18,11 @@ module Countless
18
18
  # @param dirs [Array<Hash{Symbol => Mixed}>] the configurations
19
19
  # @return [Countless::Statistics] the new instance
20
20
  #
21
- # rubocop:disable Metrics/AbcSize because of the
22
- # directory/config resolving
23
- # rubocop:disable Metrics/PerceivedComplexity dito
24
- # rubocop:disable Metrics/CyclomaticComplexity dito
25
- # rubocop:disable Metrics/MethodLength dito
21
+ # rubocop:disable Metrics/AbcSize -- because of the directory/config
22
+ # resolving
23
+ # rubocop:disable Metrics/PerceivedComplexity -- ditto
24
+ # rubocop:disable Metrics/CyclomaticComplexity -- ditto
25
+ # rubocop:disable Metrics/MethodLength -- ditto
26
26
  def initialize(*dirs)
27
27
  base_path = Countless.configuration.base_path
28
28
 
@@ -117,11 +117,11 @@ module Countless
117
117
  #
118
118
  # @return [String] the formatted code statistics
119
119
  #
120
- # rubocop:disable Metrics/MethodLength because of the complex formatting
120
+ # rubocop:disable Metrics/MethodLength -- because of the complex formatting
121
121
  # logic with fully dynamic columns widths
122
- # rubocop:disable Metrics/PerceivedComplexity dito
123
- # rubocop:disable Metrics/CyclomaticComplexity dito
124
- # rubocop:disable Metrics/AbcSize dito
122
+ # rubocop:disable Metrics/PerceivedComplexity -- ditto
123
+ # rubocop:disable Metrics/CyclomaticComplexity -- ditto
124
+ # rubocop:disable Metrics/AbcSize -- ditto
125
125
  def to_s
126
126
  col_sizes = {}
127
127
  rows = to_table.map do |row|
@@ -163,7 +163,7 @@ module Countless
163
163
  #
164
164
  # @return [Array<Array<String, Integer>, Symbol>] the raw table
165
165
  #
166
- # rubocop:disable Metrics/MethodLength because of the table construction
166
+ # rubocop:disable Metrics/MethodLength -- because of the table construction
167
167
  def to_table
168
168
  table = [
169
169
  :splitter,
@@ -219,8 +219,8 @@ module Countless
219
219
  # @param methods [Integer] the initial methods count
220
220
  # @return [Countless::Statistics::Calculator] the new instance
221
221
  #
222
- # rubocop:disable Metrics/ParameterLists because of the
223
- # various metrics we support
222
+ # rubocop:disable Metrics/ParameterLists -- because of the various
223
+ # metrics we support
224
224
  def initialize(name: nil, lines: 0, code_lines: 0, comment_lines: 0,
225
225
  classes: 0, methods: 0)
226
226
  @name = name
@@ -261,10 +261,10 @@ module Countless
261
261
  #
262
262
  # @param path [String] the path of the file
263
263
  #
264
- # rubocop:disable Metrics/AbcSize because of the pattern search by file
265
- # extension and pattern matching on each line afterwards
266
- # rubocop:disable Metrics/CyclomaticComplexity dito
267
- # rubocop:disable Metrics/PerceivedComplexity dito
264
+ # rubocop:disable Metrics/AbcSize -- because of the pattern search by
265
+ # file extension and pattern matching on each line afterwards
266
+ # rubocop:disable Metrics/CyclomaticComplexity -- ditto
267
+ # rubocop:disable Metrics/PerceivedComplexity -- ditto
268
268
  def add_details_by_file_path(path)
269
269
  all_patterns = Countless.configuration.detailed_stats_patterns
270
270
 
@@ -3,7 +3,7 @@
3
3
  # The gem version details.
4
4
  module Countless
5
5
  # The version of the +countless+ gem
6
- VERSION = '1.4.2'
6
+ VERSION = '1.5.1'
7
7
 
8
8
  class << self
9
9
  # Returns the version of gem as a string.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: countless
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.4.2
4
+ version: 1.5.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hermann Mayer
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2025-01-17 00:00:00.000000000 Z
11
+ date: 2025-05-21 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -109,7 +109,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
111
  requirements: []
112
- rubygems_version: 3.3.26
112
+ rubygems_version: 3.4.22
113
113
  signing_key:
114
114
  specification_version: 4
115
115
  summary: Code statistics/annotations helpers