outlaw 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +95 -22
- data/bin/outlaw +5 -4
- data/examples/.outlawed.example +3 -2
- data/lib/outlaw.rb +25 -11
- data/lib/outlaw/enforcement.rb +8 -8
- data/lib/outlaw/law_parser.rb +73 -0
- data/lib/outlaw/rule.rb +43 -11
- data/lib/outlaw/rule_methods.rb +8 -0
- data/lib/outlaw/version.rb +1 -1
- data/test/outlaw_test.rb +100 -100
- metadata +8 -7
- data/lib/outlaw/law_dsl.rb +0 -69
data/README.md
CHANGED
@@ -1,35 +1,108 @@
|
|
1
1
|
#Outlaw
|
2
2
|
|
3
3
|
##Keep bad code out of your projects. Your idea of bad code, no one elses.
|
4
|
+
##Because good documentation should be executable.
|
5
|
+
|
6
|
+
###NOTE: Outlaw can evaluate any version ruby code, *BUT* it runs on only 1.9 -- set your system ruby to 1.9 to use
|
4
7
|
|
5
8
|
### Part of MendicantUniversity.org S10 class, personal project.
|
6
|
-
From the included outlawed.rb example file for custom rule definition:
|
7
9
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
10
|
+
The current version of outlaw takes a user provided configuration file
|
11
|
+
(currently named simply '.outlawed' in the project directory or user's home
|
12
|
+
directory) and parses a series of method calls to the outlaw method (defined
|
13
|
+
within the Outlaw module namespace, but module_eval'd so you don't need to
|
14
|
+
namespace the file). You can also define constants in your .outlawed file as
|
15
|
+
collections of strings for use in your rules, but that will be addressed below.
|
16
|
+
|
17
|
+
Each call to the outlaw method consists of two string arguments, the first an
|
18
|
+
anti-pattern you wish to prohibit usage of in one or more projects, and the
|
19
|
+
second an explanation to be provided when the anti-pattern is detected.
|
20
|
+
|
21
|
+
### Syntax for rule creation:
|
22
|
+
|
23
|
+
Some examples are included in the .outlawed.example file for reference:
|
24
|
+
|
25
|
+
outlaw "@@", "Class variables are evil"
|
26
|
+
|
27
|
+
outlaw "protected", "use private or public, protected
|
28
|
+
is silly in ruby"
|
29
|
+
|
30
|
+
outlaw "eval", "never eval, rarely class_eval or
|
31
|
+
instance_eval, but never eval"
|
32
|
+
|
33
|
+
outlaw "module :token end", "nest modules to avoid empty module
|
34
|
+
declarations"
|
35
|
+
|
36
|
+
outlaw "class :symbol < :core_class", "core classes implemented in c,
|
37
|
+
can cause bad mojo"
|
38
|
+
|
39
|
+
outlaw :trailing_whitespace
|
40
|
+
|
41
|
+
|
42
|
+
The first three examples are actual ruby keywords and features being outlawed
|
43
|
+
and may not require much explanation except to indicate that they are detected
|
44
|
+
via regular expression matches constructed from the strings, and attempt to use
|
45
|
+
word boundaries intelligently so that eval is detected but not module_eval.
|
46
|
+
|
47
|
+
The bottom two examples above use ruby symbols as standin variable or
|
48
|
+
parameter names for identifiers that are matched at runtime with local
|
49
|
+
variables, instance variables, class names and constants that may appear
|
50
|
+
within the ruby program being analyzed. Here, :symbol can be any ruby symbol
|
51
|
+
if it appears only once, though if used multiple times it will only match the
|
52
|
+
the same identifier on subsequent usage. :core_class as used above is a
|
53
|
+
special case where Outlaw has internally defined a constant called CORE_CLASS
|
54
|
+
as a collection of string objects each containing the name of one of ruby's
|
55
|
+
core classes. You can define your own similar collections in the .outlawed
|
56
|
+
file (to be loaded from an external data file preferably, if more than a few
|
57
|
+
values) and then reference CONST_NAME as :const_name in your outlaw anti-
|
58
|
+
patterns as above. Presently mutliple references to the same collection
|
59
|
+
are independent, but if there is interest special handling could be added to
|
60
|
+
also match specific instances of a collection much like the symbol handling.
|
61
|
+
|
62
|
+
The last one is a custom rule that calls a method defined on the Outlaw
|
63
|
+
module called 'trailing_whitespace' which returns a Rule object. Any
|
64
|
+
such new methods may be defined in the .outlawed file (since it is
|
65
|
+
module_eval'd into the outlaw namespace) and then outawed the same way.
|
66
|
+
|
67
|
+
|
68
|
+
Outlaw currently ignores whitespace, parentheses and new lines, though I have
|
69
|
+
ideas to change this behavior dynamically in certain rules if desired.
|
70
|
+
|
71
|
+
Execute outlaw on your project from the root directory by simply entering
|
72
|
+
"outlaw" into your shell, or specify another directory to run
|
73
|
+
on with "outlaw /path/to/dir"
|
74
|
+
|
75
|
+
Before using outlaw in a project you should create a .outlawed file which
|
76
|
+
Outlaw will read rules from.
|
14
77
|
|
15
|
-
|
78
|
+
It comes with an example file (.outlawed.example) which is included in the
|
79
|
+
gem and will be loaded if no .outlawed file is found in current directory or
|
80
|
+
home directory, and will warn you to provide a real file (and provide
|
81
|
+
location of the sample file in your system from the gem installation).
|
16
82
|
|
17
|
-
|
83
|
+
###Planned features (unimplemented):
|
84
|
+
*Customize sensitivty , for instance whitespace is currently ignored, but
|
85
|
+
could enforce style conventions with some whitespace sensitive rules.
|
86
|
+
Also ignores parens, which might be required or prohibited in some
|
87
|
+
context.
|
88
|
+
*Specify AST-nodes of interest, and within them allow arbitrary amounts of
|
89
|
+
code with a :disjoint_code_seperator token.
|
18
90
|
|
19
|
-
|
20
|
-
|
91
|
+
This should allow, for instance, something like the following, which is not
|
92
|
+
currently possible to outlaw in a useful way:
|
21
93
|
|
22
|
-
|
94
|
+
outlaw ":conditional_branch
|
95
|
+
unless
|
96
|
+
:disjoint_code_seperator
|
97
|
+
else",
|
98
|
+
"If you write unless else and think it makes sense then you are a
|
99
|
+
cylon"
|
23
100
|
|
101
|
+
*Integrate Rails Best Practices gem, Reek gem, and perhaps others, so that individual issue
|
102
|
+
detections they provide can be added as rules in the outlawed file while
|
103
|
+
ignoring/not running other detection routines.
|
24
104
|
|
25
|
-
|
26
|
-
|
27
|
-
way :core_class is used above
|
105
|
+
*Automate optional integration with rake task and/or githooks for
|
106
|
+
enforcement/notification of rules in a project.
|
28
107
|
|
29
|
-
|
30
|
-
occur in the same file) by inserting a :disjoint_code_seperator token in
|
31
|
-
the outlawed sample definition. I don't have a good use case for this
|
32
|
-
at present though, and since it will be difficult to handle disjoint
|
33
|
-
code across different files this may not be worthwhile... but similar
|
34
|
-
special case symbol meanings may be useful, and suggestions or examples
|
35
|
-
are welcome.
|
108
|
+
*Specify classes of rules, such as log, warn and prevent for differing behavior regarding violations at runtime.
|
data/bin/outlaw
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
|
3
|
+
require_relative "../lib/outlaw"
|
4
4
|
if File.exists?(Dir.pwd + '/.outlawed')
|
5
|
-
|
5
|
+
File.open(Dir.pwd + "/.outlawed") {|f| Outlaw.module_eval(f.read)}
|
6
6
|
elsif File.exists?(Dir.home + '/.outlawed')
|
7
|
-
|
7
|
+
File.open(Dir.home + '/.outlawed') {|f| Outlaw.module_eval(f.read)}
|
8
8
|
else
|
9
9
|
puts "
|
10
10
|
***NOTICE:***
|
@@ -14,7 +14,8 @@ else
|
|
14
14
|
at pwd first for ./outlawed and then at ~/.outlawed and defaults to example
|
15
15
|
if neither is found\n\n"
|
16
16
|
|
17
|
-
|
17
|
+
File.open(File.dirname(File.dirname(`gem which outlaw`.chop)) +
|
18
|
+
'/examples/.outlawed.example') {|f| Outlaw.module_eval(f.read)}
|
18
19
|
end
|
19
20
|
|
20
21
|
puts Outlaw.enforce(ARGV[0] ? ARGV[0] : ".")
|
data/examples/.outlawed.example
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
outlaw "@@", "class variables are evil"
|
2
2
|
outlaw "protected", "use private or public, protected is silly in ruby"
|
3
3
|
outlaw "module :token end", "nest modules to avoid empty module declarations"
|
4
|
-
|
5
|
-
|
4
|
+
outlaw "eval", "never eval, rarely class_eval or instance_eval, but never eval"
|
5
|
+
outlaw "class :symbol < :core_class", "core classes implemented in c, can cause bad mojo"
|
6
|
+
outlaw :trailing_whitespace
|
data/lib/outlaw.rb
CHANGED
@@ -1,20 +1,34 @@
|
|
1
1
|
require 'ripper'
|
2
|
-
require_relative 'outlaw/
|
2
|
+
require_relative 'outlaw/law_parser'
|
3
3
|
require_relative 'outlaw/enforcement'
|
4
4
|
require_relative 'outlaw/rule'
|
5
|
+
require_relative 'outlaw/rule_methods'
|
6
|
+
|
5
7
|
|
6
|
-
def outlaw(restriction, message)
|
7
|
-
law = Outlaw::LawDSL.parse(restriction, message)
|
8
|
-
Outlaw::Enforcement.add(law)
|
9
|
-
end
|
10
8
|
|
11
9
|
module Outlaw
|
12
|
-
|
10
|
+
extend self
|
11
|
+
attr_accessor :ignore_types, :param_types
|
12
|
+
|
13
|
+
def outlaw(pattern, message=nil)
|
14
|
+
if pattern.kind_of?(String)
|
15
|
+
rule = Rule.new(pattern, message)
|
16
|
+
Outlaw::Enforcement.add(rule)
|
17
|
+
else
|
18
|
+
Outlaw::Enforcement.add(self.send(pattern))
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def enforce(dir=".")
|
13
23
|
Outlaw::Enforcement.process_directory(dir)
|
14
24
|
end
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
25
|
+
#these come from ripper's Lexer
|
26
|
+
self.param_types = [:on_const, :on_ident, :on_ivar, :on_cvar]
|
27
|
+
self.ignore_types = [:on_sp, :on_nl, :on_ignored_nl, :on_rparen, :on_lparen]
|
28
|
+
WHITESPACE = [:on_sp, :on_nl, :on_ignored_nl]
|
29
|
+
VERTICAL_WHITESPACE = [:on_nl, :on_ignored_nl]
|
30
|
+
SPECIAL_CASES = [:whitespace_sensitive, :vertical_whitespace_sensitive]
|
31
|
+
|
32
|
+
CORE_CLASSES_FILE = File.expand_path("../../data/core_classes.txt", __FILE__)
|
33
|
+
CORE_CLASS = File.readlines(CORE_CLASSES_FILE).map &:chomp
|
20
34
|
end
|
data/lib/outlaw/enforcement.rb
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
module Outlaw
|
2
2
|
class Enforcement
|
3
3
|
class << self
|
4
|
-
attr_reader :
|
5
|
-
def add(
|
6
|
-
@
|
7
|
-
@
|
4
|
+
attr_reader :rules
|
5
|
+
def add(rule)
|
6
|
+
@rules ||= []
|
7
|
+
@rules << rule
|
8
8
|
end
|
9
9
|
|
10
10
|
def process_directory(path)
|
@@ -21,10 +21,10 @@ module Outlaw
|
|
21
21
|
def handle(file)
|
22
22
|
if file.match(/.rb$/)
|
23
23
|
text = File.open(file) {|f| f.read}
|
24
|
-
|
25
|
-
if
|
26
|
-
puts "Outlaw Violation in file: #{file}\nRestriction
|
27
|
-
"#{
|
24
|
+
rules.each do |rule|
|
25
|
+
if rule.violation?(text)
|
26
|
+
puts "Outlaw Violation in file: #{file}\nRestriction:\n" +
|
27
|
+
"#{rule.pattern}\n#{rule.message}\n\n"
|
28
28
|
end
|
29
29
|
end
|
30
30
|
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Outlaw
|
2
|
+
module LawParser
|
3
|
+
extend self
|
4
|
+
def parse(restriction, rule)
|
5
|
+
tokens = restriction.split
|
6
|
+
parsed_restriction = []
|
7
|
+
tokens.each do |token|
|
8
|
+
case
|
9
|
+
when special_case?(string_to_sym(token))
|
10
|
+
handle_special(token, rule)
|
11
|
+
when multipart?(token) #this handles multi-token literals, Const.new etc
|
12
|
+
parsed_restriction += Ripper.lex(token)
|
13
|
+
.reduce([]){|array, tkn|
|
14
|
+
array << token_type_regex(tkn) }
|
15
|
+
when defined_collection?(token)
|
16
|
+
parsed_restriction << Outlaw.const_get(string_to_sym(token.upcase))
|
17
|
+
when parameter?(token)
|
18
|
+
parsed_restriction << string_to_sym(token)
|
19
|
+
else
|
20
|
+
parsed_restriction += build_regex(token)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
build_block(parsed_restriction)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def handle_special(token, rule)
|
29
|
+
rule.modifications ||= []
|
30
|
+
rule.modifications << string_to_sym(token)
|
31
|
+
end
|
32
|
+
|
33
|
+
def special_case?(token)
|
34
|
+
SPECIAL_CASES.include?(token)
|
35
|
+
end
|
36
|
+
|
37
|
+
def token_type_regex(token)
|
38
|
+
/#{token[2]}/
|
39
|
+
end
|
40
|
+
|
41
|
+
def parameter?(token)
|
42
|
+
token[0].chr == ':'
|
43
|
+
end
|
44
|
+
|
45
|
+
def defined_collection?(token)
|
46
|
+
parameter?(token) && Outlaw.const_defined?(string_to_sym(token.upcase))
|
47
|
+
end
|
48
|
+
|
49
|
+
def string_to_sym(str)
|
50
|
+
str[1..-1].to_sym
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_regex(token)
|
54
|
+
#fully expect this hack to come back & haunt me, but passes curr. examples
|
55
|
+
[/\A#{token}/]
|
56
|
+
end
|
57
|
+
|
58
|
+
def multipart?(token)
|
59
|
+
!parameter?(token) && Ripper.lex(token).count > 1
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_block(pattern)
|
63
|
+
->(file) do
|
64
|
+
program = Ripper.tokenize(file)
|
65
|
+
program.each_with_index do |token, index|
|
66
|
+
next unless token.match(pattern.first)
|
67
|
+
return true if Rule.test(program, index, pattern)
|
68
|
+
end
|
69
|
+
return false
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/outlaw/rule.rb
CHANGED
@@ -1,18 +1,50 @@
|
|
1
1
|
module Outlaw
|
2
2
|
class Rule
|
3
|
-
|
4
|
-
|
5
|
-
def initialize(
|
6
|
-
|
7
|
-
@message = message
|
8
|
-
@restriction = restriction
|
3
|
+
attr_reader :message, :pattern, :detection_block
|
4
|
+
attr_accessor :modifications
|
5
|
+
def initialize(pattern, message=nil, &detection_block)
|
6
|
+
@pattern = pattern
|
7
|
+
@message = message ? message : "Don't do this: #{pattern}"
|
9
8
|
@detection_block = detection_block
|
10
9
|
end
|
11
10
|
|
12
|
-
def
|
13
|
-
@detection_block.
|
11
|
+
def violation?(code)
|
12
|
+
@detection_block = LawParser.parse(pattern, self) if detection_block.nil?
|
13
|
+
detect_violation(code)
|
14
14
|
end
|
15
15
|
|
16
|
+
private
|
17
|
+
|
18
|
+
def detect_violation(code)
|
19
|
+
defaults = apply_modifications
|
20
|
+
result = detection_block.call(code)
|
21
|
+
apply_modifications(defaults)
|
22
|
+
result
|
23
|
+
end
|
24
|
+
|
25
|
+
def apply_modifications(restore=nil)
|
26
|
+
return nil if modifications.nil? && restore.nil?
|
27
|
+
default_ignores = Outlaw.ignore_types.clone
|
28
|
+
default_params = Outlaw.param_types.clone
|
29
|
+
if modifications.include?(:whitespace_sensitive)
|
30
|
+
WHITESPACE.each do |ws|
|
31
|
+
Outlaw.ignore_types.delete(ws)
|
32
|
+
Outlaw.param_types << ws
|
33
|
+
end
|
34
|
+
end
|
35
|
+
if modifications.include?(:vertical_whitespace_sensitive)
|
36
|
+
VERTICAL_WHITESPACE.each do |ws|
|
37
|
+
Outlaw.ignore_types.delete(ws)
|
38
|
+
Outlaw.param_types << ws
|
39
|
+
end
|
40
|
+
end
|
41
|
+
Outlaw.ignore_types = restore.first if restore
|
42
|
+
Outlaw.param_types = restore.last if restore
|
43
|
+
[default_ignores, default_params]
|
44
|
+
end
|
45
|
+
|
46
|
+
public
|
47
|
+
|
16
48
|
class << self
|
17
49
|
def test(program, start_index, pattern)
|
18
50
|
pattern_index = 0
|
@@ -20,8 +52,8 @@ module Outlaw
|
|
20
52
|
start_index.upto(program.length) do |index|
|
21
53
|
code = program[index]
|
22
54
|
part = pattern[pattern_index]
|
23
|
-
|
24
|
-
next if
|
55
|
+
return false if code.nil?
|
56
|
+
next if Outlaw.ignore_types.include?(token_type(code))
|
25
57
|
return false unless match_token?(code, part, params[part])
|
26
58
|
pattern_index +=1
|
27
59
|
return true if pattern_index >= pattern.length
|
@@ -64,7 +96,7 @@ module Outlaw
|
|
64
96
|
|
65
97
|
def param_type_equal(lex, param)
|
66
98
|
#for now just check if it's a variable type, not kw, ws or other token
|
67
|
-
|
99
|
+
Outlaw.param_types.include? lex
|
68
100
|
end
|
69
101
|
|
70
102
|
def token_type(code)
|
data/lib/outlaw/version.rb
CHANGED
data/test/outlaw_test.rb
CHANGED
@@ -1,59 +1,61 @@
|
|
1
1
|
require_relative 'test_helper'
|
2
2
|
|
3
3
|
module Outlaw
|
4
|
-
describe
|
4
|
+
describe LawParser do
|
5
5
|
it "returns a Rule which is called on code and returns true or false" do
|
6
6
|
end
|
7
7
|
|
8
8
|
before do
|
9
|
-
@okay_file =
|
10
|
-
class Whatever < Set
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
end
|
15
|
-
|
16
|
-
module WithContent
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
9
|
+
@okay_file = %{
|
10
|
+
class Whatever < Set
|
11
|
+
def okaything(here)
|
12
|
+
@here = here
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
module WithContent
|
17
|
+
def sumthin
|
18
|
+
class_eval('1 + 1')
|
19
|
+
end
|
20
|
+
|
21
|
+
def okishthing
|
22
|
+
end
|
23
|
+
end
|
24
|
+
}
|
23
25
|
end
|
24
26
|
|
25
27
|
|
26
28
|
|
27
29
|
it "correctly builds rule for class var" do
|
28
30
|
code1 = "@@"
|
29
|
-
rule1 =
|
31
|
+
rule1 = Rule.new(code1)
|
30
32
|
|
31
|
-
class_var_file =
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
class_var_file = %{
|
34
|
+
def badthing(here)
|
35
|
+
@@here = here
|
36
|
+
end
|
37
|
+
}
|
36
38
|
|
37
|
-
class_result = rule1.
|
38
|
-
result1a = rule1.
|
39
|
+
class_result = rule1.violation?(class_var_file)
|
40
|
+
result1a = rule1.violation?(@okay_file)
|
39
41
|
class_result .must_equal true
|
40
42
|
end
|
41
43
|
|
42
44
|
it "correctly builds rule for protected" do
|
43
45
|
code2 = "protected"
|
44
|
-
rule2 =
|
46
|
+
rule2 = Rule.new(code2)
|
45
47
|
|
46
|
-
protected_file =
|
47
|
-
class Whatever
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
end
|
53
|
-
|
48
|
+
protected_file = %{
|
49
|
+
class Whatever
|
50
|
+
protected
|
51
|
+
def not_really
|
52
|
+
:false
|
53
|
+
end
|
54
|
+
end
|
55
|
+
}
|
54
56
|
|
55
|
-
protected_result= rule2.
|
56
|
-
result2a = rule2.
|
57
|
+
protected_result= rule2.violation?(protected_file)
|
58
|
+
result2a = rule2.violation?(@okay_file)
|
57
59
|
|
58
60
|
protected_result.must_equal true
|
59
61
|
result2a .must_equal false
|
@@ -62,16 +64,16 @@ CODE
|
|
62
64
|
|
63
65
|
it "correctly builds rule for eval" do
|
64
66
|
code3 = "eval"
|
65
|
-
rule3 =
|
67
|
+
rule3 = Rule.new(code3)
|
66
68
|
|
67
|
-
eval_file =
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
69
|
+
eval_file = %{
|
70
|
+
def not_really
|
71
|
+
eval('1 + 1')
|
72
|
+
end
|
73
|
+
}
|
72
74
|
|
73
|
-
eval_result = rule3.
|
74
|
-
result3a = rule3.
|
75
|
+
eval_result = rule3.violation?(eval_file)
|
76
|
+
result3a = rule3.violation?(@okay_file)
|
75
77
|
|
76
78
|
eval_result.must_equal true
|
77
79
|
result3a.must_equal false
|
@@ -80,15 +82,15 @@ CODE
|
|
80
82
|
|
81
83
|
it "correctly builds rule for module" do
|
82
84
|
code4 = "module :name end"
|
83
|
-
rule4 =
|
85
|
+
rule4 = Rule.new(code4)
|
84
86
|
|
85
|
-
module_file =
|
86
|
-
module Thing
|
87
|
-
end
|
88
|
-
|
87
|
+
module_file = %{
|
88
|
+
module Thing
|
89
|
+
end
|
90
|
+
}
|
89
91
|
|
90
|
-
module_result = rule4.
|
91
|
-
result4a = rule4.
|
92
|
+
module_result = rule4.violation?(module_file)
|
93
|
+
result4a = rule4.violation?(@okay_file)
|
92
94
|
|
93
95
|
module_result.must_equal true
|
94
96
|
result4a.must_equal false
|
@@ -96,75 +98,73 @@ CODE
|
|
96
98
|
|
97
99
|
it "correctly builds rule for core" do
|
98
100
|
code5 = "class :symbol < :core_class"
|
99
|
-
rule5 =
|
100
|
-
|
101
|
-
core_file =
|
102
|
-
class Whatever < String
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
core_result = rule5.
|
109
|
-
result5a = rule5.
|
101
|
+
rule5 = Rule.new(code5)
|
102
|
+
|
103
|
+
core_file = %{
|
104
|
+
class Whatever < String
|
105
|
+
def badthing(here)
|
106
|
+
@here = here
|
107
|
+
end
|
108
|
+
end
|
109
|
+
}
|
110
|
+
core_result = rule5.violation?(core_file)
|
111
|
+
result5a = rule5.violation?(@okay_file)
|
110
112
|
core_result .must_equal true
|
111
113
|
result5a.must_equal false
|
112
114
|
end
|
113
115
|
|
114
|
-
|
115
|
-
# it "correctly builds rule for unless else" do
|
116
|
-
# code6 = "unless :symbols
|
117
|
-
# :disjoint_code_seperator
|
118
|
-
# else :more_symbols"
|
119
|
-
# rule6 = LawDSL.parse(code6)
|
120
|
-
|
121
|
-
# core_file = <<CODE
|
122
|
-
# class Whatever < String
|
123
|
-
# def badthing(here)
|
124
|
-
# @here = here
|
125
|
-
# end
|
126
|
-
# end
|
127
|
-
# CODE
|
128
|
-
|
129
116
|
it "correctly builds rule for rescue nil" do
|
130
117
|
code7 = "rescue nil"
|
131
|
-
rule7 =
|
132
|
-
|
133
|
-
nil_file =
|
134
|
-
begin
|
135
|
-
|
136
|
-
rescue nil
|
137
|
-
|
138
|
-
end
|
139
|
-
|
140
|
-
nil_result = rule7.
|
141
|
-
result7a = rule7.
|
118
|
+
rule7 = Rule.new(code7)
|
119
|
+
|
120
|
+
nil_file = %{
|
121
|
+
begin
|
122
|
+
"hi"
|
123
|
+
rescue nil
|
124
|
+
"bye"
|
125
|
+
end
|
126
|
+
}
|
127
|
+
nil_result = rule7.violation?(nil_file)
|
128
|
+
result7a = rule7.violation?(@okay_file)
|
142
129
|
nil_result .must_equal true
|
143
130
|
result7a.must_equal false
|
144
131
|
end
|
145
132
|
|
146
133
|
it "correctly builds rule for inherit struct.new" do
|
147
134
|
code8 = "class :symbol < Struct.new"
|
148
|
-
rule8 =
|
135
|
+
rule8 = Rule.new(code8)
|
149
136
|
|
150
|
-
struct_file =
|
151
|
-
class MyClass < Struct.new("Customer", :name, :address)
|
152
|
-
|
137
|
+
struct_file = %{
|
138
|
+
class MyClass < Struct.new("Customer", :name, :address)
|
139
|
+
}
|
153
140
|
|
154
|
-
struct_result = rule8.
|
155
|
-
result8a = rule8.
|
141
|
+
struct_result = rule8.violation?(struct_file)
|
142
|
+
result8a = rule8.violation?(@okay_file)
|
156
143
|
|
157
144
|
struct_result.must_equal true
|
158
145
|
result8a.must_equal false
|
159
146
|
end
|
160
147
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
148
|
+
it "correctly builds whitespace sensitive rule" do
|
149
|
+
code9 = ":vertical_whitespace_sensitive end\ndef"
|
150
|
+
rule9 = Rule.new(code9)
|
151
|
+
|
152
|
+
def_file = %{
|
153
|
+
class Mine
|
154
|
+
def method
|
155
|
+
true
|
156
|
+
end
|
157
|
+
def method2
|
158
|
+
false
|
159
|
+
end
|
160
|
+
end
|
161
|
+
}
|
162
|
+
|
163
|
+
def_result = rule9.violation?(def_file)
|
164
|
+
result9a = rule9.violation?(@okay_file)
|
165
|
+
|
166
|
+
def_result.must_equal true
|
167
|
+
result9a.must_equal false
|
168
168
|
end
|
169
169
|
|
170
170
|
it "returns a hash with key counts and nil placeholders" do
|
@@ -174,7 +174,7 @@ CODE
|
|
174
174
|
end
|
175
175
|
|
176
176
|
it "returns a block from build_block method" do
|
177
|
-
block =
|
177
|
+
block = LawParser.send(:build_block,"@@")
|
178
178
|
assert_kind_of Proc, block
|
179
179
|
end
|
180
180
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: outlaw
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.1.
|
4
|
+
version: 0.1.3
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,11 +9,11 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-02-11 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: rake
|
16
|
-
requirement: &
|
16
|
+
requirement: &20326092 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ~>
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: 0.9.0
|
22
22
|
type: :development
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *20326092
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: minitest
|
27
|
-
requirement: &
|
27
|
+
requirement: &20325792 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,7 +32,7 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :development
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *20325792
|
36
36
|
description: ! " Keep bad code out of your projects. Your idea of bad code, no
|
37
37
|
one elses.\n\n Outlaw defines an example based DSL for demonstrating anti-patterns
|
38
38
|
and\n builds a rule for each anti-pattern that it alerts the user to violations\n
|
@@ -56,8 +56,9 @@ files:
|
|
56
56
|
- examples/.outlawed.example
|
57
57
|
- lib/outlaw.rb
|
58
58
|
- lib/outlaw/enforcement.rb
|
59
|
-
- lib/outlaw/
|
59
|
+
- lib/outlaw/law_parser.rb
|
60
60
|
- lib/outlaw/rule.rb
|
61
|
+
- lib/outlaw/rule_methods.rb
|
61
62
|
- lib/outlaw/version.rb
|
62
63
|
- outlaw.gemspec
|
63
64
|
- test/outlaw_test.rb
|
data/lib/outlaw/law_dsl.rb
DELETED
@@ -1,69 +0,0 @@
|
|
1
|
-
module Outlaw
|
2
|
-
module LawDSL
|
3
|
-
class << self
|
4
|
-
def parse(restriction, message="")
|
5
|
-
tokens = restriction.split
|
6
|
-
parsed_restriction = []
|
7
|
-
tokens.each do |token|
|
8
|
-
case
|
9
|
-
when special_case?(token)
|
10
|
-
next #TODO - handle AST branches/disjoint code/meta-symbols to be defined
|
11
|
-
when multipart?(token) #this handles multi-token literals, Const.new etc
|
12
|
-
parsed_restriction += Ripper.lex(token)
|
13
|
-
.reduce([]){|array, tkn|
|
14
|
-
array << token_type_regex(tkn) }
|
15
|
-
when defined_collection?(token)
|
16
|
-
parsed_restriction << Outlaw.const_get(string_to_sym(token.upcase))
|
17
|
-
when parameter?(token)
|
18
|
-
parsed_restriction << string_to_sym(token)
|
19
|
-
else
|
20
|
-
parsed_restriction += build_regex(token)
|
21
|
-
end
|
22
|
-
end
|
23
|
-
Rule.new(message, restriction, &build_block(parsed_restriction))
|
24
|
-
end
|
25
|
-
|
26
|
-
private
|
27
|
-
|
28
|
-
def token_type_regex(token)
|
29
|
-
/#{token[2]}/
|
30
|
-
end
|
31
|
-
|
32
|
-
def parameter?(token)
|
33
|
-
token[0].chr == ':'
|
34
|
-
end
|
35
|
-
|
36
|
-
def special_case?(token)
|
37
|
-
SPECIAL_CASES.include? token
|
38
|
-
end
|
39
|
-
|
40
|
-
def defined_collection?(token)
|
41
|
-
parameter?(token) && Outlaw.const_defined?(string_to_sym(token.upcase))
|
42
|
-
end
|
43
|
-
|
44
|
-
def string_to_sym(str)
|
45
|
-
str[1..-1].to_sym
|
46
|
-
end
|
47
|
-
|
48
|
-
def build_regex(token)
|
49
|
-
#fully expect this hack to come back & haunt me, but passes curr. examples
|
50
|
-
[/\A#{token}/]
|
51
|
-
end
|
52
|
-
|
53
|
-
def multipart?(token)
|
54
|
-
!parameter?(token) && Ripper.lex(token).count > 1
|
55
|
-
end
|
56
|
-
|
57
|
-
def build_block(pattern)
|
58
|
-
lambda do |file|
|
59
|
-
program = Ripper.tokenize(file)
|
60
|
-
program.each_with_index do |token, index|
|
61
|
-
next unless token.match(pattern.first)
|
62
|
-
return true if Rule.test(program, index, pattern)
|
63
|
-
end
|
64
|
-
return false
|
65
|
-
end
|
66
|
-
end
|
67
|
-
end
|
68
|
-
end
|
69
|
-
end
|