Skip to content

Commit

Permalink
FEATURE: Add cops for services
Browse files Browse the repository at this point in the history
This patch introduces two new cops aimed at services:
- Discourse/Services/EmptyLinesAroundBlocks: checks for missing empty
  lines around multiline blocks. It has autocorrect support.
- Discourse/Services/GroupKeywords: checks for extra empty lines for
  service keywords (not block ones). It has autocorrect support.
  • Loading branch information
Flink committed Nov 28, 2024
1 parent 6504e7d commit 2a2c8bd
Show file tree
Hide file tree
Showing 7 changed files with 595 additions and 1 deletion.
6 changes: 6 additions & 0 deletions config/default.yml
Original file line number Diff line number Diff line change
Expand Up @@ -90,3 +90,9 @@ Discourse/Plugins/UseRequireRelative:

Discourse/Plugins/NoMonkeyPatching:
Enabled: true

Discourse/Services/GroupKeywords:
Enabled: true

Discourse/Services/EmptyLinesAroundBlocks:
Enabled: true
2 changes: 2 additions & 0 deletions lib/rubocop-discourse.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
require "rubocop"
require "active_support"
require "active_support/core_ext/string/inflections"
require "active_support/core_ext/object/blank"
require "active_support/core_ext/enumerable"
require_relative "rubocop/discourse"
require_relative "rubocop/discourse/inject"

Expand Down
98 changes: 98 additions & 0 deletions lib/rubocop/cop/discourse/services/empty_lines_around_blocks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Discourse
module Services
# Put empty lines around multiline blocks.
#
# @example
# # bad
# model :my_model
# params do
# attribute :my_attribute
# validates :my_attribute, presence: true
# end
# policy :my_policy
# step :another_step
#
# # good
# model :my_model
#
# params do
# attribute :my_attribute
# validates :my_attribute, presence: true
# end
#
# policy :my_policy
# step :another_step
#
class EmptyLinesAroundBlocks < Base
extend AutoCorrector

MSG = "Add empty lines around a step block."

def_node_matcher :service_include?, <<~MATCHER
(class _ _
{
(begin <(send nil? :include (const (const nil? :Service) :Base)) ...>)
<(send nil? :include (const (const nil? :Service) :Base)) ...>
}
)
MATCHER

def_node_matcher :top_level_block?, <<~MATCHER
(block (send nil? _) ...)
MATCHER

def on_class(node)
return unless service_include?(node)
@service = true
end

def on_block(node)
return unless service?
return unless top_level_block?(node)
return if node.single_line?

if missing_empty_lines?(node)
add_offense(node, message: MSG) do |corrector|
if missing_empty_line_before?(node)
corrector.insert_before(
node.loc.expression.adjust(
begin_pos: -node.loc.expression.column
),
"\n"
)
end
if missing_empty_line_after?(node)
corrector.insert_after(node.loc.end, "\n")
end
end
end
end

private

def service?
@service
end

def missing_empty_lines?(node)
missing_empty_line_before?(node) || missing_empty_line_after?(node)
end

def missing_empty_line_before?(node)
processed_source[node.loc.expression.line - 2].present? &&
node.left_siblings.present?
end

def missing_empty_line_after?(node)
processed_source[node.loc.end.line].present? &&
node.right_siblings.present?
end
end
end
end
end
end
92 changes: 92 additions & 0 deletions lib/rubocop/cop/discourse/services/group_keywords.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# frozen_string_literal: true

module RuboCop
module Cop
module Discourse
module Services
# Don’t put empty lines between keywords that are not multiline blocks.
#
# @example
# # bad
# model :my_model
#
# policy :my_policy
#
# try { step :might_raise }
#
# # good
# model :my_model
# policy :my_policy
# try { step :might_raise }
#
class GroupKeywords < Base
extend AutoCorrector

MSG = "Group one-liner steps together by removing extra empty lines."
RESTRICT_ON_SEND = %i[step model policy].freeze

def_node_matcher :service_include?, <<~MATCHER
(class _ _
{
(begin <(send nil? :include (const (const nil? :Service) :Base)) ...>)
<(send nil? :include (const (const nil? :Service) :Base)) ...>
}
)
MATCHER

def on_class(node)
return unless service_include?(node)
@service = true
end

def on_send(node)
return unless service?
return unless top_level?(node)
return unless extra_empty_line_after?(node)

add_offense(node, message: MSG) do |corrector|
range =
node.loc.expression.end.with(
end_pos: node.right_sibling.loc.expression.begin_pos
)
content = range.source.gsub(/^(\n)+/, "\n")
corrector.replace(range, content)
end
end

private

def service?
@service
end

def extra_empty_line_after?(node)
processed_source[node.loc.expression.line].blank? &&
(
service_keyword?(node.right_sibling) ||
single_line_block?(node.right_sibling)
)
end

def service_keyword?(node)
return unless node
node.send_type? && RESTRICT_ON_SEND.include?(node.method_name)
end

def single_line_block?(node)
return unless node
node.block_type? && node.single_line?
end

def top_level?(node)
while (!node.root?)
node = node.parent
return if %i[begin class block].exclude?(node.type)
end
true
end
end
end
end
end
end
2 changes: 1 addition & 1 deletion rubocop-discourse.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

Gem::Specification.new do |s|
s.name = "rubocop-discourse"
s.version = "3.8.6"
s.version = "3.9.0"
s.summary = "Custom rubocop cops used by Discourse"
s.authors = ["Discourse Team"]
s.license = "MIT"
Expand Down
Loading

0 comments on commit 2a2c8bd

Please sign in to comment.