theme-check 1.1.0 → 1.5.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/theme-check.yml +5 -9
- data/.gitignore +1 -0
- data/CHANGELOG.md +50 -0
- data/CONTRIBUTING.md +1 -1
- data/RELEASING.md +34 -2
- data/bin/theme-check +29 -0
- data/bin/theme-check-language-server +29 -0
- data/config/default.yml +15 -1
- data/config/theme_app_extension.yml +15 -0
- data/data/shopify_liquid/objects.yml +1 -0
- data/docs/checks/app_block_valid_tags.md +40 -0
- data/docs/checks/asset_size_app_block_css.md +1 -1
- data/docs/checks/deprecate_lazysizes.md +0 -3
- data/docs/checks/deprecated_global_app_block_type.md +65 -0
- data/docs/checks/missing_template.md +25 -0
- data/docs/checks/pagination_size.md +44 -0
- data/docs/checks/template_length.md +1 -1
- data/docs/checks/undefined_object.md +5 -0
- data/lib/theme_check/analyzer.rb +1 -0
- data/lib/theme_check/check.rb +3 -3
- data/lib/theme_check/checks/app_block_valid_tags.rb +36 -0
- data/lib/theme_check/checks/asset_size_css.rb +3 -3
- data/lib/theme_check/checks/asset_size_javascript.rb +2 -2
- data/lib/theme_check/checks/convert_include_to_render.rb +3 -1
- data/lib/theme_check/checks/default_locale.rb +3 -1
- data/lib/theme_check/checks/deprecate_bgsizes.rb +1 -1
- data/lib/theme_check/checks/deprecate_lazysizes.rb +7 -4
- data/lib/theme_check/checks/deprecated_global_app_block_type.rb +57 -0
- data/lib/theme_check/checks/img_lazy_loading.rb +1 -1
- data/lib/theme_check/checks/img_width_and_height.rb +3 -3
- data/lib/theme_check/checks/missing_template.rb +21 -5
- data/lib/theme_check/checks/pagination_size.rb +64 -0
- data/lib/theme_check/checks/parser_blocking_javascript.rb +1 -1
- data/lib/theme_check/checks/remote_asset.rb +3 -3
- data/lib/theme_check/checks/space_inside_braces.rb +27 -7
- data/lib/theme_check/checks/template_length.rb +1 -1
- data/lib/theme_check/checks/undefined_object.rb +1 -1
- data/lib/theme_check/checks/valid_html_translation.rb +1 -1
- data/lib/theme_check/checks.rb +11 -1
- data/lib/theme_check/cli.rb +18 -2
- data/lib/theme_check/corrector.rb +9 -0
- data/lib/theme_check/file_system_storage.rb +12 -0
- data/lib/theme_check/html_check.rb +0 -1
- data/lib/theme_check/html_node.rb +37 -16
- data/lib/theme_check/html_visitor.rb +17 -3
- data/lib/theme_check/json_check.rb +2 -2
- data/lib/theme_check/json_file.rb +11 -0
- data/lib/theme_check/json_printer.rb +27 -0
- data/lib/theme_check/language_server/constants.rb +18 -11
- data/lib/theme_check/language_server/document_link_engine.rb +3 -67
- data/lib/theme_check/language_server/document_link_provider.rb +71 -0
- data/lib/theme_check/language_server/document_link_providers/asset_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/render_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb +11 -0
- data/lib/theme_check/language_server/handler.rb +17 -9
- data/lib/theme_check/language_server/server.rb +9 -0
- data/lib/theme_check/language_server/uri_helper.rb +37 -0
- data/lib/theme_check/language_server.rb +6 -0
- data/lib/theme_check/node.rb +6 -4
- data/lib/theme_check/offense.rb +56 -3
- data/lib/theme_check/parsing_helpers.rb +4 -3
- data/lib/theme_check/position.rb +98 -14
- data/lib/theme_check/regex_helpers.rb +5 -2
- data/lib/theme_check/theme.rb +3 -0
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check.rb +1 -0
- data/theme-check.gemspec +1 -1
- metadata +20 -6
- data/bin/liquid-server +0 -4
| @@ -3,81 +3,17 @@ | |
| 3 3 | 
             
            module ThemeCheck
         | 
