liquid_lint 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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