Skip to content

Commit

Permalink
Merge pull request #26 from rubocop/documentation
Browse files Browse the repository at this point in the history
Add documentation
  • Loading branch information
bquorning committed Apr 6, 2023
2 parents b2949ff + c7ccb8a commit 828d8a3
Show file tree
Hide file tree
Showing 6 changed files with 306 additions and 2 deletions.
13 changes: 11 additions & 2 deletions .github/workflows/ci.yml
Expand Up @@ -7,10 +7,19 @@ on:
pull_request:

jobs:
test:

confirm_documentation:
runs-on: ubuntu-latest
name: Confirm documentation
steps:
- uses: actions/checkout@v3
- uses: ruby/setup-ruby@v1
with:
ruby-version: "3.2"
bundler-cache: true
- run: bundle exec rake documentation_syntax_check confirm_documentation

test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
Expand Down
14 changes: 14 additions & 0 deletions Rakefile
@@ -1,8 +1,22 @@
# frozen_string_literal: true

require 'bundler/gem_tasks'
require 'open3'
require 'rspec/core/rake_task'

Dir['tasks/**/*.rake'].each { |t| load t }

RSpec::Core::RakeTask.new(:spec)

desc 'Confirm documentation is up to date'
task confirm_documentation: :generate_cops_documentation do
_, _, _, process =
Open3.popen3('git diff --exit-code docs/')

unless process.value.success?
abort 'Please run `rake generate_cops_documentation` ' \
'and add docs/ to the commit.'
end
end

task default: :spec
6 changes: 6 additions & 0 deletions docs/modules/ROOT/pages/cops.adoc
@@ -0,0 +1,6 @@
=== Department xref:cops_threadsafety.adoc[ThreadSafety]

* xref:cops_threadsafety.adoc#threadsafetyclassandmoduleattributes[ThreadSafety/ClassAndModuleAttributes]
* xref:cops_threadsafety.adoc#threadsafetyinstancevariableinclassmethod[ThreadSafety/InstanceVariableInClassMethod]
* xref:cops_threadsafety.adoc#threadsafetymutableclassinstancevariable[ThreadSafety/MutableClassInstanceVariable]
* xref:cops_threadsafety.adoc#threadsafetynewthread[ThreadSafety/NewThread]
228 changes: 228 additions & 0 deletions docs/modules/ROOT/pages/cops_threadsafety.adoc
@@ -0,0 +1,228 @@
= ThreadSafety

== ThreadSafety/ClassAndModuleAttributes

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Enabled
| Yes
| No
| -
| -
|===

Avoid mutating class and module attributes.

They are implemented by class variables, which are not thread-safe.

=== Examples

[source,ruby]
----
# bad
class User
cattr_accessor :current_user
end
----

== ThreadSafety/InstanceVariableInClassMethod

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Enabled
| Yes
| No
| -
| -
|===

Avoid instance variables in class methods.

=== Examples

[source,ruby]
----
# bad
class User
def self.notify(info)
@info = validate(info)
Notifier.new(@info).deliver
end
end
class Model
class << self
def table_name(name)
@table_name = name
end
end
end
class Host
%i[uri port].each do |key|
define_singleton_method("#{key}=") do |value|
instance_variable_set("@#{key}", value)
end
end
end
module Example
module ClassMethods
def test(params)
@params = params
end
end
end
module Example
class_methods do
def test(params)
@params = params
end
end
end
module Example
module_function
def test(params)
@params = params
end
end
module Example
def test(params)
@params = params
end
module_function :test
end
----

== ThreadSafety/MutableClassInstanceVariable

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Enabled
| Yes
| Yes (Unsafe)
| -
| -
|===

Checks whether some class instance variable isn't a
mutable literal (e.g. array or hash).

It is based on Style/MutableConstant from RuboCop.
See https://github.com/rubocop-hq/rubocop/blob/master/lib/rubocop/cop/style/mutable_constant.rb

Class instance variables are a risk to threaded code as they are shared
between threads. A mutable object such as an array or hash may be
updated via an attr_reader so would not be detected by the
ThreadSafety/ClassAndModuleAttributes cop.

Strict mode can be used to freeze all class instance variables, rather
than just literals.
Strict mode is considered an experimental feature. It has not been
updated with an exhaustive list of all methods that will produce frozen
objects so there is a decent chance of getting some false positives.
Luckily, there is no harm in freezing an already frozen object.

=== Examples

==== EnforcedStyle: literals (default)

[source,ruby]
----
# bad
class Model
@list = [1, 2, 3]
end
# good
class Model
@list = [1, 2, 3].freeze
end
# good
class Model
@var = <<~TESTING.freeze
This is a heredoc
TESTING
end
# good
class Model
@var = Something.new
end
----

==== EnforcedStyle: strict

[source,ruby]
----
# bad
class Model
@var = Something.new
end
# bad
class Model
@var = Struct.new do
def foo
puts 1
end
end
end
# good
class Model
@var = Something.new.freeze
end
# good
class Model
@var = Struct.new do
def foo
puts 1
end
end.freeze
end
----

=== Configurable attributes

|===
| Name | Default value | Configurable values

| EnforcedStyle
| `literals`
| `literals`, `strict`
|===

== ThreadSafety/NewThread

|===
| Enabled by default | Safe | Supports autocorrection | Version Added | Version Changed

| Enabled
| Yes
| No
| -
| -
|===

Avoid starting new threads.

Let a framework like Sidekiq handle the threads.

=== Examples

[source,ruby]
----
# bad
Thread.new { do_work }
----
1 change: 1 addition & 0 deletions rubocop-thread_safety.gemspec
Expand Up @@ -37,4 +37,5 @@ Gem::Specification.new do |spec|
spec.add_development_dependency 'rspec', '~> 3.0'
spec.add_development_dependency 'rubocop-rake'
spec.add_development_dependency 'rubocop-rspec'
spec.add_development_dependency 'yard'
end
46 changes: 46 additions & 0 deletions tasks/cops_documentation.rake
@@ -0,0 +1,46 @@
# frozen_string_literal: true

require 'rubocop'
require 'rubocop-thread_safety'
require 'rubocop/cops_documentation_generator'
require 'yard'

YARD::Rake::YardocTask.new(:yard_for_generate_documentation) do |task|
task.files = ['lib/rubocop/cop/**/*.rb']
task.options = ['--no-output']
end

desc 'Generate docs of all cops departments'
task generate_cops_documentation: :yard_for_generate_documentation do
deps = ['ThreadSafety']
CopsDocumentationGenerator.new(departments: deps).call
end

desc 'Syntax check for the documentation comments'
task documentation_syntax_check: :yard_for_generate_documentation do
require 'parser/ruby31'

ok = true
YARD::Registry.load!
cops = RuboCop::Cop::Registry.global
cops.each do |cop|
examples = YARD::Registry.all(:class).find do |code_object|
next unless RuboCop::Cop::Badge.for(code_object.to_s) == cop.badge

break code_object.tags('example')
end

examples.to_a.each do |example|
buffer = Parser::Source::Buffer.new('<code>', 1)
buffer.source = example.text
parser = Parser::Ruby31.new(RuboCop::AST::Builder.new)
parser.diagnostics.all_errors_are_fatal = true
parser.parse(buffer)
rescue Parser::SyntaxError => e
path = example.object.file
puts "#{path}: Syntax Error in an example. #{e}"
ok = false
end
end
abort unless ok
end

0 comments on commit 828d8a3

Please sign in to comment.