| 4 4 | 
             
              module LanguageServer
         | 
| 5 5 | 
             
                class DocumentLinkEngine
         | 
| 6 | 
            -
                  include PositionHelper
         | 
| 7 | 
            -
                  include RegexHelpers
         | 
| 8 | 
            -
             | 
| 9 6 | 
             
                  def initialize(storage)
         | 
| 10 7 | 
             
                    @storage = storage
         | 
| 8 | 
            +
                    @providers = DocumentLinkProvider.all.map { |x| x.new(storage) }
         | 
| 11 9 | 
             
                  end
         | 
| 12 10 |  | 
| 13 11 | 
             
                  def document_links(relative_path)
         | 
| 14 12 | 
             
                    buffer = @storage.read(relative_path)
         | 
| 15 13 | 
             
                    return [] unless buffer
         | 
| 16 | 
            -
                     | 
| 17 | 
            -
                       | 
| 18 | 
            -
                        buffer,
         | 
| 19 | 
            -
                        match.begin(:partial),
         | 
| 20 | 
            -
                      )
         | 
| 21 | 
            -
             | 
| 22 | 
            -
                      end_line, end_character = from_index_to_row_column(
         | 
| 23 | 
            -
                        buffer,
         | 
| 24 | 
            -
                        match.end(:partial)
         | 
| 25 | 
            -
                      )
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                      {
         | 
| 28 | 
            -
                        target: snippet_link(match[:partial]),
         | 
| 29 | 
            -
                        range: {
         | 
| 30 | 
            -
                          start: {
         | 
| 31 | 
            -
                            line: start_line,
         | 
| 32 | 
            -
                            character: start_character,
         | 
| 33 | 
            -
                          },
         | 
| 34 | 
            -
                          end: {
         | 
| 35 | 
            -
                            line: end_line,
         | 
| 36 | 
            -
                            character: end_character,
         | 
| 37 | 
            -
                          },
         | 
| 38 | 
            -
                        },
         | 
| 39 | 
            -
                      }
         | 
| 14 | 
            +
                    @providers.flat_map do |p|
         | 
| 15 | 
            +
                      p.document_links(buffer)
         | 
| 40 16 | 
             
                    end
         | 
| 41 | 
            -
                    asset_matches = matches(buffer, ASSET_INCLUDE).map do |match|
         | 
| 42 | 
            -
                      start_line, start_character = from_index_to_row_column(
         | 
| 43 | 
            -
                        buffer,
         | 
| 44 | 
            -
                        match.begin(:partial),
         | 
| 45 | 
            -
                      )
         | 
| 46 | 
            -
             | 
| 47 | 
            -
                      end_line, end_character = from_index_to_row_column(
         | 
| 48 | 
            -
                        buffer,
         | 
| 49 | 
            -
                        match.end(:partial)
         | 
| 50 | 
            -
                      )
         | 
| 51 | 
            -
             | 
| 52 | 
            -
                      {
         | 
| 53 | 
            -
                        target: asset_link(match[:partial]),
         | 
| 54 | 
            -
                        range: {
         | 
| 55 | 
            -
                          start: {
         | 
| 56 | 
            -
                            line: start_line,
         | 
| 57 | 
            -
                            character: start_character,
         | 
| 58 | 
            -
                          },
         | 
| 59 | 
            -
                          end: {
         | 
| 60 | 
            -
                            line: end_line,
         | 
| 61 | 
            -
                            character: end_character,
         | 
| 62 | 
            -
                          },
         | 
| 63 | 
            -
                        },
         | 
| 64 | 
            -
                      }
         | 
| 65 | 
            -
                    end
         | 
| 66 | 
            -
                    snippet_matches + asset_matches
         | 
| 67 | 
            -
                  end
         | 
| 68 | 
            -
             | 
| 69 | 
            -
                  def snippet_link(partial)
         | 
| 70 | 
            -
                    file_link('snippets', partial, '.liquid')
         | 
| 71 | 
            -
                  end
         | 
| 72 | 
            -
             | 
| 73 | 
            -
                  def asset_link(partial)
         | 
| 74 | 
            -
                    file_link('assets', partial, '')
         | 
| 75 | 
            -
                  end
         | 
