diff --git a/.rubocop.yml b/.rubocop.yml index 366a760f..40b099bf 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -11,3 +11,6 @@ RequireParentheses: Naming/FileName: Enabled: false + +Style/Documentation: + Enabled: false diff --git a/Rakefile b/Rakefile index 24a27e60..87a1fa5a 100644 --- a/Rakefile +++ b/Rakefile @@ -11,7 +11,7 @@ require 'rubocop/rake_task' RuboCop::RakeTask.new(:rubocop) -task default: %i(spec proof_readme) +task default: %i[spec proof_readme] task :proof_readme do require 'html-proofer' diff --git a/bin/htmlproofer b/bin/htmlproofer index eed31fbc..d22bd203 100755 --- a/bin/htmlproofer +++ b/bin/htmlproofer @@ -57,9 +57,7 @@ Mercenary.program(:htmlproofer) do |p| # prepare everything to go to proofer p.options.reject { |o| opts[o.config_key].nil? }.each do |option| - if opts[option.config_key].is_a?(Array) - opts[option.config_key] = opts[option.config_key].map { |i| HTMLProofer::Configuration.to_regex?(i) } - end + opts[option.config_key] = opts[option.config_key].map { |i| HTMLProofer::Configuration.to_regex?(i) } if opts[option.config_key].is_a?(Array) options[option.config_key.to_sym] = opts[option.config_key] end @@ -83,9 +81,7 @@ Mercenary.program(:htmlproofer) do |p| options[:validation][:report_missing_names] = opts['report_missing_names'] unless opts['report_missing_names'].nil? options[:validation][:report_invalid_tags] = opts['report_invalid_tags'] unless opts['report_invalid_tags'].nil? - unless opts['typhoeus_config'].nil? - options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config']) - end + options[:typhoeus] = HTMLProofer::Configuration.parse_json_option('typhoeus_config', opts['typhoeus_config']) unless opts['typhoeus_config'].nil? unless opts['timeframe'].nil? options[:cache] ||= {} diff --git a/html-proofer.gemspec b/html-proofer.gemspec index 313bc143..edf6ac10 100644 --- a/html-proofer.gemspec +++ b/html-proofer.gemspec @@ -1,6 +1,6 @@ # frozen_string_literal: true -$LOAD_PATH.push File.expand_path('../lib', __FILE__) +$LOAD_PATH.push File.expand_path('lib', __dir__) require 'html-proofer/version' Gem::Specification.new do |gem| @@ -12,30 +12,29 @@ Gem::Specification.new do |gem| gem.summary = %(A set of tests to validate your HTML output. These tests check if your image references are legitimate, if they have alt tags, if your internal links are working, and so on. It's intended to be an all-in-one checker for your documentation output.) gem.homepage = 'https://github.com/gjtorikian/html-proofer' gem.license = 'MIT' - gem.executables = ['htmlproofer'] all_files = `git ls-files -z`.split("\x0") gem.files = all_files.grep(%r{^(bin|lib)/}) gem.executables = all_files.grep(%r{^bin/}) { |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(spec)/}) gem.require_paths = ['lib'] + gem.add_dependency 'addressable', '~> 2.3' gem.add_dependency 'mercenary', '~> 0.3' gem.add_dependency 'nokogiri', '~> 1.10' + gem.add_dependency 'parallel', '~> 1.3' gem.add_dependency 'rainbow', '~> 3.0' gem.add_dependency 'typhoeus', '~> 1.3' gem.add_dependency 'yell', '~> 2.0' - gem.add_dependency 'parallel', '~> 1.3' - gem.add_dependency 'addressable', '~> 2.3' + gem.add_development_dependency 'awesome_print' + gem.add_development_dependency 'codecov' + gem.add_development_dependency 'pry-byebug' + gem.add_development_dependency 'rake' gem.add_development_dependency 'redcarpet' + gem.add_development_dependency 'rspec', '~> 3.1' gem.add_development_dependency 'rubocop' - gem.add_development_dependency 'rubocop-standard' gem.add_development_dependency 'rubocop-performance' - gem.add_development_dependency 'codecov' - gem.add_development_dependency 'rspec', '~> 3.1' - gem.add_development_dependency 'rake' - gem.add_development_dependency 'pry-byebug' - gem.add_development_dependency 'awesome_print' - gem.add_development_dependency 'vcr', '~> 2.9' + gem.add_development_dependency 'rubocop-standard' gem.add_development_dependency 'timecop', '~> 0.8' + gem.add_development_dependency 'vcr', '~> 2.9' end diff --git a/lib/html-proofer.rb b/lib/html-proofer.rb index 384cbdf5..92ef0df0 100644 --- a/lib/html-proofer.rb +++ b/lib/html-proofer.rb @@ -17,38 +17,38 @@ def require_all(path) begin require 'awesome_print' require 'pry-byebug' -rescue LoadError; end +rescue LoadError; end # rubocop:disable Lint/HandleExceptions module HTMLProofer - def check_file(file, options = {}) + def self.check_file(file, options = {}) raise ArgumentError unless file.is_a?(String) raise ArgumentError, "#{file} does not exist" unless File.exist?(file) + options[:type] = :file HTMLProofer::Runner.new(file, options) end - module_function :check_file - def check_directory(directory, options = {}) + def self.check_directory(directory, options = {}) raise ArgumentError unless directory.is_a?(String) raise ArgumentError, "#{directory} does not exist" unless Dir.exist?(directory) + options[:type] = :directory HTMLProofer::Runner.new([directory], options) end - module_function :check_directory - def check_directories(directories, options = {}) + def self.check_directories(directories, options = {}) raise ArgumentError unless directories.is_a?(Array) + options[:type] = :directory directories.each do |directory| raise ArgumentError, "#{directory} does not exist" unless Dir.exist?(directory) end HTMLProofer::Runner.new(directories, options) end - module_function :check_directories - def check_links(links, options = {}) + def self.check_links(links, options = {}) raise ArgumentError unless links.is_a?(Array) + options[:type] = :links HTMLProofer::Runner.new(links, options) end - module_function :check_links end diff --git a/lib/html-proofer/cache.rb b/lib/html-proofer/cache.rb index 8836893a..1eaa2852 100644 --- a/lib/html-proofer/cache.rb +++ b/lib/html-proofer/cache.rb @@ -9,7 +9,7 @@ class Cache include HTMLProofer::Utils DEFAULT_STORAGE_DIR = File.join('tmp', '.htmlproofer') - DEFAULT_CACHE_FILE_NAME = 'cache.log'.freeze + DEFAULT_CACHE_FILE_NAME = 'cache.log' attr_reader :exists, :cache_log, :storage_dir, :cache_file @@ -120,9 +120,8 @@ def retrieve_urls(external_urls) @cache_log.each_pair do |url, cache| if within_timeframe?(cache['time']) next if cache['message'].empty? # these were successes to skip - urls_to_check[url] = cache['filenames'] # these are failures to retry else - urls_to_check[url] = cache['filenames'] # pass or fail, recheck expired links + urls_to_check[url] = cache['filenames'] # recheck expired links end end urls_to_check @@ -142,23 +141,16 @@ def clean_url(url) end def setup_cache!(options) - @storage_dir = if options[:storage_dir] - options[:storage_dir] - else - DEFAULT_STORAGE_DIR - end + @storage_dir = options[:storage_dir] || DEFAULT_STORAGE_DIR FileUtils.mkdir_p(storage_dir) unless Dir.exist?(storage_dir) - cache_file_name = if options[:cache_file] - options[:cache_file] - else - DEFAULT_CACHE_FILE_NAME - end + cache_file_name = options[:cache_file] || DEFAULT_CACHE_FILE_NAME @cache_file = File.join(storage_dir, cache_file_name) return unless File.exist?(cache_file) + contents = File.read(cache_file) @cache_log = contents.empty? ? {} : JSON.parse(contents) end @@ -174,7 +166,7 @@ def time_ago(measurement, unit) when :days @cache_datetime - measurement when :hours - @cache_datetime - Rational(measurement/24.0) + @cache_datetime - Rational(measurement / 24.0) end.to_time end end diff --git a/lib/html-proofer/check.rb b/lib/html-proofer/check.rb index 39e9b6a1..1104b6e4 100644 --- a/lib/html-proofer/check.rb +++ b/lib/html-proofer/check.rb @@ -29,6 +29,7 @@ def add_issue(desc, line: nil, status: -1, content: nil) def add_to_external_urls(url) return if @external_urls[url] + add_path_for_url(url) end @@ -45,6 +46,7 @@ def self.subchecks ObjectSpace.each_object(Class) do |c| next unless c.superclass == self + classes << c end diff --git a/lib/html-proofer/check/favicon.rb b/lib/html-proofer/check/favicon.rb index 7c0231a4..e681a8a9 100644 --- a/lib/html-proofer/check/favicon.rb +++ b/lib/html-proofer/check/favicon.rb @@ -6,22 +6,24 @@ def run @html.xpath('//link[not(ancestor::pre or ancestor::code)]').each do |node| favicon = create_element(node) next if favicon.ignore? + found = true if favicon.rel.split(' ').last.eql? 'icon' break if found end return if found - return if is_immediate_redirect? + return if immediate_redirect? add_issue('no favicon specified') end private - def is_immediate_redirect? - # allow any instant-redirect meta tag - @html.xpath("//meta[@http-equiv='refresh']").attribute('content').value.start_with? '0;' rescue false + # allow any instant-redirect meta tag + def immediate_redirect? + @html.xpath("//meta[@http-equiv='refresh']").attribute('content').value.start_with? '0;' + rescue StandardError + false end - end diff --git a/lib/html-proofer/check/html.rb b/lib/html-proofer/check/html.rb index e47bd486..9580c44c 100644 --- a/lib/html-proofer/check/html.rb +++ b/lib/html-proofer/check/html.rb @@ -1,10 +1,10 @@ # frozen_string_literal: true class HtmlCheck < ::HTMLProofer::Check - SCRIPT_EMBEDS_MSG = /Element script embeds close tag/ - INVALID_TAG_MSG = /Tag ([\w\-:]+) invalid/ - INVALID_PREFIX = /Namespace prefix/ - PARSE_ENTITY_REF = /htmlParseEntityRef: no name/ + SCRIPT_EMBEDS_MSG = /Element script embeds close tag/.freeze + INVALID_TAG_MSG = /Tag ([\w\-:]+) invalid/.freeze + INVALID_PREFIX = /Namespace prefix/.freeze + PARSE_ENTITY_REF = /htmlParseEntityRef: no name/.freeze def run @html.errors.each do |error| diff --git a/lib/html-proofer/check/images.rb b/lib/html-proofer/check/images.rb index 648a1c95..92712ffd 100644 --- a/lib/html-proofer/check/images.rb +++ b/lib/html-proofer/check/images.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class ImageCheck < ::HTMLProofer::Check - SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/ + SCREEN_SHOT_REGEX = /Screen(?: |%20)Shot(?: |%20)\d+-\d+-\d+(?: |%20)at(?: |%20)\d+.\d+.\d+/.freeze def empty_alt_tag? @img.alt.nil? || @img.alt.strip.empty? @@ -38,13 +38,9 @@ def run add_issue("internal image #{@img.url} does not exist", line: line, content: content) end - if empty_alt_tag? && !@img.ignore_empty_alt? && !@img.ignore_alt? - add_issue("image #{@img.url} does not have an alt attribute", line: line, content: content) - end + add_issue("image #{@img.url} does not have an alt attribute", line: line, content: content) if empty_alt_tag? && !@img.ignore_empty_alt? && !@img.ignore_alt? - if @img.check_img_http? && @img.scheme == 'http' - add_issue("image #{@img.url} uses the http scheme", line: line, content: content) - end + add_issue("image #{@img.url} uses the http scheme", line: line, content: content) if @img.check_img_http? && @img.scheme == 'http' end external_urls diff --git a/lib/html-proofer/check/links.rb b/lib/html-proofer/check/links.rb index dc91af9a..58f332a3 100644 --- a/lib/html-proofer/check/links.rb +++ b/lib/html-proofer/check/links.rb @@ -35,6 +35,7 @@ def run next if @link.allow_missing_href? # HTML5 allows dropping the href: http://git.io/vBX0z next if @html.internal_subset.name == 'html' && @html.internal_subset.external_id.nil? + add_issue('anchor has no href attribute', line: line, content: content) next end @@ -47,9 +48,10 @@ def run # we need to skip these for now; although the domain main be valid, # curl/Typheous inaccurately return 404s for some links. cc https://git.io/vyCFx next if @link.respond_to?(:rel) && @link.rel == 'dns-prefetch' + add_to_external_urls(@link.href) next - elsif !@link.internal? && !@link.exists? + elsif !@link.remote? && !@link.exists? add_issue("internally linking to #{@link.href}, which does not exist", line: line, content: content) end @@ -74,6 +76,7 @@ def check_schemes(link, line, content) handle_tel(link, line, content) when 'http' return unless @options[:enforce_https] + add_issue("#{link.href} is not an HTTPS link", line: line, content: content) end end @@ -103,9 +106,7 @@ def external_link_check(link, line, content) add_issue("trying to find hash of #{link.href}, but #{link.absolute_path} does not exist", line: line, content: content) else target_html = create_nokogiri link.absolute_path - unless hash_check target_html, link.hash - add_issue("linking to #{link.href}, but #{link.hash} does not exist", line: line, content: content) - end + add_issue("linking to #{link.href}, but #{link.hash} does not exist", line: line, content: content) unless hash_check target_html, link.hash end end @@ -135,6 +136,7 @@ def find_fragments(html, fragment_ids) def check_sri(line, content) return unless SRI_REL_TYPES.include?(@link.rel) + if !defined?(@link.integrity) && !defined?(@link.crossorigin) add_issue("SRI and CORS not provided in: #{@link.src}", line: line, content: content) elsif !defined?(@link.integrity) diff --git a/lib/html-proofer/check/opengraph.rb b/lib/html-proofer/check/opengraph.rb index 654d59bd..751ce460 100644 --- a/lib/html-proofer/check/opengraph.rb +++ b/lib/html-proofer/check/opengraph.rb @@ -1,4 +1,3 @@ -# encoding: utf-8 # frozen_string_literal: true class OpenGraphElement < ::HTMLProofer::Element diff --git a/lib/html-proofer/configuration.rb b/lib/html-proofer/configuration.rb index 76e14a37..187d4bdb 100644 --- a/lib/html-proofer/configuration.rb +++ b/lib/html-proofer/configuration.rb @@ -65,19 +65,19 @@ def self.to_regex?(item) end def self.parse_json_option(option_name, config) - raise ArgumentError.new('Must provide an option name in string format.') unless option_name.is_a?(String) - raise ArgumentError.new('Must provide an option name in string format.') unless !option_name.strip.empty? + raise ArgumentError, 'Must provide an option name in string format.' unless option_name.is_a?(String) + raise ArgumentError, 'Must provide an option name in string format.' if option_name.strip.empty? return {} if config.nil? - raise ArgumentError.new('Must provide a JSON configuration in string format.') unless config.is_a?(String) + raise ArgumentError, 'Must provide a JSON configuration in string format.' unless config.is_a?(String) return {} if config.strip.empty? begin JSON.parse(config) - rescue - raise ArgumentError.new("Option '" + option_name + "' did not contain valid JSON.") + rescue StandardError + raise ArgumentError, "Option '" + option_name + "' did not contain valid JSON." end end end diff --git a/lib/html-proofer/element.rb b/lib/html-proofer/element.rb index d0f88b8d..d792206e 100644 --- a/lib/html-proofer/element.rb +++ b/lib/html-proofer/element.rb @@ -18,7 +18,7 @@ def initialize(obj, check) instance_variable_set("@#{name}", value.value) end - @aria_hidden = (defined?(@aria_hidden) && @aria_hidden == 'true') ? true : false + @aria_hidden = defined?(@aria_hidden) && @aria_hidden == 'true' ? true : false @data_proofer_ignore = defined?(@data_proofer_ignore) @@ -56,9 +56,11 @@ def initialize(obj, check) def url return @url if defined?(@url) + @url = (@src || @srcset || @href || '').delete("\u200b").strip @url = Addressable::URI.join(base.attr('href') || '', url).to_s if base return @url if @check.options[:url_swap].empty? + @url = swap(@url, @check.options[:url_swap]) end @@ -77,11 +79,11 @@ def path end def hash - parts.fragment unless parts.nil? + parts&.fragment end def scheme - parts.scheme unless parts.nil? + parts&.scheme end # path is to an external server @@ -137,7 +139,7 @@ def external? !internal? end - # path is an anchor or a query + # path is an anchor, a query or refers to root def internal? hash_link || param_link || slash_link end @@ -151,24 +153,22 @@ def param_link end def slash_link - url.start_with?('|') + url.start_with?('/') end def file_path - return if path.nil? + return if path.nil? || path.empty? path_dot_ext = '' - if @check.options[:assume_extension] - path_dot_ext = path + @check.options[:extension] - end + path_dot_ext = path + @check.options[:extension] if @check.options[:assume_extension] if path =~ %r{^/} # path relative to root if File.directory?(@check.src) base = @check.src else root_dir = @check.options[:root_dir] - base = root_dir ? root_dir : File.dirname(@check.src) + base = root_dir || File.dirname(@check.src) end elsif File.exist?(File.expand_path(path, @check.src)) || File.exist?(File.expand_path(path_dot_ext, @check.src)) # relative links, path is a file base = File.dirname @check.path @@ -179,7 +179,6 @@ def file_path end file = File.join base, path - if @check.options[:assume_extension] && File.file?("#{file}#{@check.options[:extension]}") file = "#{file}#{@check.options[:extension]}" elsif File.directory?(file) && !unslashed_directory?(file) # implicit index support @@ -192,6 +191,7 @@ def file_path # checks if a file exists relative to the current pwd def exists? return @checked_paths[absolute_path] if @checked_paths.key? absolute_path + @checked_paths[absolute_path] = File.exist? absolute_path end @@ -226,9 +226,9 @@ def base def html # If link is on the same page, then URL is on the current page so can use the same HTML as for current page - if (hash_link || param_link) && internal? + if hash_link || param_link @html - elsif slash_link && internal? + elsif slash_link # link on another page, e.g. /about#Team - need to get HTML from the other page create_nokogiri(absolute_path) end diff --git a/lib/html-proofer/issue.rb b/lib/html-proofer/issue.rb index 72dd2265..1bbc3eb6 100644 --- a/lib/html-proofer/issue.rb +++ b/lib/html-proofer/issue.rb @@ -56,9 +56,7 @@ def report(sorted_issues, first_report, second_report) @logger.log :error, " * #{issue}" else msg = " * #{issue.send(second_report)}#{issue.line}" - if !issue.content.nil? && !issue.content.empty? - msg = "#{msg}\n #{issue.content}" - end + msg = "#{msg}\n #{issue.content}" if !issue.content.nil? && !issue.content.empty? @logger.log(:error, msg) end end diff --git a/lib/html-proofer/middleware.rb b/lib/html-proofer/middleware.rb index b1e7eabb..87ff5511 100644 --- a/lib/html-proofer/middleware.rb +++ b/lib/html-proofer/middleware.rb @@ -2,25 +2,24 @@ module HTMLProofer class Middleware - class InvalidHtmlError < StandardError def initialize(failures) @failures = failures end def message - "HTML Validation errors (skip by adding `?proofer-ignore` to URL): \n#{@failures.join("\n")}" + "HTML Validation errors (skip by adding `?proofer-ignore` to URL): \n#{@failures.join("\n")}" end end def self.options @options ||= { - type: :file, - allow_missing_href: true, # Permitted in html5 - allow_hash_href: true, + type: :file, + allow_missing_href: true, # Permitted in html5 + allow_hash_href: true, check_external_hash: true, - check_html: true, - url_ignore: [/.*/], # Don't try to check local files exist + check_html: true, + url_ignore: [/.*/] # Don't try to check local files exist } end @@ -46,20 +45,21 @@ def initialize(app) ' 0 - raise InvalidHtmlError.new(parsed[:failures]) - end + raise InvalidHtmlError, parsed[:failures] unless parsed[:failures].empty? end result end diff --git a/lib/html-proofer/runner.rb b/lib/html-proofer/runner.rb index 43284674..e10568f1 100644 --- a/lib/html-proofer/runner.rb +++ b/lib/html-proofer/runner.rb @@ -103,9 +103,7 @@ def check_parsed(html, path) check = Object.const_get(klass).new(src, path, html, @options) check.run external_urls = check.external_urls - if @options[:url_swap] - external_urls = Hash[check.external_urls.map { |url, file| [swap(url, @options[:url_swap]), file] }] - end + external_urls = Hash[check.external_urls.map { |url, file| [swap(url, @options[:url_swap]), file] }] if @options[:url_swap] result[:external_urls].merge!(external_urls) result[:failures].concat(check.issues) end @@ -148,6 +146,7 @@ def ignore_file?(file) def checks return @checks if defined?(@checks) && !@checks.nil? + @checks = HTMLProofer::Check.subchecks.map(&:name) @checks.delete('FaviconCheck') unless @options[:check_favicon] @checks.delete('HtmlCheck') unless @options[:check_html] @@ -159,6 +158,7 @@ def checks def failed_tests result = [] return result if @failures.empty? + @failures.each { |f| result << f.to_s } result end diff --git a/lib/html-proofer/url_validator.rb b/lib/html-proofer/url_validator.rb index febd8144..e6496400 100644 --- a/lib/html-proofer/url_validator.rb +++ b/lib/html-proofer/url_validator.rb @@ -36,6 +36,7 @@ def run def remove_query_values return nil if @external_urls.nil? + paths_with_queries = {} iterable_external_urls = @external_urls.dup @external_urls.each_key do |url| @@ -46,6 +47,7 @@ def remove_query_values nil end next if uri.nil? || uri.query.nil? + iterable_external_urls.delete(url) unless new_url_query_values?(uri, paths_with_queries) end iterable_external_urls @@ -108,9 +110,9 @@ def establish_queue(external_urls) external_urls.each_pair do |url, filenames| url = begin clean_url(url) - rescue URI::Error, Addressable::URI::InvalidURIError - add_external_issue(filenames, "#{url} is an invalid URL") - next + rescue URI::Error, Addressable::URI::InvalidURIError + add_external_issue(filenames, "#{url} is an invalid URL") + next end method = if hash?(url) && @options[:check_external_hash] @@ -146,20 +148,18 @@ def response_handler(response, filenames) response_code = response.code response.body.gsub!("\x00", '') - if filenames.nil? - debug_msg = "Received a #{response_code} for #{href}" - else - debug_msg = "Received a #{response_code} for #{href} in #{filenames.join(' ')}" - end + debug_msg = if filenames.nil? + "Received a #{response_code} for #{href}" + else + "Received a #{response_code} for #{href} in #{filenames.join(' ')}" + end @logger.log :debug, debug_msg return if @options[:http_status_ignore].include?(response_code) if response_code.between?(200, 299) - unless check_hash_in_2xx_response(href, effective_url, response, filenames) - @cache.add(href, filenames, response_code) - end + @cache.add(href, filenames, response_code) unless check_hash_in_2xx_response(href, effective_url, response, filenames) elsif response.timed_out? handle_timeout(href, filenames, response_code) elsif response_code.zero? @@ -168,6 +168,7 @@ def response_handler(response, filenames) queue_request(:get, href, filenames) else return if @options[:only_4xx] && !response_code.between?(400, 499) + # Received a non-successful http response. msg = "External link #{href} failed: #{response_code} #{response.return_message}" add_external_issue(filenames, msg, response_code) @@ -191,9 +192,7 @@ def check_hash_in_2xx_response(href, effective_url, response, filenames) xpath << [%(//*[@name="user-content-#{hash}"]|//*[@id="user-content-#{hash}"])] # when linking to a file on GitHub, like #L12-L34, only the first "L" portion # will be identified as a linkable portion - if hash =~ /\A(L\d)+/ - xpath << [%(//td[@id="#{Regexp.last_match[1]}"])] - end + xpath << [%(//td[@id="#{Regexp.last_match[1]}"])] if hash =~ /\A(L\d)+/ end return unless body_doc.xpath(xpath.join('|')).empty? @@ -208,6 +207,7 @@ def handle_timeout(href, filenames, response_code) msg = "External link #{href} failed: got a time out (response code #{response_code})" @cache.add(href, filenames, 0, msg) return if @options[:only_4xx] + add_external_issue(filenames, msg, response_code) end @@ -218,6 +218,7 @@ def handle_failure(href, filenames, response_code, return_message) Either way, the return message (if any) from the server is: #{return_message}" @cache.add(href, filenames, 0, msg) return if @options[:only_4xx] + add_external_issue(filenames, msg, response_code) end diff --git a/lib/html-proofer/utils.rb b/lib/html-proofer/utils.rb index 074ea5ec..56fa513d 100644 --- a/lib/html-proofer/utils.rb +++ b/lib/html-proofer/utils.rb @@ -8,7 +8,7 @@ def pluralize(count, single, plural) "#{count} #{(count == 1 ? single : plural)}" end - def create_nokogiri(path) + def self.create_nokogiri(path) content = if File.exist?(path) && !File.directory?(path) File.open(path).read else @@ -17,24 +17,21 @@ def create_nokogiri(path) Nokogiri::HTML(clean_content(content)) end - module_function :create_nokogiri - def swap(href, replacement) + def self.swap(href, replacement) replacement.each do |link, replace| href = href.gsub(link, replace) end href end - module_function :swap # address a problem with Nokogiri's parsing URL entities # problem from http://git.io/vBYU1 # solution from http://git.io/vBYUi - def clean_content(string) + def self.clean_content(string) string.gsub(%r{(?:https?:)?//([^>]+)}i) do |url| url.gsub(/&(?!amp;)/, '&') end end - module_function :clean_content end end diff --git a/lib/html-proofer/version.rb b/lib/html-proofer/version.rb index e6327891..1066fca9 100644 --- a/lib/html-proofer/version.rb +++ b/lib/html-proofer/version.rb @@ -1,5 +1,5 @@ # frozen_string_literal: true module HTMLProofer - VERSION = '3.13.0'.freeze + VERSION = '3.13.0' end diff --git a/spec/html-proofer/cache_spec.rb b/spec/html-proofer/cache_spec.rb index 3bfc1b0d..857c51fb 100644 --- a/spec/html-proofer/cache_spec.rb +++ b/spec/html-proofer/cache_spec.rb @@ -9,7 +9,7 @@ let(:default_cache_options) { { storage_dir: storage_dir } } - let (:logger) { HTMLProofer::Log.new(:debug) } + let(:logger) { HTMLProofer::Log.new(:debug) } def read_cache(cache_file) JSON.parse File.read(cache_file) @@ -20,7 +20,7 @@ def read_cache(cache_file) now_time = Time.local(2019, 9, 6, 12, 0, 0) Timecop.freeze(now_time) - cache = HTMLProofer::Cache.new(logger, { timeframe: '2M' }) + cache = HTMLProofer::Cache.new(logger, timeframe: '2M') check_time = Time.local(2019, 8, 6, 12, 0, 0).to_s @@ -37,7 +37,7 @@ def read_cache(cache_file) now_time = Time.local(2019, 9, 6, 12, 0, 0) Timecop.freeze(now_time) - cache = HTMLProofer::Cache.new(logger, { timeframe: '2d' }) + cache = HTMLProofer::Cache.new(logger, timeframe: '2d') check_time = Time.local(2019, 9, 5, 12, 0, 0).to_s @@ -54,7 +54,7 @@ def read_cache(cache_file) now_time = Time.local(2019, 9, 6, 12, 0, 0) Timecop.freeze(now_time) - cache = HTMLProofer::Cache.new(logger, { timeframe: '2w' }) + cache = HTMLProofer::Cache.new(logger, timeframe: '2w') check_time = Time.local(2019, 8, 30, 12, 0, 0).to_s @@ -71,7 +71,7 @@ def read_cache(cache_file) now_time = Time.local(2019, 9, 6, 12, 0, 0) Timecop.freeze(now_time) - cache = HTMLProofer::Cache.new(logger, { timeframe: '3h' }) + cache = HTMLProofer::Cache.new(logger, timeframe: '3h') check_time = Time.local(2019, 9, 6, 9, 0, 0).to_s diff --git a/spec/html-proofer/command_spec.rb b/spec/html-proofer/command_spec.rb index 61d5c75b..9e37e196 100644 --- a/spec/html-proofer/command_spec.rb +++ b/spec/html-proofer/command_spec.rb @@ -116,6 +116,7 @@ expect(bin_file).to match(key) readme.each_line do |line| next unless line =~ /\| `#{key}`/ + description = line.split('|')[2].strip description.gsub!('A hash', 'A comma-separated list') description.gsub!('An array', 'A comma-separated list') diff --git a/spec/html-proofer/fixtures/links/broken_root_link_internal.html b/spec/html-proofer/fixtures/links/broken_root_link_internal.html new file mode 100644 index 00000000..f54e4110 --- /dev/null +++ b/spec/html-proofer/fixtures/links/broken_root_link_internal.html @@ -0,0 +1,10 @@ + + + +

