haml_lint 0.13.0 → 0.14.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/config/default.yml +1 -0
  3. data/lib/haml_lint/cli.rb +25 -8
  4. data/lib/haml_lint/configuration.rb +56 -34
  5. data/lib/haml_lint/configuration_loader.rb +22 -6
  6. data/lib/haml_lint/constants.rb +1 -1
  7. data/lib/haml_lint/document.rb +102 -0
  8. data/lib/haml_lint/file_finder.rb +14 -5
  9. data/lib/haml_lint/lint.rb +17 -1
  10. data/lib/haml_lint/linter/alt_text.rb +1 -1
  11. data/lib/haml_lint/linter/class_attribute_with_static_value.rb +2 -2
  12. data/lib/haml_lint/linter/classes_before_ids.rb +2 -2
  13. data/lib/haml_lint/linter/consecutive_comments.rb +3 -5
  14. data/lib/haml_lint/linter/consecutive_silent_scripts.rb +5 -5
  15. data/lib/haml_lint/linter/empty_script.rb +1 -1
  16. data/lib/haml_lint/linter/html_attributes.rb +2 -2
  17. data/lib/haml_lint/linter/implicit_div.rb +3 -3
  18. data/lib/haml_lint/linter/leading_comment_space.rb +1 -1
  19. data/lib/haml_lint/linter/line_length.rb +2 -2
  20. data/lib/haml_lint/linter/multiline_pipe.rb +3 -3
  21. data/lib/haml_lint/linter/multiline_script.rb +3 -3
  22. data/lib/haml_lint/linter/object_reference_attributes.rb +1 -1
  23. data/lib/haml_lint/linter/rubocop.rb +60 -34
  24. data/lib/haml_lint/linter/ruby_comments.rb +1 -1
  25. data/lib/haml_lint/linter/space_before_script.rb +4 -4
  26. data/lib/haml_lint/linter/space_inside_hash_attributes.rb +2 -2
  27. data/lib/haml_lint/linter/tag_name.rb +1 -1
  28. data/lib/haml_lint/linter/trailing_whitespace.rb +2 -2
  29. data/lib/haml_lint/linter/unnecessary_interpolation.rb +3 -3
  30. data/lib/haml_lint/linter/unnecessary_string_output.rb +10 -3
  31. data/lib/haml_lint/linter.rb +30 -10
  32. data/lib/haml_lint/linter_registry.rb +13 -2
  33. data/lib/haml_lint/linter_selector.rb +77 -0
  34. data/lib/haml_lint/node_transformer.rb +5 -5
  35. data/lib/haml_lint/options.rb +13 -6
  36. data/lib/haml_lint/rake_task.rb +18 -3
  37. data/lib/haml_lint/report.rb +7 -0
  38. data/lib/haml_lint/reporter/default_reporter.rb +2 -2
  39. data/lib/haml_lint/reporter/json_reporter.rb +15 -10
  40. data/lib/haml_lint/reporter.rb +17 -11
  41. data/lib/haml_lint/{script_extractor.rb → ruby_extractor.rb} +26 -17
  42. data/lib/haml_lint/runner.rb +44 -44
  43. data/lib/haml_lint/tree/node.rb +7 -8
  44. data/lib/haml_lint/utils.rb +88 -21
  45. data/lib/haml_lint/version.rb +1 -1
  46. data/lib/haml_lint.rb +2 -1
  47. metadata +5 -4
  48. data/lib/haml_lint/parser.rb +0 -87
@@ -1,29 +1,34 @@
1
1
  module HamlLint
2
2
  # Outputs report as a JSON document.
3
3
  class Reporter::JsonReporter < Reporter
4
- def report_lints
4
+ def display_report(report)
5
+ lints = report.lints
5
6
  grouped = lints.group_by(&:filename)
6
7
 
