theme-check 0.8.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/workflows/theme-check.yml +3 -0
- data/CHANGELOG.md +44 -0
- data/CONTRIBUTING.md +2 -1
- data/README.md +4 -1
- data/RELEASING.md +5 -3
- data/config/default.yml +42 -1
- data/data/shopify_liquid/tags.yml +3 -0
- data/data/shopify_translation_keys.yml +1 -0
- data/docs/checks/asset_url_filters.md +56 -0
- data/docs/checks/content_for_header_modification.md +42 -0
- data/docs/checks/nested_snippet.md +1 -1
- data/docs/checks/parser_blocking_script_tag.md +53 -0
- data/docs/checks/space_inside_braces.md +28 -0
- data/exe/theme-check-language-server +1 -2
- data/lib/theme_check.rb +13 -1
- data/lib/theme_check/analyzer.rb +79 -13
- data/lib/theme_check/bug.rb +20 -0
- data/lib/theme_check/check.rb +36 -7
- data/lib/theme_check/checks.rb +47 -8
- data/lib/theme_check/checks/asset_url_filters.rb +46 -0
- data/lib/theme_check/checks/content_for_header_modification.rb +41 -0
- data/lib/theme_check/checks/img_width_and_height.rb +18 -49
- data/lib/theme_check/checks/missing_enable_comment.rb +4 -4
- data/lib/theme_check/checks/missing_template.rb +1 -0
- data/lib/theme_check/checks/nested_snippet.rb +1 -1
- data/lib/theme_check/checks/parser_blocking_javascript.rb +6 -38
- data/lib/theme_check/checks/parser_blocking_script_tag.rb +20 -0
- data/lib/theme_check/checks/remote_asset.rb +21 -79
- data/lib/theme_check/checks/space_inside_braces.rb +8 -2
- data/lib/theme_check/checks/template_length.rb +3 -0
- data/lib/theme_check/checks/valid_html_translation.rb +1 -0
- data/lib/theme_check/config.rb +2 -0
- data/lib/theme_check/disabled_check.rb +41 -0
- data/lib/theme_check/disabled_checks.rb +33 -29
- data/lib/theme_check/exceptions.rb +32 -0
- data/lib/theme_check/html_check.rb +7 -0
- data/lib/theme_check/html_node.rb +56 -0
- data/lib/theme_check/html_visitor.rb +38 -0
- data/lib/theme_check/json_file.rb +13 -1
- data/lib/theme_check/language_server.rb +2 -1
- data/lib/theme_check/language_server/completion_engine.rb +1 -1
- data/lib/theme_check/language_server/completion_providers/filter_completion_provider.rb +1 -0
- data/lib/theme_check/language_server/completion_providers/object_completion_provider.rb +10 -8
- data/lib/theme_check/language_server/constants.rb +5 -1
- data/lib/theme_check/language_server/diagnostics_tracker.rb +64 -0
- data/lib/theme_check/language_server/document_link_engine.rb +2 -2
- data/lib/theme_check/language_server/handler.rb +63 -50
- data/lib/theme_check/language_server/server.rb +1 -1
- data/lib/theme_check/language_server/variable_lookup_finder.rb +295 -0
- data/lib/theme_check/liquid_check.rb +1 -4
- data/lib/theme_check/node.rb +12 -0
- data/lib/theme_check/offense.rb +30 -46
- data/lib/theme_check/position.rb +77 -0
- data/lib/theme_check/position_helper.rb +37 -0
- data/lib/theme_check/remote_asset_file.rb +3 -0
- data/lib/theme_check/shopify_liquid/tag.rb +13 -0
- data/lib/theme_check/template.rb +8 -0
- data/lib/theme_check/theme.rb +7 -2
- data/lib/theme_check/version.rb +1 -1
- data/lib/theme_check/visitor.rb +4 -14
- metadata +19 -4
- data/lib/theme_check/language_server/position_helper.rb +0 -27
| @@ -17,13 +17,13 @@ module ThemeCheck | |
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 19 | 
             
                def after_document(node)
         | 
| 20 | 
            -
                   | 
| 21 | 
            -
                  return  | 
| 20 | 
            +
                  checks_missing_end_index = @disabled_checks.checks_missing_end_index
         | 