| 76 | 
            -
             | 
| 77 | 
            -
                  private
         | 
| 78 | 
            -
             | 
| 79 | 
            -
                  def file_link(directory, partial, extension)
         | 
| 80 | 
            -
                    "file://#{@storage.path(directory + '/' + partial + extension)}"
         | 
| 81 17 | 
             
                  end
         | 
| 82 18 | 
             
                end
         | 
| 83 19 | 
             
              end
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ThemeCheck
         | 
| 4 | 
            +
              module LanguageServer
         | 
| 5 | 
            +
                class DocumentLinkProvider
         | 
| 6 | 
            +
                  include RegexHelpers
         | 
| 7 | 
            +
                  include PositionHelper
         | 
| 8 | 
            +
                  include URIHelper
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                  class << self
         | 
| 11 | 
            +
                    attr_accessor :partial_regexp, :destination_directory, :destination_postfix
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                    def all
         | 
| 14 | 
            +
                      @all ||= []
         | 
| 15 | 
            +
                    end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                    def inherited(subclass)
         | 
| 18 | 
            +
                      all << subclass
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
                  end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                  def initialize(storage = InMemoryStorage.new)
         | 
| 23 | 
            +
                    @storage = storage
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def partial_regexp
         | 
| 27 | 
            +
                    self.class.partial_regexp
         | 
| 28 | 
            +
                  end
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                  def destination_directory
         | 
| 31 | 
            +
                    self.class.destination_directory
         | 
| 32 | 
            +
                  end
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  def destination_postfix
         | 
| 35 | 
            +
                    self.class.destination_postfix
         | 
| 36 | 
            +
                  end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                  def document_links(buffer)
         | 
| 39 | 
            +
                    matches(buffer, partial_regexp).map do |match|
         | 
| 40 | 
            +
                      start_line, start_character = from_index_to_row_column(
         | 
| 41 | 
            +
                        buffer,
         | 
| 42 | 
            +
                        match.begin(:partial),
         | 
| 43 | 
            +
                      )
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                      end_line, end_character = from_index_to_row_column(
         | 
| 46 | 
            +
                        buffer,
         | 
| 47 | 
            +
                        match.end(:partial)
         | 
| 48 | 
            +
                      )
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                      {
         | 
| 51 | 
            +
                        target: file_link(match[:partial]),
         | 
| 52 | 
            +
                        range: {
         | 
| 53 | 
            +
                          start: {
         | 
| 54 | 
            +
                            line: start_line,
         | 
| 55 | 
            +
                            character: start_character,
         | 
| 56 | 
            +
                          },
         | 
| 57 | 
            +
                          end: {
         | 
| 58 | 
            +
                            line: end_line,
         | 
| 59 | 
            +
                            character: end_character,
         | 
| 60 | 
            +
                          },
         | 
| 61 | 
            +
                        },
         | 
| 62 | 
            +
                      }
         | 
| 63 | 
            +
                    end
         | 
| 64 | 
            +
                  end
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                  def file_link(partial)
         | 
| 67 | 
            +
                    file_uri(@storage.path(destination_directory + '/' + partial + destination_postfix))
         | 
| 68 | 
            +
                  end
         | 
| 69 | 
            +
                end
         | 
| 70 | 
            +
              end
         | 
| 71 | 
            +
            end
         | 
    
        data/lib/theme_check/language_server/document_link_providers/include_document_link_provider.rb
    ADDED
    
    | @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ThemeCheck
         | 
| 4 | 
            +
              module LanguageServer
         | 
| 5 | 
            +
                class IncludeDocumentLinkProvider < DocumentLinkProvider
         | 
| 6 | 
            +
                  @partial_regexp = PARTIAL_INCLUDE
         | 
| 7 | 
            +
                  @destination_directory = "snippets"
         | 
| 8 | 
            +
                  @destination_postfix = ".liquid"
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ThemeCheck
         | 
| 4 | 
            +
              module LanguageServer
         | 
| 5 | 
            +
                class RenderDocumentLinkProvider < DocumentLinkProvider
         | 
| 6 | 
            +
                  @partial_regexp = PARTIAL_RENDER
         | 
| 7 | 
            +
                  @destination_directory = "snippets"
         | 
