rubocop-eightyfourcodes 0.0.1 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. checksums.yaml +4 -4
  2. data/.rspec +3 -0
  3. data/.rubocop.yml +9 -0
  4. data/CHANGELOG.md +9 -0
  5. data/Gemfile +9 -3
  6. data/Gemfile.lock +68 -0
  7. data/LICENSE.md +7 -5
  8. data/README.md +13 -61
  9. data/Rakefile +32 -0
  10. data/config/default.yml +11 -6
  11. data/lib/rubocop/cop/{eightyfourcodes → eighty_four_codes}/command_literal_injection.rb +4 -2
  12. data/lib/rubocop/cop/eighty_four_codes/ensure_redirect.rb +48 -0
  13. data/lib/rubocop/cop/eighty_four_codes/ruby_version_file.rb +44 -0
  14. data/lib/rubocop/cop/eightyfourcodes_cops.rb +5 -0
  15. data/lib/rubocop/eightyfourcodes/inject.rb +5 -1
  16. data/lib/rubocop/eightyfourcodes/version.rb +1 -4
  17. data/lib/rubocop/eightyfourcodes.rb +6 -1
  18. data/lib/rubocop-eightyfourcodes.rb +5 -16
  19. data/sig/rubocop/eightyfourcodes.rbs +6 -0
  20. metadata +22 -44
  21. data/CONTRIBUTING.md +0 -3
  22. data/lib/rubocop/cop/eightyfourcodes/cop.rb +0 -70
  23. data/lib/rubocop/eightyfourcodes/concept.rb +0 -34
  24. data/lib/rubocop/eightyfourcodes/config_formatter.rb +0 -33
  25. data/lib/rubocop/eightyfourcodes/description_extractor.rb +0 -72
  26. data/lib/rubocop/eightyfourcodes/example.rb +0 -32
  27. data/lib/rubocop/eightyfourcodes/example_group.rb +0 -95
  28. data/lib/rubocop/eightyfourcodes/hook.rb +0 -49
  29. data/lib/rubocop/eightyfourcodes/language/node_pattern.rb +0 -20
  30. data/lib/rubocop/eightyfourcodes/language.rb +0 -118
  31. data/lib/rubocop/eightyfourcodes/top_level_describe.rb +0 -57
  32. data/lib/rubocop/eightyfourcodes/util.rb +0 -19
  33. data/lib/rubocop/eightyfourcodes/wording.rb +0 -81
  34. data/rubocop-eightyfourcodes.gemspec +0 -35
