rubopolis 0.0.6
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/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: []
|