theme-check 1.4.0 → 1.6.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 +14 -6
 - data/.gitignore +1 -0
 - data/CHANGELOG.md +42 -0
 - data/CONTRIBUTING.md +58 -0
 - data/Gemfile +3 -0
 - data/config/default.yml +3 -0
 - data/docs/checks/deprecated_global_app_block_type.md +65 -0
 - data/docs/flamegraph.svg +18488 -0
 - data/lib/theme_check/analyzer.rb +5 -0
 - data/lib/theme_check/asset_file.rb +13 -2
 - data/lib/theme_check/check.rb +1 -1
 - data/lib/theme_check/checks/asset_size_css.rb +15 -0
 - data/lib/theme_check/checks/asset_size_css_stylesheet_tag.rb +18 -1
 - data/lib/theme_check/checks/convert_include_to_render.rb +2 -1
 - data/lib/theme_check/checks/deprecated_global_app_block_type.rb +57 -0
 - data/lib/theme_check/checks/liquid_tag.rb +1 -1
 - data/lib/theme_check/checks/missing_required_template_files.rb +21 -7
 - data/lib/theme_check/checks/pagination_size.rb +33 -14
 - data/lib/theme_check/checks/required_directories.rb +3 -1
 - data/lib/theme_check/checks/space_inside_braces.rb +47 -24
 - data/lib/theme_check/checks/translation_key_exists.rb +3 -1
 - data/lib/theme_check/checks/unused_snippet.rb +3 -1
 - data/lib/theme_check/cli.rb +32 -6
 - data/lib/theme_check/corrector.rb +23 -10
 - data/lib/theme_check/file_system_storage.rb +13 -2
 - data/lib/theme_check/html_node.rb +4 -4
 - data/lib/theme_check/html_visitor.rb +20 -8
 - data/lib/theme_check/in_memory_storage.rb +8 -0
 - data/lib/theme_check/json_file.rb +9 -4
 - data/lib/theme_check/json_printer.rb +6 -1
 - data/lib/theme_check/language_server/document_link_provider.rb +2 -1
 - data/lib/theme_check/language_server/handler.rb +16 -11
 - data/lib/theme_check/language_server/server.rb +11 -13
 - data/lib/theme_check/language_server/uri_helper.rb +37 -0
 - data/lib/theme_check/language_server.rb +1 -0
 - data/lib/theme_check/node.rb +118 -11
 - data/lib/theme_check/offense.rb +26 -0
 - data/lib/theme_check/position.rb +27 -16
 - data/lib/theme_check/position_helper.rb +13 -15
 - data/lib/theme_check/printer.rb +9 -5
 - data/lib/theme_check/regex_helpers.rb +1 -15
 - data/lib/theme_check/remote_asset_file.rb +4 -0
 - data/lib/theme_check/template.rb +5 -19
 - data/lib/theme_check/template_rewriter.rb +57 -0
 - data/lib/theme_check/theme_file.rb +18 -1
 - data/lib/theme_check/version.rb +1 -1
 - data/lib/theme_check.rb +1 -0
 - data/theme-check.gemspec +1 -0
 - metadata +21 -2
 
| 
         @@ -7,25 +7,20 @@ module ThemeCheck 
     | 
|
| 
       7 
7 
     | 
    
         
             
                end
         
     | 
| 
       8 
8 
     | 
    
         | 
| 
       9 
9 
     | 
    
         
             
                def insert_after(node, content)
         
     | 
| 
       10 
     | 
    
         
            -
                   
     | 
| 
       11 
     | 
    
         
            -
                  line.insert(node.range[1] + 1, content)
         
     | 
| 
      
 10 
     | 
    
         
            +
                  @template.rewriter.insert_after(node, content)
         
     | 
| 
       12 
11 
     | 
    
         
             
                end
         
     | 
| 
       13 
12 
     | 
    
         | 
| 
       14 
13 
     | 
    
         
             
                def insert_before(node, content)
         
     | 
| 
       15 
     | 
    
         
            -
                   
     | 
| 
       16 
     | 
    
         
            -
                  line.insert(node.range[0], content)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @template.rewriter.insert_before(node, content)
         
     | 
| 
       17 
15 
     | 
    
         
             
                end
         
     | 
| 
       18 
16 
     | 
    
         | 
| 
       19 
17 
     | 
    
         
             
                def replace(node, content)
         
     | 
| 
       20 
     | 
    
         
            -
                   
     | 
| 
       21 
     | 
    
         
            -
                  line[node.range[0]..node.range[1]] = content
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @template.rewriter.replace(node, content)
         
     | 
| 
       22 
19 
     | 
    
         
             
                  node.markup = content
         
     | 
| 
       23 
20 
     | 
    
         
             
                end
         
     | 
| 
       24 
21 
     | 
    
         | 
| 
       25 
22 
     | 
    
         
             
                def wrap(node, insert_before, insert_after)
         
     | 
| 
       26 
     | 
    
         
            -
                   
     | 
| 
       27 
     | 
    
         
            -
                  line.insert(node.range[0], insert_before)
         
     | 
| 
       28 
     | 
    
         
            -
                  line.insert(node.range[1] + 1 + insert_before.length, insert_after)
         
     | 
| 
      
 23 
     | 
    
         
            +
                  @template.rewriter.wrap(node, insert_before, insert_after)
         
     | 
| 
       29 
24 
     | 
    
         
             
                end
         
     | 
| 
       30 
25 
     | 
    
         | 
| 
       31 
26 
     | 
    
         
             
                def create(theme, relative_path, content)
         
     | 
| 
         @@ -34,7 +29,25 @@ module ThemeCheck 
     | 
