rubocop-eightyfourcodes 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +6 -0
- data/CONTRIBUTING.md +3 -0
- data/Gemfile +9 -0
- data/LICENSE.md +19 -0
- data/README.md +95 -0
- data/config/default.yml +9 -0
- data/lib/rubocop/cop/eightyfourcodes/command_literal_injection.rb +37 -0
- data/lib/rubocop/cop/eightyfourcodes/cop.rb +70 -0
- data/lib/rubocop/eightyfourcodes/concept.rb +34 -0
- data/lib/rubocop/eightyfourcodes/config_formatter.rb +33 -0
- data/lib/rubocop/eightyfourcodes/description_extractor.rb +72 -0
- data/lib/rubocop/eightyfourcodes/example.rb +32 -0
- data/lib/rubocop/eightyfourcodes/example_group.rb +95 -0
- data/lib/rubocop/eightyfourcodes/hook.rb +49 -0
- data/lib/rubocop/eightyfourcodes/inject.rb +16 -0
- data/lib/rubocop/eightyfourcodes/language/node_pattern.rb +20 -0
- data/lib/rubocop/eightyfourcodes/language.rb +118 -0
- data/lib/rubocop/eightyfourcodes/top_level_describe.rb +57 -0
- data/lib/rubocop/eightyfourcodes/util.rb +19 -0
- data/lib/rubocop/eightyfourcodes/version.rb +10 -0
- data/lib/rubocop/eightyfourcodes/wording.rb +81 -0
- data/lib/rubocop/eightyfourcodes.rb +10 -0
- data/lib/rubocop-eightyfourcodes.rb +22 -0
- data/rubocop-eightyfourcodes.gemspec +35 -0
- metadata +101 -0
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
data/CONTRIBUTING.md
ADDED
data/Gemfile
ADDED
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.
|
data/config/default.yml
ADDED
@@ -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,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: []
|