rubocop-eightyfourcodes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: f3bdcf879f5dbd72c539cfb616e85279915721d3520667359a6613c8183425c5
4
+ data.tar.gz: 8052007c2529fef3406fa5a0b3560720ff373030ef1ddbd12b85c96b62e0eac1
5
+ SHA512:
6
+ metadata.gz: 329bf23841b2ba450a8fa291fda6ec57bc709f47036e8cb528f6fec1abca6155febc27417fab729acbe6aed0f64456484ed8d2938795d23d71062703ffe3ba50
7
+ data.tar.gz: a8bd924b5f4e3dfe379d4ee2651ae156dd7c30654c4293de210b6973e03132449d414d45a8948fcd431d2b225844bea177d908fb16985cf019155a673d8e73ed
data/CHANGELOG.md ADDED
@@ -0,0 +1,6 @@
1
+ # Changelog
2
+
3
+ ## 0.0.1 (2019-09-11)
4
+
5
+ - Forked from gitlab-security
6
+ - Added `CommandLiteralInjection`: Passing user input to `` and %x without sanitization and parameterization can result in command injection)
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,3 @@
1
+ # Contributing
2
+
3
+ <https://docs.rubocop.org/en/latest/contributing/>
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ group :development, :test do
6
+ gem 'pry'
7
+ gem 'rspec', '~> 3.6.0'
8
+ gem 'rubocop-rspec', '~> 1.21.0'
9
+ end
data/LICENSE.md ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2019 eightyfourcodes AB
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in all
11
+ copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,95 @@
1
+ This is a collection of cops developed and used by 84codes AB
2
+ This code is based heavily upon the [rubocop-gitlab-security](https://gitlab.com/gitlab-org/rubocop-gitlab-security)
3
+ code released under the MIT License.
4
+
5
+ ## Installation
6
+
7
+ Just install the `rubocop-eightyfourcodes` gem
8
+
9
+ ```bash
10
+ gem install rubocop-eightyfourcodes
11
+ ```
12
+
13
+ or if you use bundler put this in your `Gemfile`
14
+
15
+ ```yaml
16
+ gem 'rubocop-eightyfourcodes'
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ You need to tell RuboCop to load the eightyfourcodes extension. There are three
22
+ ways to do this:
23
+
24
+ ### RuboCop configuration file
25
+
26
+ Put this into your `.rubocop.yml`.
27
+
28
+ ```yaml
29
+ require: rubocop-eightyfourcodes
30
+ ```
31
+
32
+ Now you can run `rubocop` and it will automatically load the RuboCop eightyfourcodes
33
+ cops together with the standard cops.
34
+
35
+ ### Command line
36
+
37
+ ```bash
38
+ rubocop --require rubocop-eightyfourcodes
39
+ ```
40
+
41
+ ### Rake task
42
+
43
+ ```ruby
44
+ RuboCop::RakeTask.new do |task|
45
+ task.requires << 'rubocop-eightyfourcodes'
46
+ end
47
+ ```
48
+
49
+ ## Inspecting specific files
50
+
51
+ By default, `rubocop-eightyfourcodes` inspects all files. You can override this setting in your config file by specifying one or more patterns:
52
+
53
+ ```yaml
54
+ # Inspect all files
55
+ AllCops:
56
+ EightyFourCodes:
57
+ Patterns:
58
+ - '.+'
59
+ ```
60
+
61
+ ```yaml
62
+ # Inspect only controller files.
63
+ AllCops:
64
+ EightyFourCodes:
65
+ Patterns:
66
+ - app/controllers/**/*.rb
67
+ ```
68
+
69
+ ## The Cops
70
+
71
+ All cops are located under
72
+ [`lib/rubocop/cop/eightyfourcodes`](lib/rubocop/cop/eightyfourcodes), and contain
73
+ examples/documentation.
74
+
75
+ In your `.rubocop.yml`, you may treat the eightyfourcodes cops just like any other
76
+ cop. For example:
77
+
78
+ ```yaml
79
+ EightyFourCodes/CommandLiteralInjection:
80
+ Exclude:
81
+ - 'spec/**/*'
82
+ ```
83
+
84
+ ## Contributing
85
+
86
+ 1. Fork it
87
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
88
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
89
+ 4. Push to the branch (`git push origin my-new-feature`)
90
+ 5. Create new Merge Request
91
+
92
+ ## License
93
+
94
+ `rubocop-eightyfourcodes` is MIT licensed. [See the accompanying file](LICENSE.md) for
95
+ the full text.
@@ -0,0 +1,9 @@
1
+ ---
2
+ AllCops:
3
+ EightyFourCodes:
4
+ Patterns:
5
+ - ".+"
6
+
7
+ EightyFourCodes/CommandLiteralInjection:
8
+ Description: Check for Command Injection in `` and %x
9
+ Enabled: true
@@ -0,0 +1,37 @@
1
+ module RuboCop
2
+ module Cop
3
+ module EightyFourCodes
4
+ # Check for use of `/bin/ls #{params[:file]}` and %x(/bin/ls #{params[:file]})
5
+ #
6
+ # Passing user input to `` and %x without sanitization and parameterization can result in command injection
7
+ #
8
+ # @example
9
+ #
10
+ # # bad
11
+ # %x(/bin/ls #{filename})
12
+ #
13
+ # # good (parameters)
14
+ # system("/bin/ls", filename)
15
+ # # even better
16
+ # exec("/bin/ls", shell_escape(filename))
17
+ #
18
+ class CommandLiteralInjection < RuboCop::Cop::Cop
19
+ MSG = 'Do not include variables command literals. Use parameters "system(cmd, params)" or exec() instead'.freeze
20
+
21
+ def_node_matcher :literal_var?, <<-PATTERN
22
+ (begin ...)
23
+ PATTERN
24
+
25
+ def on_xstr(node)
26
+ check_for_interpolation(node)
27
+ end
28
+
29
+ def check_for_interpolation(node)
30
+ return if node.children.none? { |n| literal_var?(n) }
31
+
32
+ add_offense(node)
33
+ end
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,70 @@
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
@@ -0,0 +1,34 @@
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
@@ -0,0 +1,33 @@
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
@@ -0,0 +1,72 @@
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
@@ -0,0 +1,32 @@
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
@@ -0,0 +1,95 @@
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
@@ -0,0 +1,49 @@
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
@@ -0,0 +1,16 @@
1
+ module RuboCop
2
+ module EightyFourCodes
3
+ # Because RuboCop doesn't yet support plugins, we have to monkey patch in a
4
+ # bit of our configuration.
5
+ module Inject
6
+ def self.defaults!
7
+ path = CONFIG_DEFAULT.to_s
8
+ hash = ConfigLoader.send(:load_yaml_configuration, path)
9
+ config = Config.new(hash, path)
10
+ puts "configuration from #{path}" if ConfigLoader.debug?
11
+ config = ConfigLoader.merge_with_default(config, path)
12
+ ConfigLoader.instance_variable_set(:@default_configuration, config)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,20 @@
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
@@ -0,0 +1,118 @@
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
@@ -0,0 +1,57 @@
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
@@ -0,0 +1,19 @@
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
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module EightyFourCodes
5
+ # Version information for the eightyfourcodes Rubocop plugin.
6
+ module Version
7
+ STRING = '0.0.1'
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,81 @@
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
@@ -0,0 +1,10 @@
1
+ module RuboCop
2
+ # RuboCop RSpec project namespace
3
+ module EightyFourCodes
4
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
5
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
6
+ CONFIG = YAML.safe_load(CONFIG_DEFAULT.read).freeze
7
+
8
+ private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
9
+ end
10
+ end
@@ -0,0 +1,22 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+
4
+ require 'rubocop'
5
+
6
+ require 'rubocop/eightyfourcodes'
7
+ require 'rubocop/eightyfourcodes/version'
8
+ require 'rubocop/eightyfourcodes/inject'
9
+ require 'rubocop/eightyfourcodes/top_level_describe'
10
+ require 'rubocop/eightyfourcodes/wording'
11
+ require 'rubocop/eightyfourcodes/util'
12
+ require 'rubocop/eightyfourcodes/language'
13
+ require 'rubocop/eightyfourcodes/language/node_pattern'
14
+ require 'rubocop/eightyfourcodes/concept'
15
+ require 'rubocop/eightyfourcodes/example_group'
16
+ require 'rubocop/eightyfourcodes/example'
17
+ require 'rubocop/eightyfourcodes/hook'
18
+ require 'rubocop/cop/eightyfourcodes/cop'
19
+
20
+ RuboCop::EightyFourCodes::Inject.defaults!
21
+
22
+ require 'rubocop/cop/eightyfourcodes/command_literal_injection'
@@ -0,0 +1,35 @@
1
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
2
+ require 'rubocop/eightyfourcodes/version'
3
+
4
+ Gem::Specification.new do |spec|
5
+ spec.name = 'rubocop-eightyfourcodes'
6
+ spec.summary = 'Basic security checks for projects'
7
+ spec.description = <<~DESCRIPTION
8
+ Basic security checking for Ruby files.
9
+ A plugin for the RuboCop code style enforcing & linting tool.
10
+ DESCRIPTION
11
+ spec.homepage = 'https://github.com/84codes/rubocop-eightyfourcodes/'
12
+ spec.authors = ['Anders Bälter', 'Brian Neel']
13
+ spec.email = [
14
+ 'anders@eightyfourcodes.com',
15
+ 'brian@gitlab.com'
16
+ ]
17
+ spec.licenses = ['MIT']
18
+
19
+ spec.version = RuboCop::EightyFourCodes::Version::STRING
20
+ spec.platform = Gem::Platform::RUBY
21
+ spec.required_ruby_version = '>= 2.3.0'
22
+
23
+ spec.require_paths = ['lib']
24
+ spec.files = Dir[
25
+ '{config,lib}/**/*',
26
+ '*.md',
27
+ '*.gemspec',
28
+ 'Gemfile'
29
+ ]
30
+ spec.extra_rdoc_files = ['LICENSE.md', 'README.md']
31
+
32
+ spec.add_runtime_dependency 'rubocop', '>= 0.51'
33
+
34
+ spec.add_development_dependency 'rake'
35
+ end
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-eightyfourcodes
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Anders Bälter
8
+ - Brian Neel
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2019-09-11 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rubocop
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0.51'
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0.51'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ description: |
43
+ Basic security checking for Ruby files.
44
+ A plugin for the RuboCop code style enforcing & linting tool.
45
+ email:
46
+ - anders@eightyfourcodes.com
47
+ - brian@gitlab.com
48
+ executables: []
49
+ extensions: []
50
+ extra_rdoc_files:
51
+ - LICENSE.md
52
+ - README.md
53
+ files:
54
+ - CHANGELOG.md
55
+ - CONTRIBUTING.md
56
+ - Gemfile
57
+ - LICENSE.md
58
+ - README.md
59
+ - config/default.yml
60
+ - lib/rubocop-eightyfourcodes.rb
61
+ - lib/rubocop/cop/eightyfourcodes/command_literal_injection.rb
62
+ - lib/rubocop/cop/eightyfourcodes/cop.rb
63
+ - lib/rubocop/eightyfourcodes.rb
64
+ - lib/rubocop/eightyfourcodes/concept.rb
65
+ - lib/rubocop/eightyfourcodes/config_formatter.rb
66
+ - lib/rubocop/eightyfourcodes/description_extractor.rb
67
+ - lib/rubocop/eightyfourcodes/example.rb
68
+ - lib/rubocop/eightyfourcodes/example_group.rb
69
+ - lib/rubocop/eightyfourcodes/hook.rb
70
+ - lib/rubocop/eightyfourcodes/inject.rb
71
+ - lib/rubocop/eightyfourcodes/language.rb
72
+ - lib/rubocop/eightyfourcodes/language/node_pattern.rb
73
+ - lib/rubocop/eightyfourcodes/top_level_describe.rb
74
+ - lib/rubocop/eightyfourcodes/util.rb
75
+ - lib/rubocop/eightyfourcodes/version.rb
76
+ - lib/rubocop/eightyfourcodes/wording.rb
77
+ - rubocop-eightyfourcodes.gemspec
78
+ homepage: https://github.com/84codes/rubocop-eightyfourcodes/
79
+ licenses:
80
+ - MIT
81
+ metadata: {}
82
+ post_install_message:
83
+ rdoc_options: []
84
+ require_paths:
85
+ - lib
86
+ required_ruby_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: 2.3.0
91
+ required_rubygems_version: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - ">="
94
+ - !ruby/object:Gem::Version
95
+ version: '0'
96
+ requirements: []
97
+ rubygems_version: 3.0.3
98
+ signing_key:
99
+ specification_version: 4
100
+ summary: Basic security checks for projects
101
+ test_files: []