querly 0.6.0 → 0.7.0
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 +4 -4
- data/CHANGELOG.md +6 -0
- data/README.md +3 -3
- data/lib/querly/cli.rb +11 -0
- data/lib/querly/pattern/expr.rb +32 -28
- data/lib/querly/pattern/parser.y +20 -13
- data/lib/querly/pp/cli.rb +10 -17
- data/lib/querly/rule.rb +1 -1
- data/lib/querly/script_enumerator.rb +3 -4
- data/lib/querly/version.rb +1 -1
- data/manual/configuration.md +94 -0
- data/manual/examples.md +35 -0
- data/manual/patterns.md +168 -0
- data/template.yml +69 -0
- metadata +7 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2a7dfd2ae45b1173ea9e23967e5c39b211bebffb
|
4
|
+
data.tar.gz: d014023c1f6e4d6989d04ea688dbaad0fed475ba
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 54969626459f41a814877e188ee487a911f01cbe9a17fe83ed2ed8b110736be3088994c2c0e73754c418c93e37e640fe409707740edd9245f63adc7d09045cf0
|
7
|
+
data.tar.gz: 100d7dbc6eb4dc7a6edadd86c08760782aa4ab65f1b4958d1b1e3f6360a2db28b00b748297367dd7df47caef9259b7b93f13b3a4f6ffc57dc694c785dcb49332
|
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@
|
|
2
2
|
|
3
3
|
## master
|
4
4
|
|
5
|
+
## 0.7.0 (2017-08-22)
|
6
|
+
|
7
|
+
* Add Wiki pages to repository in manual directory #25
|
8
|
+
* Add named literal pattern `:string: as 'name` with `where: { name: ["alice", /bob/] }` #24
|
9
|
+
* Add `init` command #28
|
10
|
+
|
5
11
|
## 0.6.0 (2017-06-27)
|
6
12
|
|
7
13
|
* Load current directory when no path is given (@wata727) #18
|
data/README.md
CHANGED
@@ -87,10 +87,10 @@ If your code contains `p` or `pp` calls, querly will print warning messages.
|
|
87
87
|
|
88
88
|
## Configuration
|
89
89
|
|
90
|
-
|
90
|
+
See the following manual for configuration and query language reference.
|
91
91
|
|
92
|
-
* [Configuration](https://github.com/soutaro/querly/
|
93
|
-
* [Patterns](https://github.com/soutaro/querly/
|
92
|
+
* [Configuration](https://github.com/soutaro/querly/blob/master/manual/configuration.md)
|
93
|
+
* [Patterns](https://github.com/soutaro/querly/blob/master/manual/patterns.md)
|
94
94
|
|
95
95
|
Use `querly console` command to test patterns interactively.
|
96
96
|
|
data/lib/querly/cli.rb
CHANGED
@@ -86,6 +86,17 @@ Specify configuration file by --config option.
|
|
86
86
|
puts "Querly #{VERSION}"
|
87
87
|
end
|
88
88
|
|
89
|
+
def self.source_root
|
90
|
+
File.join(__dir__, "../..")
|
91
|
+
end
|
92
|
+
|
93
|
+
include Thor::Actions
|
94
|
+
|
95
|
+
desc "init", "Generate Querly config file (querly.yml)"
|
96
|
+
def init()
|
97
|
+
copy_file("template.yml", "querly.yml")
|
98
|
+
end
|
99
|
+
|
89
100
|
private
|
90
101
|
|
91
102
|
def config_path
|
data/lib/querly/pattern/expr.rb
CHANGED
@@ -23,7 +23,7 @@ module Querly
|
|
23
23
|
|
24
24
|
class Any < Base
|
25
25
|
def test_node(node)
|
26
|
-
|
26
|
+
!!node
|
27
27
|
end
|
28
28
|
end
|
29
29
|
|
@@ -81,56 +81,53 @@ module Querly
|
|
81
81
|
|
82
82
|
class Literal < Base
|
83
83
|
attr_reader :type
|
84
|
-
attr_reader :
|
84
|
+
attr_reader :values
|
85
85
|
|
86
|
-
def initialize(type:,
|
86
|
+
def initialize(type:, values: nil)
|
87
87
|
@type = type
|
88
|
-
@
|
88
|
+
@values = values ? Array(values) : nil
|
89
|
+
end
|
90
|
+
|
91
|
+
def with_values(values)
|
92
|
+
self.class.new(type: type, values: values)
|
93
|
+
end
|
94
|
+
|
95
|
+
def test_value(object)
|
96
|
+
if values
|
97
|
+
values.any? {|value| value === object }
|
98
|
+
else
|
99
|
+
true
|
100
|
+
end
|
89
101
|
end
|
90
102
|
|
91
103
|
def test_node(node)
|
92
104
|
case node&.type
|
93
105
|
when :int
|
94
106
|
return false unless type == :int || type == :number
|
95
|
-
|
96
|
-
value == node.children.first
|
97
|
-
else
|
98
|
-
true
|
99
|
-
end
|
107
|
+
test_value(node.children.first)
|
100
108
|
|
101
109
|
when :float
|
102
110
|
return false unless type == :float || type == :number
|
103
|
-
|
104
|
-
value == node.children.first
|
105
|
-
else
|
106
|
-
true
|
107
|
-
end
|
111
|
+
test_value(node.children.first)
|
108
112
|
|
109
113
|
when :true
|
110
|
-
type == :bool && (
|
114
|
+
type == :bool && (values == nil || values == [true])
|
111
115
|
|
112
116
|
when :false
|
113
|
-
type == :bool && (
|
117
|
+
type == :bool && (values == nil || values == [false])
|
114
118
|
|
115
119
|
when :str
|
116
120
|
return false unless type == :string
|
117
|
-
|
118
|
-
value == node.children.first
|
119
|
-
else
|
120
|
-
true
|
121
|
-
end
|
121
|
+
test_value(node.children.first)
|
122
122
|
|
123
123
|
when :sym
|
124
124
|
return false unless type == :symbol
|
125
|
-
|
126
|
-
value == node.children.first
|
127
|
-
else
|
128
|
-
true
|
129
|
-
end
|
125
|
+
test_value(node.children.first)
|
130
126
|
|
131
127
|
when :regexp
|
132
128
|
type == :regexp
|
133
|
-
|
129
|
+
test_value(node.children.first)
|
130
|
+
|
134
131
|
end
|
135
132
|
end
|
136
133
|
end
|
@@ -162,7 +159,14 @@ module Querly
|
|
162
159
|
end
|
163
160
|
|
164
161
|
def test_name(node)
|
165
|
-
name.
|
162
|
+
name.map do |n|
|
163
|
+
case n
|
164
|
+
when String
|
165
|
+
n.to_sym
|
166
|
+
else
|
167
|
+
n
|
168
|
+
end
|
169
|
+
end.any? {|n| n === node.children[1] }
|
166
170
|
end
|
167
171
|
|
168
172
|
def test_node(node)
|
data/lib/querly/pattern/parser.y
CHANGED
@@ -17,19 +17,23 @@ expr: constant { result = Expr::Constant.new(path: val[0]) }
|
|
17
17
|
| send
|
18
18
|
| SELF { result = Expr::Self.new }
|
19
19
|
| EXCLAMATION expr { result = Expr::Not.new(pattern: val[1]) }
|
20
|
-
| BOOL { result = Expr::Literal.new(type: :bool,
|
21
|
-
|
|
22
|
-
|
|
23
|
-
| FLOAT { result = Expr::Literal.new(type: :float, value: val[0]) }
|
24
|
-
| SYMBOL { result = Expr::Literal.new(type: :symbol, value: val[0]) }
|
25
|
-
| NUMBER { result = Expr::Literal.new(type: :number, value: val[0]) }
|
26
|
-
| REGEXP { result = Expr::Literal.new(type: :regexp, value: nil) }
|
20
|
+
| BOOL { result = Expr::Literal.new(type: :bool, values: val[0]) }
|
21
|
+
| literal { result = val[0] }
|
22
|
+
| literal AS META { result = val[0].with_values(resolve_meta(val[2])) }
|
27
23
|
| DSTR { result = Expr::Dstr.new() }
|
28
24
|
| UNDERBAR { result = Expr::Any.new }
|
29
25
|
| NIL { result = Expr::Nil.new }
|
30
26
|
| LPAREN expr RPAREN { result = val[1] }
|
31
27
|
| IVAR { result = Expr::Ivar.new(name: val[0]) }
|
32
28
|
|
29
|
+
literal:
|
30
|
+
STRING { result = Expr::Literal.new(type: :string, values: val[0]) }
|
31
|
+
| INT { result = Expr::Literal.new(type: :int, values: val[0]) }
|
32
|
+
| FLOAT { result = Expr::Literal.new(type: :float, values: val[0]) }
|
33
|
+
| SYMBOL { result = Expr::Literal.new(type: :symbol, values: val[0]) }
|
34
|
+
| NUMBER { result = Expr::Literal.new(type: :number, values: val[0]) }
|
35
|
+
| REGEXP { result = Expr::Literal.new(type: :regexp, values: nil) }
|
36
|
+
|
33
37
|
args: { result = nil }
|
34
38
|
| expr { result = Argument::Expr.new(expr: val[0], tail: nil)}
|
35
39
|
| expr COMMA args { result = Argument::Expr.new(expr: val[0], tail: val[2]) }
|
@@ -55,6 +59,7 @@ key_value: keyword COLON expr { result = { key: val[0], value: val[2], negated:
|
|
55
59
|
|
56
60
|
method_name: METHOD
|
57
61
|
| EXCLAMATION
|
62
|
+
| AS
|
58
63
|
| META { result = resolve_meta(val[0]) }
|
59
64
|
|
60
65
|
method_name_or_ident: method_name
|
@@ -66,10 +71,10 @@ keyword: LIDENT | UIDENT
|
|
66
71
|
constant: UIDENT { result = [val[0]] }
|
67
72
|
| UIDENT COLONCOLON constant { result = [val[0]] + val[2] }
|
68
73
|
|
69
|
-
send: LIDENT block { result = val[1] != nil ? Expr::Send.new(receiver:
|
70
|
-
| UIDENT block { result = Expr::Send.new(receiver:
|
71
|
-
| method_name { result = Expr::Send.new(receiver:
|
72
|
-
| method_name_or_ident LPAREN args RPAREN block { result = Expr::Send.new(receiver:
|
74
|
+
send: LIDENT block { result = val[1] != nil ? Expr::Send.new(receiver: nil, name: val[0], args: Argument::AnySeq.new, block: val[1]) : Expr::Vcall.new(name: val[0]) }
|
75
|
+
| UIDENT block { result = Expr::Send.new(receiver: nil, name: val[0], block: val[1]) }
|
76
|
+
| method_name { result = Expr::Send.new(receiver: nil, name: val[0], block: nil) }
|
77
|
+
| method_name_or_ident LPAREN args RPAREN block { result = Expr::Send.new(receiver: nil,
|
73
78
|
name: val[0],
|
74
79
|
args: val[2],
|
75
80
|
block: val[4]) }
|
@@ -122,9 +127,9 @@ def next_token
|
|
122
127
|
case
|
123
128
|
when input.eos?
|
124
129
|
[false, false]
|
125
|
-
when input.scan(/true/)
|
130
|
+
when input.scan(/true\b/)
|
126
131
|
[:BOOL, true]
|
127
|
-
when input.scan(/false/)
|
132
|
+
when input.scan(/false\b/)
|
128
133
|
[:BOOL, false]
|
129
134
|
when input.scan(/nil/)
|
130
135
|
[:NIL, false]
|
@@ -147,6 +152,8 @@ def next_token
|
|
147
152
|
when input.scan(/:\w+/)
|
148
153
|
s = input.matched
|
149
154
|
[:SYMBOL, s[1, s.size - 1].to_sym]
|
155
|
+
when input.scan(/as\b/)
|
156
|
+
[:AS, :as]
|
150
157
|
when input.scan(/{}/)
|
151
158
|
[:WITH_BLOCK, nil]
|
152
159
|
when input.scan(/!{}/)
|
data/lib/querly/pp/cli.rb
CHANGED
@@ -55,27 +55,20 @@ module Querly
|
|
55
55
|
|
56
56
|
def run_haml
|
57
57
|
require "haml"
|
58
|
-
if Haml::VERSION >= '5.0.0'
|
59
|
-
raise <<~ERROR
|
60
|
-
HAML 5.0+ is detected.
|
61
|
-
`querly-pp haml` does not work on HAML 5.0+.
|
62
|
-
Use `haml -d` instead.
|
63
|
-
ERROR
|
64
|
-
end
|
65
|
-
|
66
58
|
load_libs
|
67
|
-
|
68
59
|
source = stdin.read
|
69
60
|
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
61
|
+
if Haml::VERSION >= '5.0.0'
|
62
|
+
stdout.print Haml::Engine.new(source).precompiled
|
63
|
+
else
|
64
|
+
options = Haml::Options.new
|
65
|
+
parser = Haml::Parser.new(source, options)
|
66
|
+
parser.parse
|
67
|
+
compiler = Haml::Compiler.new(options)
|
68
|
+
compiler.compile(parser.root)
|
77
69
|
|
78
|
-
|
70
|
+
stdout.print compiler.precompiled
|
71
|
+
end
|
79
72
|
end
|
80
73
|
end
|
81
74
|
end
|
data/lib/querly/rule.rb
CHANGED
@@ -11,11 +11,10 @@ module Querly
|
|
11
11
|
def each(&block)
|
12
12
|
if block_given?
|
13
13
|
paths.each do |path|
|
14
|
-
|
15
|
-
when path.file?
|
16
|
-
load_script_from_path path, &block
|
17
|
-
when path.directory?
|
14
|
+
if path.directory?
|
18
15
|
enumerate_files_in_dir(path, &block)
|
16
|
+
else
|
17
|
+
load_script_from_path path, &block
|
19
18
|
end
|
20
19
|
end
|
21
20
|
else
|
data/lib/querly/version.rb
CHANGED
@@ -0,0 +1,94 @@
|
|
1
|
+
# Overview
|
2
|
+
|
3
|
+
The configuration file, default name is `querly.yml`, will look like the following.
|
4
|
+
|
5
|
+
```yml
|
6
|
+
rules:
|
7
|
+
...
|
8
|
+
preprocessor:
|
9
|
+
...
|
10
|
+
check:
|
11
|
+
...
|
12
|
+
```
|
13
|
+
|
14
|
+
# rules
|
15
|
+
|
16
|
+
`rules` is array of rule hash.
|
17
|
+
|
18
|
+
```yml
|
19
|
+
- id: com.sideci.json
|
20
|
+
pattern: Net::HTTP
|
21
|
+
message: "Should use HTTPClient instead of Net::HTTP"
|
22
|
+
justification:
|
23
|
+
- No exception!
|
24
|
+
before:
|
25
|
+
- "Net::HTTP.get(url)"
|
26
|
+
after:
|
27
|
+
- HTTPClient.new.get_content(url)
|
28
|
+
```
|
29
|
+
|
30
|
+
The rule hash contains following keys:
|
31
|
+
|
32
|
+
* `id` Identifier of the rule, must be unique (string)
|
33
|
+
* `pattern` Patterns to find out (string, or array of string)
|
34
|
+
* `message` Error message to explain why the code fragment needs special care (string)
|
35
|
+
* `justification` When the *bad use* is allowed (string, or array of string)
|
36
|
+
* `before` Sample ruby code to find out (string, or array of string)
|
37
|
+
* `after` Sample ruby code to be fixed (string, or array of string)
|
38
|
+
|
39
|
+
# preprocessor
|
40
|
+
|
41
|
+
When your project contains `.slim`, `.haml`, or any templates which contains Ruby code, preprocessor is to translate the templates to Ruby code.
|
42
|
+
`preprocessor` is a hash; key of extension of the templates, value of command line.
|
43
|
+
|
44
|
+
```yml
|
45
|
+
.slim: slimrb --compile
|
46
|
+
.haml: bundle exec querly-pp haml -I lib -r your_custom_plugin
|
47
|
+
```
|
48
|
+
|
49
|
+
The command will be executed with stdin of template code, and should emit ruby code to stdout.
|
50
|
+
|
51
|
+
## querly-pp
|
52
|
+
|
53
|
+
Querly 0.2.0 ships with `querly-pp` command line tool which compiles given HAML source to Ruby script.
|
54
|
+
`-I` and `-r` options can be used to use plugins.
|
55
|
+
|
56
|
+
# check
|
57
|
+
|
58
|
+
Define set of rules to check for each file.
|
59
|
+
|
60
|
+
```yml
|
61
|
+
check:
|
62
|
+
- path: /test
|
63
|
+
rules:
|
64
|
+
- com.acme.corp
|
65
|
+
- append: com.acme.corp
|
66
|
+
- except: com.acme.corp
|
67
|
+
- only: com.acme.corp
|
68
|
+
- path: /test/unit
|
69
|
+
rules:
|
70
|
+
- append:
|
71
|
+
tags: foo bar
|
72
|
+
- except:
|
73
|
+
tags: foo bar
|
74
|
+
- only:
|
75
|
+
tags: foo bar
|
76
|
+
```
|
77
|
+
|
78
|
+
* `path` Files to apply the rules in `.gitignore` syntax
|
79
|
+
* `rules` Rules to check
|
80
|
+
|
81
|
+
All matching `check` element against given file name will be applied, sequentially.
|
82
|
+
|
83
|
+
* `/lib/bar.rb` => no checks will be applied (all rules)
|
84
|
+
* `/test/test_helper.rb` => `/test` check will be applied
|
85
|
+
* `/test/unit/account_test.rb` => `/test` and `/test/unit` checks will be applied
|
86
|
+
|
87
|
+
## Rules
|
88
|
+
|
89
|
+
You can use `append:`, `except:` and `only:` operation.
|
90
|
+
|
91
|
+
* `append:` appends rules to current rule set
|
92
|
+
* `except:` removes rules from current rule set
|
93
|
+
* `only:` update current rule set
|
94
|
+
|
data/manual/examples.md
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
In this page, I will show some rules I have written. They are all real rules from my repos.
|
2
|
+
|
3
|
+
# `js: true` option with feature spec
|
4
|
+
|
5
|
+
I see some of Feature Spec scenarios have `js: true` option, and others do not. The reason they look strange to me is some scenarios without `js: true` depend on JavaScript. I changed one of the `js: true` to `js: false`, and run the specs again. They run! What is happening?? The `js` does not stand for *JavaScript*?? What else?
|
6
|
+
|
7
|
+
The magic happens in `rails_helper.rb`.
|
8
|
+
|
9
|
+
```rb
|
10
|
+
Capybara.configure do |config|
|
11
|
+
config.default_driver = :poltergeist
|
12
|
+
config.javascript_driver = :poltergeist
|
13
|
+
end
|
14
|
+
```
|
15
|
+
|
16
|
+
Okay, `js: true` does not make any sense, because both `default_driver` and `javascript_driver` are same.
|
17
|
+
|
18
|
+
Should we fix all of the scenarios now? If we leave them, new teammates will misunderstand `js: true` does something important and required. However, we don't want to fix them now. It does not do anything bad right now. So, my conclusion is *I will do that, but not now* 😸
|
19
|
+
|
20
|
+
It's the time to add a new Querly rule. When someone tries to add new scenario with `js: true`, tell the person that it does not make any sense.
|
21
|
+
|
22
|
+
```yaml
|
23
|
+
- id: sample.scenario_with_js_option
|
24
|
+
pattern: "scenario(..., js: _, ...)"
|
25
|
+
message: |
|
26
|
+
You do not need js:true option
|
27
|
+
|
28
|
+
We are using Poltergeist as both default_driver and javascript_driver!
|
29
|
+
before:
|
30
|
+
- "scenario 'hello world', js: true, type: :feature do end"
|
31
|
+
after:
|
32
|
+
- "scenario 'foo bar' do end"
|
33
|
+
```
|
34
|
+
|
35
|
+
No new `js: true` scenario will be written. Our new teammate may try to write that. But Querly will tell they don't have to do that, instead of me.
|
data/manual/patterns.md
ADDED
@@ -0,0 +1,168 @@
|
|
1
|
+
# Syntax
|
2
|
+
|
3
|
+
## Toplevel
|
4
|
+
|
5
|
+
* *expr*
|
6
|
+
* *expr* `[` *kind* `]` (kinded expr)
|
7
|
+
* *expr* `[!` *kind* `]` (negated kinded expr)
|
8
|
+
|
9
|
+
## expr
|
10
|
+
|
11
|
+
* `_` (any expr)
|
12
|
+
* *method* (method call, with any receiver and any args)
|
13
|
+
* *method* `(` *args* `)` *block_spec* (method call with any receiver)
|
14
|
+
* *receiver* *method* (method call with any args)
|
15
|
+
* *receiver* *method* `(` *args* `)` *block_spec* (method call)
|
16
|
+
* *literal*
|
17
|
+
* `self` (self)
|
18
|
+
* `!` *expr*
|
19
|
+
|
20
|
+
### block_spec
|
21
|
+
|
22
|
+
* (no spec)
|
23
|
+
* `{}` (method call should be with block)
|
24
|
+
* `!{}` (method call should not be with block)
|
25
|
+
|
26
|
+
### receiver
|
27
|
+
|
28
|
+
* *expr* `.` (receiver matching with the pattern)
|
29
|
+
* *expr* `...` (some receiver in the chain matching with the pattern)
|
30
|
+
|
31
|
+
### Examples
|
32
|
+
|
33
|
+
* `p(_)` `p` call with one argument, any receiver
|
34
|
+
* `self.p(1)` `p` call with `1`, receiver is `self` or omitted.
|
35
|
+
* `foo.bar.baz` `baz` call with receiver of `bar` call of receiver of `foo` call
|
36
|
+
* `update_attribute(:symbol:, :string:)` `update_attribute` call with symbol and string literals
|
37
|
+
* `File.open(...) !{}` `File.open` call but without block
|
38
|
+
|
39
|
+
```rb
|
40
|
+
p 1 # p(_) matches
|
41
|
+
p 2 # p(_) matches
|
42
|
+
p 1, 2, 3 # p(_) does not match
|
43
|
+
|
44
|
+
p(1) # self.p(1) matches
|
45
|
+
|
46
|
+
foo(1).bar {|x| x+1 }.baz(3) # foo.bar.baz matches
|
47
|
+
(1+2).foo.bar(*args).baz.bla # foo.bar.baz matches, partially
|
48
|
+
foo.xyz.bar.baz # foo.bar.baz does not match
|
49
|
+
|
50
|
+
update_attribute(:name, "hoge") # f(:symbol:, :string:) matches
|
51
|
+
update_attribute(:name, name) # f(:symbol:, :string:) does not match
|
52
|
+
|
53
|
+
foo.bar.baz # foo.bar.baz matches
|
54
|
+
foo.bar.baz # foo...baz matches
|
55
|
+
bar.foo.baz # foo...bar...baz does not match
|
56
|
+
```
|
57
|
+
|
58
|
+
## args & kwargs
|
59
|
+
|
60
|
+
### args
|
61
|
+
|
62
|
+
* *expr* `,` *args*
|
63
|
+
* *expr* `,` *kwargs*
|
64
|
+
* *expr*
|
65
|
+
* `...` `,` *kwargs* (any argument sequence, followed by keyword arguments)
|
66
|
+
* `...` (any argument sequence, including any keyword arguments)
|
67
|
+
|
68
|
+
### Literals
|
69
|
+
|
70
|
+
* `123` (integer)
|
71
|
+
* `1.23` (float)
|
72
|
+
* `:foobar` (symbol)
|
73
|
+
* `:symbol:` (any symbol literal)
|
74
|
+
* `:string:` (any string literal)
|
75
|
+
* `:dstr:` (any dstr `"hi #{name}"`)
|
76
|
+
* `true`, `false` (true and false)
|
77
|
+
* `nil` (nil)
|
78
|
+
* `:number:`, `:int:`, `:float:` (any number, any integer, any float)
|
79
|
+
* `:bool:` (true or false)
|
80
|
+
|
81
|
+
### kwargs
|
82
|
+
|
83
|
+
* *symbol* `:` *expr* `,` ...
|
84
|
+
* `!` *symbol* `:` *expr* `,` ...
|
85
|
+
* `...`
|
86
|
+
* `&` *expr*
|
87
|
+
|
88
|
+
### Examples
|
89
|
+
|
90
|
+
```rb
|
91
|
+
f(1,2,3) # f(...), f(1,2,...), and f(1, ...) matches
|
92
|
+
# f(_,_), f(0, ...) does not match
|
93
|
+
|
94
|
+
JSON.load(string, symbolize_names: true) # JSON.load(..., symbolize_names: true) matches
|
95
|
+
# JSON.load(symbolize_names: true) does not match
|
96
|
+
|
97
|
+
record.update(email: email, name: name) # update(name: _, email: _) matches
|
98
|
+
# update(name: _) does not match
|
99
|
+
# update(name: _, ...) matches
|
100
|
+
# update(!id: _, ...) matches
|
101
|
+
|
102
|
+
article.try(&:author) # try(&:symbol:) matches
|
103
|
+
article.try(:author) # try(&:symbol:) does not match
|
104
|
+
article.try {|x| x.author } # try(&:symbol:) does not match
|
105
|
+
```
|
106
|
+
|
107
|
+
## kind
|
108
|
+
|
109
|
+
* `conditional` (When expr appears in *conditional* context)
|
110
|
+
* `discarded` (When expr appears in *discarded* context)
|
111
|
+
|
112
|
+
Kind allows you to find out something like:
|
113
|
+
|
114
|
+
* `save` call but does not check its result for error recovery
|
115
|
+
|
116
|
+
*conditional* context is
|
117
|
+
|
118
|
+
* Condition of `if` construct
|
119
|
+
* Condition of loop constructs
|
120
|
+
* LHS of `&&` and `||`
|
121
|
+
|
122
|
+
```rb
|
123
|
+
# record.save is in conditional context
|
124
|
+
unless record.save
|
125
|
+
# error recovery
|
126
|
+
end
|
127
|
+
|
128
|
+
# record.save is not in conditional context
|
129
|
+
x = record.save
|
130
|
+
|
131
|
+
# record.save is in conditional context
|
132
|
+
record.save or abort()
|
133
|
+
```
|
134
|
+
|
135
|
+
*discarded* context is where the value of the expression is completely discarded, a bit looser than *conditional*.
|
136
|
+
|
137
|
+
```rb
|
138
|
+
def f()
|
139
|
+
# record.save is in discarded context
|
140
|
+
foo()
|
141
|
+
record.save()
|
142
|
+
bar
|
143
|
+
end
|
144
|
+
```
|
145
|
+
|
146
|
+
# Difference from Ruby
|
147
|
+
|
148
|
+
* Method call parenthesis cannot be omitted (if omitted, it means *any arguments*)
|
149
|
+
* `+`, `-`, `[]` or other *operator* should be written as method calls like `_.+(_)`, `[]=(:string:, _)`
|
150
|
+
|
151
|
+
# Testing
|
152
|
+
|
153
|
+
You can test patterns by `querly console .` command interactively.
|
154
|
+
|
155
|
+
```
|
156
|
+
Querly 0.1.0, interactive console
|
157
|
+
|
158
|
+
Commands:
|
159
|
+
- find PATTERN Find PATTERN from given paths
|
160
|
+
- reload! Reload program from paths
|
161
|
+
- quit
|
162
|
+
|
163
|
+
Loading... ready!
|
164
|
+
>
|
165
|
+
```
|
166
|
+
|
167
|
+
Also `querly test` will help you.
|
168
|
+
It test configuration file by checking patterns in rules against `before` and `after` examples.
|
data/template.yml
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
rules:
|
2
|
+
- id: sample.debug_print
|
3
|
+
pattern:
|
4
|
+
- self.p
|
5
|
+
- self.pp
|
6
|
+
message: Delete debug print
|
7
|
+
examples:
|
8
|
+
- before: |
|
9
|
+
pp some: error
|
10
|
+
|
11
|
+
- id: sample.file.open
|
12
|
+
pattern: File.open(...) !{}
|
13
|
+
message: |
|
14
|
+
Use block to read/write file
|
15
|
+
|
16
|
+
If you use block, the open method closes file implicitly.
|
17
|
+
You don't have to close files explicitly.
|
18
|
+
examples:
|
19
|
+
- before: |
|
20
|
+
io = File.open("foo.txt")
|
21
|
+
io.write("hello world")
|
22
|
+
io.close
|
23
|
+
after: |
|
24
|
+
File.open("foo.txt") do |io|
|
25
|
+
io.write("hello world")
|
26
|
+
end
|
27
|
+
|
28
|
+
- id: sample.exception
|
29
|
+
pattern: Exception
|
30
|
+
message: |
|
31
|
+
You probablly should use StandardError
|
32
|
+
|
33
|
+
If you are trying to define error class, inherit that from StandardError.
|
34
|
+
justification:
|
35
|
+
- You are sure you want to define an exception which is not rescued by default
|
36
|
+
examples:
|
37
|
+
- before: class MyError < Exception; end
|
38
|
+
after: class MyError < StandardError; end
|
39
|
+
|
40
|
+
- id: sample.test.assert_equal_size
|
41
|
+
pattern:
|
42
|
+
subject: "assert_equal(:int: as 'zero, _.'size, ...)"
|
43
|
+
where:
|
44
|
+
zero: 0
|
45
|
+
size:
|
46
|
+
- size
|
47
|
+
- count
|
48
|
+
message: |
|
49
|
+
Comparing size of something with 0 can be written using assert_empty
|
50
|
+
examples:
|
51
|
+
- before: |
|
52
|
+
assert_equal 0, some.size
|
53
|
+
after: |
|
54
|
+
assert_empty some.size
|
55
|
+
- before: |
|
56
|
+
assert_equal 0, some.count
|
57
|
+
after: |
|
58
|
+
assert_empty some.count
|
59
|
+
|
60
|
+
preprocessor:
|
61
|
+
.slim: slimrb --compile
|
62
|
+
|
63
|
+
checks:
|
64
|
+
- path: /
|
65
|
+
rules:
|
66
|
+
except: sample.test
|
67
|
+
- path: /test
|
68
|
+
rules:
|
69
|
+
add: sample.test
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: querly
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.7.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Soutaro Matsumoto
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2017-
|
11
|
+
date: 2017-08-22 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -180,9 +180,13 @@ files:
|
|
180
180
|
- lib/querly/script.rb
|
181
181
|
- lib/querly/script_enumerator.rb
|
182
182
|
- lib/querly/version.rb
|
183
|
+
- manual/configuration.md
|
184
|
+
- manual/examples.md
|
185
|
+
- manual/patterns.md
|
183
186
|
- querly.gemspec
|
184
187
|
- rules/sample.yml
|
185
188
|
- sample.yaml
|
189
|
+
- template.yml
|
186
190
|
homepage: https://github.com/soutaro/querly
|
187
191
|
licenses: []
|
188
192
|
metadata: {}
|
@@ -202,7 +206,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
202
206
|
version: '0'
|
203
207
|
requirements: []
|
204
208
|
rubyforge_project:
|
205
|
-
rubygems_version: 2.6.
|
209
|
+
rubygems_version: 2.6.10
|
206
210
|
signing_key:
|
207
211
|
specification_version: 4
|
208
212
|
summary: Pattern Based Checking Tool for Ruby
|