7
- report = {
8
- metadata: {
9
- hamllint_version: VERSION,
10
- ruby_engine: RUBY_ENGINE,
11
- ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
12
- ruby_platform: RUBY_PLATFORM,
13
- },
8
+ report_hash = {
9
+ metadata: metadata,
14
10
  files: grouped.map { |l| map_file(l) },
15
11
  summary: {
16
12
  offense_count: lints.length,
17
13
  target_file_count: grouped.length,
18
- inspected_file_count: files.length,
14
+ inspected_file_count: report.files.length,
19
15
  },
20
16
  }
21
17
 
22
- log.log report.to_json
18
+ log.log report_hash.to_json
23
19
  end
24
20
 
25
21
  private
26
22
 
23
+ def metadata
24
+ {
25
+ haml_lint_version: HamlLint::VERSION,
26
+ ruby_engine: RUBY_ENGINE,
27
+ ruby_patchlevel: RUBY_PATCHLEVEL.to_s,
28
+ ruby_platform: RUBY_PLATFORM,
29
+ }
30
+ end
31
+
27
32
  def map_file(file)
28
33
  {
29
34
  path: file.first,
@@ -1,36 +1,42 @@
1
1
  module HamlLint
2
- # Abstract lint reporter. Subclass and override {#report_lints} to
2
+ # Abstract lint reporter. Subclass and override {#display_report} to
3
3
  # implement a custom lint reporter.
4
4
  #
5
5
  # @abstract
6
6
  class Reporter
7
- attr_reader :lints
8
- attr_reader :files
9
-
7
+ # Creates the reporter that will display the given report.
8
+ #
10
9
  # @param logger [HamlLint::Logger]
11
- # @param report [HamlLint::Report]
12
- def initialize(logger, report)
10
+ def initialize(logger)
13
11
  @log = logger
14
- @lints = report.lints
15
- @files = report.files
16
12
  end
17
13
 
18
14
  # Implemented by subclasses to display lints from a {HamlLint::Report}.
19
- def report_lints
20
- raise NotImplementedError
15
+ #
16
+ # @param report [HamlLint::Report]
17
+ def display_report(report)
18
+ raise NotImplementedError,
19
+ "Implement `display_report` to display #{report}"
21
20
  end
22
21
 
23
- # Keep tracking all the descendants of this class for the list of available reporters
22
+ # Keep tracking all the descendants of this class for the list of available
23
+ # reporters.
24
+ #
25
+ # @return [Array<Class>]
24
26
  def self.descendants
25
27
  @descendants ||= []
26
28
  end
27
29
 
30
+ # Executed when this class is subclassed.
31
+ #
32
+ # @param descendant [Class]
28
33
  def self.inherited(descendant)
29
34
  descendants << descendant
30
35
  end
31
36
 
32
37
  private
33
38
 
39
+ # @return [HamlLint::Logger] logger to send output to
34
40
  attr_reader :log
35
41
  end
36
42
  end
@@ -19,24 +19,33 @@ module HamlLint
19
19
  # link_to 'Sign In', sign_in_path
20
20
  # end
21
21
  #
22
- class ScriptExtractor
22
+ # The translation won't be perfect, and won't make any real sense, but the
23
+ # relationship between variable declarations/uses and the flow control graph
24
+ # will remain intact.
25
+ class RubyExtractor
23
26
  include HamlVisitor
24
27
 
25
- attr_reader :source, :source_map
26
-
27
- def initialize(parser)
28
- @parser = parser
29
- end
30
-
31
- def extract
32
- visit(@parser.tree)
33
- @source = @code.join("\n")
28
+ # Stores the extracted source and a map of lines of generated source to the
29
+ # original source that created them.
30
+ #
31
+ # @attr_reader source [String] generated source code
32
+ # @attr_reader source_map [Hash] map of line numbers from generated source
33
+ # to original source line number
34
+ RubySource = Struct.new(:source, :source_map)
35
+
36
+ # Extracts Ruby code from Sexp representing a Slim document.
37
+ #
38
+ # @param document [HamlLint::Document]
39
+ # @return [HamlLint::RubyExtractor::RubySource]
40
+ def extract(document)
41
+ visit(document.tree)
42
+ RubySource.new(@source_lines.join("\n"), @source_map)
34
43
  end
35
44
 
36
45
  def visit_root(_node)
37
- @code = []
38
- @total_lines = 0
46
+ @source_lines = []
39
47
  @source_map = {}
48
+ @line_count = 0
40
49
  @indent_level = 0
41
50
 
42
51
  yield # Collect lines of code from children
@@ -104,8 +113,8 @@ module HamlLint
104
113
  end
105
114
  else
106
115
  add_line('puts', node)
107
- HamlLint::Utils.extract_interpolated_values(node.text) do |interpolated_code|
108
- add_line(interpolated_code, node)
116
+ HamlLint::Utils.extract_interpolated_values(node.text) do |interpolated_code, line|
117
+ add_line(interpolated_code, node.line + line)
109
118
  end
110
119
  end
111
120
  end
@@ -140,7 +149,7 @@ module HamlLint
140
149
 
141
150
  indent = (' ' * 2 * indent_level)
142
151
 
143
- @code << indent + code
152
+ @source_lines << indent + code
144
153
 
145
154
  original_line =
146
155
  node_or_line.respond_to?(:line) ? node_or_line.line : node_or_line
@@ -149,8 +158,8 @@ module HamlLint
149
158
  # resulting code will span multiple lines, so we need to create a
150
159
  # mapping for each line.
151
160
  (code.count("\n") + 1).times do
152
- @total_lines += 1
153
- @source_map[@total_lines] = original_line
161
+ @line_count += 1
162
+ @source_map[@line_count] = original_line
154
163
  end
155
164
  end
156
165
 
@@ -1,76 +1,76 @@
1
1
  module HamlLint
2
2
  # Responsible for running the applicable linters against the desired files.
3
3
  class Runner
4
- # Make the list of applicable files available
5
- attr_reader :files
6
-
7
4
  # Runs the appropriate linters against the desired files given the specified
8
5
  # options.
9
6
  #
10
- # @param options [Hash]
11
- # @raise [HamlLint::Exceptions::NoLintersError] when no linters are enabled
7
+ # @param [Hash] options
8
+ # @option options :config_file [String] path of configuration file to load
9
+ # @option options :config [HamlLint::Configuration] configuration to use
10
+ # @option options :excluded_files [Array<String>]
11
+ # @option options :included_linters [Array<String>]
12
+ # @option options :excluded_linters [Array<String>]
12
13
  # @return [HamlLint::Report] a summary of all lints found
13
14
  def run(options = {})
14
15
  config = load_applicable_config(options)
15
- files = extract_applicable_files(options, config)
16
- linters = extract_enabled_linters(config, options)
16
+ files = extract_applicable_files(config, options)
17
17
 
18
- raise HamlLint::Exceptions::NoLintersError, 'No linters specified' if linters.empty?
18
+ linter_selector = HamlLint::LinterSelector.new(config, options)
19
19
 
20
- @lints = []
21
- files.each do |file|
22
- find_lints(file, linters, config)
23
- end
24
-
25
- linters.each do |linter|
26
- @lints += linter.lints
27
- end
20
+ lints = files.map do |file|
21
+ collect_lints(file, linter_selector, config)
22
+ end.flatten
28
23
 
29
- HamlLint::Report.new(@lints, files)
24
+ HamlLint::Report.new(lints, files)
30
25
  end
31
26
 
32
27
  private
33
28
 
29
+ # Returns the {HamlLint::Configuration} that should be used given the
30
+ # specified options.
31
+ #
32
+ # @param options [Hash]
33
+ # @return [HamlLint::Configuration]
34
34
  def load_applicable_config(options)
35
35
  if options[:config_file]
36
36
  HamlLint::ConfigurationLoader.load_file(options[:config_file])
37
+ elsif options[:config]
38
+ options[:config]
37
39
  else
38
40
  HamlLint::ConfigurationLoader.load_applicable_config
39
41
  end
40
42
  end
41
43
 
42
- def extract_enabled_linters(config, options)
43
- included_linters = LinterRegistry
44
- .extract_linters_from(options.fetch(:included_linters, []))
45
-
46
- included_linters = LinterRegistry.linters if included_linters.empty?
47
-
48
- excluded_linters = LinterRegistry
49
- .extract_linters_from(options.fetch(:excluded_linters, []))
50
-
51
- # After filtering out explicitly included/excluded linters, only include
52
- # linters which are enabled in the configuration
53
- (included_linters - excluded_linters).map do |linter_class|
54
- linter_config = config.for_linter(linter_class)
55
- linter_class.new(linter_config) if linter_config['enabled']
56
- end.compact
57
- end
58
-
59
- def find_lints(file, linters, config)
60
- parser = Parser.new(file, config.hash)
61
-
62
- linters.each do |linter|
63
- linter.run(parser)
44
+ # Runs all provided linters using the specified config against the given
45
+ # file.
46
+ #
47
+ # @param file [String] path to file to lint
48
+ # @param linter_selector [HamlLint::LinterSelector]
49
+ # @param config [HamlLint::Configuration]
50
+ def collect_lints(file, linter_selector, config)
51
+ begin
52
+ document = HamlLint::Document.new(File.read(file), file: file, config: config)
53
+ rescue Haml::Error => ex
54
+ return [HamlLint::Lint.new(nil, file, ex.line, ex.to_s, :error)]
64
55
  end
65
- rescue Haml::Error => ex
66
- @lints << Lint.new(nil, file, ex.line, ex.to_s, :error)
56
+
57
+ linter_selector.linters_for_file(file).map do |linter|
58
+ linter.run(document)
59
+ end.flatten
67
60
  end
68
61
 
69
- def extract_applicable_files(options, config)
62
+ # Returns the list of files that should be linted given the specified
63
+ # configuration and options.
64
+ #
65
+ # @param config [HamlLint::Configuration]
66
+ # @param options [Hash]
67
+ # @return [Array<String>]
68
+ def extract_applicable_files(config, options)
70
69
  included_patterns = options[:files]
71
- excluded_files = options.fetch(:excluded_files, [])
70
+ excluded_patterns = config['exclude']
71
+ excluded_patterns += options.fetch(:excluded_files, [])
72
72
 
73
- HamlLint::FileFinder.new(config).find(included_patterns, excluded_files)
73
+ HamlLint::FileFinder.new(config).find(included_patterns, excluded_patterns)
74
74
  end
75
75
  end
76
76
  end
@@ -14,12 +14,11 @@ module HamlLint::Tree
14
14
 
15
15
  # Creates a node wrapping the given {Haml::Parser::ParseNode} struct.
16
16
  #
17
- # @param parser [HamlLint::Parser] parser that created this node
17
+ # @param document [HamlLint::Document] Haml document that created this node
18
18
  # @param parse_node [Haml::Parser::ParseNode] parse node created by HAML's parser
19
- def initialize(parser, parse_node)
20
- # TODO: Change signature to take source code object, not parser
19
+ def initialize(document, parse_node)
21
20
  @line = parse_node.line
22
- @parser = parser
21
+ @document = document
23
22
  @value = parse_node.value
24
23
  @type = parse_node.type
25
24
  end
@@ -50,12 +49,12 @@ module HamlLint::Tree
50
49
  if next_node
51
50
  next_node.line - 1
52
51
  else
53
- @parser.lines.count + 1
52
+ @document.source_lines.count + 1
54
53
  end
55
54
 
56
- @parser.lines[@line - 1...next_node_line]
57
- .join("\n")
58
- .gsub(/^\s*\z/m, '') # Remove blank lines at the end
55
+ @document.source_lines[@line - 1...next_node_line]
56
+ .join("\n")
57
+ .gsub(/^\s*\z/m, '') # Remove blank lines at the end
59
58
  end
60
59
 
61
60
  def inspect
@@ -3,56 +3,123 @@ module HamlLint
3
3
  module Utils
4
4
  module_function
5
5
 
6
- # Yields interpolated values within a block of filter text.
7
- def extract_interpolated_values(filter_text)
8
- Haml::Util.handle_interpolation(filter_text.dump) do |scan|
6
+ # Returns whether a glob pattern (or any of a list of patterns) matches the
7
+ # specified file.
8
+ #
9
+ # This is defined here so our file globbing options are consistent
10
+ # everywhere we perform globbing.
11
+ #
12
+ # @param glob [String, Array]
13
+ # @param file [String]
14
+ # @return [Boolean]
15
+ def any_glob_matches?(globs_or_glob, file)
16
+ Array(globs_or_glob).any? do |glob|
17
+ ::File.fnmatch?(glob, file,
18
+ ::File::FNM_PATHNAME | # Wildcards don't match path separators
19
+ ::File::FNM_DOTMATCH) # `*` wildcard matches dotfiles
20
+ end
21
+ end
22
+
23
+ # Yields interpolated values within a block of text.
24
+ #
25
+ # @param text [String]
26
+ # @yield Passes interpolated code and line number that code appears on in
27
+ # the text.
28
+ # @yieldparam interpolated_code [String] code that was interpolated
29
+ # @yieldparam line [Integer] line number code appears on in text
30
+ def extract_interpolated_values(text) # rubocop:disable Metrics/AbcSize
31
+ dumped_text = text.dump
32
+ newline_positions = extract_substring_positions(dumped_text, '\\\n')
33
+
34
+ Haml::Util.handle_interpolation(dumped_text) do |scan|
35
+ line = (newline_positions.find_index { |marker| scan.pos <= marker } ||
36
+ newline_positions.size) + 1
37
+
9
38
  escape_count = (scan[2].size - 1) / 2
10
- return unless escape_count.even? # rubocop:disable Lint/NonLocalExitFromIterator
39
+ break unless escape_count.even?
11
40
 
12
41
  dumped_interpolated_str = Haml::Util.balance(scan, '{', '}', 1)[0][0...-1]
13
42
 
14
43
  # Hacky way to turn a dumped string back into a regular string
15
- yield eval('"' + dumped_interpolated_str + '"') # rubocop:disable Eval
44
+ yield [eval('"' + dumped_interpolated_str + '"'), line] # rubocop:disable Eval
16
45
  end
17
46
  end
18
47
 
48
+ # Returns indexes of all occurrences of a substring within a string.
49
+ #
50
+ # Note, this will not return overlaping substrings, so searching for "aa"
51
+ # in "aaa" will only find one substring, not two.
52
+ #
53
+ # @param text [String] the text to search
54
+ # @param substr [String] the substring to search for
55
+ # @return [Array<Integer>] list of indexes where the substring occurs
56
+ def extract_substring_positions(text, substr)
57
+ positions = []
58
+ scanner = StringScanner.new(text)
59
+ positions << scanner.pos while scanner.scan(/(.*?)#{substr}/)
60
+ positions
61
+ end
62
+
19
63
  # Converts a string containing underscores/hyphens/spaces into CamelCase.
20
64
  def camel_case(str)
21
65
  str.split(/_|-| /).map { |part| part.sub(/^\w/) { |c| c.upcase } }.join
22
66
  end
23
67
 
24
- # Find all consecutive nodes satisfying the given {Proc} of a minimum size
25
- # and yield each group.
68
+ # Find all consecutive items satisfying the given block of a minimum size,
69
+ # yielding each group of consecutive items to the provided block.
26
70
  #
27
71
  # @param items [Array]
28
- # @param min_size [Fixnum] minimum number of consecutive items before
29
- # yielding
30
72
  # @param satisfies [Proc] function that takes an item and returns true/false
31
- def find_consecutive(items, min_size, satisfies)
32
- current = -1
73
+ # @param min_consecutive [Fixnum] minimum number of consecutive items before
74
+ # yielding the group
75
+ # @yield Passes list of consecutive items all matching the criteria defined
76
+ # by the `satisfies` {Proc} to the provided block
77
+ # @yieldparam group [Array] List of consecutive items
78
+ # @yieldreturn [Boolean] block should return whether item matches criteria
79
+ # for inclusion
80
+ def for_consecutive_items(items, satisfies, min_consecutive = 2)
81
+ current_index = -1
33
82
 
34
- while (current += 1) < items.count
35
- next unless satisfies[items[current]]
83
+ while (current_index += 1) < items.count
84
+ next unless satisfies[items[current_index]]
36
85
 
37
- count = count_consecutive(items, current, satisfies)
38
- next unless count >= min_size
86
+ count = count_consecutive(items, current_index, &satisfies)
87
+ next unless count >= min_consecutive
39
88
 
40
89
  # Yield the chunk of consecutive items
41
- yield items[current...(current + count)]
90
+ yield items[current_index...(current_index + count)]
42
91
 
43
- current += count # Skip this patch of consecutive items to find more
92
+ current_index += count # Skip this patch of consecutive items to find more
44
93
  end
45
94
  end
46
95
 
47
96
  # Count the number of consecutive items satisfying the given {Proc}.
48
97
  #
49
98
  # @param items [Array]
50
- # @param offset [Fixnum] index to start searching
51
- # @param satisfies [Proc] function to evaluate item with
52
- def count_consecutive(items, offset, satisfies)
99
+ # @param offset [Fixnum] index to start searching from
100
+ # @yield [item] Passes item to the provided block.
101
+ # @yieldparam item [Object] Item to evaluate as matching criteria for
102
+ # inclusion
103
+ # @yieldreturn [Boolean] whether to include the item
104
+ # @return [Integer]
105
+ def count_consecutive(items, offset = 0, &block)
53
106
  count = 1
54
- count += 1 while (offset + count < items.count) && satisfies[items[offset + count]]
107
+ count += 1 while (offset + count < items.count) && block.call(items[offset + count])
55
108
  count
56
109
  end
110
+
111
+ # Calls a block of code with a modified set of environment variables,
112
+ # restoring them once the code has executed.
113
+ def with_environment(env)
114
+ old_env = {}
115
+ env.each do |var, value|
116
+ old_env[var] = ENV[var.to_s]
117
+ ENV[var.to_s] = value
118
+ end
119
+
120
+ yield
121
+ ensure
122
+ old_env.each { |var, value| ENV[var.to_s] = value }
123
+ end
57
124
  end
58
125
  end
@@ -1,4 +1,4 @@
1
1
  # Defines the gem version.
2
2
  module HamlLint
3
- VERSION = '0.13.0'
3
+ VERSION = '0.14.0'
4
4
  end
data/lib/haml_lint.rb CHANGED
@@ -2,7 +2,7 @@ require 'haml_lint/constants'
2
2
  require 'haml_lint/exceptions'
3
3
  require 'haml_lint/configuration'
4
4
  require 'haml_lint/configuration_loader'
5
- require 'haml_lint/parser'
5
+ require 'haml_lint/document'
6
6
  require 'haml_lint/haml_visitor'
7
7
  require 'haml_lint/lint'
8
8
  require 'haml_lint/linter_registry'
@@ -11,6 +11,7 @@ require 'haml_lint/linter'
11
11
  require 'haml_lint/logger'
12
12
  require 'haml_lint/reporter'
13
13
  require 'haml_lint/report'
14
+ require 'haml_lint/linter_selector'
14
15
  require 'haml_lint/file_finder'
15
16
  require 'haml_lint/runner'
16
17
  require 'haml_lint/utils'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: haml_lint
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.13.0
4
+ version: 0.14.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brigade Engineering
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2015-05-03 00:00:00.000000000 Z
12
+ date: 2015-06-09 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: haml
@@ -97,6 +97,7 @@ files:
97
97
  - lib/haml_lint/configuration.rb
98
98
  - lib/haml_lint/configuration_loader.rb
99
99
  - lib/haml_lint/constants.rb
100
+ - lib/haml_lint/document.rb
100
101
  - lib/haml_lint/exceptions.rb
101
102
  - lib/haml_lint/file_finder.rb
102
103
  - lib/haml_lint/haml_visitor.rb
@@ -124,18 +125,18 @@ files:
124
125
  - lib/haml_lint/linter/unnecessary_interpolation.rb
125
126
  - lib/haml_lint/linter/unnecessary_string_output.rb
126
127
  - lib/haml_lint/linter_registry.rb
128
+ - lib/haml_lint/linter_selector.rb
127
129
  - lib/haml_lint/logger.rb
128
130
  - lib/haml_lint/node_transformer.rb
129
131
  - lib/haml_lint/options.rb
130
- - lib/haml_lint/parser.rb
131
132
  - lib/haml_lint/rake_task.rb
132
133
  - lib/haml_lint/report.rb
133
134
  - lib/haml_lint/reporter.rb
134
135
  - lib/haml_lint/reporter/default_reporter.rb
135
136
  - lib/haml_lint/reporter/json_reporter.rb
137
+ - lib/haml_lint/ruby_extractor.rb
136
138
  - lib/haml_lint/ruby_parser.rb
137
139
  - lib/haml_lint/runner.rb
138
- - lib/haml_lint/script_extractor.rb
139
140
  - lib/haml_lint/tree/comment_node.rb
140
141
  - lib/haml_lint/tree/doctype_node.rb
141
142
  - lib/haml_lint/tree/filter_node.rb
@@ -1,87 +0,0 @@
1
- require 'haml'
2
-
3
- module HamlLint
4
- # Parses a HAML document for inspection by linters.
5
- class Parser
6
- attr_reader :contents, :filename, :lines, :tree
7
-
8
- # Creates a parser containing the parse tree of a HAML document.
9
- #
10
- # @param haml_or_filename [String]
11
- # @param options [Hash]
12
- # @option options [true,false] 'skip_frontmatter' Whether to skip
13
- # frontmatter included by frameworks such as Middleman or Jekyll
14
- def initialize(haml_or_filename, options = {})
15
- if File.exist?(haml_or_filename)
16
- build_from_file(haml_or_filename)
17
- else
18
- build_from_string(haml_or_filename)
19
- end
20
-
21
- process_options(options)
22
-
23
- build_parse_tree
24
- end
25
-
26
- private
27
-
28
- # @param path [String]
29
- def build_from_file(path)
30
- @filename = path
31
- @contents = File.read(path)
32
- end
33
-
34
- # @param haml [String]
35
- def build_from_string(haml)
36
- @contents = haml
37
- end
38
-
39
- def build_parse_tree
40
- original_tree = Haml::Parser.new(@contents, Haml::Options.new).parse
41
-
42
- # Remove the trailing empty HAML comment that the parser creates to signal
43
- # the end of the HAML document
44
- if Gem::Requirement.new('~> 4.0.0').satisfied_by?(Gem.loaded_specs['haml'].version)
45
- original_tree.children.pop
46
- end
47
-
48
- @node_transformer = HamlLint::NodeTransformer.new(self)
49
- @tree = convert_tree(original_tree)
50
- end
51
-
52
- def process_options(options)
53
- if options['skip_frontmatter'] &&
54
- @contents =~ /
55
- # From the start of the string
56
- \A
57
- # First-capture match --- followed by optional whitespace up
58
- # to a newline then 0 or more chars followed by an optional newline.
59
- # This matches the --- and the contents of the frontmatter
60
- (---\s*\n.*?\n?)
61
- # From the start of the line
62
- ^
63
- # Second capture match --- or ... followed by optional whitespace
64
- # and newline. This matches the closing --- for the frontmatter.
65
- (---|\.\.\.)\s*$\n?/mx
66
- @contents = $POSTMATCH
67
- end
68
-
69
- @lines = @contents.split("\n")
70
- end
71
-
72
- # Converts a HAML parse tree to a tree of {HamlLint::Tree::Node} objects.
73
- #
74
- # This provides a cleaner interface with which the linters can interact with
75
- # the parse tree.
76
- def convert_tree(haml_node, parent = nil)
77
- new_node = @node_transformer.transform(haml_node)
78
- new_node.parent = parent
79
-
80
- new_node.children = haml_node.children.map do |child|
81
- convert_tree(child, new_node)
82
- end
83
-
84
- new_node
85
- end
86
- end
87
- end