|
| 
       34 
29 
     | 
    
         | 
| 
       35 
30 
     | 
    
         
             
                def create_default_locale_json(theme)
         
     | 
| 
       36 
31 
     | 
    
         
             
                  theme.default_locale_json = JsonFile.new("locales/#{theme.default_locale}.default.json", theme.storage)
         
     | 
| 
       37 
     | 
    
         
            -
                  theme.default_locale_json.update_contents( 
     | 
| 
      
 32 
     | 
    
         
            +
                  theme.default_locale_json.update_contents({})
         
     | 
| 
      
 33 
     | 
    
         
            +
                end
         
     | 
| 
      
 34 
     | 
    
         
            +
             
     | 
| 
      
 35 
     | 
    
         
            +
                def remove(theme, relative_path)
         
     | 
| 
      
 36 
     | 
    
         
            +
                  theme.storage.remove(relative_path)
         
     | 
| 
      
 37 
     | 
    
         
            +
                end
         
     | 
| 
      
 38 
     | 
    
         
            +
             
     | 
| 
      
 39 
     | 
    
         
            +
                def mkdir(theme, relative_path)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  theme.storage.mkdir(relative_path)
         
     | 
| 
      
 41 
     | 
    
         
            +
                end
         
     | 
| 
      
 42 
     | 
    
         
            +
             
     | 
| 
      
 43 
     | 
    
         
            +
                def add_default_translation_key(file, key, value)
         
     | 
| 
      
 44 
     | 
    
         
            +
                  hash = file.content
         
     | 
| 
      
 45 
     | 
    
         
            +
                  key.reduce(hash) do |pointer, token|
         
     | 
| 
      
 46 
     | 
    
         
            +
                    return pointer[token] = value if token == key.last
         
     | 
| 
      
 47 
     | 
    
         
            +
                    pointer[token] = {} unless pointer.key?(token)
         
     | 
| 
      
 48 
     | 
    
         
            +
                    pointer[token]
         
     | 
| 
      
 49 
     | 
    
         
            +
                  end
         
     | 
| 
      
 50 
     | 
    
         
            +
                  file.update_contents(hash)
         
     | 
| 
       38 
51 
     | 
    
         
             
                end
         
     | 
| 
       39 
52 
     | 
    
         
             
              end
         
     | 
| 
       40 
53 
     | 
    
         
             
            end
         
     | 
| 
         @@ -16,14 +16,25 @@ module ThemeCheck 
     | 
|
| 
       16 
16 
     | 
    
         
             
                end
         
     | 
| 
       17 
17 
     | 
    
         | 
| 
       18 
18 
     | 
    
         
             
                def read(relative_path)
         
     | 
| 
       19 
     | 
    
         
            -
                  file(relative_path).read
         
     | 
| 
      
 19 
     | 
    
         
            +
                  file(relative_path).read(mode: 'rb', encoding: 'UTF-8')
         
     | 
| 
       20 
20 
     | 
    
         
             
                end
         
     | 
| 
       21 
21 
     | 
    
         | 
| 
       22 
22 
     | 
    
         
             
                def write(relative_path, content)
         
     | 
| 
       23 
23 
     | 
    
         
             
                  reset_memoizers unless file_exists?(relative_path)
         
     | 
| 
       24 
24 
     | 
    
         | 
| 
       25 
25 
     | 
    
         
             
                  file(relative_path).dirname.mkpath unless file(relative_path).dirname.directory?
         
     | 
| 
       26 
     | 
    
         
            -
                  file(relative_path).write(content)
         
     | 
| 
      
 26 
     | 
    
         
            +
                  file(relative_path).write(content, mode: 'w+b', encoding: 'UTF-8')
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
             
     | 
| 
      
 29 
     | 
    
         
            +
                def remove(relative_path)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  file(relative_path).delete
         
     | 
| 
      
 31 
     | 
    
         
            +
                  reset_memoizers
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
             
     | 
| 
      
 34 
     | 
    
         
            +
                def mkdir(relative_path)
         
     | 
| 
      
 35 
     | 
    
         
            +
                  reset_memoizers unless file_exists?(relative_path)
         
     | 
| 
      
 36 
     | 
    
         
            +
             
     | 
| 
      
 37 
     | 
    
         
            +
                  file(relative_path).mkpath unless file(relative_path).directory?
         
     | 
| 
       27 
38 
     | 
    
         
             
                end
         
     | 
| 
       28 
39 
     | 
    
         | 
| 
       29 
40 
     | 
    
         
             
                def files
         
     | 
| 
         @@ -67,10 +67,10 @@ module ThemeCheck 
     | 
|
| 
       67 
67 
     | 
    
         
             
                private
         
     | 
| 
       68 
68 
     | 
    
         | 
| 
       69 
69 
     | 
    
         
             
                def replace_placeholders(string)
         
     | 
| 
       70 
     | 
    
         
            -
                  # Replace all { 
     | 
| 
       71 
     | 
    
         
            -
                  string.gsub( 
     | 
| 
       72 
     | 
    
         
            -
                    key =  
     | 
| 
       73 
     | 
    
         
            -
                    @placeholder_values[key.to_i]
         
     | 
| 
      
 70 
     | 
    
         
            +
                  # Replace all ≬{i}####≬ with the actual content.
         
     | 
| 
      
 71 
     | 
    
         
            +
                  string.gsub(HTML_LIQUID_PLACEHOLDER) do |match|
         
     | 
| 
      
 72 
     | 
    
         
            +
                    key = /[0-9a-z]+/.match(match)[0]
         
     | 
| 
      
 73 
     | 
    
         
            +
                    @placeholder_values[key.to_i(36)]
         
     | 
| 
       74 
74 
     | 
    
         
             
                  end
         
     | 
| 
       75 
75 
     | 
    
         
             
                end
         
     | 
| 
       76 
76 
     | 
    
         
             
              end
         
     | 
| 
         @@ -9,12 +9,11 @@ module ThemeCheck 
     | 
|
| 
       9 
9 
     | 
    
         | 
| 
       10 
10 
     | 
    
         
             
                def initialize(checks)
         
     | 
| 
       11 
11 
     | 
    
         
             
                  @checks = checks
         
     | 
| 
       12 
     | 
    
         
            -
                  @placeholder_values = []
         
     | 
| 
       13 
12 
     | 
    
         
             
                end
         
     | 
| 
       14 
13 
     | 
    
         | 
| 
       15 
14 
     | 
    
         
             
                def visit_template(template)
         
     | 
| 
       16 
     | 
    
         
            -
                  doc = parse(template)
         
     | 
| 
       17 
     | 
    
         
            -
                  visit(HtmlNode.new(doc, template,  
     | 
| 
      
 15 
     | 
    
         
            +
                  doc, placeholder_values = parse(template)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  visit(HtmlNode.new(doc, template, placeholder_values))
         
     | 
| 
       18 
17 
     | 
    
         
             
                rescue ArgumentError => e
         
     | 
| 
       19 
18 
     | 
    
         
             
                  call_checks(:on_parse_error, e, template)
         
     | 
| 
       20 
19 
     | 
    
         
             
                end
         
     | 
| 
         @@ -22,19 +21,32 @@ module ThemeCheck 
     | 
|
| 
       22 
21 
     | 
    
         
             
                private
         
     | 
| 
       23 
22 
     | 
    
         | 
| 
       24 
23 
     | 
    
         
             
                def parse(template)
         
     | 
| 
      
 24 
     | 
    
         
            +
                  placeholder_values = []
         
     | 
| 
       25 
25 
     | 
    
         
             
                  parseable_source = +template.source.clone
         
     | 
| 
       26 
26 
     | 
    
         | 
| 
       27 
     | 
    
         
            -
                  # Replace all liquid tags with { 
     | 
| 
      
 27 
     | 
    
         
            +
                  # Replace all non-empty liquid tags with ≬{i}######≬ to prevent the HTML
         
     | 
| 
       28 
28 
     | 
    
         
             
                  # parser from freaking out. We transparently replace those placeholders in
         
     | 
| 
       29 
29 
     | 
    
         
             
                  # HtmlNode.
         
     | 
| 
      
 30 
     | 
    
         
            +
                  #
         
     | 
| 
      
 31 
     | 
    
         
            +
                  # We're using base36 to prevent index bleeding on 36^3 tags.
         
     | 
| 
      
 32 
     | 
    
         
            +
                  # `{{x}}` -> `≬#{i}≬` would properly be transformed for 46656 tags in a single file.
         
     | 
| 
      
 33 
     | 
    
         
            +
                  # Should be enough.
         
     | 
| 
      
 34 
     | 
    
         
            +
                  #
         
     | 
| 
      
 35 
     | 
    
         
            +
                  # The base10 alternative would have overflowed at 1000 (`{{x}}` -> `≬1000≬`) which seemed more likely.
         
     | 
| 
      
 36 
     | 
    
         
            +
                  #
         
     | 
| 
      
 37 
     | 
    
         
            +
                  # Didn't go with base64 because of the `=` character that would have messed with HTML parsing.
         
     | 
| 
       30 
38 
     | 
    
         
             
                  matches(parseable_source, LIQUID_TAG_OR_VARIABLE).each do |m|
         
     | 
| 
       31 
39 
     | 
    
         
             
                    value = m[0]
         
     | 
| 
       32 
     | 
    
         
            -
                     
     | 
| 
       33 
     | 
    
         
            -
                     
     | 
| 
       34 
     | 
    
         
            -
                     
     | 
| 
      
 40 
     | 
    
         
            +
                    next unless value.size > 4 # skip empty tags/variables {%%} and {{}}
         
     | 
| 
      
 41 
     | 
    
         
            +
                    placeholder_values.push(value)
         
     | 
| 
      
 42 
     | 
    
         
            +
                    key = (placeholder_values.size - 1).to_s(36)
         
     | 
| 
      
 43 
     | 
    
         
            +
                    parseable_source[m.begin(0)...m.end(0)] = "≬#{key.ljust(m.end(0) - m.begin(0) - 2, '#')}≬"
         
     | 
| 
       35 
44 
     | 
    
         
             
                  end
         
     | 
| 
       36 
45 
     | 
    
         | 
| 
       37 
     | 
    
         
            -
                   
     | 
| 
      
 46 
     | 
    
         
            +
                  [
         
     | 
| 
      
 47 
     | 
    
         
            +
                    Nokogiri::HTML5.fragment(parseable_source, max_tree_depth: 400, max_attributes: 400),
         
     | 
| 
      
 48 
     | 
    
         
            +
                    placeholder_values,
         
     | 
| 
      
 49 
     | 
    
         
            +
                  ]
         
     | 
| 
       38 
50 
     | 
    
         
             
                end
         
     | 
| 
       39 
51 
     | 
    
         | 
| 
       40 
52 
     | 
    
         
             
                def visit(node)
         
     | 
| 
         @@ -20,14 +20,19 @@ module ThemeCheck 
     | 
|
| 
       20 
20 
     | 
    
         
             
                  @parser_error
         
     | 
| 
       21 
21 
     | 
    
         
             
                end
         
     | 
| 
       22 
22 
     | 
    
         | 
| 
       23 
     | 
    
         
            -
                def update_contents(new_content =  
     | 
| 
      
 23 
     | 
    
         
            +
                def update_contents(new_content = {})
         
     | 
| 
      
 24 
     | 
    
         
            +
                  raise ArgumentError if new_content.is_a?(String)
         
     | 
| 
       24 
25 
     | 
    
         
             
                  @content = new_content
         
     | 
| 
       25 
26 
     | 
    
         
             
                end
         
     | 
| 
       26 
27 
     | 
    
         | 
| 
       27 
28 
     | 
    
         
             
                def write
         
     | 
| 
       28 
     | 
    
         
            -
                   
     | 
| 
       29 
     | 
    
         
            -
             
     | 
| 
       30 
     | 
    
         
            -
                     
     | 
| 
      
 29 
     | 
    
         
            +
                  pretty = JSON.pretty_generate(@content)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  if source.rstrip != pretty.rstrip
         
     | 
| 
      
 31 
     | 
    
         
            +
                    # Most editors add a trailing \n at the end of files. Here we
         
     | 
| 
      
 32 
     | 
    
         
            +
                    # try to maintain the convention.
         
     | 
| 
      
 33 
     | 
    
         
            +
                    eof = source.end_with?("\n") ? "\n" : ""
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @storage.write(@relative_path, pretty.gsub("\n", @eol) + eof)
         
     | 
| 
      
 35 
     | 
    
         
            +
                    @source = pretty
         
     | 
| 
       31 
36 
     | 
    
         
             
                  end
         
     | 
| 
       32 
37 
     | 
    
         
             
                end
         
     | 
| 
       33 
38 
     | 
    
         | 
| 
         @@ -3,9 +3,13 @@ require 'json' 
     | 
|
| 
       3 
3 
     | 
    
         | 
| 
       4 
4 
     | 
    
         
             
            module ThemeCheck
         
     | 
| 
       5 
5 
     | 
    
         
             
              class JsonPrinter
         
     | 
| 
      
 6 
     | 
    
         
            +
                def initialize(out_stream = STDOUT)
         
     | 
| 
      
 7 
     | 
    
         
            +
                  @out = out_stream
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
       6 
10 
     | 
    
         
             
                def print(offenses)
         
     | 
| 
       7 
11 
     | 
    
         
             
                  json = offenses_by_path(offenses)
         
     | 
| 
       8 
     | 
    
         
            -
                  puts JSON.dump(json)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @out.puts JSON.dump(json)
         
     | 
| 
       9 
13 
     | 
    
         
             
                end
         
     | 
| 
       10 
14 
     | 
    
         | 
| 
       11 
15 
     | 
    
         
             
                def offenses_by_path(offenses)
         
     | 
| 
         @@ -21,6 +25,7 @@ module ThemeCheck 
     | 
|
| 
       21 
25 
     | 
    
         
             
                        styleCount: path_offenses.count { |offense| offense[:severity] == Check::SEVERITY_VALUES[:style] },
         
     | 
| 
       22 
26 
     | 
    
         
             
                      }
         
     | 
| 
       23 
27 
     | 
    
         
             
                    end
         
     | 
| 
      
 28 
     | 
    
         
            +
                    .sort_by { |o| o[:path] }
         
     | 
| 
       24 
29 
     | 
    
         
             
                end
         
     | 
| 
       25 
30 
     | 
    
         
             
              end
         
     | 
| 
       26 
31 
     | 
    
         
             
            end
         
     | 
| 
         @@ -5,6 +5,7 @@ module ThemeCheck 
     | 
|
| 
       5 
5 
     | 
    
         
             
                class DocumentLinkProvider
         
     | 
| 
       6 
6 
     | 
    
         
             
                  include RegexHelpers
         
     | 
| 
       7 
7 
     | 
    
         
             
                  include PositionHelper
         
     | 
| 
      
 8 
     | 
    
         
            +
                  include URIHelper
         
     | 
| 
       8 
9 
     | 
    
         | 
| 
       9 
10 
     | 
    
         
             
                  class << self
         
     | 
| 
       10 
11 
     | 
    
         
             
                    attr_accessor :partial_regexp, :destination_directory, :destination_postfix
         
     | 
| 
         @@ -63,7 +64,7 @@ module ThemeCheck 
     | 
|
| 
       63 
64 
     | 
    
         
             
                  end
         
     | 
| 
       64 
65 
     | 
    
         | 
| 
       65 
66 
     | 
    
         
             
                  def file_link(partial)
         
     | 
| 
       66 
     | 
    
         
            -
                     
     | 
| 
      
 67 
     | 
    
         
            +
                    file_uri(@storage.path(destination_directory + '/' + partial + destination_postfix))
         
     | 
| 
       67 
68 
     | 
    
         
             
                  end
         
     | 
| 
       68 
69 
     | 
    
         
             
                end
         
     | 
| 
       69 
70 
     | 
    
         
             
              end
         
     | 
| 
         @@ -1,11 +1,12 @@ 
     | 
|
| 
       1 
1 
     | 
    
         
             
            # frozen_string_literal: true
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
       2 
3 
     | 
    
         
             
            require "benchmark"
         
     | 
| 
       3 
     | 
    
         
            -
            require "uri"
         
     | 
| 
       4 
     | 
    
         
            -
            require "cgi"
         
     | 
| 
       5 
4 
     | 
    
         | 
| 
       6 
5 
     | 
    
         
             
            module ThemeCheck
         
     | 
| 
       7 
6 
     | 
    
         
             
              module LanguageServer
         
     | 
| 
       8 
7 
     | 
    
         
             
                class Handler
         
     | 
| 
      
 8 
     | 
    
         
            +
                  include URIHelper
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
       9 
10 
     | 
    
         
             
                  CAPABILITIES = {
         
     | 
| 
       10 
11 
     | 
    
         
             
                    completionProvider: {
         
     | 
| 
       11 
12 
     | 
    
         
             
                      triggerCharacters: ['.', '{{ ', '{% '],
         
     | 
| 
         @@ -26,7 +27,7 @@ module ThemeCheck 
     | 
|
| 
       26 
27 
     | 
    
         
             
                  end
         
     | 
| 
       27 
28 
     | 
    
         | 
| 
       28 
29 
     | 
    
         
             
                  def on_initialize(id, params)
         
     | 
| 
       29 
     | 
    
         
            -
                    @root_path =  
     | 
| 
      
 30 
     | 
    
         
            +
                    @root_path = root_path_from_params(params)
         
     | 
| 
       30 
31 
     | 
    
         | 
| 
       31 
32 
     | 
    
         
             
                    # Tell the client we don't support anything if there's no rootPath
         
     | 
| 
       32 
33 
     | 
    
         
             
                    return send_response(id, { capabilities: {} }) if @root_path.nil?
         
     | 
| 
         @@ -96,19 +97,23 @@ module ThemeCheck 
     | 
|
| 
       96 
97 
     | 
    
         
             
                  end
         
     | 
| 
       97 
98 
     | 
    
         | 
| 
       98 
99 
     | 
    
         
             
                  def text_document_uri(params)
         
     | 
| 
       99 
     | 
    
         
            -
                     
     | 
| 
       100 
     | 
    
         
            -
                  end
         
     | 
| 
       101 
     | 
    
         
            -
             
     | 
| 
       102 
     | 
    
         
            -
                  def path_from_uri(uri_string)
         
     | 
| 
       103 
     | 
    
         
            -
                    return if uri_string.nil?
         
     | 
| 
       104 
     | 
    
         
            -
                    uri = URI(uri_string)
         
     | 
| 
       105 
     | 
    
         
            -
                    CGI.unescape(uri.path)
         
     | 
| 
      
 100 
     | 
    
         
            +
                    file_path(params.dig('textDocument', 'uri'))
         
     | 
| 
       106 
101 
     | 
    
         
             
                  end
         
     | 
| 
       107 
102 
     | 
    
         | 
| 
       108 
103 
     | 
    
         
             
                  def relative_path_from_text_document_uri(params)
         
     | 
| 
       109 
104 
     | 
    
         
             
                    @storage.relative_path(text_document_uri(params))
         
     | 
| 
       110 
105 
     | 
    
         
             
                  end
         
     | 
| 
       111 
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 
     | 
    
         
            +
             
     | 
| 
       112 
117 
     | 
    
         
             
                  def text_document_text(params)
         
     | 
| 
       113 
118 
     | 
    
         
             
                    params.dig('textDocument', 'text')
         
     | 
| 
       114 
119 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -174,7 +179,7 @@ module ThemeCheck 
     | 
|
| 
       174 
179 
     | 
    
         
             
                  def send_diagnostic(path, offenses)
         
     | 
| 
       175 
180 
     | 
    
         
             
                    # https://microsoft.github.io/language-server-protocol/specifications/specification-current/#notificationMessage
         
     | 
| 
       176 
181 
     | 
    
         
             
                    send_notification('textDocument/publishDiagnostics', {
         
     | 
| 
       177 
     | 
    
         
            -
                      uri:  
     | 
| 
      
 182 
     | 
    
         
            +
                      uri: file_uri(path),
         
     | 
| 
       178 
183 
     | 
    
         
             
                      diagnostics: offenses.map { |offense| offense_to_diagnostic(offense) },
         
     | 
| 
       179 
184 
     | 
    
         
             
                    })
         
     | 
| 
       180 
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 
     | 
    
         | 
| 
         @@ -52,19 +61,8 @@ module ThemeCheck 
     | 
|
| 
       52 
61 
     | 
    
         
             
                    response_body = JSON.dump(response)
         
     | 
| 
       53 
62 
     | 
    
         
             
                    log(JSON.pretty_generate(response)) if $DEBUG
         
     | 
| 
       54 
63 
     | 
    
         | 
| 
       55 
     | 
    
         
            -
                    # 
     | 
| 
       56 
     | 
    
         
            -
                     
     | 
| 
       57 
     | 
    
         
            -
                    # Ruby on Windows turns \n into \r\n. Which means that \r\n
         
     | 
| 
       58 
     | 
    
         
            -
                    # gets turned into \r\r\n. Which means that the protocol
         
     | 
| 
       59 
     | 
    
         
            -
                    # breaks on windows unless we turn STDOUT into binary mode and
         
     | 
| 
       60 
     | 
    
         
            -
                    # set the encoding manually (yuk!) or we do this little hack
         
     | 
| 
       61 
     | 
    
         
            -
                    # here and put \n which gets transformed into \r\n on windows
         
     | 
| 
       62 
     | 
    
         
            -
                    # only...
         
     | 
| 
       63 
     | 
    
         
            -
                    #
         
     | 
| 
       64 
     | 
    
         
            -
                    # Hours wasted: 8.
         
     | 
| 
       65 
     | 
    
         
            -
                    eol = Gem.win_platform? ? "\n" : "\r\n"
         
     | 
| 
       66 
     | 
    
         
            -
                    @out.write("Content-Length: #{response_body.bytesize}#{eol}")
         
     | 
| 
       67 
     | 
    
         
            -
                    @out.write(eol)
         
     | 
| 
      
 64 
     | 
    
         
            +
                    @out.write("Content-Length: #{response_body.bytesize}\r\n")
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @out.write("\r\n")
         
     | 
| 
       68 
66 
     | 
    
         
             
                    @out.write(response_body)
         
     | 
| 
       69 
67 
     | 
    
         
             
                    @out.flush
         
     | 
| 
       70 
68 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -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"
         
     | 
    
        data/lib/theme_check/node.rb
    CHANGED
    
    | 
         @@ -10,12 +10,14 @@ module ThemeCheck 
     | 
|
| 
       10 
10 
     | 
    
         
             
                  @value = value
         
     | 
| 
       11 
11 
     | 
    
         
             
                  @parent = parent
         
     | 
| 
       12 
12 
     | 
    
         
             
                  @template = template
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @tag_markup = nil
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @line_number_offset = 0
         
     | 
| 
       13 
15 
     | 
    
         
             
                end
         
     | 
| 
       14 
16 
     | 
    
         | 
| 
       15 
17 
     | 
    
         
             
                # The original source code of the node. Doesn't contain wrapping braces.
         
     | 
| 
       16 
18 
     | 
    
         
             
                def markup
         
     | 
| 
       17 
19 
     | 
    
         
             
                  if tag?
         
     | 
| 
       18 
     | 
    
         
            -
                     
     | 
| 
      
 20 
     | 
    
         
            +
                    tag_markup
         
     | 
| 
       19 
21 
     | 
    
         
             
                  elsif @value.instance_variable_defined?(:@markup)
         
     | 
| 
       20 
22 
     | 
    
         
             
                    @value.instance_variable_get(:@markup)
         
     | 
| 
       21 
23 
     | 
    
         
             
                  end
         
     | 
| 
         @@ -64,6 +66,10 @@ module ThemeCheck 
     | 
|
| 
       64 
66 
     | 
    
         
             
                  @value.is_a?(Liquid::Tag)
         
     | 
| 
       65 
67 
     | 
    
         
             
                end
         
     | 
| 
       66 
68 
     | 
    
         | 
| 
      
 69 
     | 
    
         
            +
                def variable?
         
     | 
| 
      
 70 
     | 
    
         
            +
                  @value.is_a?(Liquid::Variable)
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
             
     | 
| 
       67 
73 
     | 
    
         
             
                # A {% comment %} block node?
         
     | 
| 
       68 
74 
     | 
    
         
             
                def comment?
         
     | 
| 
       69 
75 
     | 
    
         
             
                  @value.is_a?(Liquid::Comment)
         
     | 
| 
         @@ -92,7 +98,12 @@ module ThemeCheck 
     | 
|
| 
       92 
98 
     | 
    
         | 
| 
       93 
99 
     | 
    
         
             
                # Most nodes have a line number, but it's not guaranteed.
         
     | 
| 
       94 
100 
     | 
    
         
             
                def line_number
         
     | 
| 
       95 
     | 
    
         
            -
                   
     | 
| 
      
 101 
     | 
    
         
            +
                  if tag? && @value.respond_to?(:line_number)
         
     | 
| 
      
 102 
     | 
    
         
            +
                    markup # initialize the line_number_offset
         
     | 
| 
      
 103 
     | 
    
         
            +
                    @value.line_number - @line_number_offset
         
     | 
| 
      
 104 
     | 
    
         
            +
                  elsif @value.respond_to?(:line_number)
         
     | 
| 
      
 105 
     | 
    
         
            +
                    @value.line_number
         
     | 
| 
      
 106 
     | 
    
         
            +
                  end
         
     | 
| 
       96 
107 
     | 
    
         
             
                end
         
     | 
| 
       97 
108 
     | 
    
         | 
| 
       98 
109 
     | 
    
         
             
                # The `:under_score_name` of this type of node. Used to dispatch to the `on_<type_name>`
         
     | 
| 
         @@ -101,27 +112,48 @@ module ThemeCheck 
     | 
|
| 
       101 
112 
     | 
    
         
             
                  @type_name ||= StringHelpers.underscore(StringHelpers.demodulize(@value.class.name)).to_sym
         
     | 
| 
       102 
113 
     | 
    
         
             
                end
         
     | 
| 
       103 
114 
     | 
    
         | 
| 
      
 115 
     | 
    
         
            +
                def source
         
     | 
| 
      
 116 
     | 
    
         
            +
                  template&.source
         
     | 
| 
      
 117 
     | 
    
         
            +
                end
         
     | 
| 
      
 118 
     | 
    
         
            +
             
     | 
| 
      
 119 
     | 
    
         
            +
                WHITESPACE = /\s/
         
     | 
| 
      
 120 
     | 
    
         
            +
             
     | 
| 
       104 
121 
     | 
    
         
             
                # Is this node inside a `{% liquid ... %}` block?
         
     | 
| 
       105 
122 
     | 
    
         
             
                def inside_liquid_tag?
         
     | 
| 
       106 
     | 
    
         
            -
                   
     | 
| 
       107 
     | 
    
         
            -
             
     | 
| 
      
 123 
     | 
    
         
            +
                  # What we're doing here is starting at the start of the tag and
         
     | 
| 
      
 124 
     | 
    
         
            +
                  # backtrack on all the whitespace until we land on something. If
         
     | 
| 
      
 125 
     | 
    
         
            +
                  # that something is {% or %-, then we can safely assume that
         
     | 
| 
      
 126 
     | 
    
         
            +
                  # we're inside a full tag and not a liquid tag.
         
     | 
| 
      
 127 
     | 
    
         
            +
                  @inside_liquid_tag ||= if tag? && line_number && source
         
     | 
| 
      
 128 
     | 
    
         
            +
                    i = 1
         
     | 
| 
      
 129 
     | 
    
         
            +
                    i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
         
     | 
| 
      
 130 
     | 
    
         
            +
                    first_two_backtracked_characters = source[(start_index - i - 1)..(start_index - i)]
         
     | 
| 
      
 131 
     | 
    
         
            +
                    first_two_backtracked_characters != "{%" && first_two_backtracked_characters != "%-"
         
     | 
| 
       108 
132 
     | 
    
         
             
                  else
         
     | 
| 
       109 
133 
     | 
    
         
             
                    false
         
     | 
| 
       110 
134 
     | 
    
         
             
                  end
         
     | 
| 
       111 
135 
     | 
    
         
             
                end
         
     | 
| 
       112 
136 
     | 
    
         | 
| 
       113 
     | 
    
         
            -
                # Is this node inside a  
     | 
| 
       114 
     | 
    
         
            -
                def  
     | 
| 
       115 
     | 
    
         
            -
                  if line_number
         
     | 
| 
       116 
     | 
    
         
            -
                     
     | 
| 
      
 137 
     | 
    
         
            +
                # Is this node inside a tag or variable that starts by removing whitespace. i.e. {%- or {{-
         
     | 
| 
      
 138 
     | 
    
         
            +
                def whitespace_trimmed_start?
         
     | 
| 
      
 139 
     | 
    
         
            +
                  @whitespace_trimmed_start ||= if line_number && source && !inside_liquid_tag?
         
     | 
| 
      
 140 
     | 
    
         
            +
                    i = 1
         
     | 
| 
      
 141 
     | 
    
         
            +
                    i += 1 while source[start_index - i] =~ WHITESPACE && i < start_index
         
     | 
| 
      
 142 
     | 
    
         
            +
                    source[start_index - i] == "-"
         
     | 
| 
       117 
143 
     | 
    
         
             
                  else
         
     | 
| 
       118 
144 
     | 
    
         
             
                    false
         
     | 
| 
       119 
145 
     | 
    
         
             
                  end
         
     | 
| 
       120 
146 
     | 
    
         
             
                end
         
     | 
| 
       121 
147 
     | 
    
         | 
| 
       122 
     | 
    
         
            -
                 
     | 
| 
       123 
     | 
    
         
            -
             
     | 
| 
       124 
     | 
    
         
            -
                   
     | 
| 
      
 148 
     | 
    
         
            +
                # Is this node inside a tag or variable ends starts by removing whitespace. i.e. -%} or -}}
         
     | 
| 
      
 149 
     | 
    
         
            +
                def whitespace_trimmed_end?
         
     | 
| 
      
 150 
     | 
    
         
            +
                  @whitespace_trimmed_end ||= if line_number && source && !inside_liquid_tag?
         
     | 
| 
      
 151 
     | 
    
         
            +
                    i = 0
         
     | 
| 
      
 152 
     | 
    
         
            +
                    i += 1 while source[end_index + i] =~ WHITESPACE && i < source.size
         
     | 
| 
      
 153 
     | 
    
         
            +
                    source[end_index + i] == "-"
         
     | 
| 
      
 154 
     | 
    
         
            +
                  else
         
     | 
| 
      
 155 
     | 
    
         
            +
                    false
         
     | 
| 
      
 156 
     | 
    
         
            +
                  end
         
     | 
| 
       125 
157 
     | 
    
         
             
                end
         
     | 
| 
       126 
158 
     | 
    
         | 
| 
       127 
159 
     | 
    
         
             
                def position
         
     | 
| 
         @@ -139,5 +171,80 @@ module ThemeCheck 
     | 
|
| 
       139 
171 
     | 
    
         
             
                def end_index
         
     | 
| 
       140 
172 
     | 
    
         
             
                  position.end_index
         
     | 
| 
       141 
173 
     | 
    
         
             
                end
         
     | 
| 
      
 174 
     | 
    
         
            +
             
     | 
| 
      
 175 
     | 
    
         
            +
                def start_token
         
     | 
| 
      
 176 
     | 
    
         
            +
                  return "" if inside_liquid_tag?
         
     | 
| 
      
 177 
     | 
    
         
            +
                  output = ""
         
     | 
| 
      
 178 
     | 
    
         
            +
                  output += "{{" if variable?
         
     | 
| 
      
 179 
     | 
    
         
            +
                  output += "{%" if tag?
         
     | 
| 
      
 180 
     | 
    
         
            +
                  output += "-" if whitespace_trimmed_start?
         
     | 
| 
      
 181 
     | 
    
         
            +
                  output
         
     | 
| 
      
 182 
     | 
    
         
            +
                end
         
     | 
| 
      
 183 
     | 
    
         
            +
             
     | 
| 
      
 184 
     | 
    
         
            +
                def end_token
         
     | 
| 
      
 185 
     | 
    
         
            +
                  return "" if inside_liquid_tag?
         
     | 
| 
      
 186 
     | 
    
         
            +
                  output = ""
         
     | 
| 
      
 187 
     | 
    
         
            +
                  output += "-" if whitespace_trimmed_end?
         
     | 
| 
      
 188 
     | 
    
         
            +
                  output += "}}" if variable?
         
     | 
| 
      
 189 
     | 
    
         
            +
                  output += "%}" if tag?
         
     | 
| 
      
 190 
     | 
    
         
            +
                  output
         
     | 
| 
      
 191 
     | 
    
         
            +
                end
         
     | 
| 
      
 192 
     | 
    
         
            +
             
     | 
| 
      
 193 
     | 
    
         
            +
                private
         
     | 
| 
      
 194 
     | 
    
         
            +
             
     | 
| 
      
 195 
     | 
    
         
            +
                # Here we're hacking around a glorious bug in Liquid that makes it so the
         
     | 
| 
      
 196 
     | 
    
         
            +
                # line_number and markup of a tag is wrong if there's whitespace
         
     | 
| 
      
 197 
     | 
    
         
            +
                # between the tag_name and the markup of the tag.
         
     | 
| 
      
 198 
     | 
    
         
            +
                #
         
     | 
| 
      
 199 
     | 
    
         
            +
                # {%
         
     | 
| 
      
 200 
     | 
    
         
            +
                #   render
         
     | 
| 
      
 201 
     | 
    
         
            +
                #   'foo'
         
     | 
| 
      
 202 
     | 
    
         
            +
                # %}
         
     | 
| 
      
 203 
     | 
    
         
            +
                #
         
     | 
| 
      
 204 
     | 
    
         
            +
                # Returns a raw value of "render 'foo'\n".
         
     | 
| 
      
 205 
     | 
    
         
            +
                # The "\n  " between render and 'foo' got replaced by a single space.
         
     | 
| 
      
 206 
     | 
    
         
            +
                #
         
     | 
| 
      
 207 
     | 
    
         
            +
                # And the line number is the one of 'foo'\n%}. Yay!
         
     | 
| 
      
 208 
     | 
    
         
            +
                #
         
     | 
| 
      
 209 
     | 
    
         
            +
                # This breaks any kind of position logic we have since that string
         
     | 
| 
      
 210 
     | 
    
         
            +
                # does not exist in the template.
         
     | 
| 
      
 211 
     | 
    
         
            +
                def tag_markup
         
     | 
| 
      
 212 
     | 
    
         
            +
                  return @value.raw if @value.instance_variable_get('@markup').empty?
         
     | 
| 
      
 213 
     | 
    
         
            +
                  return @tag_markup if @tag_markup
         
     | 
| 
      
 214 
     | 
    
         
            +
             
     | 
| 
      
 215 
     | 
    
         
            +
                  l = 1
         
     | 
| 
      
 216 
     | 
    
         
            +
                  scanner = StringScanner.new(source)
         
     | 
| 
      
 217 
     | 
    
         
            +
                  scanner.scan_until(/\n/) while l < @value.line_number && (l += 1)
         
     | 
| 
      
 218 
     | 
    
         
            +
                  start = scanner.charpos
         
     | 
| 
      
 219 
     | 
    
         
            +
             
     | 
| 
      
 220 
     | 
    
         
            +
                  tag_markup = @value.instance_variable_get('@markup')
         
     | 
| 
      
 221 
     | 
    
         
            +
             
     | 
| 
      
 222 
     | 
    
         
            +
                  # See https://github.com/Shopify/theme-check/pull/423/files#r701936559 for a detailed explanation
         
     | 
| 
      
 223 
     | 
    
         
            +
                  # of why we're doing the check below.
         
     | 
| 
      
 224 
     | 
    
         
            +
                  #
         
     | 
| 
      
 225 
     | 
    
         
            +
                  # TL;DR it's because line_numbers are not enough to accurately
         
     | 
| 
      
 226 
     | 
    
         
            +
                  # determine the position of the raw markup and because that
         
     | 
| 
      
 227 
     | 
    
         
            +
                  # markup could be present on the same line outside of a Tag. e.g.
         
     | 
| 
      
 228 
     | 
    
         
            +
                  #
         
     | 
| 
      
 229 
     | 
    
         
            +
                  # uhoh {% if uhoh %}
         
     | 
| 
      
 230 
     | 
    
         
            +
                  if (match = /#{@value.tag_name} +#{Regexp.escape(tag_markup)}/.match(source, start))
         
     | 
| 
      
 231 
     | 
    
         
            +
                    return @tag_markup = match[0]
         
     | 
| 
      
 232 
     | 
    
         
            +
                  end
         
     | 
| 
      
 233 
     | 
    
         
            +
             
     | 
| 
      
 234 
     | 
    
         
            +
                  # find the markup
         
     | 
| 
      
 235 
     | 
    
         
            +
                  markup_start = source.index(tag_markup, start)
         
     | 
| 
      
 236 
     | 
    
         
            +
                  markup_end = markup_start + tag_markup.size
         
     | 
| 
      
 237 
     | 
    
         
            +
             
     | 
| 
      
 238 
     | 
    
         
            +
                  # go back until you find the tag_name
         
     | 
| 
      
 239 
     | 
    
         
            +
                  tag_start = markup_start
         
     | 
| 
      
 240 
     | 
    
         
            +
                  tag_start -= 1 while source[tag_start - 1] =~ WHITESPACE
         
     | 
| 
      
 241 
     | 
    
         
            +
                  tag_start -= @value.tag_name.size
         
     | 
| 
      
 242 
     | 
    
         
            +
             
     | 
| 
      
 243 
     | 
    
         
            +
                  # keep track of the error in line_number
         
     | 
| 
      
 244 
     | 
    
         
            +
                  @line_number_offset = source[tag_start...markup_start].count("\n")
         
     | 
| 
      
 245 
     | 
    
         
            +
             
     | 
| 
      
 246 
     | 
    
         
            +
                  # return the real raw content
         
     | 
| 
      
 247 
     | 
    
         
            +
                  @tag_markup = source[tag_start...markup_end]
         
     | 
| 
      
 248 
     | 
    
         
            +
                end
         
     | 
| 
       142 
249 
     | 
    
         
             
              end
         
     | 
| 
       143 
250 
     | 
    
         
             
            end
         
     |