smart_todo 1.10.0 → 1.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/build.yml +1 -1
- data/.github/workflows/rubocop.yml +1 -1
- data/Gemfile.lock +10 -10
- data/lib/smart_todo/cli.rb +29 -1
- data/lib/smart_todo/events.rb +32 -0
- data/lib/smart_todo/todo.rb +24 -0
- data/lib/smart_todo/version.rb +1 -1
- data/lib/smart_todo_comment_format_cop.rb +96 -0
- metadata +4 -3
    
        checksums.yaml
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            SHA256:
         | 
| 3 | 
            -
              metadata.gz:  | 
| 4 | 
            -
              data.tar.gz:  | 
| 3 | 
            +
              metadata.gz: cd76c16d18b12ed3caada5a661031dd5f14c60aa1b0b227c0eb720116755c2cc
         | 
| 4 | 
            +
              data.tar.gz: 91dadbb687dcfd22c4152f2b38054873f6d64fce3b43d8d96b8ac6891947e92a
         | 
| 5 5 | 
             
            SHA512:
         | 
| 6 | 
            -
              metadata.gz:  | 
| 7 | 
            -
              data.tar.gz:  | 
| 6 | 
            +
              metadata.gz: cede44f989ddbc2a4174c5c273e1e7e08521bbaaddc2a18fe5afc88d04fb84cdb6644e9d6c6839f6e7257dc1271f7f188bbdc6edec21a19debb8e0a438c20130
         | 
| 7 | 
            +
              data.tar.gz: fc3d770921d2c2d7a275236ea068c9aef5f2202de990c644afeb4cfb974581e62e8ba77ae91bc6d6af265b438dde88b62052a9046f57301808a1a695d12e3c1c
         | 
    
        data/.github/workflows/build.yml
    CHANGED
    
    
    
        data/Gemfile.lock
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            PATH
         | 
| 2 2 | 
             
              remote: .
         | 
| 3 3 | 
             
              specs:
         | 
