rubopolis 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +22 -0
- data/config/default.yml +12 -0
- data/lib/rubopolis/cop/migration_filename.rb +40 -0
- data/lib/rubopolis/cop/query_injection.rb +82 -0
- data/lib/rubopolis/cop/time_usage.rb +58 -0
- data/lib/rubopolis/inject.rb +19 -0
- data/lib/rubopolis/version.rb +7 -0
- data/lib/rubopolis.rb +22 -0
- metadata +149 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 7a7ed5bfea758072102a9bbea085269d3acb334d7adcb5dc981fdabdb52c4085
|
4
|
+
data.tar.gz: 5ba582879db9e949f97491045bf986a9536c74618d844cf47b7045b20d75c7c7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0ff04065bc7620d1983b3acbf99c1e9b8bb8168d5f604d030ea23e3b5f7e26f748af07bc29a4ae560d9ac0e40c7baf189b2d8b498ad6dcd0a91c03e2c54fa729
|
7
|
+
data.tar.gz: 7c6cce7704b0336b6a4f13c2669f51a63c250b1c3052207d0c95cb4d7cc8215d0b52a01f5527e7ffa4ceac2f9f1f80054e46bf63e36a2708a82425171fae3e32
|
data/README.md
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# rubopolis
|
2
|
+
## About
|
3
|
+
This repo is a collection of useful rubocops for Rails project.
|
4
|
+
|
5
|
+
These are the list of custom cops in this project right now:
|
6
|
+
1. Migration filename convention
|
7
|
+
2. Time usage
|
8
|
+
3. Query injection prevention
|
9
|
+
|
10
|
+
|
11
|
+
## Installation
|
12
|
+
Add rubopolis gem to you application:
|
13
|
+
```
|
14
|
+
TODO
|
15
|
+
```
|
16
|
+
|
17
|
+
## Contribute
|
18
|
+
Contributions are welcome!
|
19
|
+
|
20
|
+
Fork the repository
|
21
|
+
Write code and tests
|
22
|
+
Submit a PR
|
data/config/default.yml
ADDED
@@ -0,0 +1,12 @@
|
|
1
|
+
Cop/MigrationFilename:
|
2
|
+
Description: 'Enforce the database migration filename is not a future date'
|
3
|
+
Enabled: true
|
4
|
+
VersionAdded: '0.0.1'
|
5
|
+
Cop/QueryInject:
|
6
|
+
Description: 'Ensure code does not have an query inject-able code'
|
7
|
+
Enabled: true
|
8
|
+
VersionAdded: '0.0.1'
|
9
|
+
Cop/TimeUsage:
|
10
|
+
Description: 'Use `Time.current` or `Date.current` instead for standardisation.'
|
11
|
+
Enabled: true
|
12
|
+
VersionAdded: '0.0.1'
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
module Rubopolis
|
6
|
+
module Cop
|
7
|
+
# This cop is used to enforce proper migration filename in codebase
|
8
|
+
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# Manipulating the filename timestamp to be the future
|
12
|
+
#
|
13
|
+
# # good
|
14
|
+
# System generated filename
|
15
|
+
class MigrationFilename < RuboCop::Cop::Base
|
16
|
+
include RuboCop::Cop::RangeHelp
|
17
|
+
|
18
|
+
MSG_FUTURE_TIMESTAMP = 'The name of this file (`%<basename>s`) should not use future timestamp.'
|
19
|
+
|
20
|
+
def on_new_investigation
|
21
|
+
file_path = processed_source.file_path
|
22
|
+
|
23
|
+
return unless file_path.include?('/db/')
|
24
|
+
|
25
|
+
for_bad_filename(file_path) { |range, msg| add_offense(range, message: msg) }
|
26
|
+
end
|
27
|
+
|
28
|
+
def for_bad_filename(file_path)
|
29
|
+
basename = File.basename(file_path)
|
30
|
+
|
31
|
+
filename_datetime = basename.split('_', 2).first
|
32
|
+
current_timestamp = Time.now.strftime('%Y%m%d%H%M%S')
|
33
|
+
|
34
|
+
msg = format(MSG_FUTURE_TIMESTAMP, basename: basename) if filename_datetime > current_timestamp
|
35
|
+
|
36
|
+
yield source_range(processed_source.buffer, 1, 0), msg if msg
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop/cop/mixin/active_record_helper'
|
4
|
+
|
5
|
+
module Rubopolis
|
6
|
+
module Cop
|
7
|
+
# This cop is used to identify usages of `find_by` or `where` with string or params inputs.
|
8
|
+
# Use either hash or array input to avoid potential SQL injection.
|
9
|
+
# Feel free to disable the rule manually if code is assessed to be safe from injection.
|
10
|
+
|
11
|
+
# @example
|
12
|
+
# # bad
|
13
|
+
# User.find_by("name = 'Bruce'")
|
14
|
+
# User.find_by(params[:name_query])
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# User.find_by('name = ?', 'Bruce')
|
18
|
+
# User.find_by(['name = ?', 'Bruce'])
|
19
|
+
# User.find_by(name: 'Bruce')
|
20
|
+
class QueryInjection < RuboCop::Cop::Base
|
21
|
+
include RuboCop::Cop::RangeHelp
|
22
|
+
include RuboCop::Cop::ActiveRecordHelper
|
23
|
+
|
24
|
+
MSG = '`%s` should be called with hash or array arguments only: see lib/custom_cops/query_injection'
|
25
|
+
|
26
|
+
def on_send(node)
|
27
|
+
return if node.receiver.nil? && !inherit_active_record_base?(node)
|
28
|
+
return unless method?(node)
|
29
|
+
return unless where_or_find_by?(node)
|
30
|
+
return if acceptable_arg?(node.arguments[0])
|
31
|
+
|
32
|
+
# when arguments are > 1 strings, it should be templated and are most likely safe.
|
33
|
+
return if node.arguments.length > 1
|
34
|
+
|
35
|
+
range = offense_range(node)
|
36
|
+
add_offense(range, message: format(MSG, @method))
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def offense_range(node)
|
42
|
+
range_between(node.loc.selector.begin_pos, node.arguments[0].loc.expression.end_pos)
|
43
|
+
end
|
44
|
+
|
45
|
+
def method?(node)
|
46
|
+
node.arguments.any? && node.respond_to?(:method?)
|
47
|
+
end
|
48
|
+
|
49
|
+
def where_or_find_by?(node)
|
50
|
+
if node.method?(:where)
|
51
|
+
@method = 'where'
|
52
|
+
return true
|
53
|
+
elsif node.method?(:find_by)
|
54
|
+
@method = 'find_by'
|
55
|
+
return true
|
56
|
+
end
|
57
|
+
|
58
|
+
false
|
59
|
+
end
|
60
|
+
|
61
|
+
def array?(node)
|
62
|
+
node.is_a?(RuboCop::AST::ArrayNode)
|
63
|
+
end
|
64
|
+
|
65
|
+
def kwarg?(node)
|
66
|
+
node.is_a?(RuboCop::AST::HashNode)
|
67
|
+
end
|
68
|
+
|
69
|
+
def acceptable_arg?(node)
|
70
|
+
# TODO: current implementation does not allow generated arguments
|
71
|
+
# i.e. find_by(function_that_returns_hash)
|
72
|
+
#
|
73
|
+
# if this is desired:
|
74
|
+
# - raise a chore ticket to update this cop
|
75
|
+
# - update rubocop_todo.yml to ignore the file in question OR
|
76
|
+
# - temporarily disable the rule in the file with an inline comment
|
77
|
+
|
78
|
+
array?(node) || kwarg?(node)
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Rubopolis
|
4
|
+
module Cop
|
5
|
+
# This cop is used to identify usages of `Time.zone.now` or `Time.zone.today` and any Time class related arithmetic.
|
6
|
+
# Use `Time.current` or `Date.current` instead for standardisation.
|
7
|
+
# Feel free to disable the rule manually if necessary.
|
8
|
+
|
9
|
+
# @example
|
10
|
+
# # bad
|
11
|
+
# Time.zone.today
|
12
|
+
# Time.zone.now
|
13
|
+
# Time.current + 2.weeks
|
14
|
+
# Time.current - 2.weeks
|
15
|
+
#
|
16
|
+
# # good
|
17
|
+
# Date.current
|
18
|
+
# Time.current
|
19
|
+
# 2.weeks.ago
|
20
|
+
# 2.weeks.since or 2.weeks.from_now
|
21
|
+
class TimeUsage < RuboCop::Cop::Base
|
22
|
+
USAGE_MSG = '`Time.zone.now` or `Time.zone.today` should not be used, to consider Time.current or ' \
|
23
|
+
'Date.current instead: see lib/custom_cops/time_usage'
|
24
|
+
|
25
|
+
ARITHMETIC_MSG = 'Avoid subtracting or adding for Time, to use methods like `ago` and `since/from_now` e.g. ' \
|
26
|
+
'`2.weeks.ago`, `5.minutes.since`. Refer to https://api.rubyonrails.org/classes/Time.html ' \
|
27
|
+
'for more info'
|
28
|
+
|
29
|
+
def_node_matcher :time_now?, <<~PATTERN
|
30
|
+
(send (send (const nil? :Time) :zone) :now)
|
31
|
+
PATTERN
|
32
|
+
|
33
|
+
def_node_matcher :time_today?, <<~PATTERN
|
34
|
+
(send (send (const nil? :Time) :zone) :today)
|
35
|
+
PATTERN
|
36
|
+
|
37
|
+
def_node_matcher :time_addition?, <<~PATTERN
|
38
|
+
(send (send (const nil? :Time) :current) :+ $(...))
|
39
|
+
PATTERN
|
40
|
+
|
41
|
+
def_node_matcher :time_subtract?, <<~PATTERN
|
42
|
+
(send (send (const nil? :Time) :current) :- $(...))
|
43
|
+
PATTERN
|
44
|
+
|
45
|
+
def on_send(node)
|
46
|
+
if time_now?(node) || time_today?(node)
|
47
|
+
message = USAGE_MSG
|
48
|
+
elsif time_addition?(node) || time_subtract?(node)
|
49
|
+
message = ARITHMETIC_MSG
|
50
|
+
else
|
51
|
+
return
|
52
|
+
end
|
53
|
+
|
54
|
+
add_offense(node, message: message)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rubocop'
|
4
|
+
|
5
|
+
# Original from https://github.com/rubocop/rubocop-rspec/blob/master/lib/rubocop/rspec/inject.rb
|
6
|
+
module Rubopolis
|
7
|
+
# Because RuboCop doesn't yet support plugins, we have to monkey patch in a
|
8
|
+
# bit of our configuration.
|
9
|
+
module Inject
|
10
|
+
def self.defaults!
|
11
|
+
path = CONFIG_DEFAULT.to_s
|
12
|
+
hash = ::RuboCop::ConfigLoader.send(:load_yaml_configuration, path)
|
13
|
+
config = ::RuboCop::Config.new(hash, path).tap(&:make_excludes_absolute)
|
14
|
+
puts "configuration from #{path}" if ::RuboCop::ConfigLoader.debug?
|
15
|
+
config = ::RuboCop::ConfigLoader.merge_with_default(config, path)
|
16
|
+
::RuboCop::ConfigLoader.instance_variable_set(:@default_configuration, config)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
data/lib/rubopolis.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'pathname'
|
4
|
+
require 'yaml'
|
5
|
+
|
6
|
+
require_relative 'rubopolis/inject'
|
7
|
+
require_relative 'rubopolis/version'
|
8
|
+
require_relative 'rubopolis/cop/migration_filename'
|
9
|
+
require_relative 'rubopolis/cop/query_injection'
|
10
|
+
require_relative 'rubopolis/cop/time_usage'
|
11
|
+
|
12
|
+
##
|
13
|
+
# Base Module
|
14
|
+
module Rubopolis
|
15
|
+
PROJECT_ROOT = ::Pathname.new(__dir__).parent.expand_path.freeze
|
16
|
+
CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'default.yml').freeze
|
17
|
+
CONFIG = ::YAML.safe_load(CONFIG_DEFAULT.read).freeze
|
18
|
+
|
19
|
+
private_constant(:CONFIG_DEFAULT, :PROJECT_ROOT)
|
20
|
+
end
|
21
|
+
|
22
|
+
Rubopolis::Inject.defaults!
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rubopolis
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.6
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eileen Kang
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2023-01-17 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rspec
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '3.12'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '3.12'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rubocop
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '1.33'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '1.33'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rubocop-rails
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.17'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.17'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: simplecov
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - "~>"
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0.22'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - "~>"
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '0.22'
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: timecop
|
71
|
+
requirement: !ruby/object:Gem::Requirement
|
72
|
+
requirements:
|
73
|
+
- - "~>"
|
74
|
+
- !ruby/object:Gem::Version
|
75
|
+
version: 0.9.6
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: 0.9.6
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: rubocop
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - "~>"
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '1.3'
|
90
|
+
type: :runtime
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - "~>"
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '1.3'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rubocop-rails
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2'
|
111
|
+
description: All custom rubocop for Rails project
|
112
|
+
email: eileen_kang@tech.gov.sg
|
113
|
+
executables: []
|
114
|
+
extensions: []
|
115
|
+
extra_rdoc_files: []
|
116
|
+
files:
|
117
|
+
- README.md
|
118
|
+
- config/default.yml
|
119
|
+
- lib/rubopolis.rb
|
120
|
+
- lib/rubopolis/cop/migration_filename.rb
|
121
|
+
- lib/rubopolis/cop/query_injection.rb
|
122
|
+
- lib/rubopolis/cop/time_usage.rb
|
123
|
+
- lib/rubopolis/inject.rb
|
124
|
+
- lib/rubopolis/version.rb
|
125
|
+
homepage: https://github.com/GovTechSG/rubopolis
|
126
|
+
licenses:
|
127
|
+
- MIT
|
128
|
+
metadata:
|
129
|
+
rubygems_mfa_required: 'true'
|
130
|
+
post_install_message:
|
131
|
+
rdoc_options: []
|
132
|
+
require_paths:
|
133
|
+
- lib
|
134
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
135
|
+
requirements:
|
136
|
+
- - ">="
|
137
|
+
- !ruby/object:Gem::Version
|
138
|
+
version: '3.0'
|
139
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
requirements: []
|
145
|
+
rubygems_version: 3.2.33
|
146
|
+
signing_key:
|
147
|
+
specification_version: 4
|
148
|
+
summary: Custom rubocop for Rails project
|
149
|
+
test_files: []
|