outlaw 0.1.2 → 0.1.3
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.
- 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
|