| 4 | 
            -
                smart_todo (1. | 
| 4 | 
            +
                smart_todo (1.11.0)
         | 
| 5 5 | 
             
                  prism (~> 1.0)
         | 
| 6 6 |  | 
| 7 7 | 
             
            GEM
         | 
| @@ -10,26 +10,26 @@ GEM | |
| 10 10 | 
             
                addressable (2.8.7)
         | 
| 11 11 | 
             
                  public_suffix (>= 2.0.2, < 7.0)
         | 
| 12 12 | 
             
                ast (2.4.3)
         | 
| 13 | 
            -
                bigdecimal (3.1 | 
| 14 | 
            -
                crack (1.0. | 
| 13 | 
            +
                bigdecimal (3.3.1)
         | 
| 14 | 
            +
                crack (1.0.1)
         | 
| 15 15 | 
             
                  bigdecimal
         | 
| 16 16 | 
             
                  rexml
         | 
| 17 | 
            -
                hashdiff (1.1 | 
| 17 | 
            +
                hashdiff (1.2.1)
         | 
| 18 18 | 
             
                json (2.10.2)
         | 
| 19 19 | 
             
                language_server-protocol (3.17.0.4)
         | 
| 20 20 | 
             
                lint_roller (1.1.0)
         | 
| 21 | 
            -
                minitest (5. | 
| 21 | 
            +
                minitest (5.26.0)
         | 
| 22 22 | 
             
                parallel (1.26.3)
         | 
| 23 23 | 
             
                parser (3.3.7.3)
         | 
| 24 24 | 
             
                  ast (~> 2.4.1)
         | 
| 25 25 | 
             
                  racc
         | 
| 26 | 
            -
                prism (1. | 
| 27 | 
            -
                public_suffix (6.0. | 
| 26 | 
            +
                prism (1.6.0)
         | 
| 27 | 
            +
                public_suffix (6.0.2)
         | 
| 28 28 | 
             
                racc (1.8.1)
         | 
| 29 29 | 
             
                rainbow (3.1.1)
         | 
| 30 | 
            -
                rake (13. | 
| 30 | 
            +
                rake (13.3.0)
         | 
| 31 31 | 
             
                regexp_parser (2.10.0)
         | 
| 32 | 
            -
                rexml (3.4. | 
| 32 | 
            +
                rexml (3.4.4)
         | 
| 33 33 | 
             
                rubocop (1.74.0)
         | 
| 34 34 | 
             
                  json (~> 2.3)
         | 
| 35 35 | 
             
                  language_server-protocol (~> 3.17.0.2)
         | 
| @@ -49,7 +49,7 @@ GEM | |
| 49 49 | 
             
                unicode-display_width (3.1.4)
         | 
| 50 50 | 
             
                  unicode-emoji (~> 4.0, >= 4.0.4)
         | 
| 51 51 | 
             
                unicode-emoji (4.0.4)
         | 
| 52 | 
            -
                webmock (3. | 
| 52 | 
            +
                webmock (3.26.0)
         | 
| 53 53 | 
             
                  addressable (>= 2.8.0)
         | 
| 54 54 | 
             
                  crack (>= 0.3.2)
         | 
| 55 55 | 
             
                  hashdiff (>= 0.4.0, < 2.0.0)
         | 
    
        data/lib/smart_todo/cli.rb
    CHANGED
    
    | @@ -106,12 +106,40 @@ module SmartTodo | |
| 106 106 | 
             
                    end
         | 
| 107 107 |  | 
| 108 108 | 
             
                    @errors.concat(todo.errors)
         | 
| 109 | 
            -
             | 
| 109 | 
            +
             | 
| 110 | 
            +
                    next unless event_met
         | 
| 111 | 
            +
             | 
| 112 | 
            +
                    event_message = append_context_if_applicable(event_message, todo, event_met, events)
         | 
| 113 | 
            +
             | 
| 114 | 
            +
                    dispatches << [event_message, todo]
         | 
| 110 115 | 
             
                  end
         | 
| 111 116 |  | 
| 112 117 | 
             
                  dispatches
         | 
| 113 118 | 
             
                end
         | 
| 114 119 |  | 
| 120 | 
            +
                private
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                # @param event_message [String] the original event message
         | 
| 123 | 
            +
                # @param todo [Todo] the todo object that may contain context
         | 
| 124 | 
            +
                # @param event [Event] the event that was met
         | 
| 125 | 
            +
                # @param events [Events] the events instance for fetching issue context
         | 
| 126 | 
            +
                # @return [String] the event message, potentially with context appended
         | 
| 127 | 
            +
                def append_context_if_applicable(event_message, todo, event, events)
         | 
| 128 | 
            +
                  return event_message unless should_apply_context?(todo, event)
         | 
| 129 | 
            +
             | 
| 130 | 
            +
                  org, repo, issue_number = todo.context.arguments
         | 
| 131 | 
            +
                  context_message = events.issue_context(org, repo, issue_number)
         | 
| 132 | 
            +
             | 
| 133 | 
            +
                  context_message ? "#{event_message}\n\n#{context_message}" : event_message
         | 
| 134 | 
            +
                end
         | 
| 135 | 
            +
             | 
| 136 | 
            +
                # @param todo [Todo] the todo object to check for context
         | 
| 137 | 
            +
                # @param event [Event] the event to check
         | 
| 138 | 
            +
                # @return [Boolean] true if context should be applied, false otherwise
         | 
| 139 | 
            +
                def should_apply_context?(todo, event)
         | 
| 140 | 
            +
                  !!todo.context
         | 
| 141 | 
            +
                end
         | 
| 142 | 
            +
             | 
| 115 143 | 
             
                def process_dispatches(dispatches)
         | 
| 116 144 | 
             
                  queue = Queue.new
         | 
| 117 145 | 
             
                  dispatches.each { |dispatch| queue << dispatch }
         | 
    
        data/lib/smart_todo/events.rb
    CHANGED
    
    | @@ -75,6 +75,8 @@ module SmartTodo | |
| 75 75 | 
             
                      false
         | 
| 76 76 | 
             
                    end
         | 
| 77 77 | 
             
                  end
         | 
| 78 | 
            +
                rescue Net::HTTPError, JSON::ParserError
         | 
| 79 | 
            +
                  "Error retrieving gem information for *#{gem_name}*."
         | 
| 78 80 | 
             
                end
         | 
| 79 81 |  | 
| 80 82 | 
             
                # Check if +gem_name+ was bumped to the +requirements+ expected
         | 
| @@ -130,6 +132,8 @@ module SmartTodo | |
| 130 132 | 
             
                  else
         | 
| 131 133 | 
             
                    false
         | 
| 132 134 | 
             
                  end
         | 
| 135 | 
            +
                rescue Net::HTTPError, JSON::ParserError
         | 
| 136 | 
            +
                  "Error retrieving issue information for #{organization}/#{repo}##{issue_number}."
         | 
| 133 137 | 
             
                end
         | 
| 134 138 |  | 
| 135 139 | 
             
                # Check if the pull request +pr_number+ is closed
         | 
| @@ -155,6 +159,34 @@ module SmartTodo | |
| 155 159 | 
             
                  else
         | 
| 156 160 | 
             
                    false
         | 
| 157 161 | 
             
                  end
         | 
| 162 | 
            +
                rescue Net::HTTPError, JSON::ParserError
         | 
| 163 | 
            +
                  "Error retrieving pull request information for #{organization}/#{repo}##{pr_number}."
         | 
| 164 | 
            +
                end
         | 
| 165 | 
            +
             | 
| 166 | 
            +
                # Retrieve context information for an issue
         | 
| 167 | 
            +
                # This is used when a TODO has a context: issue() attribute
         | 
| 168 | 
            +
                #
         | 
| 169 | 
            +
                # @param organization [String] the GitHub organization name
         | 
| 170 | 
            +
                # @param repo [String] the GitHub repo name
         | 
| 171 | 
            +
                # @param issue_number [String, Integer]
         | 
| 172 | 
            +
                # @return [String, nil]
         | 
| 173 | 
            +
                def issue_context(organization, repo, issue_number)
         | 
| 174 | 
            +
                  headers = github_headers(organization, repo)
         | 
| 175 | 
            +
                  response = github_client.get("/repos/#{organization}/#{repo}/issues/#{issue_number}", headers)
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  if response.code_type < Net::HTTPClientError
         | 
| 178 | 
            +
                    nil
         | 
| 179 | 
            +
                  else
         | 
| 180 | 
            +
                    issue = JSON.parse(response.body)
         | 
| 181 | 
            +
                    state = issue["state"]
         | 
| 182 | 
            +
                    title = issue["title"]
         | 
| 183 | 
            +
                    assignee = issue["assignee"] ? "@#{issue["assignee"]["login"]}" : "unassigned"
         | 
| 184 | 
            +
             | 
| 185 | 
            +
                    "📌 Context: Issue ##{issue_number} - \"#{title}\" [#{state}] (#{assignee}) - " \
         | 
| 186 | 
            +
                      "https://github.com/#{organization}/#{repo}/issues/#{issue_number}"
         | 
| 187 | 
            +
                  end
         | 
| 188 | 
            +
                rescue Net::HTTPError, JSON::ParserError
         | 
| 189 | 
            +
                  nil
         | 
| 158 190 | 
             
                end
         | 
| 159 191 |  | 
| 160 192 | 
             
                # Check if the installed ruby version meets requirements.
         | 
    
        data/lib/smart_todo/todo.rb
    CHANGED
    
    | @@ -4,6 +4,7 @@ module SmartTodo | |
| 4 4 | 
             
              class Todo
         | 
| 5 5 | 
             
                attr_reader :filepath, :comment, :indent
         | 
| 6 6 | 
             
                attr_reader :events, :assignees, :errors
         | 
| 7 | 
            +
                attr_accessor :context
         | 
| 7 8 |  | 
| 8 9 | 
             
                def initialize(source, filepath = "-e")
         | 
| 9 10 | 
             
                  @filepath = filepath
         | 
| @@ -12,6 +13,7 @@ module SmartTodo | |
| 12 13 |  | 
| 13 14 | 
             
                  @events = []
         | 
| 14 15 | 
             
                  @assignees = []
         | 
| 16 | 
            +
                  @context = nil
         | 
| 15 17 | 
             
                  @errors = []
         | 
| 16 18 |  | 
| 17 19 | 
             
                  parse(source[(indent + 1)..])
         | 
| @@ -66,6 +68,28 @@ module SmartTodo | |
| 66 68 | 
             
                        end
         | 
| 67 69 | 
             
                      when :to
         | 
| 68 70 | 
             
                        metadata.assignees << visit(element.value)
         | 
| 71 | 
            +
                      when :context
         | 
| 72 | 
            +
                        value = visit(element.value)
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                        unless value.is_a?(String)
         | 
| 75 | 
            +
                          metadata.errors << "Incorrect `:context` format: expected string value"
         | 
| 76 | 
            +
                          next
         | 
| 77 | 
            +
                        end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                        unless value =~ %r{^([^/]+)/([^#]+)#(\d+)$}
         | 
| 80 | 
            +
                          metadata.errors << "Incorrect `:context` format: expected \"org/repo#issue_number\""
         | 
| 81 | 
            +
                          next
         | 
| 82 | 
            +
                        end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                        org = ::Regexp.last_match(1)
         | 
| 85 | 
            +
                        repo = ::Regexp.last_match(2)
         | 
| 86 | 
            +
                        issue_number = ::Regexp.last_match(3)
         | 
| 87 | 
            +
             | 
| 88 | 
            +
                        if org.empty? || repo.empty?
         | 
| 89 | 
            +
                          metadata.errors << "Incorrect `:context` format: org and repo cannot be empty"
         | 
| 90 | 
            +
                        else
         | 
| 91 | 
            +
                          metadata.context = CallNode.new(:issue, [org, repo, issue_number], element.value.location)
         | 
| 92 | 
            +
                        end
         | 
| 69 93 | 
             
                      end
         | 
| 70 94 | 
             
                    end
         | 
| 71 95 | 
             
                  end
         | 
    
        data/lib/smart_todo/version.rb
    CHANGED
    
    
| @@ -0,0 +1,96 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "smart_todo"
         | 
| 4 | 
            +
            require "parser"
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            module RuboCop
         | 
| 7 | 
            +
              module Cop
         | 
| 8 | 
            +
                module SmartTodo
         | 
| 9 | 
            +
                  # A RuboCop cop to enforce proper formatting of SmartTodo comments.
         | 
| 10 | 
            +
                  # SmartTodo comments must have their description on separate lines, indented by 2 spaces.
         | 
| 11 | 
            +
                  #
         | 
| 12 | 
            +
                  # Bad:
         | 
| 13 | 
            +
                  #   # TODO(on: date('2024-03-29'), to: 'john@example.com'): Remove this
         | 
| 14 | 
            +
                  #   # TODO(on: date('2024-03-29'), to: 'john@example.com') Remove this
         | 
| 15 | 
            +
                  #   # TODO(on: date('2024-03-29'), to: 'john@example.com')
         | 
| 16 | 
            +
                  #   # Remove this (not indented)
         | 
| 17 | 
            +
                  #
         | 
| 18 | 
            +
                  # Good:
         | 
| 19 | 
            +
                  #   # TODO(on: date('2024-03-29'), to: 'john@example.com')
         | 
| 20 | 
            +
                  #   #   Remove this (indented by 2 extra spaces)
         | 
| 21 | 
            +
                  #
         | 
| 22 | 
            +
                  class SmartTodoCommentFormatCop < Base
         | 
| 23 | 
            +
                    extend AutoCorrector
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    MSG_INLINE = "SmartTodo comment must not be on the same line as the TODO. " \
         | 
| 26 | 
            +
                      "For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
         | 
| 27 | 
            +
                    MSG_INDENT = "SmartTodo continuation line must be indented by 2 spaces. " \
         | 
| 28 | 
            +
                      "For more info please look at https://github.com/Shopify/smart_todo/wiki/Syntax"
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                    SMART_TODO_PATTERN = /\A\s*#\s*(TODO|FIXME|OPTIMIZE)\(on:/
         | 
| 31 | 
            +
                    INLINE_TEXT_PATTERN = /\A(\s*#\s*(?:TODO|FIXME|OPTIMIZE)\(.+\))\s*:?\s+(.+)/
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                    def on_new_investigation
         | 
| 34 | 
            +
                      processed_source.comments.each_with_index do |comment, index|
         | 
| 35 | 
            +
                        next unless smart_todo_comment?(comment)
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                        check_inline_text(comment)
         | 
| 38 | 
            +
                        check_continuation_indent(comment, processed_source.comments[index + 1])
         | 
| 39 | 
            +
                      end
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    private
         | 
| 43 | 
            +
             | 
| 44 | 
            +
                    def smart_todo_comment?(comment)
         | 
| 45 | 
            +
                      comment.text.match?(SMART_TODO_PATTERN)
         | 
| 46 | 
            +
                    end
         | 
| 47 | 
            +
             | 
| 48 | 
            +
                    def check_inline_text(comment)
         | 
| 49 | 
            +
                      match = comment.text.match(INLINE_TEXT_PATTERN)
         | 
| 50 | 
            +
                      return unless match
         | 
| 51 | 
            +
             | 
| 52 | 
            +
                      add_offense(comment.location.expression, message: MSG_INLINE) do |corrector|
         | 
| 53 | 
            +
                        todo_part = match[1]
         | 
| 54 | 
            +
                        text_part = match[2]
         | 
| 55 | 
            +
                        indentation = " " * comment.location.column
         | 
| 56 | 
            +
             | 
| 57 | 
            +
                        corrected = "#{todo_part}\n#{indentation}#   #{text_part}"
         | 
| 58 | 
            +
                        corrector.replace(comment.location.expression, corrected)
         | 
| 59 | 
            +
                      end
         | 
| 60 | 
            +
                    end
         | 
| 61 | 
            +
             | 
| 62 | 
            +
                    def check_continuation_indent(comment, next_comment)
         | 
| 63 | 
            +
                      return unless next_comment
         | 
| 64 | 
            +
                      return unless next_comment.location.line == comment.location.line + 1
         | 
| 65 | 
            +
                      return if smart_todo_comment?(next_comment)
         | 
| 66 | 
            +
                      return if empty_comment?(next_comment)
         | 
| 67 | 
            +
                      return if properly_indented?(next_comment)
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                      add_offense(next_comment.location.expression, message: MSG_INDENT) do |corrector|
         | 
| 70 | 
            +
                        corrected = fix_indentation(next_comment.text)
         | 
| 71 | 
            +
                        corrector.replace(next_comment.location.expression, corrected)
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
                    end
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    def empty_comment?(comment)
         | 
| 76 | 
            +
                      comment.text.match?(/\A\s*#\s*\z/)
         | 
| 77 | 
            +
                    end
         | 
| 78 | 
            +
             | 
| 79 | 
            +
                    def properly_indented?(comment)
         | 
| 80 | 
            +
                      # A properly indented continuation has exactly 2 spaces after the #
         | 
| 81 | 
            +
                      comment.text.match?(/\A\s*#   \S/)
         | 
| 82 | 
            +
                    end
         | 
| 83 | 
            +
             | 
| 84 | 
            +
                    def fix_indentation(text)
         | 
| 85 | 
            +
                      # Extract leading whitespace and content
         | 
| 86 | 
            +
                      match = text.match(/\A(\s*)#\s*(\S.*)\z/)
         | 
| 87 | 
            +
                      return text unless match
         | 
| 88 | 
            +
             | 
| 89 | 
            +
                      leading_space = match[1]
         | 
| 90 | 
            +
                      content = match[2]
         | 
| 91 | 
            +
                      "#{leading_space}#   #{content}"
         | 
| 92 | 
            +
                    end
         | 
| 93 | 
            +
                  end
         | 
| 94 | 
            +
                end
         | 
| 95 | 
            +
              end
         | 
| 96 | 
            +
            end
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification
         | 
| 2 2 | 
             
            name: smart_todo
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version
         | 
| 4 | 
            -
              version: 1. | 
| 4 | 
            +
              version: 1.11.0
         | 
| 5 5 | 
             
            platform: ruby
         | 
| 6 6 | 
             
            authors:
         | 
| 7 7 | 
             
            - Shopify
         | 
| 8 8 | 
             
            bindir: exe
         | 
| 9 9 | 
             
            cert_chain: []
         | 
| 10 | 
            -
            date:  | 
| 10 | 
            +
            date: 1980-01-02 00:00:00.000000000 Z
         | 
| 11 11 | 
             
            dependencies:
         | 
| 12 12 | 
             
            - !ruby/object:Gem::Dependency
         | 
| 13 13 | 
             
              name: prism
         | 
| @@ -121,6 +121,7 @@ files: | |
| 121 121 | 
             
            - lib/smart_todo/slack_client.rb
         | 
| 122 122 | 
             
            - lib/smart_todo/todo.rb
         | 
| 123 123 | 
             
            - lib/smart_todo/version.rb
         | 
| 124 | 
            +
            - lib/smart_todo_comment_format_cop.rb
         | 
| 124 125 | 
             
            - lib/smart_todo_cop.rb
         | 
| 125 126 | 
             
            - service.yml
         | 
| 126 127 | 
             
            - shipit.rubygems.yml
         | 
| @@ -147,7 +148,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement | |
| 147 148 | 
             
                - !ruby/object:Gem::Version
         | 
| 148 149 | 
             
                  version: '0'
         | 
| 149 150 | 
             
            requirements: []
         | 
| 150 | 
            -
            rubygems_version: 3. | 
| 151 | 
            +
            rubygems_version: 3.7.2
         | 
| 151 152 | 
             
            specification_version: 4
         | 
| 152 153 | 
             
            summary: Enhance todo's comments in your codebase.
         | 
| 153 154 | 
             
            test_files: []
         |