gitlab-styles 4.3.0 → 5.4.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/.editorconfig +18 -0
  3. data/.gitlab-ci.yml +5 -16
  4. data/.gitlab/merge_request_templates/New Static Analysis Check.md +21 -0
  5. data/.gitlab/merge_request_templates/Release.md +4 -4
  6. data/.rubocop.yml +8 -1
  7. data/Gemfile +2 -2
  8. data/gitlab-styles.gemspec +6 -5
  9. data/lib/gitlab/styles/rubocop.rb +3 -15
  10. data/lib/gitlab/styles/rubocop/cop/active_record_dependent.rb +1 -1
  11. data/lib/gitlab/styles/rubocop/cop/code_reuse/active_record.rb +131 -0
  12. data/lib/gitlab/styles/rubocop/cop/custom_error_class.rb +6 -3
  13. data/lib/gitlab/styles/rubocop/cop/gem_fetcher.rb +1 -1
  14. data/lib/gitlab/styles/rubocop/cop/internal_affairs/deprecate_cop_helper.rb +43 -0
  15. data/lib/gitlab/styles/rubocop/cop/line_break_after_guard_clauses.rb +1 -1
  16. data/lib/gitlab/styles/rubocop/cop/line_break_around_conditional_block.rb +132 -0
  17. data/lib/gitlab/styles/rubocop/cop/migration/update_large_table.rb +1 -1
  18. data/lib/gitlab/styles/rubocop/cop/polymorphic_associations.rb +1 -1
  19. data/lib/gitlab/styles/rubocop/cop/rspec/base.rb +18 -0
  20. data/lib/gitlab/styles/rubocop/cop/rspec/empty_line_after_let_block.rb +7 -7
  21. data/lib/gitlab/styles/rubocop/cop/rspec/empty_line_after_shared_example.rb +6 -6
  22. data/lib/gitlab/styles/rubocop/cop/rspec/example_starting_character.rb +124 -0
  23. data/lib/gitlab/styles/rubocop/cop/rspec/have_link_parameters.rb +10 -5
  24. data/lib/gitlab/styles/rubocop/cop/rspec/single_line_hook.rb +3 -2
  25. data/lib/gitlab/styles/rubocop/cop/rspec/verbose_include_metadata.rb +10 -13
  26. data/lib/gitlab/styles/rubocop/model_helpers.rb +1 -1
  27. data/lib/gitlab/styles/rubocop/rspec/helpers.rb +17 -0
  28. data/lib/gitlab/styles/version.rb +1 -1
  29. data/rubocop-all.yml +1 -0
  30. data/rubocop-bundler.yml +1 -0
  31. data/rubocop-code_reuse.yml +24 -0
  32. data/rubocop-default.yml +4 -0
  33. data/rubocop-gemspec.yml +1 -0
  34. data/rubocop-layout.yml +6 -0
  35. data/rubocop-lint.yml +63 -5
  36. data/rubocop-metrics.yml +1 -0
  37. data/rubocop-migrations.yml +1 -0
  38. data/rubocop-naming.yml +1 -0
  39. data/rubocop-performance.yml +48 -0
  40. data/rubocop-rails.yml +79 -0
  41. data/rubocop-rspec.yml +6 -0
  42. data/rubocop-security.yml +1 -0
  43. data/rubocop-style.yml +91 -1
  44. metadata +22 -14
  45. data/.rubocop_todo.yml +0 -7
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 00dda1d4ea5cfd931d74ccf3def7dd6ed3552359c2519e7b6a73464cb3612b92
4
- data.tar.gz: 8fd90715af2921b22e53d241f12e4a546f8d53923a688028132af3e5e28f0349
3
+ metadata.gz: b2ceb4c3c99df5b88255a7b7bcd4cbfaa5e029f81cc73f09b937cb15b7e53ffd
4
+ data.tar.gz: e4e5a5425299c7cb3d8e40f79fb4f817444aec62c378d8c8b547a309e3fe7211
5
5
  SHA512:
6
- metadata.gz: 58e29fee8c087356c272e72e9d3a8b719581e5f185ceed85d554a9ccb664654288373b2a6d1e4c1ba9587bfce4c2225960797e4c89e321688da8fa52d4a12b81
7
- data.tar.gz: a2c2f9843238a87f1af60f28beac5ec8c1ae5f1d236ab1554e88d07f95824e6474dda3b96ff5ec26a3219015c5415ea21824d41cc98d3cd06990b5ff55104097
6
+ metadata.gz: 1626e2623b8e2d5bb2eeff292a4746a8352f302a53277b9118bd20494f6489501f7f19c0442510435ca55ac92cdf61536dfe5bb5c3ad09a9f3eea1b769636780
7
+ data.tar.gz: b64077d9097e0d3adb6365fe41662b6e5264921ebfa5ed493b7dec7c8eaa8dbc4ae63c286b51f34b55811664a8a8e3fa9da5aa0098840f5dfddac54c9af419b4
@@ -0,0 +1,18 @@
1
+ # top-most EditorConfig file
2
+ root = true
3
+
4
+ # Unix-style newlines with a newline ending every file
5
+ [*]
6
+ end_of_line = lf
7
+ trim_trailing_whitespace = true
8
+ insert_final_newline = true
9
+
10
+ [*.{rb,yml}]
11
+ indent_size = 2
12
+
13
+ [*.{rb,yml,md}]
14
+ indent_style = space
15
+ charset = utf-8
16
+
17
+ [*.{md,markdown}]
18
+ trim_trailing_whitespace = false
@@ -1,9 +1,9 @@
1
1
  stages:
2
- - release
3
2
  - test
3
+ - deploy
4
4
 
5
5
  default:
6
- image: ruby:2.6
6
+ image: ruby:2.7
7
7
  tags:
8
8
  - gitlab-org
9
9
  before_script:
@@ -29,17 +29,6 @@ specs:
29
29
  script:
30
30
  - bundle exec rspec
31
31
 