| 21 | 
            +
                  return if checks_missing_end_index.empty?
         | 
| 22 22 |  | 
| 23 | 
            -
                  message = if  | 
| 23 | 
            +
                  message = if checks_missing_end_index.any? { |name| name == :all }
         | 
| 24 24 | 
             
                    "All checks were"
         | 
| 25 25 | 
             
                  else
         | 
| 26 | 
            -
                     | 
| 26 | 
            +
                    checks_missing_end_index.join(', ') + " " + (checks_missing_end_index.size == 1 ? "was" : "were")
         | 
| 27 27 | 
             
                  end
         | 
| 28 28 |  | 
| 29 29 | 
             
                  add_offense("#{message} disabled but not re-enabled with theme-check-enable", node: node)
         | 
| @@ -1,48 +1,16 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 | 
             
            module ThemeCheck
         | 
| 3 3 | 
             
              # Reports errors when trying to use parser-blocking script tags
         | 
| 4 | 
            -
              class ParserBlockingJavaScript <  | 
| 5 | 
            -
                include RegexHelpers
         | 
| 4 | 
            +
              class ParserBlockingJavaScript < HtmlCheck
         | 
| 6 5 | 
             
                severity :error
         | 
| 7 | 
            -
                categories : | 
| 6 | 
            +
                categories :html, :performance
         | 
| 8 7 | 
             
                doc docs_url(__FILE__)
         | 
| 9 8 |  | 
| 10 | 
            -
                 | 
| 11 | 
            -
                   | 
| 12 | 
            -
                   | 
| 13 | 
            -
                  (?:(?!defer|async|type=["']module['"]).)*? # Find tags that don't have defer|async|type="module"
         | 
| 14 | 
            -
                  /?>
         | 
| 15 | 
            -
                }xim
         | 
| 16 | 
            -
                SCRIPT_TAG_FILTER = /\{\{[^}]+script_tag\s+\}\}/
         | 
| 9 | 
            +
                def on_script(node)
         | 
| 10 | 
            +
                  return unless node.attributes["src"]
         | 
| 11 | 
            +
                  return if node.attributes["defer"] || node.attributes["async"] || node.attributes["type"]&.value == "module"
         | 
| 17 12 |  | 
| 18 | 
            -
             | 
| 19 | 
            -
                  @source = node.template.source
         | 
| 20 | 
            -
                  @node = node
         | 
| 21 | 
            -
                  record_offenses
         | 
| 22 | 
            -
                end
         | 
| 23 | 
            -
             | 
| 24 | 
            -
                private
         | 
| 25 | 
            -
             | 
| 26 | 
            -
                def record_offenses
         | 
| 27 | 
            -
                  record_offenses_from_regex(
         | 
| 28 | 
            -
                    message: "Missing async or defer attribute on script tag",
         | 
| 29 | 
            -
                    regex: PARSER_BLOCKING_SCRIPT_TAG,
         | 
| 30 | 
            -
                  )
         | 
| 31 | 
            -
                  record_offenses_from_regex(
         | 
| 32 | 
            -
                    message: "The script_tag filter is parser-blocking. Use a script tag with the async or defer attribute for better performance",
         | 
| 33 | 
            -
                    regex: SCRIPT_TAG_FILTER,
         | 
| 34 | 
            -
                  )
         | 
| 35 | 
            -
                end
         | 
| 36 | 
            -
             | 
| 37 | 
            -
                def record_offenses_from_regex(regex: nil, message: nil)
         | 
| 38 | 
            -
                  matches(@source, regex).each do |match|
         | 
| 39 | 
            -
                    add_offense(
         | 
| 40 | 
            -
                      message,
         | 
| 41 | 
            -
                      node: @node,
         | 
| 42 | 
            -
                      markup: match[0],
         | 
| 43 | 
            -
                      line_number: @source[0...match.begin(0)].count("\n") + 1
         | 
| 44 | 
            -
                    )
         | 
| 45 | 
            -
                  end
         | 
| 13 | 
            +
                  add_offense("Missing async or defer attribute on script tag", node: node)
         | 
| 46 14 | 
             
                end
         | 
| 47 15 | 
             
              end
         | 
