lib/goodcheck/config_loader.rb in goodcheck-1.7.1 vs lib/goodcheck/config_loader.rb in goodcheck-2.1.0

- old
+ new

@@ -33,13 +33,40 @@ string end }) let :glob, array_or(one_glob) + let :var_pattern, any + let :variable_pattern, array_or(var_pattern) + let :negated_variable_pattern, object(not: variable_pattern) + + let :where, hash( + enum( + variable_pattern, + negated_variable_pattern, + literal(true), + detector: -> (value) { + case + when value.is_a?(Hash) && value.key?(:not) + negated_variable_pattern + when value == true + literal(true) + else + variable_pattern + end + } + ) + ) + let :regexp_pattern, object(regexp: string, case_sensitive: boolean?, multiline: boolean?, glob: optional(glob)) let :literal_pattern, object(literal: string, case_sensitive: boolean?, glob: optional(glob)) - let :token_pattern, object(token: string, case_sensitive: boolean?, glob: optional(glob)) + let :token_pattern, object( + token: string, + case_sensitive: boolean?, + glob: optional(glob), + where: optional(where) + ) let :pattern, enum(regexp_pattern, literal_pattern, token_pattern, deprecated_regexp_pattern, @@ -93,16 +120,62 @@ message: string, justification: optional(array_or(string)), glob: glob ) + let :positive_trigger, object( + pattern: array_or(pattern), + glob: optional(glob), + pass: optional(array_or(string)), + fail: optional(array_or(string)) + ) + + let :negative_trigger, object( + not: object(pattern: array_or(pattern)), + glob: optional(glob), + pass: optional(array_or(string)), + fail: optional(array_or(string)) + ) + + let :nopattern_trigger, object( + glob: glob_obj + ) + + let :trigger, enum( + positive_trigger, + negative_trigger, + nopattern_trigger, + detector: -> (hash) { + if hash.is_a?(Hash) + case + when hash.key?(:pattern) + positive_trigger + when hash.key?(:not) + negative_trigger + else + nopattern_trigger + end + end + } + ) + + let :triggered_rule, object( + id: string, + message: string, + justification: optional(array_or(string)), + trigger: array_or(trigger) + ) + let :rule, enum(positive_rule, negative_rule, nopattern_rule, + triggered_rule, detector: -> (hash) { if hash.is_a?(Hash) case + when hash[:trigger] + triggered_rule when hash[:pattern] positive_rule when hash[:not] negative_rule when hash.key?(:glob) && !hash.key?(:pattern) && !hash.key?(:not) @@ -178,18 +251,80 @@ def load_rule(hash) Goodcheck.logger.debug "Loading rule: #{hash[:id]}" id = hash[:id] - patterns, negated = retrieve_patterns(hash) + triggers = retrieve_triggers(hash) justifications = array(hash[:justification]) - globs = load_globs(array(hash[:glob])) message = hash[:message].chomp + + Rule.new(id: id, message: message, justifications: justifications, triggers: triggers) + end + + def retrieve_triggers(hash) + if hash.key?(:trigger) + array(hash[:trigger]).map do |trigger| + retrieve_trigger(trigger) + end + else + globs = load_globs(array(hash[:glob])) + passes = array(hash[:pass]) + fails = array(hash[:fail]) + + if hash.key?(:not) || hash.key?(:pattern) + if hash.key?(:not) + negated = true + patterns = array(hash[:not][:pattern]) + else + negated = false + patterns = array(hash[:pattern]) + end + + glob_patterns, noglob_patterns = patterns.partition {|pat| + pat.is_a?(Hash) && pat.key?(:glob) + } + + skip_fails = !fails.empty? && !glob_patterns.empty? + + glob_patterns.map do |pat| + Trigger.new( + patterns: [load_pattern(pat)], + globs: load_globs(array(pat[:glob])), + passes: passes, + fails: [], + negated: negated + ).by_pattern!.skips_fail_examples!(skip_fails) + end.push( + Trigger.new( + patterns: noglob_patterns.map {|pat| load_pattern(pat) }, + globs: globs, + passes: passes, + fails: glob_patterns.empty? ? fails : [], + negated: negated + ).by_pattern!.skips_fail_examples!(skip_fails) + ) + else + [Trigger.new(patterns: [], + globs: globs, + passes: passes, + fails: fails, + negated: false).by_pattern!] + end + end + end + + def retrieve_trigger(hash) + patterns, negated = retrieve_patterns(hash) + globs = load_globs(array(hash[:glob])) passes = array(hash[:pass]) fails = array(hash[:fail]) - Rule.new(id: id, patterns: patterns, justifications: justifications, globs: globs, message: message, passes: passes, fails: fails, negated: negated) + Trigger.new(patterns: patterns, + globs: globs, + passes: passes, + fails: fails, + negated: negated) end def retrieve_patterns(hash) if hash.is_a?(Hash) && hash.key?(:not) negated = true @@ -217,28 +352,85 @@ end def load_pattern(pattern) case pattern when String - Pattern.literal(pattern, case_sensitive: true) + case (pat = load_string_pattern(pattern)) + when String + Pattern::Literal.new(source: pat, case_sensitive: true) + when ::Regexp + Pattern::Regexp.new(source: pattern, + regexp: pat, + multiline: pat.multiline?, + case_sensitive: !pat.casefold?) + end when Hash - globs = load_globs(array(pattern[:glob])) + if pattern[:glob] + print_warning_once "🌏 Pattern with glob is deprecated: globs are ignored at all." + end + case when pattern[:literal] cs = case_sensitive?(pattern) literal = pattern[:literal] - Pattern.literal(literal, case_sensitive: cs, globs: globs) + Pattern::Literal.new(source: literal, case_sensitive: cs) when pattern[:regexp] regexp = pattern[:regexp] cs = case_sensitive?(pattern) multiline = pattern[:multiline] - Pattern.regexp(regexp, case_sensitive: cs, multiline: multiline, globs: globs) + Pattern::Regexp.new(source: regexp, case_sensitive: cs, multiline: multiline) when pattern[:token] tok = pattern[:token] cs = case_sensitive?(pattern) - Pattern.token(tok, case_sensitive: cs, globs: globs) + Pattern::Token.new(source: tok, variables: load_token_vars(pattern[:where]), case_sensitive: cs) end end + end + + def load_string_pattern(string) + if string =~ /\A\/(.*)\/([im]*)\Z/ + source = $1 + opts = $2 + options = 0 + options |= ::Regexp::IGNORECASE if opts =~ /i/ + options |= ::Regexp::MULTILINE if opts =~ /m/ + ::Regexp.new(source, options) + else + string + end + end + + def load_token_vars(pattern) + case pattern + when Hash + pattern.each.with_object({}) do |(key, value), hash| + hash[key.to_sym] = load_var_pattern(value) + end + else + {} + end + end + + def load_var_pattern(pattern) + if pattern.is_a?(Hash) && pattern[:not] + negated = true + pattern = pattern[:not] + else + negated = false + end + + pattern = [] if pattern == true + + patterns = array(pattern).map do |pat| + case pat + when String + load_string_pattern(pat) + else + pat + end + end + + Pattern::Token::VarPattern.new(patterns: patterns, negated: negated) end def case_sensitive?(pattern) return true if pattern.is_a?(String) case