32
- release:
33
- stage: release
34
- rules:
35
- - if: '$CI_COMMIT_TAG'
36
- script:
37
- - gem update --system
38
- - ruby --version
39
- - gem env version
40
- - gem build gitlab-styles.gemspec
41
- - gem push gitlab-styles*.gem
42
- artifacts:
43
- paths:
44
- - gitlab-styles*.gem
45
- expire_in: 30 days
32
+ include:
33
+ - project: 'gitlab-org/quality/pipeline-common'
34
+ file: '/ci/gem-release.yml'
@@ -0,0 +1,21 @@
1
+ ## Description of the proposal
2
+
3
+ <!--
4
+ Please describe the proposal and add a link to the source (for example, http://www.betterspecs.org/).
5
+ -->
6
+
7
+ ### Check-list
8
+
9
+ - [ ] Mention this proposal in the relevant Slack channels (e.g. `#development`, `#backend`, `#frontend`)
10
+ - [ ] If there is a choice to make between two potential styles, set up an emoji vote in the MR:
11
+ - CHOICE_A: :a:
12
+ - CHOICE_B: :b:
13
+ - Vote yourself for both choices so that people know these are the choices
14
+ - [ ] The MR doesn't have significant objections, and is getting a majority of :+1: vs :-1: (remember that [we don't need to reach a consensus](https://about.gitlab.com/handbook/values/#collaboration-is-not-consensus))
15
+ - [ ] (If applicable) One style is getting a majority of vote (compared to the other choice)
16
+ - [ ] (If applicable) Update the MR with the chosen style
17
+ - [ ] Follow the [review process](https://docs.gitlab.com/ee/development/code_review.html) as usual
18
+
19
+ /label ~"Engineering Productivity" ~"development guidelines" ~"static code analysis"
20
+
21
+ /cc @gitlab-org/maintainers/rails-backend
@@ -7,7 +7,7 @@ with the latest commit from https://gitlab.com/gitlab-org/gitlab-styles/commits/
7
7
  <!-- Keep the sections order but remove the empty sections -->
8
8
 
9
9
  ```markdown
10
- ### New scenarios and scenario updates
10
+ ### New features and features updates
11
11
 
12
12
  - !aaa <Title of the aaa MR>.
13
13
 
@@ -19,7 +19,7 @@ with the latest commit from https://gitlab.com/gitlab-org/gitlab-styles/commits/
19
19
 
20
20
  - !ccc <Title of the ccc MR>.
21
21
 
22
- ### Other changes (CI, backstage)
22
+ ### Other changes (tooling, technical debt)
23
23
 
24
24
  - !ddd <Title of the ddd MR>.
25
25
  ```
@@ -30,6 +30,6 @@ with the latest commit from https://gitlab.com/gitlab-org/gitlab-styles/commits/
30
30
  - [ ] Release notes are accurate.
31
31
 
32
32
  - Checklist after merging:
33
- - [ ] [Create a tag for the new release version](docs/release_process.md#how-to).
33
+ - [ ] [Update the release notes for the newly created tag](docs/release_process.md#how-to).
34
34
 
35
- /label ~"Engineering Productivity" ~"tooling::workflow"
35
+ /label ~"Engineering Productivity" ~"feature" ~"feature::maintenance" ~"static code analysis"
@@ -1,3 +1,10 @@
1
1
  inherit_from:
2
2
  - rubocop-default.yml
3
- - .rubocop_todo.yml
3
+
4
+ require:
5
+ - rubocop/cop/internal_affairs
6
+
7
+ InternalAffairs/DeprecateCopHelper:
8
+ Enabled: true
9
+ Include:
10
+ - spec/**/*.rb
data/Gemfile CHANGED
@@ -7,7 +7,7 @@ gemspec
7
7
 
8
8
  group :test do
9
9
  # Pin these dependencies, otherwise a new rule could break the CI pipelines
10
- gem 'rubocop', '0.82.0'
11
- gem 'rubocop-rspec', '1.36.0'
10
+ gem 'rubocop', '0.89.1'
11
+ gem 'rubocop-rspec', '1.44.1'
12
12
  gem 'rspec-parameterized', '0.4.2', require: false
13
13
  end
@@ -5,6 +5,7 @@ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
5
  require 'gitlab/styles/version'
6
6
 
7
7
  Gem::Specification.new do |spec|
8
+ spec.required_ruby_version = '>= 2.6'
8
9
  spec.name = 'gitlab-styles'
9
10
  spec.version = Gitlab::Styles::VERSION
10
11
  spec.authors = ['GitLab']
@@ -21,13 +22,13 @@ Gem::Specification.new do |spec|
21
22
  spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
22
23
  spec.require_paths = ['lib']
23
24
 
24
- spec.add_dependency 'rubocop', '~> 0.82.0'
25
+ spec.add_dependency 'rubocop', '~> 0.89.1'
25
26
  spec.add_dependency 'rubocop-gitlab-security', '~> 0.1.0'
26
- spec.add_dependency 'rubocop-performance', '~> 1.5.2'
27
- spec.add_dependency 'rubocop-rails', '~> 2.5'
28
- spec.add_dependency 'rubocop-rspec', '~> 1.36'
27
+ spec.add_dependency 'rubocop-performance', '~> 1.8.1'
28
+ spec.add_dependency 'rubocop-rails', '~> 2.8'
29
+ spec.add_dependency 'rubocop-rspec', '~> 1.44'
29
30
 
30
- spec.add_development_dependency 'bundler', '~> 1.16'
31
+ spec.add_development_dependency 'bundler', '~> 2.1'
31
32
  spec.add_development_dependency 'rake', '~> 10.0'
32
33
  spec.add_development_dependency 'rspec', '~> 3.0'
33
34
  end
@@ -1,20 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'gitlab/styles/rubocop/cop/custom_error_class'
4
- require 'gitlab/styles/rubocop/cop/gem_fetcher'
5
- require 'gitlab/styles/rubocop/cop/active_record_serialize'
6
- require 'gitlab/styles/rubocop/cop/redirect_with_status'
7
- require 'gitlab/styles/rubocop/cop/polymorphic_associations'
8
- require 'gitlab/styles/rubocop/cop/active_record_dependent'
9
- require 'gitlab/styles/rubocop/cop/in_batches'
10
- require 'gitlab/styles/rubocop/cop/line_break_after_guard_clauses'
11
- require 'gitlab/styles/rubocop/cop/migration/update_large_table'
12
- require 'gitlab/styles/rubocop/cop/without_reactive_cache'
13
- require 'gitlab/styles/rubocop/cop/rspec/single_line_hook'
14
- require 'gitlab/styles/rubocop/cop/rspec/have_link_parameters'
15
- require 'gitlab/styles/rubocop/cop/rspec/verbose_include_metadata'
16
- require 'gitlab/styles/rubocop/cop/rspec/empty_line_after_shared_example'
17
- require 'gitlab/styles/rubocop/cop/rspec/empty_line_after_let_block'
3
+ # Auto-require all cops under `gitlab/styles/rubocop/cop/**/*.rb`
4
+ cops_glob = File.join(__dir__, 'rubocop', 'cop', '**', '*.rb')
5
+ Dir[cops_glob].sort.each(&method(:require))
18
6
 
19
7
  module Gitlab
20
8
  module Styles
@@ -22,7 +22,7 @@ module Gitlab
22
22
  node.children.last.each_node(:pair) do |pair|
23
23
  key_name = pair.children[0].children[0]
24
24
 
25
- add_offense(pair, location: :expression) if key_name == :dependent
25
+ add_offense(pair) if key_name == :dependent
26
26
  end
27
27
  end
28
28
  end
@@ -0,0 +1,131 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Styles
5
+ module Rubocop
6
+ module Cop
7
+ module CodeReuse
8
+ # Cop that denies the use of ActiveRecord methods outside of models.
9
+ class ActiveRecord < RuboCop::Cop::Cop
10
+ MSG = 'This method can only be used inside an ActiveRecord model: ' \
11
+ 'https://gitlab.com/gitlab-org/gitlab-foss/issues/49653'
12
+
13
+ # Various methods from ActiveRecord::Querying that are denied. We
14
+ # exclude some generic ones such as `any?` and `first`, as these may
15
+ # lead to too many false positives, since `Array` also supports these
16
+ # methods.
17
+ #
18
+ # The keys of this Hash are the denied method names. The values are
19
+ # booleans that indicate if the method should only be denied if any
20
+ # arguments are provided.
21
+ NOT_ALLOWED = {
22
+ average: true,
23
+ calculate: true,
24
+ count_by_sql: true,
25
+ create_with: true,
26
+ distinct: false,
27
+ eager_load: true,
28
+ exists?: true,
29
+ find_by: true,
30
+ find_by!: true,
31
+ find_by_sql: true,
32
+ find_each: true,
33
+ find_in_batches: true,
34
+ find_or_create_by: true,
35
+ find_or_create_by!: true,
36
+ find_or_initialize_by: true,
37
+ first!: false,
38
+ first_or_create: true,
39
+ first_or_create!: true,
40
+ first_or_initialize: true,
41
+ from: true,
42
+ group: true,
43
+ having: true,
44
+ ids: false,
45
+ includes: true,
46
+ joins: true,
47
+ limit: true,
48
+ lock: false,
49
+ many?: false,
50
+ offset: true,
51
+ order: true,
52
+ pluck: true,
53
+ preload: true,
54
+ readonly: false,
55
+ references: true,
56
+ reorder: true,
57
+ rewhere: true,
58
+ take: false,
59
+ take!: false,
60
+ unscope: false,
61
+ where: false,
62
+ with: true
63
+ }.freeze
64
+
65
+ def on_send(node)
66
+ receiver = node.children[0]
67
+ send_name = node.children[1]
68
+ first_arg = node.children[2]
69
+
70
+ return unless receiver && NOT_ALLOWED.key?(send_name)
71
+
72
+ # If the rule requires an argument to be given, but none are
73
+ # provided, we won't register an offense. This prevents us from
74
+ # adding offenses for `project.group`, while still covering
75
+ # `Project.group(:name)`.
76
+ return if NOT_ALLOWED[send_name] && !first_arg
77
+
78
+ add_offense(node, location: :selector)
79
+ end
80
+
81
+ # We can not auto correct code like this, as it requires manual
82
+ # refactoring. Instead, we'll just allow the surrounding scope.
83
+ #
84
+ # Despite this method's presence, you should not use it. This method
85
+ # exists to make it possible to allow large chunks of offenses we
86
+ # can't fix in the short term. If you are writing new code, follow the
87
+ # code reuse guidelines, instead of allowing any new offenses.
88
+ def autocorrect(node)
89
+ scope = surrounding_scope_of(node)
90
+ indent = indentation_of(scope)
91
+
92
+ lambda do |corrector|
93
+ # This prevents us from inserting the same enable/disable comment
94
+ # for a method or block that has multiple offenses.
95
+ next if allowed_scopes.include?(scope)
96
+
97
+ corrector.insert_before(
98
+ scope.source_range,
99
+ "# rubocop: disable #{cop_name}\n#{indent}"
100
+ )
101
+
102
+ corrector.insert_after(
103
+ scope.source_range,
104
+ "\n#{indent}# rubocop: enable #{cop_name}"
105
+ )
106
+
107
+ allowed_scopes << scope
108
+ end
109
+ end
110
+
111
+ def indentation_of(node)
112
+ ' ' * node.loc.expression.source_line[/\A */].length
113
+ end
114
+
115
+ def surrounding_scope_of(node)
116
+ %i[def defs block begin].each do |type|
117
+ if (found = node.each_ancestor(type).first)
118
+ return found
119
+ end
120
+ end
121
+ end
122
+
123
+ def allowed_scopes
124
+ @allowed_scopes ||= Set.new
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
@@ -21,7 +21,8 @@ module Gitlab
21
21
  MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'
22
22
 
23
23
  def on_class(node)
24
- _klass, parent, body = node.children
24
+ parent = node.parent_class
25
+ body = node.body
25
26
 
26
27
  return if body
27
28
 
@@ -29,11 +30,13 @@ module Gitlab
29
30
 
30
31
  return unless parent_klass&.to_s&.end_with?('Error')
31
32
 
32
- add_offense(node, location: :expression)
33
+ add_offense(node)
33
34
  end
34
35
 
35
36
  def autocorrect(node)
36
- klass, parent, _body = node.children
37
+ klass = node.identifier
38
+ parent = node.parent_class
39
+
37
40
  replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
38
41
 
39
42
  lambda do |corrector|
@@ -20,7 +20,7 @@ module Gitlab
20
20
 
21
21
  node.children.last.each_node(:pair) do |pair|
22
22
  key_name = pair.children[0].children[0].to_sym
23
- add_offense(node, location: pair.source_range, message: MSG) if GIT_KEYS.include?(key_name)
23
+ add_offense(node, location: pair.source_range) if GIT_KEYS.include?(key_name)
24
24
  end
25
25
  end
26
26
 
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Styles
5
+ module Rubocop
6
+ module Cop
7
+ module InternalAffairs
8
+ # Cop that denies the use of CopHelper.
9
+ class DeprecateCopHelper < RuboCop::Cop::Cop
10
+ MSG = 'Do not use `CopHelper` or methods from it, use improved patterns described in https://github.com/rubocop-hq/rubocop/issues/8003'
11
+
12
+ def_node_matcher :cop_helper, <<~PATTERN
13
+ (send nil? ${:include :extend :prepend}
14
+ (const _ {:CopHelper}))
15
+ PATTERN
16
+
17
+ def_node_search :cop_helper_method, <<~PATTERN
18
+ (send nil? {:inspect_source :inspect_source_file :parse_source :autocorrect_source_file :autocorrect_source :_investigate} ...)
19
+ PATTERN
20
+
21
+ def_node_search :cop_helper_method_on_instance, <<~PATTERN
22
+ (send (send nil? _) {:messages :highlights :offenses} ...)
23
+ PATTERN
24
+
25
+ def on_send(node)
26
+ cop_helper(node) do
27
+ add_offense(node)
28
+ end
29
+
30
+ cop_helper_method(node) do
31
+ add_offense(node)
32
+ end
33
+
34
+ cop_helper_method_on_instance(node) do
35
+ add_offense(node)
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -69,7 +69,7 @@ module Gitlab
69
69
  return unless guard_clause?(node)
70
70
  return if next_line(node).blank? || clause_last_line?(next_line(node)) || guard_clause?(next_sibling(node))
71
71
 
72
- add_offense(node, location: :expression, message: MSG)
72
+ add_offense(node)
73
73
  end
74
74
 
75
75
  def autocorrect(node)
@@ -0,0 +1,132 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Gitlab
4
+ module Styles
5
+ module Rubocop
6
+ module Cop
7
+ # Ensures a line break around conditional blocks.
8
+ #
9
+ # @example
10
+ # # bad
11
+ # do_something
12
+ # if condition
13
+ # do_extra_stuff
14
+ # end
15
+ # do_something_more
16
+ #
17
+ # # good
18
+ # do_something
19
+ #
20
+ # if condition
21
+ # do_extra_stuff
22
+ # end
23
+ #
24
+ # do_something_more
25
+ #
26
+ # # bad
27
+ # do_something
28
+ # unless condition
29
+ # do_extra_stuff
30
+ # end
31
+ #
32
+ # do_something_more
33
+ #
34
+ # # good
35
+ # def a_method
36
+ # if condition
37
+ # do_something
38
+ # end
39
+ # end
40
+ #
41
+ # # good
42
+ # on_block do
43
+ # if condition
44
+ # do_something
45
+ # end
46
+ # end
47
+ class LineBreakAroundConditionalBlock < RuboCop::Cop::Cop
48
+ include RuboCop::Cop::RangeHelp
49
+
50
+ MSG = 'Add a line break around conditional blocks'
51
+
52
+ def on_if(node)
53
+ # This cop causes errors in haml files, so let's skip those
54
+ return if in_haml?(node)
55
+ return if node.single_line?
56
+ return unless node.if? || node.unless?
57
+
58
+ add_offense(node) unless previous_line_valid?(node)
59
+ add_offense(node) unless last_line_valid?(node)
60
+ end
61
+
62
+ def autocorrect(node)
63
+ lambda do |corrector|
64
+ line = range_by_whole_lines(node.source_range)
65
+
66
+ corrector.insert_before(line, "\n") unless previous_line_valid?(node)
67
+ corrector.insert_after(line, "\n") unless last_line_valid?(node)
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def previous_line_valid?(node)
74
+ previous_line(node).empty? ||
75
+ start_clause_line?(previous_line(node)) ||
76
+ block_start?(previous_line(node)) ||
77
+ begin_line?(previous_line(node)) ||
78
+ assignment_line?(previous_line(node)) ||
79
+ rescue_line?(previous_line(node))
80
+ end
81
+
82
+ def last_line_valid?(node)
83
+ last_line(node).empty? ||
84
+ end_line?(last_line(node)) ||
85
+ end_clause_line?(last_line(node))
86
+ end
87
+
88
+ def previous_line(node)
89
+ processed_source[node.loc.line - 2]
90
+ end
91
+
92
+ def last_line(node)
93
+ processed_source[node.loc.last_line]
94
+ end
95
+
96
+ def start_clause_line?(line)
97
+ line =~ /^\s*(def|=end|#|module|class|if|unless|else|elsif|ensure|when)/
98
+ end
99
+
100
+ def end_clause_line?(line)
101
+ line =~ /^\s*(#|rescue|else|elsif|when)/
102
+ end
103
+
104
+ def begin_line?(line)
105
+ # an assignment followed by a begin or ust a begin
106
+ line =~ /^\s*(@?(\w|\|+|=|\[|\]|\s)+begin|begin)/
107
+ end
108
+
109
+ def assignment_line?(line)
110
+ line =~ /^\s*.*=/
111
+ end
112
+
113
+ def rescue_line?(line)
114
+ line =~ /^\s*rescue/
115
+ end
116
+
117
+ def block_start?(line)
118
+ line.match(/ (do|{)( \|.*?\|)?\s?(#.+)?\z/)
119
+ end
120
+
121
+ def end_line?(line)
122
+ line =~ /^\s*(end|})/
123
+ end
124
+
125
+ def in_haml?(node)
126
+ node.location.expression.source_buffer.name.end_with?('.haml.rb')
127
+ end
128
+ end
129
+ end
130
+ end
131
+ end
132
+ end