@@ -1,70 +0,0 @@
1
- module RuboCop
2
- module Cop # rubocop:disable Style/Documentation
3
- WorkaroundCop84 = Cop.dup
4
-
5
- # Clone of the the normal RuboCop::Cop::Cop class so we can rewrite
6
- # the inherited method without breaking functionality
7
- class WorkaroundCop84
8
- # Overwrite the cop inherited method to be a noop. Our RSpec::Cop
9
- # class will invoke the inherited hook instead
10
- def self.inherited(*); end
11
-
12
- # Special case `Module#<` so that the rspec support rubocop exports
13
- # is compatible with our subclass
14
- def self.<(other)
15
- other.equal?(RuboCop::Cop::Cop) || super
16
- end
17
- end
18
- private_constant(:WorkaroundCop84)
19
-
20
- module EightyFourCodes
21
- # @abstract parent class to rspec cops
22
- #
23
- # The criteria for whether rubocop-rspec analyzes a certain ruby file
24
- # is configured via `AllCops/RSpec`. For example, if you want to
25
- # customize your project to scan all files within a `test/` directory
26
- # then you could add this to your configuration:
27
- #
28
- # @example configuring analyzed paths
29
- #
30
- # AllCops:
31
- # RSpec:
32
- # Patterns:
33
- # - '_test.rb$'
34
- # - '(?:^|/)test/'
35
- class Cop < WorkaroundCop84
36
- include RuboCop::EightyFourCodes::Language
37
- include RuboCop::EightyFourCodes::Language::NodePattern
38
-
39
- DEFAULT_CONFIGURATION =
40
- RuboCop::EightyFourCodes::CONFIG.fetch('AllCops').fetch('EightyFourCodes')
41
-
42
- # Invoke the original inherited hook so our cops are recognized
43
- def self.inherited(subclass)
44
- RuboCop::Cop::Cop.inherited(subclass)
45
- end
46
-
47
- def relevant_file?(file)
48
- relevant_rubocop_rspec_file?(file) && super
49
- end
50
-
51
- private
52
-
53
- def relevant_rubocop_rspec_file?(file)
54
- rspec_pattern =~ file
55
- end
56
-
57
- def rspec_pattern
58
- Regexp.union(rspec_pattern_config.map(&Regexp.public_method(:new)))
59
- end
60
-
61
- def rspec_pattern_config
62
- config
63
- .for_all_cops
64
- .fetch('EightyFourCodes', DEFAULT_CONFIGURATION)
65
- .fetch('Patterns')
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # Wrapper for RSpec DSL methods
6
- class Concept
7
- include Language
8
- include Language::NodePattern
9
- extend NodePattern::Macros
10
-
11
- def initialize(node)
12
- @node = node
13
- end
14
-
15
- def eql?(other)
16
- node == other.node
17
- end
18
-
19
- alias == eql?
20
-
21
- def hash
22
- [self.class, node].hash
23
- end
24
-
25
- def to_node
26
- node
27
- end
28
-
29
- protected
30
-
31
- attr_reader :node
32
- end
33
- end
34
- end
@@ -1,33 +0,0 @@
1
- require 'yaml'
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # Builds a YAML config file from two config hashes
6
- class ConfigFormatter
7
- NAMESPACES = /^(#{Regexp.union('eightyfourcodes')})/
8
-
9
- def initialize(config, descriptions)
10
- @config = config
11
- @descriptions = descriptions
12
- end
13
-
14
- def dump
15
- YAML.dump(unified_config).gsub(NAMESPACES, "\n\\1")
16
- end
17
-
18
- private
19
-
20
- def unified_config
21
- cops.each_with_object(config.dup) do |cop, unified|
22
- unified[cop] = config.fetch(cop).merge(descriptions.fetch(cop))
23
- end
24
- end
25
-
26
- def cops
27
- (descriptions.keys | config.keys).grep(NAMESPACES)
28
- end
29
-
30
- attr_reader :config, :descriptions
31
- end
32
- end
33
- end
@@ -1,72 +0,0 @@
1
- module RuboCop
2
- module EightyFourCodes
3
- # Extracts cop descriptions from YARD docstrings
4
- class DescriptionExtractor
5
- def initialize(yardocs)
6
- @code_objects = yardocs.map(&CodeObject.public_method(:new))
7
- end
8
-
9
- def to_h
10
- code_objects
11
- .select(&:rspec_cop?)
12
- .map(&:configuration)
13
- .reduce(:merge)
14
- end
15
-
16
- private
17
-
18
- attr_reader :code_objects
19
-
20
- # Decorator of a YARD code object for working with documented rspec cops
21
- class CodeObject
22
- RSPEC_NAMESPACE = 'RuboCop::Cop::eightyfourcodes'.freeze
23
-
24
- def initialize(yardoc)
25
- @yardoc = yardoc
26
- end
27
-
28
- # Test if the YARD code object documents a concrete rspec cop class
29
- #
30
- # @return [Boolean]
31
- def rspec_cop?
32
- class_documentation? && rspec_cop_namespace? && !abstract?
33
- end
34
-
35
- # Configuration for the documented cop that would live in default.yml
36
- #
37
- # @return [Hash]
38
- def configuration
39
- { cop_name => { 'Description' => description } }
40
- end
41
-
42
- private
43
-
44
- def cop_name
45
- Object.const_get(documented_constant).cop_name
46
- end
47
-
48
- def description
49
- yardoc.docstring.split("\n\n").first.to_s
50
- end
51
-
52
- def class_documentation?
53
- yardoc.type.equal?(:class)
54
- end
55
-
56
- def rspec_cop_namespace?
57
- documented_constant.start_with?(RSPEC_NAMESPACE)
58
- end
59
-
60
- def documented_constant
61
- yardoc.to_s
62
- end
63
-
64
- def abstract?
65
- yardoc.tags.any? { |tag| tag.tag_name.eql?('abstract') }
66
- end
67
-
68
- attr_reader :yardoc
69
- end
70
- end
71
- end
72
- end
@@ -1,32 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # Wrapper for RSpec examples
6
- class Example < Concept
7
- def_node_matcher :extract_doc_string, '(send _ _ $str ...)'
8
- def_node_matcher :extract_metadata, '(send _ _ _ $...)'
9
- def_node_matcher :extract_implementation, '(block send args $_)'
10
-
11
- def doc_string
12
- extract_doc_string(definition)
13
- end
14
-
15
- def metadata
16
- extract_metadata(definition)
17
- end
18
-
19
- def implementation
20
- extract_implementation(node)
21
- end
22
-
23
- def definition
24
- if node.send_type?
25
- node
26
- else
27
- node.children.first
28
- end
29
- end
30
- end
31
- end
32
- end
@@ -1,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # Wrapper for RSpec example groups
6
- class ExampleGroup < Concept
7
- # @!method scope_change?(node)
8
- #
9
- # Detect if the node is an example group or shared example
10
- #
11
- # Selectors which indicate that we should stop searching
12
- #
13
- def_node_matcher :scope_change?,
14
- (ExampleGroups::ALL + SharedGroups::ALL).block_pattern
15
-
16
- # @!method hook(node)
17
- #
18
- # Detect if node is `before`, `after`, `around`
19
- def_node_matcher :hook, <<-PATTERN
20
- (block {$(send nil #{Hooks::ALL.node_pattern_union} ...)} ...)
21
- PATTERN
22
-
23
- def_node_matcher :subject, Subject::ALL.block_pattern
24
-
25
- def subjects
26
- subjects_in_scope(node)
27
- end
28
-
29
- def examples
30
- examples_in_scope(node).map(&Example.public_method(:new))
31
- end
32
-
33
- def hooks
34
- hooks_in_scope(node).map(&Hook.public_method(:new))
35
- end
36
-
37
- private
38
-
39
- def subjects_in_scope(node)
40
- node.each_child_node.flat_map do |child|
41
- find_subjects(child)
42
- end
43
- end
44
-
45
- def find_subjects(node)
46
- return [] if scope_change?(node)
47
-
48
- if subject(node)
49
- [node]
50
- else
51
- subjects_in_scope(node)
52
- end
53
- end
54
-
55
- def hooks_in_scope(node)
56
- node.each_child_node.flat_map do |child|
57
- find_hooks(child)
58
- end
59
- end
60
-
61
- def find_hooks(node)
62
- return [] if scope_change?(node) || example?(node)
63
-
64
- if hook(node)
65
- [node]
66
- else
67
- hooks_in_scope(node)
68
- end
69
- end
70
-
71
- def examples_in_scope(node, &blk)
72
- node.each_child_node.flat_map do |child|
73
- find_examples(child, &blk)
74
- end
75
- end
76
-
77
- # Recursively search for examples within the current scope
78
- #
79
- # Searches node for examples and halts when a scope change is detected
80
- #
81
- # @param node [RuboCop::Node] node to recursively search for examples
82
- #
83
- # @return [Array<RuboCop::Node>] discovered example nodes
84
- def find_examples(node)
85
- return [] if scope_change?(node)
86
-
87
- if example?(node)
88
- [node]
89
- else
90
- examples_in_scope(node)
91
- end
92
- end
93
- end
94
- end
95
- end
@@ -1,49 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # Wrapper for RSpec hook
6
- class Hook < Concept
7
- STANDARDIZED_SCOPES = %i[each context suite].freeze
8
- private_constant(:STANDARDIZED_SCOPES)
9
-
10
- def name
11
- node.method_name
12
- end
13
-
14
- def knowable_scope?
15
- return true unless scope_argument
16
-
17
- scope_argument.sym_type?
18
- end
19
-
20
- def valid_scope?
21
- STANDARDIZED_SCOPES.include?(scope)
22
- end
23
-
24
- def example?
25
- scope.equal?(:each)
26
- end
27
-
28
- def scope
29
- case scope_name
30
- when nil, :each, :example then :each
31
- when :context, :all then :context
32
- when :suite then :suite
33
- else
34
- scope_name
35
- end
36
- end
37
-
38
- private
39
-
40
- def scope_name
41
- scope_argument.to_a.first
42
- end
43
-
44
- def scope_argument
45
- node.first_argument
46
- end
47
- end
48
- end
49
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- module Language
6
- # Common node matchers used for matching against the rspec DSL
7
- module NodePattern
8
- extend RuboCop::NodePattern::Macros
9
-
10
- def_node_matcher :example_group?, ExampleGroups::ALL.block_pattern
11
-
12
- def_node_matcher :example_group_with_body?, <<-PATTERN
13
- (block #{ExampleGroups::ALL.send_pattern} args [!nil])
14
- PATTERN
15
-
16
- def_node_matcher :example?, Examples::ALL.block_pattern
17
- end
18
- end
19
- end
20
- end
@@ -1,118 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # RSpec public API methods that are commonly used in cops
6
- module Language
7
- # Set of method selectors
8
- class SelectorSet
9
- def initialize(selectors)
10
- @selectors = selectors
11
- end
12
-
13
- def ==(other)
14
- selectors.eql?(other.selectors)
15
- end
16
-
17
- def +(other)
18
- self.class.new(selectors + other.selectors)
19
- end
20
-
21
- def include?(selector)
22
- selectors.include?(selector)
23
- end
24
-
25
- def block_pattern
26
- "(block #{send_pattern} ...)"
27
- end
28
-
29
- def send_pattern
30
- "(send _ #{node_pattern_union} ...)"
31
- end
32
-
33
- def node_pattern_union
34
- "{#{node_pattern}}"
35
- end
36
-
37
- def node_pattern
38
- selectors.map(&:inspect).join(' ')
39
- end
40
-
41
- protected
42
-
43
- attr_reader :selectors
44
- end
45
-
46
- module Matchers
47
- MESSAGE_CHAIN = SelectorSet.new(%i[receive_message_chain stub_chain])
48
- end
49
-
50
- module ExampleGroups
51
- GROUPS = SelectorSet.new(%i[describe context feature example_group])
52
- SKIPPED = SelectorSet.new(%i[xdescribe xcontext xfeature])
53
- FOCUSED = SelectorSet.new(%i[fdescribe fcontext ffeature])
54
-
55
- ALL = GROUPS + SKIPPED + FOCUSED
56
- end
57
-
58
- module SharedGroups
59
- EXAMPLES = SelectorSet.new(%i[shared_examples shared_examples_for])
60
- CONTEXT = SelectorSet.new(%i[shared_context])
61
-
62
- ALL = EXAMPLES + CONTEXT
63
- end
64
-
65
- module Includes
66
- EXAMPLES = SelectorSet.new(
67
- %i[
68
- it_behaves_like
69
- it_should_behave_like
70
- include_examples
71
- ]
72
- )
73
- CONTEXT = SelectorSet.new(%i[include_context])
74
-
75
- ALL = EXAMPLES + CONTEXT
76
- end
77
-
78
- module Examples
79
- EXAMPLES = SelectorSet.new(%i[it specify example scenario its])
80
- FOCUSED = SelectorSet.new(%i[fit fspecify fexample fscenario focus])
81
- SKIPPED = SelectorSet.new(%i[xit xspecify xexample xscenario skip])
82
- PENDING = SelectorSet.new(%i[pending])
83
-
84
- ALL = EXAMPLES + FOCUSED + SKIPPED + PENDING
85
- end
86
-
87
- module Hooks
88
- ALL = SelectorSet.new(
89
- %i[
90
- prepend_before
91
- before
92
- append_before
93
- around
94
- prepend_after
95
- after
96
- append_after
97
- ]
98
- )
99
- end
100
-
101
- module Helpers
102
- ALL = SelectorSet.new(%i[let let!])
103
- end
104
-
105
- module Subject
106
- ALL = SelectorSet.new(%i[subject subject!])
107
- end
108
-
109
- ALL =
110
- ExampleGroups::ALL +
111
- SharedGroups::ALL +
112
- Examples::ALL +
113
- Hooks::ALL +
114
- Helpers::ALL +
115
- Subject::ALL
116
- end
117
- end
118
- end
@@ -1,57 +0,0 @@
1
- module RuboCop
2
- module EightyFourCodes
3
- # Helper methods for top level describe cops
4
- module TopLevelDescribe
5
- extend NodePattern::Macros
6
-
7
- def_node_matcher :described_constant, <<-PATTERN
8
- (block $(send _ :describe $(const ...)) (args) $_)
9
- PATTERN
10
-
11
- def on_send(node)
12
- return unless respond_to?(:on_top_level_describe)
13
- return unless top_level_describe?(node)
14
-
15
- _receiver, _method_name, *args = *node
16
-
17
- on_top_level_describe(node, args)
18
- end
19
-
20
- private
21
-
22
- def top_level_describe?(node)
23
- _receiver, method_name, *_args = *node
24
- return false unless method_name == :describe
25
-
26
- top_level_nodes.include?(node)
27
- end
28
-
29
- def top_level_nodes
30
- nodes = describe_statement_children(root_node)
31
- # If we have no top level describe statements, we need to check any
32
- # blocks on the top level (e.g. after a require).
33
- if nodes.empty?
34
- nodes = root_node.each_child_node(:block).flat_map do |child|
35
- describe_statement_children(child)
36
- end
37
- end
38
-
39
- nodes
40
- end
41
-
42
- def root_node
43
- processed_source.ast
44
- end
45
-
46
- def single_top_level_describe?
47
- top_level_nodes.one?
48
- end
49
-
50
- def describe_statement_children(node)
51
- node.each_child_node(:send).select do |element|
52
- element.children[1] == :describe
53
- end
54
- end
55
- end
56
- end
57
- end
@@ -1,19 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # Utility methods
6
- module Util
7
- # Error raised by `Util.one` if size is less than zero or greater than one
8
- SizeError = Class.new(IndexError)
9
-
10
- # Return only element in array if it contains exactly one member
11
- def one(array)
12
- return array.first if array.one?
13
-
14
- raise SizeError,
15
- "expected size to be exactly 1 but size was #{array.size}"
16
- end
17
- end
18
- end
19
- end
@@ -1,81 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RuboCop
4
- module EightyFourCodes
5
- # RSpec example wording rewriter
6
- class Wording
7
- SHOULDNT_PREFIX = /\Ashould(?:n't| not)\b/i
8
- SHOULDNT_BE_PREFIX = /#{SHOULDNT_PREFIX} be\b/i
9
- ES_SUFFIX_PATTERN = /(?:o|s|x|ch|sh|z)\z/i
10
- IES_SUFFIX_PATTERN = /[^aeou]y\z/i
11
-
12
- def initialize(text, ignore:, replace:)
13
- @text = text
14
- @ignores = ignore
15
- @replacements = replace
16
- end
17
-
18
- def rewrite
19
- case text
20
- when SHOULDNT_BE_PREFIX
21
- replace_prefix(SHOULDNT_BE_PREFIX, 'is not')
22
- when SHOULDNT_PREFIX
23
- replace_prefix(SHOULDNT_PREFIX, 'does not')
24
- else
25
- remove_should_and_pluralize
26
- end
27
- end
28
-
29
- private
30
-
31
- attr_reader :text, :ignores, :replacements
32
-
33
- def replace_prefix(pattern, replacement)
34
- text.sub(pattern) do |shouldnt|
35
- uppercase?(shouldnt) ? replacement.upcase : replacement
36
- end
37
- end
38
-
39
- def uppercase?(word)
40
- word.upcase.eql?(word)
41
- end
42
-
43
- def remove_should_and_pluralize
44
- _should, *words = text.split
45
-
46
- words.each_with_index do |word, index|
47
- next if ignored_word?(word)
48
-
49
- words[index] = substitute(word)
50
-
51
- break
52
- end
53
-
54
- words.join(' ')
55
- end
56
-
57
- def ignored_word?(word)
58
- ignores.any? { |ignore| ignore.casecmp(word).zero? }
59
- end
60
-
61
- def substitute(word)
62
- # NOTE: Custom replacements are case sensitive.
63
- return replacements.fetch(word) if replacements.key?(word)
64
-
65
- case word
66
- when ES_SUFFIX_PATTERN then append_suffix(word, 'es')
67
- when IES_SUFFIX_PATTERN then append_suffix(word[0..-2], 'ies')
68
- else append_suffix(word, 's')
69
- end
70
- end
71
-
72
- def append_suffix(word, suffix)
73
- suffix = suffix.upcase if uppercase?(word)
74
-
75
- "#{word}#{suffix}"
76
- end
77
-
78
- private_constant(*constants(false))
79
- end
80
- end
81
- end