| 8 | 
            +
                  @destination_postfix = ".liquid"
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
    
        data/lib/theme_check/language_server/document_link_providers/section_document_link_provider.rb
    ADDED
    
    | @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            module ThemeCheck
         | 
| 4 | 
            +
              module LanguageServer
         | 
| 5 | 
            +
                class SectionDocumentLinkProvider < DocumentLinkProvider
         | 
| 6 | 
            +
                  @partial_regexp = PARTIAL_SECTION
         | 
| 7 | 
            +
                  @destination_directory = "sections"
         | 
| 8 | 
            +
                  @destination_postfix = ".liquid"
         | 
| 9 | 
            +
                end
         | 
| 10 | 
            +
              end
         | 
| 11 | 
            +
            end
         | 
| @@ -1,9 +1,12 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 2 3 | 
             
            require "benchmark"
         | 
| 3 4 |  | 
| 4 5 | 
             
            module ThemeCheck
         | 
| 5 6 | 
             
              module LanguageServer
         | 
| 6 7 | 
             
                class Handler
         | 
| 8 | 
            +
                  include URIHelper
         | 
| 9 | 
            +
             | 
| 7 10 | 
             
                  CAPABILITIES = {
         | 
| 8 11 | 
             
                    completionProvider: {
         | 
| 9 12 | 
             
                      triggerCharacters: ['.', '{{ ', '{% '],
         | 
| @@ -24,7 +27,7 @@ module ThemeCheck | |
| 24 27 | 
             
                  end
         | 
| 25 28 |  | 
| 26 29 | 
             
                  def on_initialize(id, params)
         | 
| 27 | 
            -
                    @root_path =  | 
| 30 | 
            +
                    @root_path = root_path_from_params(params)
         | 
| 28 31 |  | 
| 29 32 | 
             
                    # Tell the client we don't support anything if there's no rootPath
         | 
| 30 33 | 
             
                    return send_response(id, { capabilities: {} }) if @root_path.nil?
         | 
| @@ -53,10 +56,9 @@ module ThemeCheck | |
| 53 56 | 
             
                  end
         | 
| 54 57 |  | 
| 55 58 | 
             
                  def on_text_document_did_open(_id, params)
         | 
| 56 | 
            -
                    return unless @diagnostics_tracker.first_run?
         | 
| 57 59 | 
             
                    relative_path = relative_path_from_text_document_uri(params)
         | 
| 58 60 | 
             
                    @storage.write(relative_path, text_document_text(params))
         | 
| 59 | 
            -
                    analyze_and_send_offenses(text_document_uri(params))
         | 
| 61 | 
            +
                    analyze_and_send_offenses(text_document_uri(params)) if @diagnostics_tracker.first_run?
         | 
| 60 62 | 
             
                  end
         | 
| 61 63 |  | 
| 62 64 | 
             
                  def on_text_document_did_save(_id, params)
         | 
| @@ -95,17 +97,23 @@ module ThemeCheck | |
| 95 97 | 
             
                  end
         | 
| 96 98 |  | 
| 97 99 | 
             
                  def text_document_uri(params)
         | 
| 98 | 
            -
                     | 
| 99 | 
            -
                  end
         | 
| 100 | 
            -
             | 
| 101 | 
            -
                  def path_from_uri(uri)
         | 
| 102 | 
            -
                    uri&.sub('file://', '')
         | 
| 100 | 
            +
                    file_path(params.dig('textDocument', 'uri'))
         | 
| 103 101 | 
             
                  end
         | 
| 104 102 |  | 
| 105 103 | 
             
                  def relative_path_from_text_document_uri(params)
         | 
| 106 104 | 
             
                    @storage.relative_path(text_document_uri(params))
         | 
| 107 105 | 
             
                  end
         | 
| 108 106 |  | 
| 107 | 
            +
                  def root_path_from_params(params)
         | 
| 108 | 
            +
                    root_uri = params["rootUri"]
         | 
| 109 | 
            +
                    root_path = params["rootPath"]
         | 
| 110 | 
            +
                    if root_uri
         | 
| 111 | 
            +
                      file_path(root_uri)
         | 
| 112 | 
            +
                    elsif root_path
         | 
| 113 | 
            +
                      root_path
         | 
| 114 | 
            +
                    end
         | 
| 115 | 
            +
                  end
         | 
| 116 | 
            +
             | 
| 109 117 | 
             
                  def text_document_text(params)
         | 
| 110 118 | 
             
                    params.dig('textDocument', 'text')
         | 
| 111 119 | 
             
                  end
         | 
| @@ -171,7 +179,7 @@ module ThemeCheck | |
| 171 179 | 
             
                  def send_diagnostic(path, offenses)
         | 
| 172 180 | 
             
                    # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
         | 
| 173 181 | 
             
                    send_notification('textDocument/publishDiagnostics', {
         | 
| 174 | 
            -
                      uri:  | 
| 182 | 
            +
                      uri: file_uri(path),
         | 
| 175 183 | 
             
                      diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
         | 
| 176 184 | 
             
                    })
         | 
| 177 185 | 
             
                  end
         | 
| @@ -25,6 +25,15 @@ module ThemeCheck | |
| 25 25 | 
             
                    @out = out_stream
         | 
| 26 26 | 
             
                    @err = err_stream
         | 
| 27 27 |  | 
| 28 | 
            +
                    # Because programming is fun,
         | 
| 29 | 
            +
                    #
         | 
| 30 | 
            +
                    # Ruby on Windows turns \n into \r\n. Which means that \r\n
         | 
| 31 | 
            +
                    # gets turned into \r\r\n. Which means that the protocol
         | 
| 32 | 
            +
                    # breaks on windows unless we turn STDOUT into binary mode.
         | 
| 33 | 
            +
                    #
         | 
| 34 | 
            +
                    # Hours wasted: 9.
         | 
| 35 | 
            +
                    @out.binmode
         | 
| 36 | 
            +
             | 
| 28 37 | 
             
                    @out.sync = true # do not buffer
         | 
| 29 38 | 
             
                    @err.sync = true # do not buffer
         | 
| 30 39 |  | 
| @@ -0,0 +1,37 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            require "benchmark"
         | 
| 4 | 
            +
            require "uri"
         | 
| 5 | 
            +
            require "cgi"
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            module ThemeCheck
         | 
| 8 | 
            +
              module LanguageServer
         | 
| 9 | 
            +
                module URIHelper
         | 
| 10 | 
            +
                  # Will URI.encode a string the same way VS Code would. There are two things
         | 
| 11 | 
            +
                  # to watch out for:
         | 
| 12 | 
            +
                  #
         | 
| 13 | 
            +
                  # 1. VS Code still uses the outdated '%20' for spaces
         | 
| 14 | 
            +
                  # 2. VS Code prefixes Windows paths with / (so /C:/Users/... is expected)
         | 
| 15 | 
            +
                  #
         | 
| 16 | 
            +
                  # Exists because of https://github.com/Shopify/theme-check/issues/360
         | 
| 17 | 
            +
                  def file_uri(absolute_path)
         | 
| 18 | 
            +
                    "file://" + absolute_path
         | 
| 19 | 
            +
                      .to_s
         | 
| 20 | 
            +
                      .split('/')
         | 
| 21 | 
            +
                      .map { |x| CGI.escape(x).gsub('+', '%20') }
         | 
| 22 | 
            +
                      .join('/')
         | 
| 23 | 
            +
                      .sub(%r{^/?}, '/') # Windows paths should be prefixed by /c:
         | 
| 24 | 
            +
                  end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                  def file_path(uri_string)
         | 
| 27 | 
            +
                    return if uri_string.nil?
         | 
| 28 | 
            +
                    uri = URI(uri_string)
         | 
| 29 | 
            +
                    path = CGI.unescape(uri.path)
         | 
| 30 | 
            +
                    # On Windows, VS Code sends the URLs as file:///C:/...
         | 
| 31 | 
            +
                    # /C:/1234 is not a valid path in ruby. So we strip the slash.
         | 
| 32 | 
            +
                    path = path.sub(%r{^/([a-z]:/)}i, '\1')
         | 
| 33 | 
            +
                    path
         | 
| 34 | 
            +
                  end
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
              end
         | 
| 37 | 
            +
            end
         | 
| @@ -1,6 +1,7 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 | 
             
            require_relative "language_server/protocol"
         | 
| 3 3 | 
             
            require_relative "language_server/constants"
         | 
| 4 | 
            +
            require_relative "language_server/uri_helper"
         | 
| 4 5 | 
             
            require_relative "language_server/handler"
         | 
| 5 6 | 
             
            require_relative "language_server/server"
         | 
| 6 7 | 
             
            require_relative "language_server/tokens"
         | 
| @@ -8,6 +9,7 @@ require_relative "language_server/variable_lookup_finder" | |
| 8 9 | 
             
            require_relative "language_server/completion_helper"
         | 
| 9 10 | 
             
            require_relative "language_server/completion_provider"
         | 
| 10 11 | 
             
            require_relative "language_server/completion_engine"
         | 
| 12 | 
            +
            require_relative "language_server/document_link_provider"
         | 
| 11 13 | 
             
            require_relative "language_server/document_link_engine"
         | 
| 12 14 | 
             
            require_relative "language_server/diagnostics_tracker"
         | 
| 13 15 |  | 
| @@ -15,6 +17,10 @@ Dir[__dir__ + "/language_server/completion_providers/*.rb"].each do |file| | |
| 15 17 | 
             
              require file
         | 
| 16 18 | 
             
            end
         | 
| 17 19 |  | 
| 20 | 
            +
            Dir[__dir__ + "/language_server/document_link_providers/*.rb"].each do |file|
         | 
| 21 | 
            +
              require file
         | 
| 22 | 
            +
            end
         | 
| 23 | 
            +
             | 
| 18 24 | 
             
            module ThemeCheck
         | 
| 19 25 | 
             
              module LanguageServer
         | 
| 20 26 | 
             
                def self.start
         | 
    
        data/lib/theme_check/node.rb
    CHANGED
    
    | @@ -22,9 +22,7 @@ module ThemeCheck | |
| 22 22 | 
             
                end
         | 
| 23 23 |  | 
| 24 24 | 
             
                def markup=(markup)
         | 
| 25 | 
            -
                  if  | 
| 26 | 
            -
                    @value.raw = markup
         | 
| 27 | 
            -
                  elsif @value.instance_variable_defined?(:@markup)
         | 
| 25 | 
            +
                  if @value.instance_variable_defined?(:@markup)
         | 
| 28 26 | 
             
                    @value.instance_variable_set(:@markup, markup)
         | 
| 29 27 | 
             
                  end
         | 
| 30 28 | 
             
                end
         | 
| @@ -127,7 +125,11 @@ module ThemeCheck | |
| 127 125 | 
             
                end
         | 
| 128 126 |  | 
| 129 127 | 
             
                def position
         | 
| 130 | 
            -
                  @position ||= Position.new( | 
| 128 | 
            +
                  @position ||= Position.new(
         | 
| 129 | 
            +
                    markup,
         | 
| 130 | 
            +
                    template&.source,
         | 
| 131 | 
            +
                    line_number_1_indexed: line_number
         | 
| 132 | 
            +
                  )
         | 
| 131 133 | 
             
                end
         | 
| 132 134 |  | 
| 133 135 | 
             
                def start_index
         | 
    
        data/lib/theme_check/offense.rb
    CHANGED
    
    | @@ -1,11 +1,32 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 | 
             
            module ThemeCheck
         | 
| 3 3 | 
             
              class Offense
         | 
| 4 | 
            +
                include PositionHelper
         | 
| 5 | 
            +
             | 
| 4 6 | 
             
                MAX_SOURCE_EXCERPT_SIZE = 120
         | 
| 5 7 |  | 
| 6 8 | 
             
                attr_reader :check, :message, :template, :node, :markup, :line_number, :correction
         | 
| 7 9 |  | 
| 8 | 
            -
                def initialize( | 
| 10 | 
            +
                def initialize(
         | 
| 11 | 
            +
                  check:, # instance of a ThemeCheck::Check
         | 
| 12 | 
            +
                  message: nil, # error message for the offense
         | 
| 13 | 
            +
                  template: nil, # Template
         | 
| 14 | 
            +
                  node: nil, # Node or HtmlNode
         | 
| 15 | 
            +
                  markup: nil, # string
         | 
| 16 | 
            +
                  line_number: nil, # line number of the error (1-indexed)
         | 
| 17 | 
            +
                  # node_markup_offset is the index inside node.markup to start
         | 
| 18 | 
            +
                  # looking for markup :mindblow:.
         | 
| 19 | 
            +
                  # This is so we can accurately highlight node substrings.
         | 
| 20 | 
            +
                  # e.g. if we have the following scenario in which we
         | 
| 21 | 
            +
                  # want to highlight the middle comma
         | 
| 22 | 
            +
                  #   * node.markup == "replace ',',', '"
         | 
| 23 | 
            +
                  #   * markup == ","
         | 
| 24 | 
            +
                  # Then we need some way of telling our Position class to start
         | 
| 25 | 
            +
                  # looking for the second comma. This is done with node_markup_offset.
         | 
| 26 | 
            +
                  # More context can be found in #376.
         | 
| 27 | 
            +
                  node_markup_offset: 0,
         | 
| 28 | 
            +
                  correction: nil # block
         | 
| 29 | 
            +
                )
         | 
| 9 30 | 
             
                  @check = check
         | 
| 10 31 | 
             
                  @correction = correction
         | 
| 11 32 |  | 
| @@ -39,7 +60,13 @@ module ThemeCheck | |
| 39 60 | 
             
                    @node.line_number
         | 
| 40 61 | 
             
                  end
         | 
| 41 62 |  | 
| 42 | 
            -
                  @position = Position.new( | 
| 63 | 
            +
                  @position = Position.new(
         | 
| 64 | 
            +
                    @markup,
         | 
| 65 | 
            +
                    @template&.source,
         | 
| 66 | 
            +
                    line_number_1_indexed: @line_number,
         | 
| 67 | 
            +
                    node_markup_offset: node_markup_offset,
         | 
| 68 | 
            +
                    node_markup: node&.markup
         | 
| 69 | 
            +
                  )
         | 
| 43 70 | 
             
                end
         | 
| 44 71 |  | 
| 45 72 | 
             
                def source_excerpt
         | 
| @@ -103,8 +130,13 @@ module ThemeCheck | |
| 103 130 | 
             
                  tokens.join(":") if tokens.any?
         | 
| 104 131 | 
             
                end
         | 
| 105 132 |  | 
| 133 | 
            +
                def location_range
         | 
| 134 | 
            +
                  tokens = [template&.relative_path, start_index, end_index].compact
         | 
| 135 | 
            +
                  tokens.join(":") if tokens.any?
         | 
| 136 | 
            +
                end
         | 
| 137 | 
            +
             | 
| 106 138 | 
             
                def correctable?
         | 
| 107 | 
            -
                   | 
| 139 | 
            +
                  !!correction
         | 
| 108 140 | 
             
                end
         | 
| 109 141 |  | 
| 110 142 | 
             
                def correct
         | 
| @@ -139,5 +171,26 @@ module ThemeCheck | |
| 139 171 | 
             
                    message
         | 
| 140 172 | 
             
                  end
         | 
| 141 173 | 
             
                end
         | 
| 174 | 
            +
             | 
| 175 | 
            +
                def to_s_range
         | 
| 176 | 
            +
                  if template
         | 
| 177 | 
            +
                    "#{message} at #{location_range}"
         | 
| 178 | 
            +
                  else
         | 
| 179 | 
            +
                    message
         | 
| 180 | 
            +
                  end
         | 
| 181 | 
            +
                end
         | 
| 182 | 
            +
             | 
| 183 | 
            +
                def to_h
         | 
| 184 | 
            +
                  {
         | 
| 185 | 
            +
                    check: check.code_name,
         | 
| 186 | 
            +
                    path: template&.relative_path,
         | 
| 187 | 
            +
                    severity: check.severity_value,
         | 
| 188 | 
            +
                    start_line: start_line,
         | 
| 189 | 
            +
                    start_column: start_column,
         | 
| 190 | 
            +
                    end_line: end_line,
         | 
| 191 | 
            +
                    end_column: end_column,
         | 
| 192 | 
            +
                    message: message,
         | 
| 193 | 
            +
                  }
         | 
| 194 | 
            +
                end
         | 
| 142 195 | 
             
              end
         | 
| 143 196 | 
             
            end
         |