+ Blah blah blah. + Not a real link! +

+ + + diff --git a/spec/html-proofer/fixtures/links/link_to_another_folder.html b/spec/html-proofer/fixtures/links/link_to_another_folder.html new file mode 100644 index 00000000..ac9868cb --- /dev/null +++ b/spec/html-proofer/fixtures/links/link_to_another_folder.html @@ -0,0 +1,8 @@ + + +

+ Blah blah blah. + A real link! +

+ + diff --git a/spec/html-proofer/fixtures/links/working_root_link_internal.html b/spec/html-proofer/fixtures/links/working_root_link_internal.html index 9ca8490c..5d1762b3 100644 --- a/spec/html-proofer/fixtures/links/working_root_link_internal.html +++ b/spec/html-proofer/fixtures/links/working_root_link_internal.html @@ -2,8 +2,8 @@

- Blah blah blah. - A real link! + Blah blah blah. + Not a real link!

diff --git a/spec/html-proofer/images_spec.rb b/spec/html-proofer/images_spec.rb index 2e3a55f9..a8c9f8a7 100644 --- a/spec/html-proofer/images_spec.rb +++ b/spec/html-proofer/images_spec.rb @@ -180,7 +180,7 @@ it 'translates src via url_swap' do translate_src = "#{FIXTURES_DIR}/images/replace_abs_url_src.html" - proofer = run_proofer(translate_src, :file, { url_swap: { %r{^http://baseurl.com} => '' } }) + proofer = run_proofer(translate_src, :file, url_swap: { %r{^http://baseurl.com} => '' }) expect(proofer.failed_tests).to eq [] end diff --git a/spec/html-proofer/links_spec.rb b/spec/html-proofer/links_spec.rb index bf87b0c4..d3060c79 100644 --- a/spec/html-proofer/links_spec.rb +++ b/spec/html-proofer/links_spec.rb @@ -29,7 +29,7 @@ it 'passes for GitHub hashes to a file on the web when asked' do github_hash = "#{FIXTURES_DIR}/links/github_file_hash.html" - proofer = run_proofer(github_hash, :file, {check_external_hash: true}) + proofer = run_proofer(github_hash, :file, check_external_hash: true) expect(proofer.failed_tests).to eq [] end @@ -79,8 +79,20 @@ expect(proofer.failed_tests.first).to match(%r{internally linking to .\/notreal.html, which does not exist}) end - it 'succeeds for working internal-root-links pointing to other folder' do + it 'fails for broken internal root links' do + broken_root_link_internal_filepath = "#{FIXTURES_DIR}/links/broken_root_link_internal.html" + proofer = run_proofer(broken_root_link_internal_filepath, :file) + expect(proofer.failed_tests.first).to match(%r{internally linking to \/broken_root_link_internalz.html, which does not exist}) + end + + it 'succeeds for working internal root links' do broken_root_link_internal_filepath = "#{FIXTURES_DIR}/links/working_root_link_internal.html" + proofer = run_proofer(broken_root_link_internal_filepath, :file) + expect(proofer.failed_tests).to eq [] + end + + it 'succeeds for working internal-root-links pointing to other folder' do + broken_root_link_internal_filepath = "#{FIXTURES_DIR}/links/link_to_another_folder.html" proofer = run_proofer(broken_root_link_internal_filepath, :file, root_dir: 'spec/html-proofer/fixtures') expect(proofer.failed_tests).to eq [] end diff --git a/spec/html-proofer/middleware_spec.rb b/spec/html-proofer/middleware_spec.rb index 36a1bcd0..af608428 100644 --- a/spec/html-proofer/middleware_spec.rb +++ b/spec/html-proofer/middleware_spec.rb @@ -3,18 +3,18 @@ require 'spec_helper' describe 'Middleware test' do - let(:request) { {'REQUEST_METHOD' => 'GET'} } + let(:request) { { 'REQUEST_METHOD' => 'GET' } } let(:response) { File.open(response_fixture) } - let(:app) { Proc.new { |*args| [200, {}, response] } } + let(:app) { proc { |*_args| [200, {}, response] } } let(:middleware) { HTMLProofer::Middleware.new(app) } subject { middleware.call(request) } context 'with invalid HTML' do let(:response_fixture) { File.join(FIXTURES_DIR, 'html', 'missing_closing_quotes.html') } it 'raises an error' do - expect { + expect do subject - }.to raise_error(HTMLProofer::Middleware::InvalidHtmlError) + end.to raise_error(HTMLProofer::Middleware::InvalidHtmlError) end end @@ -33,7 +33,7 @@ end context 'proofer-ignore' do - let(:skip_request) { {'REQUEST_METHOD' => 'GET', 'QUERY_STRING' => 'proofer-ignore'} } + let(:skip_request) { { 'REQUEST_METHOD' => 'GET', 'QUERY_STRING' => 'proofer-ignore' } } let(:subject) { middleware.call(skip_request) } let(:response_fixture) { File.join(FIXTURES_DIR, 'html', 'missing_closing_quotes.html') } it 'does not raise an error' do diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 7853539e..f9fcf900 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -11,7 +11,7 @@ require 'timecop' require_relative '../lib/html-proofer' -FIXTURES_DIR = 'spec/html-proofer/fixtures'.freeze +FIXTURES_DIR = 'spec/html-proofer/fixtures' RSpec.configure do |config| # Use color in STDOUT @@ -31,10 +31,10 @@ def capture_stderr(*) original_stderr = $stderr original_stdout = $stdout $stderr = fake_err = StringIO.new - $stdout = fake_out = StringIO.new unless ENV['VERBOSE'] + $stdout = StringIO.new unless ENV['VERBOSE'] begin yield - rescue RuntimeError + rescue RuntimeError # rubocop:disable Lint/HandleExceptions ensure $stderr = original_stderr $stdout = original_stdout unless ENV['VERBOSE']