rubocop-gitlab-security 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
+ SHA1:
3
+ metadata.gz: 2c65bedf0e3528bc2dc5170eb1dbf121e9c699bf
4
+ data.tar.gz: 83066a7d3c32e86a9fc1af8d6eb08f0419b5c445
5
+ SHA512:
6
+ metadata.gz: 84d7f7cac070f0544e069c38cc78538d127f3887a34ccbded7b3110e5b63cdfcddaed5b3324f460ed792f1fea383b0126c5358517b1890cd4c21b28ce8fda177
7
+ data.tar.gz: 5f17b1cede77035a9baf73e8d4b38e12fa1175e810b863f9e294e1ae6837a3a3435d5766d38aafecbc1d602653c9b7cd3132446925218a8e7e9f127965ba1e84
data/MIT-LICENSE.md ADDED
@@ -0,0 +1,22 @@
1
+ The MIT License (MIT)
2
+ =====================
3
+
4
+ Copyright (c) 2014 Ian MacLeod <ian@nevir.net>
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy of
7
+ this software and associated documentation files (the "Software"), to deal in
8
+ the Software without restriction, including without limitation the rights to
9
+ use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
10
+ of the Software, and to permit persons to whom the Software is furnished to do
11
+ so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,96 @@
1
+ This is an early attempt at creating Rubocop rules, similar to Rubocop-RSpec, for
2
+ blocking dangerous code. This code is based heavily upon the [Rubocop-RSpec](https://github.com/backus/rubocop-rspec)
3
+ code released under the MIT License.
4
+
5
+ Installation is the same as with Rubocop-RSpec.
6
+
7
+ Just install the `rubocop-gitlab-security` gem
8
+
9
+ ```bash
10
+ gem install rubocop-gitlab-security
11
+ ```
12
+
13
+ or if you use bundler put this in your `Gemfile`
14
+
15
+ ```
16
+ gem 'rubocop-gitlab-security'
17
+ ```
18
+
19
+
20
+ ## Usage
21
+
22
+ You need to tell RuboCop to load the Gitlab-Security extension. There are three
23
+ ways to do this:
24
+
25
+ ### RuboCop configuration file
26
+
27
+ Put this into your `.rubocop.yml`.
28
+
29
+ ```
30
+ require: rubocop-gitlab-security
31
+ ```
32
+
33
+ Now you can run `rubocop` and it will automatically load the RuboCop Gitlab-Security
34
+ cops together with the standard cops.
35
+
36
+ ### Command line
37
+
38
+ ```bash
39
+ rubocop --require rubocop-gitlab-security
40
+ ```
41
+
42
+ ### Rake task
43
+
44
+ ```ruby
45
+ RuboCop::RakeTask.new do |task|
46
+ task.requires << 'rubocop-gitlab-security'
47
+ end
48
+ ```
49
+
50
+ ## Inspecting specific files
51
+
52
+ By default, `rubocop-gitlab-security` inspects all files. You can override this setting in your config file by specifying one or more patterns:
53
+
54
+ ```yaml
55
+ # Inspect all files
56
+ AllCops:
57
+ RSpec:
58
+ Patterns:
59
+ - '.+'
60
+ ```
61
+
62
+ ```yaml
63
+ # Inspect only files ending with `_test.rb`
64
+ AllCops:
65
+ RSpec:
66
+ Patterns:
67
+ - '_test.rb$'
68
+ ```
69
+
70
+ ## The Cops
71
+
72
+ All cops are located under
73
+ [`lib/rubocop/cop/gitlab-security`](lib/rubocop/cop/gitlab-security), and contain
74
+ examples/documentation.
75
+
76
+ In your `.rubocop.yml`, you may treat the Gitlab-Security cops just like any other
77
+ cop. For example:
78
+
79
+ ```yaml
80
+ GitlabSecurity/PublicSend:
81
+ Exclude:
82
+ - app/my_file.rb
83
+ ```
84
+
85
+ ## Contributing
86
+
87
+ 1. Fork it
88
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
89
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
90
+ 4. Push to the branch (`git push origin my-new-feature`)
91
+ 5. Create new Merge Request
92
+
93
+ ## License
94
+
95
+ `rubocop-gitlab-security` is MIT licensed. [See the accompanying file](MIT-LICENSE.md) for
96
+ the full text.
@@ -0,0 +1,10 @@
1
+ ---
2
+
3
+ AllCops:
4
+ GitlabSecurity:
5
+ Patterns:
6
+ - '.+'
7
+
8
+ GitlabSecurity/PublicSend:
9
+ Description: Check for use of send()/public_send()
10
+ Enabled: true
@@ -0,0 +1,22 @@
1
+ require 'pathname'
2
+ require 'yaml'
3
+
4
+ require 'rubocop'
5
+
6
+ require 'rubocop/gitlab-security'
7
+ require 'rubocop/gitlab-security/version'
8
+ require 'rubocop/gitlab-security/inject'
9
+ require 'rubocop/gitlab-security/top_level_describe'
10
+ require 'rubocop/gitlab-security/wording'
11
+ require 'rubocop/gitlab-security/util'
12
+ require 'rubocop/gitlab-security/language'
13
+ require 'rubocop/gitlab-security/language/node_pattern'
14
+ require 'rubocop/gitlab-security/concept'
15
+ require 'rubocop/gitlab-security/example_group'
16
+ require 'rubocop/gitlab-security/example'
17
+ require 'rubocop/gitlab-security/hook'
18
+ require 'rubocop/cop/gitlab-security/cop'
19
+
20
+ RuboCop::GitlabSecurity::Inject.defaults!
21
+
22
+ require 'rubocop/cop/gitlab-security/public_send'
@@ -0,0 +1,70 @@
1
+ module RuboCop
2
+ module Cop # rubocop:disable Style/Documentation
3
+ WorkaroundCop2 = 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 WorkaroundCop2
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(:WorkaroundCop2)
19
+
20
+ module GitlabSecurity
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 < WorkaroundCop2
36
+ include RuboCop::GitlabSecurity::Language
37
+ include RuboCop::GitlabSecurity::Language::NodePattern
38
+
39
+ DEFAULT_CONFIGURATION =
40
+ RuboCop::GitlabSecurity::CONFIG.fetch('AllCops').fetch('GitlabSecurity')
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('GitlabSecurity', DEFAULT_CONFIGURATION)
65
+ .fetch('Patterns')
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,14 @@
1
+ module RuboCop
2
+ module Cop
3
+ module GitlabSecurity
4
+ class PublicSend < RuboCop::Cop::Cop
5
+ MSG = 'Avoid using `send`'
6
+
7
+ def on_send(node)
8
+ return unless node.command?(:send) || node.command?(:public_send)
9
+ add_offense(node, :selector)
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,10 @@
1
+ module RuboCop
2
+ # RuboCop RSpec project namespace
3
+ module GitlabSecurity
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,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GitlabSecurity
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 GitlabSecurity
5
+ # Builds a YAML config file from two config hashes
6
+ class ConfigFormatter
7
+ NAMESPACES = /^(#{Regexp.union('GitlabSecurity')})/
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 GitlabSecurity
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::GitlabSecurity'.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 GitlabSecurity
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 GitlabSecurity
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 GitlabSecurity
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.method_args.first
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ module RuboCop
2
+ module GitlabSecurity
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,118 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GitlabSecurity
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,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GitlabSecurity
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,57 @@
1
+ module RuboCop
2
+ module GitlabSecurity
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 GitlabSecurity
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 GitlabSecurity
5
+ # Version information for the GitlabSecurity Rubocop plugin.
6
+ module Version
7
+ STRING = '0.0.1'.freeze
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,81 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module GitlabSecurity
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,36 @@
1
+ # encoding: utf-8
2
+
3
+ $LOAD_PATH.unshift File.expand_path('../lib', __FILE__)
4
+ require 'rubocop/gitlab-security/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'rubocop-gitlab-security'
8
+ spec.summary = 'Basic security checks for projects'
9
+ spec.description = <<-end_description
10
+ Basic security checking for Ruby files.
11
+ A plugin for the RuboCop code style enforcing & linting tool.
12
+ end_description
13
+ spec.homepage = 'http://gitlab.com/briann/rubocop-gitlab-security'
14
+ spec.authors = ['Brian Neel']
15
+ spec.email = [
16
+ 'brian@gitlab.com'
17
+ ]
18
+ spec.licenses = ['MIT']
19
+
20
+ spec.version = RuboCop::GitlabSecurity::Version::STRING
21
+ spec.platform = Gem::Platform::RUBY
22
+ spec.required_ruby_version = '>= 2.1.0'
23
+
24
+ spec.require_paths = ['lib']
25
+ spec.files = Dir[
26
+ '{config,lib}/**/*',
27
+ '*.md',
28
+ '*.gemspec',
29
+ 'Gemfile'
30
+ ]
31
+ spec.extra_rdoc_files = ['MIT-LICENSE.md', 'README.md']
32
+
33
+ spec.add_runtime_dependency 'rubocop', '>= 0.49.0'
34
+
35
+ spec.add_development_dependency 'rake'
36
+ end
metadata ADDED
@@ -0,0 +1,97 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rubocop-gitlab-security
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Brian Neel
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2017-06-12 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 0.49.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 0.49.0
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: |2
42
+ Basic security checking for Ruby files.
43
+ A plugin for the RuboCop code style enforcing & linting tool.
44
+ email:
45
+ - brian@gitlab.com
46
+ executables: []
47
+ extensions: []
48
+ extra_rdoc_files:
49
+ - MIT-LICENSE.md
50
+ - README.md
51
+ files:
52
+ - MIT-LICENSE.md
53
+ - README.md
54
+ - config/default.yml
55
+ - lib/rubocop-gitlab-security.rb
56
+ - lib/rubocop/cop/gitlab-security/cop.rb
57
+ - lib/rubocop/cop/gitlab-security/public_send.rb
58
+ - lib/rubocop/gitlab-security.rb
59
+ - lib/rubocop/gitlab-security/concept.rb
60
+ - lib/rubocop/gitlab-security/config_formatter.rb
61
+ - lib/rubocop/gitlab-security/description_extractor.rb
62
+ - lib/rubocop/gitlab-security/example.rb
63
+ - lib/rubocop/gitlab-security/example_group.rb
64
+ - lib/rubocop/gitlab-security/hook.rb
65
+ - lib/rubocop/gitlab-security/inject.rb
66
+ - lib/rubocop/gitlab-security/language.rb
67
+ - lib/rubocop/gitlab-security/language/node_pattern.rb
68
+ - lib/rubocop/gitlab-security/top_level_describe.rb
69
+ - lib/rubocop/gitlab-security/util.rb
70
+ - lib/rubocop/gitlab-security/version.rb
71
+ - lib/rubocop/gitlab-security/wording.rb
72
+ - rubocop-gitlab-security.gemspec
73
+ homepage: http://gitlab.com/briann/rubocop-gitlab-security
74
+ licenses:
75
+ - MIT
76
+ metadata: {}
77
+ post_install_message:
78
+ rdoc_options: []
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: 2.1.0
86
+ required_rubygems_version: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - ">="
89
+ - !ruby/object:Gem::Version
90
+ version: '0'
91
+ requirements: []
92
+ rubyforge_project:
93
+ rubygems_version: 2.6.11
94
+ signing_key:
95
+ specification_version: 4
96
+ summary: Basic security checks for projects
97
+ test_files: []