liquid_lint 1.0.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.
Files changed (61) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.md +1 -0
  3. data/bin/liquid-lint +7 -0
  4. data/config/default.yml +99 -0
  5. data/lib/liquid_lint/atom.rb +98 -0
  6. data/lib/liquid_lint/capture_map.rb +19 -0
  7. data/lib/liquid_lint/cli.rb +163 -0
  8. data/lib/liquid_lint/configuration.rb +109 -0
  9. data/lib/liquid_lint/configuration_loader.rb +86 -0
  10. data/lib/liquid_lint/constants.rb +10 -0
  11. data/lib/liquid_lint/document.rb +76 -0
  12. data/lib/liquid_lint/engine.rb +45 -0
  13. data/lib/liquid_lint/exceptions.rb +20 -0
  14. data/lib/liquid_lint/file_finder.rb +88 -0
  15. data/lib/liquid_lint/filters/attribute_processor.rb +31 -0
  16. data/lib/liquid_lint/filters/control_processor.rb +47 -0
  17. data/lib/liquid_lint/filters/inject_line_numbers.rb +43 -0
  18. data/lib/liquid_lint/filters/sexp_converter.rb +17 -0
  19. data/lib/liquid_lint/filters/splat_processor.rb +15 -0
  20. data/lib/liquid_lint/lint.rb +43 -0
  21. data/lib/liquid_lint/linter/comment_control_statement.rb +22 -0
  22. data/lib/liquid_lint/linter/consecutive_control_statements.rb +26 -0
  23. data/lib/liquid_lint/linter/control_statement_spacing.rb +24 -0
  24. data/lib/liquid_lint/linter/embedded_engines.rb +22 -0
  25. data/lib/liquid_lint/linter/empty_control_statement.rb +15 -0
  26. data/lib/liquid_lint/linter/empty_lines.rb +26 -0
  27. data/lib/liquid_lint/linter/file_length.rb +20 -0
  28. data/lib/liquid_lint/linter/line_length.rb +21 -0
  29. data/lib/liquid_lint/linter/redundant_div.rb +22 -0
  30. data/lib/liquid_lint/linter/rubocop.rb +116 -0
  31. data/lib/liquid_lint/linter/tab.rb +19 -0
  32. data/lib/liquid_lint/linter/tag_case.rb +15 -0
  33. data/lib/liquid_lint/linter/trailing_blank_lines.rb +21 -0
  34. data/lib/liquid_lint/linter/trailing_whitespace.rb +19 -0
  35. data/lib/liquid_lint/linter/zwsp.rb +18 -0
  36. data/lib/liquid_lint/linter.rb +93 -0
  37. data/lib/liquid_lint/linter_registry.rb +39 -0
  38. data/lib/liquid_lint/linter_selector.rb +79 -0
  39. data/lib/liquid_lint/logger.rb +103 -0
  40. data/lib/liquid_lint/matcher/anything.rb +11 -0
  41. data/lib/liquid_lint/matcher/base.rb +21 -0
  42. data/lib/liquid_lint/matcher/capture.rb +32 -0
  43. data/lib/liquid_lint/matcher/nothing.rb +13 -0
  44. data/lib/liquid_lint/options.rb +110 -0
  45. data/lib/liquid_lint/rake_task.rb +125 -0
  46. data/lib/liquid_lint/report.rb +25 -0
  47. data/lib/liquid_lint/reporter/checkstyle_reporter.rb +42 -0
  48. data/lib/liquid_lint/reporter/default_reporter.rb +41 -0
  49. data/lib/liquid_lint/reporter/emacs_reporter.rb +44 -0
  50. data/lib/liquid_lint/reporter/json_reporter.rb +52 -0
  51. data/lib/liquid_lint/reporter.rb +44 -0
  52. data/lib/liquid_lint/ruby_extract_engine.rb +36 -0
  53. data/lib/liquid_lint/ruby_extractor.rb +106 -0
  54. data/lib/liquid_lint/ruby_parser.rb +40 -0
  55. data/lib/liquid_lint/runner.rb +82 -0
  56. data/lib/liquid_lint/sexp.rb +106 -0
  57. data/lib/liquid_lint/sexp_visitor.rb +146 -0
  58. data/lib/liquid_lint/utils.rb +85 -0
  59. data/lib/liquid_lint/version.rb +6 -0
  60. data/lib/liquid_lint.rb +52 -0
  61. metadata +185 -0
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rake'
4
+ require 'rake/tasklib'
5
+ require 'liquid_lint/constants'
6
+
7
+ module LiquidLint
8
+ # Rake task interface for liquid-lint command line interface.
9
+ #
10
+ # @example
11
+ # # Add the following to your Rakefile...
12
+ # require 'liquid_lint/rake_task'
13
+ #
14
+ # LiquidLint::RakeTask.new do |t|
15
+ # t.config = 'path/to/custom/liquid-lint.yml'
16
+ # t.files = %w[app/views/**/*.liquid custom/*.liquid]
17
+ # t.quiet = true # Don't display output from liquid-lint
18
+ # end
19
+ #
20
+ # # ...and then execute from the command line:
21
+ # rake liquid_lint
22
+ #
23
+ # You can also specify the list of files as explicit task arguments:
24
+ #
25
+ # @example
26
+ # # Add the following to your Rakefile...
27
+ # require 'liquid_lint/rake_task'
28
+ #
29
+ # LiquidLint::RakeTask.new
30
+ #
31
+ # # ...and then execute from the command line (single quotes prevent shell
32
+ # # glob expansion and allow us to have a space after commas):
33
+ # rake 'liquid_lint[app/views/**/*.liquid, other_files/**/*.liquid]'
34
+ #
35
+ class RakeTask < Rake::TaskLib
36
+ # Name of the task.
37
+ # @return [String]
38
+ attr_accessor :name
39
+
40
+ # Configuration file to use.
41
+ # @return [String]
42
+ attr_accessor :config
43
+
44
+ # List of files to lint (can contain shell globs).
45
+ #
46
+ # Note that this will be ignored if you explicitly pass a list of files as
47
+ # task arguments via the command line or a task definition.
48
+ # @return [Array<String>]
49
+ attr_accessor :files
50
+
51
+ # Whether output from liquid-lint should not be displayed to the standard out
52
+ # stream.
53
+ # @return [true,false]
54
+ attr_accessor :quiet
55
+
56
+ # Create the task so it exists in the current namespace.
57
+ #
58
+ # @param name [Symbol] task name
59
+ def initialize(name = :liquid_lint)
60
+ @name = name
61
+ @files = ['.'] # Search for everything under current directory by default
62
+ @quiet = false
63
+
64
+ yield self if block_given?
65
+
66
+ define
67
+ end
68
+
69
+ private
70
+
71
+ # Defines the Rake task.
72
+ def define
73
+ desc default_description unless ::Rake.application.last_description
74
+
75
+ task(name, [:files]) do |_task, task_args|
76
+ # Lazy-load so task doesn't affect Rakefile load time
77
+ require 'liquid_lint'
78
+ require 'liquid_lint/cli'
79
+
80
+ run_cli(task_args)
81
+ end
82
+ end
83
+
84
+ # Executes the CLI given the specified task arguments.
85
+ #
86
+ # @param task_args [Rake::TaskArguments]
87
+ def run_cli(task_args)
88
+ cli_args = ['--config', config] if config
89
+
90
+ logger = quiet ? LiquidLint::Logger.silent : LiquidLint::Logger.new($stdout)
91
+ result = LiquidLint::CLI.new(logger).run(Array(cli_args) + files_to_lint(task_args))
92
+
93
+ fail "#{LiquidLint::APP_NAME} failed with exit code #{result}" unless result == 0
94
+ end
95
+
96
+ # Returns the list of files that should be linted given the specified task
97
+ # arguments.
98
+ #
99
+ # @param task_args [Rake::TaskArguments]
100
+ def files_to_lint(task_args)
101
+ # NOTE: we're abusing Rake's argument handling a bit here. We call the
102
+ # first argument `files` but it's actually only the first file--we pull
103
+ # the rest out of the `extras` from the task arguments. This is so we
104
+ # can specify an arbitrary list of files separated by commas on the
105
+ # command line or in a custom task definition.
106
+ explicit_files = Array(task_args[:files]) + Array(task_args.extras)
107
+
108
+ explicit_files.any? ? explicit_files : files
109
+ end
110
+
111
+ # Friendly description that shows the full command that will be executed.
112
+ #
113
+ # This allows us to change the information displayed by `rake --tasks` based
114
+ # on the options passed to the constructor which defined the task.
115
+ #
116
+ # @return [String]
117
+ def default_description
118
+ description = "Run `#{LiquidLint::APP_NAME}"
119
+ description += " --config #{config}" if config
120
+ description += " #{files.join(' ')}" if files.any?
121
+ description += ' [files...]`'
122
+ description
123
+ end
124
+ end
125
+ end
@@ -0,0 +1,25 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Contains information about all lints detected during a scan.
5
+ class Report
6
+ # List of lints that were found.
7
+ attr_accessor :lints
8
+
9
+ # List of files that were linted.
10
+ attr_reader :files
11
+
12
+ # Creates a report.
13
+ #
14
+ # @param lints [Array<LiquidLint::Lint>] lints that were found
15
+ # @param files [Array<String>] files that were linted
16
+ def initialize(lints, files)
17
+ @lints = lints.sort_by { |l| [l.filename, l.line] }
18
+ @files = files
19
+ end
20
+
21
+ def failed?
22
+ @lints.any?
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rexml/document'
4
+
5
+ module LiquidLint
6
+ # Outputs report as a Checkstyle XML document.
7
+ class Reporter::CheckstyleReporter < Reporter
8
+ def display_report(report)
9
+ document = REXML::Document.new.tap do |d|
10
+ d << REXML::XMLDecl.new
11
+ end
12
+ checkstyle = REXML::Element.new('checkstyle', document)
13
+
14
+ report.lints.group_by(&:filename).map do |lint|
15
+ map_file(lint, checkstyle)
16
+ end
17
+
18
+ log.log document.to_s
19
+ end
20
+
21
+ private
22
+
23
+ def map_file(file, checkstyle)
24
+ REXML::Element.new('file', checkstyle).tap do |f|
25
+ path_name = file.first
26
+ path_name = relative_path(file) if defined?(relative_path)
27
+ f.attributes['name'] = path_name
28
+
29
+ file.last.map { |o| map_offense(o, f) }
30
+ end
31
+ end
32
+
33
+ def map_offense(offence, parent)
34
+ REXML::Element.new('error', parent).tap do |e|
35
+ e.attributes['line'] = offence.line
36
+ e.attributes['severity'] = offence.error? ? 'error' : 'warning'
37
+ e.attributes['message'] = offence.message
38
+ e.attributes['source'] = 'liquid-lint'
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Outputs lints in a simple format with the filename, line number, and lint
5
+ # message.
6
+ class Reporter::DefaultReporter < Reporter
7
+ def display_report(report)
8
+ sorted_lints = report.lints.sort_by { |l| [l.filename, l.line] }
9
+
10
+ sorted_lints.each do |lint|
11
+ print_location(lint)
12
+ print_type(lint)
13
+ print_message(lint)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def print_location(lint)
20
+ log.info lint.filename, false
21
+ log.log ':', false
22
+ log.bold lint.line, false
23
+ end
24
+
25
+ def print_type(lint)
26
+ if lint.error?
27
+ log.error ' [E] ', false
28
+ else
29
+ log.warning ' [W] ', false
30
+ end
31
+ end
32
+
33
+ def print_message(lint)
34
+ if lint.linter
35
+ log.success("#{lint.linter.name}: ", false)
36
+ end
37
+
38
+ log.log lint.message
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Outputs lints in format: {filename}:{line}:{column}: {kind}: {message}.
5
+ class Reporter::EmacsReporter < Reporter
6
+ def display_report(report)
7
+ sorted_lints = report.lints.sort_by { |l| [l.filename, l.line] }
8
+
9
+ sorted_lints.each do |lint|
10
+ print_location(lint)
11
+ print_type(lint)
12
+ print_message(lint)
13
+ end
14
+ end
15
+
16
+ private
17
+
18
+ def print_location(lint)
19
+ log.info lint.filename, false
20
+ log.log ':', false
21
+ log.bold lint.line, false
22
+ log.log ':', false
23
+ # TODO: change 1 to column number when linter will have this info.
24
+ log.bold 1, false
25
+ log.log ':', false
26
+ end
27
+
28
+ def print_type(lint)
29
+ if lint.error?
30
+ log.error ' E: ', false
31
+ else
32
+ log.warning ' W: ', false
33
+ end
34
+ end
35
+
36
+ def print_message(lint)
37
+ if lint.linter
38
+ log.success("#{lint.linter.name}: ", false)
39
+ end
40
+
41
+ log.log lint.message
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Outputs report as a JSON document.
5
+ class Reporter::JsonReporter < Reporter
6
+ def display_report(report)
7
+ lints = report.lints
8
+ grouped = lints.group_by(&:filename)
9
+
10
+ report_hash = {
11
+ metadata: metadata,
12
+ files: grouped.map { |l| map_file(l) },
13
+ summary: {
14
+ offense_count: lints.length,
15
+ target_file_count: grouped.length,
16
+ inspected_file_count: report.files.length,
17
+ },
18
+ }
19
+
20
+ log.log report_hash.to_json
21
+ end
22
+
23
+ private
24
+
25
+ def metadata
26
+ {
27
+ liquid_lint_version: LiquidLint::VERSION,
28
+ ruby_engine: RUBY_ENGINE,
29
+ ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
30
+ ruby_platform: RUBY_PLATFORM,
31
+ }
32
+ end
33
+
34
+ def map_file(file)
35
+ {
36
+ path: file.first,
37
+ offenses: file.last.map { |o| map_offense(o) },
38
+ }
39
+ end
40
+
41
+ def map_offense(offense)
42
+ {
43
+ severity: offense.severity,
44
+ message: offense.message,
45
+ location: {
46
+ line: offense.line,
47
+ },
48
+ linter: offense.linter&.name,
49
+ }
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Abstract lint reporter. Subclass and override {#display_report} to
5
+ # implement a custom lint reporter.
6
+ #
7
+ # @abstract
8
+ class Reporter
9
+ # Creates the reporter that will display the given report.
10
+ #
11
+ # @param logger [LiquidLint::Logger]
12
+ def initialize(logger)
13
+ @log = logger
14
+ end
15
+
16
+ # Implemented by subclasses to display lints from a {LiquidLint::Report}.
17
+ #
18
+ # @param report [LiquidLint::Report]
19
+ def display_report(report)
20
+ raise NotImplementedError,
21
+ "Implement `display_report` to display #{report}"
22
+ end
23
+
24
+ # Keep tracking all the descendants of this class for the list of available
25
+ # reporters.
26
+ #
27
+ # @return [Array<Class>]
28
+ def self.descendants
29
+ @descendants ||= []
30
+ end
31
+
32
+ # Executed when this class is subclassed.
33
+ #
34
+ # @param descendant [Class]
35
+ def self.inherited(descendant)
36
+ descendants << descendant
37
+ end
38
+
39
+ private
40
+
41
+ # @return [LiquidLint::Logger] logger to send output to
42
+ attr_reader :log
43
+ end
44
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Generates a {LiquidLint::Sexp} suitable for consumption by the
5
+ # {RubyExtractor}.
6
+ #
7
+ # This is mostly copied from Liquid::Engine, with some filters and generators
8
+ # omitted.
9
+ class RubyExtractEngine < Temple::Engine
10
+ filter :Encoding
11
+ filter :RemoveBOM
12
+
13
+ # Parse into S-expression using Liquid parser
14
+ use Liquid::Parser
15
+
16
+ # Perform additional processing so extracting Ruby code in {RubyExtractor}
17
+ # is easier. We don't do this for regular linters because some information
18
+ # about the original syntax tree is lost in the process, but that doesn't
19
+ # matter in this case.
20
+ use Liquid::Embedded
21
+ use Liquid::Interpolation
22
+ use LiquidLint::Filters::SplatProcessor
23
+ use Liquid::DoInserter
24
+ use Liquid::EndInserter
25
+ use LiquidLint::Filters::ControlProcessor
26
+ use LiquidLint::Filters::AttributeProcessor
27
+ filter :MultiFlattener
28
+ filter :StaticMerger
29
+
30
+ # Converts Array-based S-expressions into LiquidLint::Sexp objects, and gives
31
+ # them line numbers so we can easily map from the Ruby source to the
32
+ # original source
33
+ use LiquidLint::Filters::SexpConverter
34
+ use LiquidLint::Filters::InjectLineNumbers
35
+ end
36
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Utility class for extracting Ruby script from a Liquid template that can then
5
+ # be linted with a Ruby linter (i.e. is "legal" Ruby).
6
+ #
7
+ # The goal is to turn this:
8
+ #
9
+ # - if items.any?
10
+ # table#items
11
+ # - for item in items
12
+ # tr
13
+ # td.name = item.name
14
+ # td.price = item.price
15
+ # - else
16
+ # p No items found.
17
+ #
18
+ # into (something like) this:
19
+ #
20
+ # if items.any?
21
+ # for item in items
22
+ # puts item.name
23
+ # puts item.price
24
+ # else
25
+ # puts 'No items found'
26
+ # end
27
+ #
28
+ # The translation won't be perfect, and won't make any real sense, but the
29
+ # relationship between variable declarations/uses and the flow control graph
30
+ # will remain intact.
31
+ class RubyExtractor
32
+ include SexpVisitor
33
+ extend SexpVisitor::DSL
34
+
35
+ # Stores the extracted source and a map of lines of generated source to the
36
+ # original source that created them.
37
+ #
38
+ # @attr_reader source [String] generated source code
39
+ # @attr_reader source_map [Hash] map of line numbers from generated source
40
+ # to original source line number
41
+ RubySource = Struct.new(:source, :source_map)
42
+
43
+ # Extracts Ruby code from Sexp representing a Liquid document.
44
+ #
45
+ # @param sexp [LiquidLint::Sexp]
46
+ # @return [LiquidLint::RubyExtractor::RubySource]
47
+ def extract(sexp)
48
+ trigger_pattern_callbacks(sexp)
49
+ RubySource.new(@source_lines.join("\n"), @source_map)
50
+ end
51
+
52
+ on_start do |_sexp|
53
+ @source_lines = []
54
+ @source_map = {}
55
+ @line_count = 0
56
+ @dummy_puts_count = 0
57
+ end
58
+
59
+ on [:html, :doctype] do |sexp|
60
+ append_dummy_puts(sexp)
61
+ end
62
+
63
+ on [:html, :tag] do |sexp|
64
+ append_dummy_puts(sexp)
65
+ end
66
+
67
+ on [:static] do |sexp|
68
+ append_dummy_puts(sexp)
69
+ end
70
+
71
+ on [:dynamic] do |sexp|
72
+ _, ruby = sexp
73
+ append(ruby, sexp)
74
+ end
75
+
76
+ on [:code] do |sexp|
77
+ _, ruby = sexp
78
+ append(ruby, sexp)
79
+ end
80
+
81
+ private
82
+
83
+ # Append code to the buffer.
84
+ #
85
+ # @param code [String]
86
+ # @param sexp [LiquidLint::Sexp]
87
+ def append(code, sexp)
88
+ return if code.empty?
89
+
90
+ original_line = sexp.line
91
+
92
+ # For code that spans multiple lines, the resulting code will span
93
+ # multiple lines, so we need to create a mapping for each line.
94
+ code.split("\n").each_with_index do |line, index|
95
+ @source_lines << line
96
+ @line_count += 1
97
+ @source_map[@line_count] = original_line + index
98
+ end
99
+ end
100
+
101
+ def append_dummy_puts(sexp)
102
+ append("_liquid_lint_puts_#{@dummy_puts_count}", sexp)
103
+ @dummy_puts_count += 1
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop'
4
+ require 'rubocop/ast/builder'
5
+
6
+ def require_parser(path)
7
+ prev = $VERBOSE
8
+ $VERBOSE = nil
9
+ require "parser/#{path}"
10
+ ensure
11
+ $VERBOSE = prev
12
+ end
13
+
14
+ module LiquidLint
15
+ # Parser for the Ruby language.
16
+ #
17
+ # This provides a convenient wrapper around the `parser` gem and the
18
+ # `astrolabe` integration to go with it. It is intended to be used for linter
19
+ # checks that require deep inspection of Ruby code.
20
+ class RubyParser
21
+ # Creates a reusable parser.
22
+ def initialize
23
+ require_parser('current')
24
+ @builder = ::RuboCop::AST::Builder.new
25
+ @parser = ::Parser::CurrentRuby.new(@builder)
26
+ end
27
+
28
+ # Parse the given Ruby source into an abstract syntax tree.
29
+ #
30
+ # @param source [String] Ruby source code
31
+ # @return [Array] syntax tree in the form returned by Parser gem
32
+ def parse(source)
33
+ buffer = ::Parser::Source::Buffer.new('(string)')
34
+ buffer.source = source
35
+
36
+ @parser.reset
37
+ @parser.parse(buffer)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module LiquidLint
4
+ # Responsible for running the applicable linters against the desired files.
5
+ class Runner
6
+ # Runs the appropriate linters against the desired files given the specified
7
+ # options.
8
+ #
9
+ # @param [Hash] options
10
+ # @option options :config_file [String] path of configuration file to load
11
+ # @option options :config [LiquidLint::Configuration] configuration to use
12
+ # @option options :excluded_files [Array<String>]
13
+ # @option options :included_linters [Array<String>]
14
+ # @option options :excluded_linters [Array<String>]
15
+ # @return [LiquidLint::Report] a summary of all lints found
16
+ def run(options = {})
17
+ config = load_applicable_config(options)
18
+ linter_selector = LiquidLint::LinterSelector.new(config, options)
19
+
20
+ if options[:stdin_file_path].nil?
21
+ files = extract_applicable_files(config, options)
22
+ lints = files.map do |file|
23
+ collect_lints(File.read(file), file, linter_selector, config)
24
+ end.flatten
25
+ else
26
+ files = [options[:stdin_file_path]]
27
+ lints = collect_lints($stdin.read, options[:stdin_file_path], linter_selector, config)
28
+ end
29
+
30
+ LiquidLint::Report.new(lints, files)
31
+ end
32
+
33
+ private
34
+
35
+ # Returns the {LiquidLint::Configuration} that should be used given the
36
+ # specified options.
37
+ #
38
+ # @param options [Hash]
39
+ # @return [LiquidLint::Configuration]
40
+ def load_applicable_config(options)
41
+ if options[:config_file]
42
+ LiquidLint::ConfigurationLoader.load_file(options[:config_file])
43
+ elsif options[:config]
44
+ options[:config]
45
+ else
46
+ LiquidLint::ConfigurationLoader.load_applicable_config
47
+ end
48
+ end
49
+
50
+ # Runs all provided linters using the specified config against the given
51
+ # file.
52
+ #
53
+ # @param file [String] path to file to lint
54
+ # @param linter_selector [LiquidLint::LinterSelector]
55
+ # @param config [LiquidLint::Configuration]
56
+ def collect_lints(file_content, file_name, linter_selector, config)
57
+ begin
58
+ document = LiquidLint::Document.new(file_content, file: file_name, config: config)
59
+ rescue LiquidLint::Exceptions::ParseError => e
60
+ return [LiquidLint::Lint.new(nil, file_name, e.lineno, e.error, :error)]
61
+ end
62
+
63
+ linter_selector.linters_for_file(file_name).map do |linter|
64
+ linter.run(document)
65
+ end.flatten
66
+ end
67
+
68
+ # Returns the list of files that should be linted given the specified
69
+ # configuration and options.
70
+ #
71
+ # @param config [LiquidLint::Configuration]
72
+ # @param options [Hash]
73
+ # @return [Array<String>]
74
+ def extract_applicable_files(config, options)
75
+ included_patterns = options[:files]
76
+ excluded_patterns = config['exclude']
77
+ excluded_patterns += options.fetch(:excluded_files, [])
78
+
79
+ LiquidLint::FileFinder.new(config).find(included_patterns, excluded_patterns)
80
+ end
81
+ end
82
+ end