| 48 16 | 
             
            end
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            module ThemeCheck
         | 
| 3 | 
            +
              # Reports errors when trying to use parser-blocking script tags
         | 
| 4 | 
            +
              class ParserBlockingScriptTag < LiquidCheck
         | 
| 5 | 
            +
                severity :error
         | 
| 6 | 
            +
                categories :liquid, :performance
         | 
| 7 | 
            +
                doc docs_url(__FILE__)
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                def on_variable(node)
         | 
| 10 | 
            +
                  used_filters = node.value.filters.map { |name, *_rest| name }
         | 
| 11 | 
            +
                  if used_filters.include?("script_tag")
         | 
| 12 | 
            +
                    add_offense(
         | 
| 13 | 
            +
                      "The script_tag filter is parser-blocking. Use a script tag with the async or defer " \
         | 
| 14 | 
            +
                      "attribute for better performance",
         | 
| 15 | 
            +
                      node: node
         | 
| 16 | 
            +
                    )
         | 
| 17 | 
            +
                  end
         | 
| 18 | 
            +
                end
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
            end
         | 
| @@ -1,99 +1,41 @@ | |
| 1 1 | 
             
            # frozen_string_literal: true
         | 
| 2 2 | 
             
            module ThemeCheck
         | 
| 3 | 
            -
              class RemoteAsset <  | 
| 4 | 
            -
                include RegexHelpers
         | 
| 3 | 
            +
              class RemoteAsset < HtmlCheck
         | 
| 5 4 | 
             
                severity :suggestion
         | 
| 6 | 
            -
                categories : | 
| 5 | 
            +
                categories :html, :performance
         | 
| 7 6 | 
             
                doc docs_url(__FILE__)
         | 
| 8 7 |  | 
| 9 | 
            -
                 | 
| 10 | 
            -
             | 
| 11 | 
            -
                HTML_FILTERS = [
         | 
| 12 | 
            -
                  'stylesheet_tag',
         | 
| 13 | 
            -
                  'script_tag',
         | 
| 14 | 
            -
                  'img_tag',
         | 
| 15 | 
            -
                ]
         | 
| 16 | 
            -
                ASSET_URL_FILTERS = [
         | 
| 17 | 
            -
                  'asset_url',
         | 
| 18 | 
            -
                  'asset_img_url',
         | 
| 19 | 
            -
                  'file_img_url',
         | 
| 20 | 
            -
                  'file_url',
         | 
| 21 | 
            -
                  'global_asset_url',
         | 
| 22 | 
            -
                  'img_url',
         | 
| 23 | 
            -
                  'payment_type_img_url',
         | 
| 24 | 
            -
                  'shopify_asset_url',
         | 
| 25 | 
            -
                ]
         | 
| 26 | 
            -
             | 
