Merge branch 'custom-empty-exception-class-cop' into 'master'
Add RuboCop cop for custom error classes Closes #28770 See merge request !9573
This commit is contained in:
commit
981c730fdb
43 changed files with 239 additions and 81 deletions
|
@ -5,7 +5,7 @@ class Projects::BlobController < Projects::ApplicationController
|
|||
include ActionView::Helpers::SanitizeHelper
|
||||
|
||||
# Raised when given an invalid file path
|
||||
class InvalidPathError < StandardError; end
|
||||
InvalidPathError = Class.new(StandardError)
|
||||
|
||||
before_action :require_non_empty_project, except: [:new, :create]
|
||||
before_action :authorize_download_code!
|
||||
|
|
|
@ -19,7 +19,7 @@ class Project < ActiveRecord::Base
|
|||
|
||||
extend Gitlab::ConfigHelper
|
||||
|
||||
class BoardLimitExceeded < StandardError; end
|
||||
BoardLimitExceeded = Class.new(StandardError)
|
||||
|
||||
NUMBER_OF_PERMITTED_BOARDS = 1
|
||||
UNKNOWN_IMPORT_URL = 'http://unknown.git'.freeze
|
||||
|
|
|
@ -7,7 +7,7 @@ class ProjectWiki
|
|||
'AsciiDoc' => :asciidoc
|
||||
}.freeze unless defined?(MARKUPS)
|
||||
|
||||
class CouldNotCreateWikiError < StandardError; end
|
||||
CouldNotCreateWikiError = Class.new(StandardError)
|
||||
|
||||
# Returns a string describing what went wrong after
|
||||
# an operation fails.
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
class PipelineSerializer < BaseSerializer
|
||||
class InvalidResourceError < StandardError; end
|
||||
InvalidResourceError = Class.new(StandardError)
|
||||
|
||||
entity PipelineEntity
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Commits
|
||||
class ChangeService < ::BaseService
|
||||
class ValidationError < StandardError; end
|
||||
class ChangeError < StandardError; end
|
||||
ValidationError = Class.new(StandardError)
|
||||
ChangeError = Class.new(StandardError)
|
||||
|
||||
def execute
|
||||
@start_project = params[:start_project] || @project
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Files
|
||||
class BaseService < ::BaseService
|
||||
class ValidationError < StandardError; end
|
||||
ValidationError = Class.new(StandardError)
|
||||
|
||||
def execute
|
||||
@start_project = params[:start_project] || @project
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Files
|
||||
class MultiService < Files::BaseService
|
||||
class FileChangedError < StandardError; end
|
||||
FileChangedError = Class.new(StandardError)
|
||||
|
||||
ACTIONS = %w[create update delete move].freeze
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Files
|
||||
class UpdateService < Files::BaseService
|
||||
class FileChangedError < StandardError; end
|
||||
FileChangedError = Class.new(StandardError)
|
||||
|
||||
def commit
|
||||
repository.update_file(current_user, @file_path, @file_content,
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Issues
|
||||
class MoveService < Issues::BaseService
|
||||
class MoveError < StandardError; end
|
||||
MoveError = Class.new(StandardError)
|
||||
|
||||
def execute(issue, new_project)
|
||||
@old_issue = issue
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
module MergeRequests
|
||||
class ResolveService < MergeRequests::BaseService
|
||||
class MissingFiles < Gitlab::Conflict::ResolutionError
|
||||
end
|
||||
MissingFiles = Class.new(Gitlab::Conflict::ResolutionError)
|
||||
|
||||
attr_accessor :conflicts, :rugged, :merge_index, :merge_request
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ module Projects
|
|||
class DestroyService < BaseService
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
class DestroyError < StandardError; end
|
||||
DestroyError = Class.new(StandardError)
|
||||
|
||||
DELETED_FLAG = '+deleted'.freeze
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ module Projects
|
|||
class ImportService < BaseService
|
||||
include Gitlab::ShellAdapter
|
||||
|
||||
class Error < StandardError; end
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
def execute
|
||||
add_repository_to_project unless project.gitlab_project_import?
|
||||
|
|
|
@ -9,7 +9,7 @@
|
|||
module Projects
|
||||
class TransferService < BaseService
|
||||
include Gitlab::ShellAdapter
|
||||
class TransferError < StandardError; end
|
||||
TransferError = Class.new(StandardError)
|
||||
|
||||
def execute(new_namespace)
|
||||
if allowed_transfer?(current_user, project, new_namespace)
|
||||
|
|
|
@ -160,13 +160,10 @@ module API
|
|||
# Exceptions
|
||||
#
|
||||
|
||||
class MissingTokenError < StandardError; end
|
||||
|
||||
class TokenNotFoundError < StandardError; end
|
||||
|
||||
class ExpiredError < StandardError; end
|
||||
|
||||
class RevokedError < StandardError; end
|
||||
MissingTokenError = Class.new(StandardError)
|
||||
TokenNotFoundError = Class.new(StandardError)
|
||||
ExpiredError = Class.new(StandardError)
|
||||
RevokedError = Class.new(StandardError)
|
||||
|
||||
class InsufficientScopeError < StandardError
|
||||
attr_reader :scopes
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
module Bitbucket
|
||||
module Error
|
||||
class Unauthorized < StandardError
|
||||
end
|
||||
Unauthorized = Class.new(StandardError)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Ci
|
||||
class GitlabCiYamlProcessor
|
||||
class ValidationError < StandardError; end
|
||||
ValidationError = Class.new(StandardError)
|
||||
|
||||
include Gitlab::Ci::Config::Entry::LegacyValidationHelpers
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
# file path string when combined in a request parameter
|
||||
module ExtractsPath
|
||||
# Raised when given an invalid file path
|
||||
class InvalidPathError < StandardError; end
|
||||
InvalidPathError = Class.new(StandardError)
|
||||
|
||||
# Given a string containing both a Git tree-ish, such as a branch or tag, and
|
||||
# a filesystem path joined by forward slashes, attempts to separate the two.
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
module Gitlab
|
||||
module Access
|
||||
class AccessDeniedError < StandardError; end
|
||||
AccessDeniedError = Class.new(StandardError)
|
||||
|
||||
NO_ACCESS = 0
|
||||
GUEST = 10
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Gitlab
|
||||
module Auth
|
||||
class MissingPersonalTokenError < StandardError; end
|
||||
MissingPersonalTokenError = Class.new(StandardError)
|
||||
|
||||
SCOPES = [:api, :read_user].freeze
|
||||
DEFAULT_SCOPES = [:api].freeze
|
||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
module Build
|
||||
module Artifacts
|
||||
class Metadata
|
||||
class ParserError < StandardError; end
|
||||
ParserError = Class.new(StandardError)
|
||||
|
||||
VERSION_PATTERN = /^[\w\s]+(\d+\.\d+\.\d+)/
|
||||
INVALID_PATH_PATTERN = %r{(^\.?\.?/)|(/\.?\.?/)}
|
||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
# Factory class responsible for fabricating entry objects.
|
||||
#
|
||||
class Factory
|
||||
class InvalidFactory < StandardError; end
|
||||
InvalidFactory = Class.new(StandardError)
|
||||
|
||||
def initialize(entry)
|
||||
@entry = entry
|
||||
|
|
|
@ -6,7 +6,7 @@ module Gitlab
|
|||
# Base abstract class for each configuration entry node.
|
||||
#
|
||||
class Node
|
||||
class InvalidError < StandardError; end
|
||||
InvalidError = Class.new(StandardError)
|
||||
|
||||
attr_reader :config, :metadata
|
||||
attr_accessor :key, :parent, :description
|
||||
|
|
|
@ -2,7 +2,7 @@ module Gitlab
|
|||
module Ci
|
||||
class Config
|
||||
class Loader
|
||||
class FormatError < StandardError; end
|
||||
FormatError = Class.new(StandardError)
|
||||
|
||||
def initialize(config)
|
||||
@config = YAML.safe_load(config, [Symbol], [], true)
|
||||
|
|
|
@ -4,8 +4,7 @@ module Gitlab
|
|||
include Gitlab::Routing.url_helpers
|
||||
include IconsHelper
|
||||
|
||||
class MissingResolution < ResolutionError
|
||||
end
|
||||
MissingResolution = Class.new(ResolutionError)
|
||||
|
||||
CONTEXT_LINES = 3
|
||||
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
module Gitlab
|
||||
module Conflict
|
||||
class FileCollection
|
||||
class ConflictSideMissing < StandardError
|
||||
end
|
||||
ConflictSideMissing = Class.new(StandardError)
|
||||
|
||||
attr_reader :merge_request, :our_commit, :their_commit
|
||||
|
||||
|
|
|
@ -1,25 +1,15 @@
|
|||
module Gitlab
|
||||
module Conflict
|
||||
class Parser
|
||||
class UnresolvableError < StandardError
|
||||
end
|
||||
|
||||
class UnmergeableFile < UnresolvableError
|
||||
end
|
||||
|
||||
class UnsupportedEncoding < UnresolvableError
|
||||
end
|
||||
UnresolvableError = Class.new(StandardError)
|
||||
UnmergeableFile = Class.new(UnresolvableError)
|
||||
UnsupportedEncoding = Class.new(UnresolvableError)
|
||||
|
||||
# Recoverable errors - the conflict can be resolved in an editor, but not with
|
||||
# sections.
|
||||
class ParserError < StandardError
|
||||
end
|
||||
|
||||
class UnexpectedDelimiter < ParserError
|
||||
end
|
||||
|
||||
class MissingEndDelimiter < ParserError
|
||||
end
|
||||
ParserError = Class.new(StandardError)
|
||||
UnexpectedDelimiter = Class.new(ParserError)
|
||||
MissingEndDelimiter = Class.new(ParserError)
|
||||
|
||||
def parse(text, our_path:, their_path:, parent_file: nil)
|
||||
raise UnmergeableFile if text.blank? # Typically a binary file
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
module Gitlab
|
||||
module Conflict
|
||||
class ResolutionError < StandardError
|
||||
end
|
||||
ResolutionError = Class.new(StandardError)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,19 +4,19 @@ require_dependency 'gitlab/email/handler'
|
|||
# Inspired in great part by Discourse's Email::Receiver
|
||||
module Gitlab
|
||||
module Email
|
||||
class ProcessingError < StandardError; end
|
||||
class EmailUnparsableError < ProcessingError; end
|
||||
class SentNotificationNotFoundError < ProcessingError; end
|
||||
class ProjectNotFound < ProcessingError; end
|
||||
class EmptyEmailError < ProcessingError; end
|
||||
class AutoGeneratedEmailError < ProcessingError; end
|
||||
class UserNotFoundError < ProcessingError; end
|
||||
class UserBlockedError < ProcessingError; end
|
||||
class UserNotAuthorizedError < ProcessingError; end
|
||||
class NoteableNotFoundError < ProcessingError; end
|
||||
class InvalidNoteError < ProcessingError; end
|
||||
class InvalidIssueError < ProcessingError; end
|
||||
class UnknownIncomingEmail < ProcessingError; end
|
||||
ProcessingError = Class.new(StandardError)
|
||||
EmailUnparsableError = Class.new(ProcessingError)
|
||||
SentNotificationNotFoundError = Class.new(ProcessingError)
|
||||
ProjectNotFound = Class.new(ProcessingError)
|
||||
EmptyEmailError = Class.new(ProcessingError)
|
||||
AutoGeneratedEmailError = Class.new(ProcessingError)
|
||||
UserNotFoundError = Class.new(ProcessingError)
|
||||
UserBlockedError = Class.new(ProcessingError)
|
||||
UserNotAuthorizedError = Class.new(ProcessingError)
|
||||
NoteableNotFoundError = Class.new(ProcessingError)
|
||||
InvalidNoteError = Class.new(ProcessingError)
|
||||
InvalidIssueError = Class.new(ProcessingError)
|
||||
UnknownIncomingEmail = Class.new(ProcessingError)
|
||||
|
||||
class Receiver
|
||||
def initialize(raw)
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
module Gitlab
|
||||
module Git
|
||||
class Diff
|
||||
class TimeoutError < StandardError; end
|
||||
TimeoutError = Class.new(StandardError)
|
||||
include Gitlab::Git::EncodingHelper
|
||||
|
||||
# Diff properties
|
||||
|
|
|
@ -10,9 +10,9 @@ module Gitlab
|
|||
|
||||
SEARCH_CONTEXT_LINES = 3
|
||||
|
||||
class NoRepository < StandardError; end
|
||||
class InvalidBlobName < StandardError; end
|
||||
class InvalidRef < StandardError; end
|
||||
NoRepository = Class.new(StandardError)
|
||||
InvalidBlobName = Class.new(StandardError)
|
||||
InvalidRef = Class.new(StandardError)
|
||||
|
||||
# Full path to repo
|
||||
attr_reader :path
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module Gitlab
|
||||
module ImportExport
|
||||
class Error < StandardError; end
|
||||
Error = Class.new(StandardError)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
#
|
||||
module Gitlab
|
||||
module OAuth
|
||||
class SignupDisabledError < StandardError; end
|
||||
SignupDisabledError = Class.new(StandardError)
|
||||
|
||||
class User
|
||||
attr_accessor :auth_hash, :gl_user
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
module Gitlab
|
||||
class RouteMap
|
||||
class FormatError < StandardError; end
|
||||
FormatError = Class.new(StandardError)
|
||||
|
||||
def initialize(data)
|
||||
begin
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Gitlab
|
||||
module Serializer
|
||||
class Pagination
|
||||
class InvalidResourceError < StandardError; end
|
||||
InvalidResourceError = Class.new(StandardError)
|
||||
include ::API::Helpers::Pagination
|
||||
|
||||
def initialize(request, response)
|
||||
|
|
|
@ -2,7 +2,7 @@ require 'securerandom'
|
|||
|
||||
module Gitlab
|
||||
class Shell
|
||||
class Error < StandardError; end
|
||||
Error = Class.new(StandardError)
|
||||
|
||||
KeyAdder = Struct.new(:io) do
|
||||
def add_key(id, key)
|
||||
|
|
|
@ -4,7 +4,7 @@ module Gitlab
|
|||
module Finders
|
||||
class RepoTemplateFinder < BaseTemplateFinder
|
||||
# Raised when file is not found
|
||||
class FileNotFoundError < StandardError; end
|
||||
FileNotFoundError = Class.new(StandardError)
|
||||
|
||||
def initialize(project, base_dir, extension, categories = {})
|
||||
@categories = categories
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Gitlab
|
||||
class UpdatePathError < StandardError; end
|
||||
UpdatePathError = Class.new(StandardError)
|
||||
end
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module Mattermost
|
||||
class ClientError < Mattermost::Error; end
|
||||
ClientError = Class.new(Mattermost::Error)
|
||||
|
||||
class Client
|
||||
attr_reader :user
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
module Mattermost
|
||||
class Error < StandardError; end
|
||||
Error = Class.new(StandardError)
|
||||
end
|
||||
|
|
|
@ -5,7 +5,7 @@ module Mattermost
|
|||
end
|
||||
end
|
||||
|
||||
class ConnectionError < Mattermost::Error; end
|
||||
ConnectionError = Class.new(Mattermost::Error)
|
||||
|
||||
# This class' prime objective is to obtain a session token on a Mattermost
|
||||
# instance with SSO configured where this GitLab instance is the provider.
|
||||
|
|
64
rubocop/cop/custom_error_class.rb
Normal file
64
rubocop/cop/custom_error_class.rb
Normal file
|
@ -0,0 +1,64 @@
|
|||
module RuboCop
|
||||
module Cop
|
||||
# This cop makes sure that custom error classes, when empty, are declared
|
||||
# with Class.new.
|
||||
#
|
||||
# @example
|
||||
# # bad
|
||||
# class FooError < StandardError
|
||||
# end
|
||||
#
|
||||
# # okish
|
||||
# class FooError < StandardError; end
|
||||
#
|
||||
# # good
|
||||
# FooError = Class.new(StandardError)
|
||||
class CustomErrorClass < RuboCop::Cop::Cop
|
||||
MSG = 'Use `Class.new(SuperClass)` to define an empty custom error class.'.freeze
|
||||
|
||||
def on_class(node)
|
||||
_klass, parent, body = node.children
|
||||
|
||||
return if body
|
||||
|
||||
parent_klass = class_name_from_node(parent)
|
||||
|
||||
return unless parent_klass && parent_klass.to_s.end_with?('Error')
|
||||
|
||||
add_offense(node, :expression)
|
||||
end
|
||||
|
||||
def autocorrect(node)
|
||||
klass, parent, _body = node.children
|
||||
replacement = "#{class_name_from_node(klass)} = Class.new(#{class_name_from_node(parent)})"
|
||||
|
||||
lambda do |corrector|
|
||||
corrector.replace(node.source_range, replacement)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
# The nested constant `Foo::Bar::Baz` looks like:
|
||||
#
|
||||
# s(:const,
|
||||
# s(:const,
|
||||
# s(:const, nil, :Foo), :Bar), :Baz)
|
||||
#
|
||||
# So recurse through that to get the name as written in the source.
|
||||
#
|
||||
def class_name_from_node(node, suffix = nil)
|
||||
return unless node&.type == :const
|
||||
|
||||
name = node.children[1].to_s
|
||||
name = "#{name}::#{suffix}" if suffix
|
||||
|
||||
if node.children[0]
|
||||
class_name_from_node(node.children[0], name)
|
||||
else
|
||||
name
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,3 +1,4 @@
|
|||
require_relative 'cop/custom_error_class'
|
||||
require_relative 'cop/gem_fetcher'
|
||||
require_relative 'cop/migration/add_column'
|
||||
require_relative 'cop/migration/add_column_with_default'
|
||||
|
|
111
spec/rubocop/cop/custom_error_class_spec.rb
Normal file
111
spec/rubocop/cop/custom_error_class_spec.rb
Normal file
|
@ -0,0 +1,111 @@
|
|||
require 'spec_helper'
|
||||
|
||||
require 'rubocop'
|
||||
require 'rubocop/rspec/support'
|
||||
|
||||
require_relative '../../../rubocop/cop/custom_error_class'
|
||||
|
||||
describe RuboCop::Cop::CustomErrorClass do
|
||||
include CopHelper
|
||||
|
||||
subject(:cop) { described_class.new }
|
||||
|
||||
context 'when a class has a body' do
|
||||
it 'does nothing' do
|
||||
inspect_source(cop, 'class CustomError < StandardError; def foo; end; end')
|
||||
|
||||
expect(cop.offenses).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a class has no explicit superclass' do
|
||||
it 'does nothing' do
|
||||
inspect_source(cop, 'class CustomError; end')
|
||||
|
||||
expect(cop.offenses).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a class has a superclass that does not end in Error' do
|
||||
it 'does nothing' do
|
||||
inspect_source(cop, 'class CustomError < BasicObject; end')
|
||||
|
||||
expect(cop.offenses).to be_empty
|
||||
end
|
||||
end
|
||||
|
||||
context 'when a class is empty and inherits from a class ending in Error' do
|
||||
context 'when the class is on a single line' do
|
||||
let(:source) do
|
||||
<<-SOURCE
|
||||
module Foo
|
||||
class CustomError < Bar::Baz::BaseError; end
|
||||
end
|
||||
SOURCE
|
||||
end
|
||||
|
||||
let(:expected) do
|
||||
<<-EXPECTED
|
||||
module Foo
|
||||
CustomError = Class.new(Bar::Baz::BaseError)
|
||||
end
|
||||
EXPECTED
|
||||
end
|
||||
|
||||
it 'registers an offense' do
|
||||
expected_highlights = source.split("\n")[1].strip
|
||||
|
||||
inspect_source(cop, source)
|
||||
|
||||
aggregate_failures do
|
||||
expect(cop.offenses.size).to eq(1)
|
||||
expect(cop.offenses.map(&:line)).to eq([2])
|
||||
expect(cop.highlights).to contain_exactly(expected_highlights)
|
||||
end
|
||||
end
|
||||
|
||||
it 'autocorrects to the right version' do
|
||||
autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb')
|
||||
|
||||
expect(autocorrected).to eq(expected)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the class is on multiple lines' do
|
||||
let(:source) do
|
||||
<<-SOURCE
|
||||
module Foo
|
||||
class CustomError < Bar::Baz::BaseError
|
||||
end
|
||||
end
|
||||
SOURCE
|
||||
end
|
||||
|
||||
let(:expected) do
|
||||
<<-EXPECTED
|
||||
module Foo
|
||||
CustomError = Class.new(Bar::Baz::BaseError)
|
||||
end
|
||||
EXPECTED
|
||||
end
|
||||
|
||||
it 'registers an offense' do
|
||||
expected_highlights = source.split("\n")[1..2].join("\n").strip
|
||||
|
||||
inspect_source(cop, source)
|
||||
|
||||
aggregate_failures do
|
||||
expect(cop.offenses.size).to eq(1)
|
||||
expect(cop.offenses.map(&:line)).to eq([2])
|
||||
expect(cop.highlights).to contain_exactly(expected_highlights)
|
||||
end
|
||||
end
|
||||
|
||||
it 'autocorrects to the right version' do
|
||||
autocorrected = autocorrect_source(cop, source, 'foo/custom_error.rb')
|
||||
|
||||
expect(autocorrected).to eq(expected)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in a new issue