| 27 | 
            -
                RESOURCE_TAG = %r{<(?<tag_name>img|script|link|source)#{HTML_ATTRIBUTES}/?>}oim
         | 
| 28 | 
            -
                RESOURCE_URL = /\s(?:src|href)=(?<resource_url>#{QUOTED_LIQUID_ATTRIBUTE})/oim
         | 
| 29 | 
            -
                ASSET_URL_FILTER = /[\|\s]*(#{ASSET_URL_FILTERS.join('|')})/omi
         | 
| 8 | 
            +
                TAGS = %w[img script link source]
         | 
| 30 9 | 
             
                PROTOCOL = %r{(https?:)?//}
         | 
| 31 10 | 
             
                ABSOLUTE_PATH = %r{\A/[^/]}im
         | 
| 32 11 | 
             
                RELATIVE_PATH = %r{\A(?!#{PROTOCOL})[^/\{]}oim
         | 
| 33 | 
            -
                REL = /\srel=(?<rel>#{QUOTED_LIQUID_ATTRIBUTE})/oim
         | 
| 34 | 
            -
             | 
| 35 | 
            -
                def on_variable(node)
         | 
| 36 | 
            -
                  record_variable_offense(node)
         | 
| 37 | 
            -
                end
         | 
| 38 12 |  | 
| 39 | 
            -
                def  | 
| 40 | 
            -
                   | 
| 41 | 
            -
                  record_html_offenses(node, source)
         | 
| 42 | 
            -
                end
         | 
| 13 | 
            +
                def on_element(node)
         | 
| 14 | 
            +
                  return unless TAGS.include?(node.name)
         | 
| 43 15 |  | 
| 44 | 
            -
             | 
| 16 | 
            +
                  resource_url = node.attributes["src"]&.value || node.attributes["href"]&.value
         | 
| 17 | 
            +
                  return if resource_url.nil? || resource_url.empty?
         | 
| 45 18 |  | 
| 46 | 
            -
             | 
| 47 | 
            -
                   | 
| 48 | 
            -
                  return if  | 
| 49 | 
            -
                   | 
| 50 | 
            -
                end
         | 
| 19 | 
            +
                  # Ignore if URL is Liquid, taken care of by AssetUrlFilters check
         | 
| 20 | 
            +
                  return if resource_url =~ ABSOLUTE_PATH
         | 
| 21 | 
            +
                  return if resource_url =~ RELATIVE_PATH
         | 
| 22 | 
            +
                  return if url_hosted_by_shopify?(resource_url)
         | 
| 51 23 |  | 
| 52 | 
            -
             | 
| 53 | 
            -
                   | 
| 54 | 
            -
             | 
| 55 | 
            -
                end
         | 
| 24 | 
            +
                  # Ignore non-stylesheet rel tags
         | 
| 25 | 
            +
                  rel = node.attributes["rel"]
         | 
| 26 | 
            +
                  return if rel && rel.value != "stylesheet"
         | 
| 56 27 |  | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
                     | 
| 28 | 
            +
                  add_offense(
         | 
| 29 | 
            +
                    "Asset should be served by the Shopify CDN for better performance.",
         | 
| 30 | 
            +
                    node: node,
         | 
| 31 | 
            +
                  )
         | 
| 60 32 | 
             
                end
         | 
| 61 33 |  | 
| 62 | 
            -
                 | 
| 63 | 
            -
                # HTML AST. We have to resort to looking at the HTML with regexes
         | 
| 64 | 
            -
                # to figure out if we have a resource (stylesheet, script, or media)
         | 
| 65 | 
            -
                # that points to a remote domain.
         | 
| 66 | 
            -
                def record_html_offenses(node, source)
         | 
| 67 | 
            -
                  matches(source, RESOURCE_TAG).each do |match|
         | 
| 68 | 
            -
                    tag = match[0]
         | 
| 69 | 
            -
             | 
| 70 | 
            -
                    # We don't flag stuff without URLs
         | 
| 71 | 
            -
                    next unless tag =~ RESOURCE_URL
         | 
| 72 | 
            -
                    resource_match = Regexp.last_match
         | 
| 73 | 
            -
                    resource_url = resource_match[:resource_url].gsub(START_OR_END_QUOTE, '')
         | 
| 74 | 
            -
             | 
| 75 | 
            -
                    next if non_stylesheet_link?(tag)
         | 
| 76 | 
            -
                    next if url_hosted_by_shopify?(resource_url)
         | 
| 77 | 
            -
                    next if resource_url =~ ABSOLUTE_PATH
         | 
| 78 | 
            -
                    next if resource_url =~ RELATIVE_PATH
         | 
| 79 | 
            -
                    next if resource_url.empty?
         | 
| 80 | 
            -
             | 
| 81 | 
            -
                    start = match.begin(0) + resource_match.begin(:resource_url)
         | 
| 82 | 
            -
                    add_offense(
         | 
| 83 | 
            -
                      OFFENSE_MESSAGE,
         | 
| 84 | 
            -
                      node: node,
         | 
| 85 | 
            -
                      markup: resource_url,
         | 
| 86 | 
            -
                      line_number: source[0...start].count("\n") + 1,
         | 
| 87 | 
            -
                    )
         | 
| 88 | 
            -
                  end
         | 
| 89 | 
            -
                end
         | 
| 90 | 
            -
             | 
| 91 | 
            -
                def non_stylesheet_link?(tag)
         | 
| 92 | 
            -
                  tag =~ REL && !(Regexp.last_match[:rel] =~ /\A['"]stylesheet['"]\Z/)
         | 
| 93 | 
            -
                end
         | 
| 34 | 
            +
                private
         | 
| 94 35 |  | 
| 95 36 | 
             
                def url_hosted_by_shopify?(url)
         | 
| 96 | 
            -
                  url  | 
| 37 | 
            +
                  url.start_with?(Liquid::VariableStart) &&
         | 
| 38 | 
            +
                    AssetUrlFilters::ASSET_URL_FILTERS.any? { |filter| url.include?(filter) }
         | 
| 97 39 | 
             
                end
         | 
| 98 40 | 
             
              end
         | 
| 99 41 | 
             
            end
         | 
| @@ -15,12 +15,18 @@ module ThemeCheck | |
| 15 15 | 
             
                  return if :assign == node.type_name
         | 
| 16 16 |  | 
| 17 17 | 
             
                  outside_of_strings(node.markup) do |chunk|
         | 
| 18 | 
            -
                    chunk.scan(/([ | 
| 18 | 
            +
                    chunk.scan(/([,:|]|==|<>|<=|>=|<|>|!=)  +/) do |_match|
         | 
| 19 19 | 
             
                      add_offense("Too many spaces after '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
         | 
| 20 20 | 
             
                    end
         | 
| 21 | 
            -
                    chunk.scan(/([ | 
| 21 | 
            +
                    chunk.scan(/([,:|]|==|<>|<=|>=|<\b|>\b|!=)(\S|\z)/) do |_match|
         | 
| 22 22 | 
             
                      add_offense("Space missing after '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
         | 
| 23 23 | 
             
                    end
         | 
| 24 | 
            +
                    chunk.scan(/  (\||==|<>|<=|>=|<|>|!=)+/) do |_match|
         | 
| 25 | 
            +
                      add_offense("Too many spaces before '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
         | 
| 26 | 
            +
                    end
         | 
| 27 | 
            +
                    chunk.scan(/(\A|\S)(?<match>\||==|<>|<=|>=|<|\b>|!=)/) do |_match|
         | 
| 28 | 
            +
                      add_offense("Space missing before '#{Regexp.last_match(1)}'", node: node, markup: Regexp.last_match(0))
         | 
| 29 | 
            +
                    end
         | 
| 24 30 | 
             
                  end
         | 
| 25 31 | 
             
                end
         | 
| 26 32 |  | 
    
        data/lib/theme_check/config.rb
    CHANGED
    
    | @@ -91,11 +91,13 @@ module ThemeCheck | |
| 91 91 |  | 
| 92 92 | 
             
                    options_for_check = options.transform_keys(&:to_sym)
         | 
| 93 93 | 
             
                    options_for_check.delete(:enabled)
         | 
| 94 | 
            +
                    ignored_patterns = options_for_check.delete(:ignore) || []
         | 
| 94 95 | 
             
                    check = if options_for_check.empty?
         | 
| 95 96 | 
             
                      check_class.new
         | 
| 96 97 | 
             
                    else
         | 
| 97 98 | 
             
                      check_class.new(**options_for_check)
         | 
| 98 99 | 
             
                    end
         | 
| 100 | 
            +
                    check.ignored_patterns = ignored_patterns
         | 
| 99 101 | 
             
                    check.options = options_for_check
         | 
| 100 102 | 
             
                    check
         | 
| 101 103 | 
             
                  end.compact
         | 
| @@ -0,0 +1,41 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            # This class keeps track of checks being turned on and off in ranges.
         | 
| 4 | 
            +
            # We'll use the node position to figure out if the test is disabled or not.
         | 
| 5 | 
            +
            module ThemeCheck
         | 
| 6 | 
            +
              class DisabledCheck
         | 
| 7 | 
            +
                attr_reader :name, :template, :ranges
         | 
| 8 | 
            +
                attr_accessor :first_line
         | 
| 9 | 
            +
             | 
| 10 | 
            +
                def initialize(template, name)
         | 
| 11 | 
            +
                  @template = template
         | 
| 12 | 
            +
                  @name = name
         | 
| 13 | 
            +
                  @ranges = []
         | 
| 14 | 
            +
                  @first_line = false
         | 
| 15 | 
            +
                end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                def start_index=(index)
         | 
| 18 | 
            +
                  return unless ranges.empty? || !last.end.nil?
         | 
| 19 | 
            +
                  @ranges << (index..)
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
             | 
| 22 | 
            +
                def end_index=(index)
         | 
| 23 | 
            +
                  return if ranges.empty? || !last.end.nil?
         | 
| 24 | 
            +
                  @ranges << (@ranges.pop.begin..index)
         | 
| 25 | 
            +
                end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                def disabled?(index)
         | 
| 28 | 
            +
                  index == 0 && first_line ||
         | 
| 29 | 
            +
                    ranges.any? { |range| range.cover?(index) }
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                def last
         | 
| 33 | 
            +
                  ranges.last
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
             | 
| 36 | 
            +
                def missing_end_index?
         | 
| 37 | 
            +
                  return false if first_line && ranges.size == 1
         | 
| 38 | 
            +
                  last&.end.nil?
         | 
| 39 | 
            +
                end
         | 
| 40 | 
            +
              end
         | 
| 41 | 
            +
            end
         | 
| @@ -8,50 +8,52 @@ module ThemeCheck | |
| 8 8 |  | 
| 9 9 | 
             
                ACTION_DISABLE_CHECKS = :disable
         | 
| 10 10 | 
             
                ACTION_ENABLE_CHECKS = :enable
         | 
| 11 | 
            -
                ACTION_UNRELATED_COMMENT = :unrelated
         | 
| 12 11 |  | 
| 13 12 | 
             
                def initialize
         | 
| 14 | 
            -
                  @ | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 13 | 
            +
                  @disabled_checks = Hash.new do |hash, key|
         | 
| 14 | 
            +
                    template, check_name = key
         | 
| 15 | 
            +
                    hash[key] = DisabledCheck.new(template, check_name)
         | 
| 16 | 
            +
                  end
         | 
| 17 17 | 
             
                end
         | 
| 18 18 |  | 
| 19 19 | 
             
                def update(node)
         | 
| 20 20 | 
             
                  text = comment_text(node)
         | 
| 21 | 
            -
             | 
| 22 21 | 
             
                  if start_disabling?(text)
         | 
| 23 | 
            -
                     | 
| 24 | 
            -
             | 
| 25 | 
            -
             | 
| 26 | 
            -
             | 
| 27 | 
            -
                      @full_document_disabled = true
         | 
| 22 | 
            +
                    checks_from_text(text).each do |check_name|
         | 
| 23 | 
            +
                      disabled = @disabled_checks[[node.template, check_name]]
         | 
| 24 | 
            +
                      disabled.start_index = node.start_index
         | 
| 25 | 
            +
                      disabled.first_line = true if node.line_number == 1
         | 
| 28 26 | 
             
                    end
         | 
| 29 27 | 
             
                  elsif stop_disabling?(text)
         | 
| 30 | 
            -
                     | 
| 31 | 
            -
             | 
| 32 | 
            -
             | 
| 33 | 
            -
             | 
| 28 | 
            +
                    checks_from_text(text).each do |check_name|
         | 
| 29 | 
            +
                      disabled = @disabled_checks[[node.template, check_name]]
         | 
| 30 | 
            +
                      next unless disabled
         | 
| 31 | 
            +
                      disabled.end_index = node.end_index
         | 
| 32 | 
            +
                    end
         | 
| 34 33 | 
             
                  end
         | 
| 35 34 | 
             
                end
         | 
| 36 35 |  | 
| 37 | 
            -
                 | 
| 38 | 
            -
             | 
| 39 | 
            -
             | 
| 40 | 
            -
             | 
| 36 | 
            +
                def disabled?(check, template, check_name, index)
         | 
| 37 | 
            +
                  return true if check.ignored_patterns&.any? do |pattern|
         | 
| 38 | 
            +
                    template.relative_path.fnmatch?(pattern)
         | 
| 39 | 
            +
                  end
         | 
| 41 40 |  | 
| 42 | 
            -
             | 
| 43 | 
            -
             | 
| 44 | 
            -
                  @all_disabled
         | 
| 41 | 
            +
                  @disabled_checks[[template, :all]]&.disabled?(index) ||
         | 
| 42 | 
            +
                    @disabled_checks[[template, check_name]]&.disabled?(index)
         | 
| 45 43 | 
             
                end
         | 
| 46 44 |  | 
| 47 | 
            -
                 | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 45 | 
            +
                def checks_missing_end_index
         | 
| 46 | 
            +
                  @disabled_checks.values
         | 
| 47 | 
            +
                    .select(&:missing_end_index?)
         | 
| 48 | 
            +
                    .map(&:name)
         | 
| 50 49 | 
             
                end
         | 
| 51 50 |  | 
| 52 | 
            -
                 | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 51 | 
            +
                def remove_disabled_offenses(checks)
         | 
| 52 | 
            +
                  checks.disableable.each do |check|
         | 
| 53 | 
            +
                    check.offenses.reject! do |offense|
         | 
| 54 | 
            +
                      disabled?(check, offense.template, offense.code_name, offense.start_index)
         | 
| 55 | 
            +
                    end
         | 
| 56 | 
            +
                  end
         | 
| 55 57 | 
             
                end
         | 
| 56 58 |  | 
| 57 59 | 
             
                private
         | 
| @@ -69,9 +71,11 @@ module ThemeCheck | |
| 69 71 | 
             
                end
         | 
| 70 72 |  | 
| 71 73 | 
             
                # Return a list of checks from a theme-check-disable comment
         | 
| 72 | 
            -
                # Returns [] if all checks are meant to be disabled
         | 
| 74 | 
            +
                # Returns [:all] if all checks are meant to be disabled
         | 
| 73 75 | 
             
                def checks_from_text(text)
         | 
| 74 | 
            -
                  text.gsub(DISABLE_PREFIX_PATTERN, '').strip.split(',').map(&:strip)
         | 
| 76 | 
            +
                  checks = text.gsub(DISABLE_PREFIX_PATTERN, '').strip.split(',').map(&:strip)
         | 
| 77 | 
            +
                  return [:all] if checks.empty?
         | 
| 78 | 
            +
                  checks
         | 
| 75 79 | 
             
                end
         | 
| 76 80 | 
             
              end
         | 
| 77 81 | 
             
            end
         | 
| @@ -0,0 +1,32 @@ | |
| 1 | 
            +
            # frozen_string_literal: true
         | 
| 2 | 
            +
            require "net/http"
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            TIMEOUT_EXCEPTIONS = [
         | 
| 5 | 
            +
              Net::ReadTimeout,
         | 
| 6 | 
            +
              Net::OpenTimeout,
         | 
| 7 | 
            +
              Net::WriteTimeout,
         | 
| 8 | 
            +
              Errno::ETIMEDOUT,
         | 
| 9 | 
            +
              Timeout::Error,
         | 
| 10 | 
            +
            ]
         | 
| 11 | 
            +
             | 
| 12 | 
            +
            CONNECTION_EXCEPTIONS = [
         | 
| 13 | 
            +
              IOError,
         | 
| 14 | 
            +
              EOFError,
         | 
| 15 | 
            +
              SocketError,
         | 
| 16 | 
            +
              Errno::EINVAL,
         | 
| 17 | 
            +
              Errno::ECONNRESET,
         | 
| 18 | 
            +
              Errno::ECONNABORTED,
         | 
| 19 | 
            +
              Errno::EPIPE,
         | 
| 20 | 
            +
              Errno::ECONNREFUSED,
         | 
| 21 | 
            +
              Errno::EAGAIN,
         | 
| 22 | 
            +
              Errno::EHOSTUNREACH,
         | 
| 23 | 
            +
              Errno::ENETUNREACH,
         | 
| 24 | 
            +
            ]
         | 
| 25 | 
            +
             | 
| 26 | 
            +
            NET_HTTP_EXCEPTIONS = [
         | 
| 27 | 
            +
              Net::HTTPBadResponse,
         | 
| 28 | 
            +
              Net::HTTPHeaderSyntaxError,
         | 
| 29 | 
            +
              Net::ProtocolError,
         | 
| 30 | 
            +
              *TIMEOUT_EXCEPTIONS,
         | 
| 31 | 
            +
              *CONNECTION_EXCEPTIONS,
         | 
| 32 | 
            +
            ]
         |