skeptic 0.0.3 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.md ADDED
@@ -0,0 +1,12 @@
1
+ ## 0.0.4 (2013-11-14)
2
+
3
+ Features:
4
+
5
+ - Add a rule for whitespace around operators
6
+ - Add a rule for naming conventions (snake case, camel case, screaming snake case)
7
+ - Add a rule that allows only definitions of names containing valid English words
8
+ - Add a rule that detects the use of global variables
9
+
10
+ Bugfixes:
11
+
12
+ - Fix counting class methods in `--methods-per-class` rule
data/Gemfile CHANGED
@@ -14,4 +14,6 @@ group :development do
14
14
  gem "bundler"
15
15
  gem "jeweler"
16
16
  gem "simplecov"
17
+ gem "ffi-aspell"
17
18
  end
19
+
data/README.markdown ADDED
@@ -0,0 +1,22 @@
1
+ # skeptic
2
+
3
+ _Skeptic_ is a *very* experimental static code analyzer for Ruby 1.9. It points out annoying things in your Ruby code.
4
+
5
+ I am using it for a [Ruby course in the University of Sofia](http://fmi.ruby.bg/). All the assignments the students hand it should adhere to specific style, that is automatically checked with Skeptic. Assignments that fail to meet the critera are rejected outright. The main assumption is the "theory" of constraints - when one operates under constraints, one is more creative and ends up learning more.
6
+
7
+ You should probably not use it for anythin.
8
+
9
+ ## Rules
10
+
11
+ Skeptic checks if the code complies to a number of rules and reports the violations. Here is a list of the rules:
12
+
13
+ * **Valid syntax** - skeptic can check if your syntax is valid.
14
+ * **Line length** - line length does not exceed a certain number.
15
+ * **Lines per method** - methods are at most N lines long. Ignores empty lines and lines with `end`
16
+ * **Max nesting depth** - at most N levels of nesting. blocks, conditions and loops are considered a level of nesting.
17
+ * **No semicolons** - stops you from using semicolons as expression delimiters.
18
+ * **No trailing whitespace** - points out if your lines end on whitespace.
19
+
20
+ ## Copyright
21
+
22
+ Copyright (c) 2011 Stefan Kanev. See LICENSE.txt for further details.
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.4
@@ -0,0 +1,99 @@
1
+ require 'ffi/aspell'
2
+ require 'set'
3
+
4
+ module Skeptic
5
+ module Rules
6
+ class EnglishWordsForNames
7
+ DESCRIPTION = 'Detect if names contain non-English words'
8
+
9
+ include SexpVisitor
10
+
11
+ PERMANENT_WORD_WHITELIST = Set.new ['args', 'kwargs']
12
+
13
+ def initialize(whitelist = '')
14
+ @word_whitelist = PERMANENT_WORD_WHITELIST.merge whitelist.split
15
+ @violations = []
16
+ @aspell_speller = FFI::Aspell::Speller.new 'en_US'
17
+ end
18
+
19
+ def apply_to(code, tokens, sexp)
20
+ visit sexp
21
+ self
22
+ end
23
+
24
+ def violations
25
+ @violations.map do |name, line|
26
+ "#{name} on line #{line} is not in english"
27
+ end
28
+ end
29
+
30
+ def name
31
+ 'English words for names'
32
+ end
33
+
34
+ private
35
+
36
+ on :class, :module do |name, *, body|
37
+ check_ident name
38
+ visit body
39
+ end
40
+
41
+ on :def, :defs do |*, name, params, body|
42
+ check_ident name
43
+ visit params
44
+ visit body
45
+ end
46
+
47
+ on :lambda do |params, body|
48
+ visit params if params
49
+ visit body
50
+ end
51
+
52
+ on :do_block, :brace_block do |(_, params, _), body|
53
+ visit params if params
54
+ visit body
55
+ end
56
+
57
+ on :assign do |target, value|
58
+ check_ident target
59
+ visit value
60
+ end
61
+
62
+ on :params do |*params|
63
+ extract_param_idents(params).each do |param_ident|
64
+ check_ident param_ident
65
+ end
66
+ end
67
+
68
+ def check_ident(ident)
69
+ check_name(extract_name(ident), extract_line_number(ident))
70
+ end
71
+
72
+ def check_name(name, line_number)
73
+ words = split_name_to_words(name)
74
+
75
+ unless words.all? { |word| english_word? word }
76
+ @violations << [name, line_number]
77
+ end
78
+ end
79
+
80
+ def split_name_to_words(text)
81
+ if text.include? '_'
82
+ text.split '_'
83
+ else
84
+ text.scan(/((\A[^A-Z]+)|[A-Z][^A-Z]*)/).map &:first
85
+ end.map do |word|
86
+ strip_word_punctuation(word)
87
+ end.map(&:downcase).reject(&:empty?)
88
+ end
89
+
90
+ def strip_word_punctuation(word)
91
+ word.gsub(/[^[^[:ascii:]]a-zA-Z0-9_]/, '')
92
+ end
93
+
94
+ def english_word?(word)
95
+ @word_whitelist.include? word or @aspell_speller.correct? word
96
+ end
97
+ end
98
+ end
99
+ end
@@ -46,6 +46,18 @@ module Skeptic
46
46
  visit body
47
47
  end
48
48
 
49
+ on :defs do |target, _, name, params, body|
50
+ target_name = extract_name(target)
51
+ method_name = extract_name(name)
52
+ class_name = env[:class]
53
+
54
+ target_name = class_name if target_name == "self"
55
+ @methods[target_name] << method_name
56
+
57
+ visit params
58
+ visit body
59
+ end
60
+
49
61
  on :class do |name, parents, body|
50
62
  env.push :class => qualified_class_name(name)
51
63
 
@@ -0,0 +1,110 @@
1
+ module Skeptic
2
+ module Rules
3
+ class NamingConventions
4
+ DESCRIPTION = 'Check if the names of variables/methods/classes follow the convention'
5
+
6
+ include SexpVisitor
7
+
8
+ EXPECTED_CONVENTIONS = {
9
+ class: :camel_case,
10
+ module: :camel_case,
11
+ def: :snake_case,
12
+ defs: :snake_case,
13
+ symbol: :snake_case,
14
+ :@ident => :snake_case,
15
+ :@ivar => :snake_case,
16
+ :@cvar => :snake_case,
17
+ :@const => :screaming_snake_case
18
+ }
19
+
20
+ CONVENTION_EXAMPLES = {
21
+ camel_case: 'CamelCase',
22
+ snake_case: 'snake_case',
23
+ screaming_snake_case: 'SCREAMING_SNAKE_CASE',
24
+ }
25
+
26
+ CONVENTION_REGEXES = {
27
+ snake_case: /\A[a-z_][a-z_0-9]*\z/,
28
+ camel_case: /\A[A-Z][a-zA-Z0-9]*\z/,
29
+ screaming_snake_case: /\A[A-Z][A-Z_0-9]*\z/
30
+ }
31
+
32
+ NODE_NAMES = {
33
+ class: 'class',
34
+ module: 'module',
35
+ symbol: 'symbol',
36
+ def: 'method',
37
+ defs: 'method',
38
+ :@ident => 'local variable',
39
+ :@ivar => 'instance variable',
40
+ :@cvar => 'class variable',
41
+ :@const => 'constant'
42
+ }
43
+
44
+ def initialize(data)
45
+ @violations = []
46
+ end
47
+
48
+ def apply_to(code, tokens, sexp)
49
+ visit sexp
50
+ self
51
+ end
52
+
53
+ def violations
54
+ @violations.map do |type, name, line_number|
55
+ "#{NODE_NAMES[type]} named #{name} on line #{line_number}" +
56
+ " is not #{CONVENTION_EXAMPLES[EXPECTED_CONVENTIONS[type]]}"
57
+ end
58
+ end
59
+
60
+ def name
61
+ 'Detect bad naming'
62
+ end
63
+
64
+ private
65
+
66
+ on :class, :module, :def do |name, *args, body|
67
+ extracted_name = strip_name_suffix(extract_name(name))
68
+ check_name sexp_type, extracted_name, extract_line_number(name)
69
+ visit args.first if args.first
70
+ visit body
71
+ end
72
+
73
+ on :defs do |target, _, name, params, body|
74
+ if [target, name].any? do |ident|
75
+ bad_name? :defs, strip_name_suffix(extract_name(ident))
76
+ end
77
+ @violations << [:defs, "#{target}.#{name}", extract_line_number(name)]
78
+ end
79
+ visit params
80
+ visit body
81
+ end
82
+
83
+ on :symbol do |type, text, location|
84
+ check_name :symbol, text, location.first
85
+ end
86
+
87
+ on :@ident, :@ivar, :@cvar, :@const do |text, location|
88
+ check_name sexp_type, strip_name_prefix(text), location.first
89
+ end
90
+
91
+ def check_name(type, name, line)
92
+ if bad_name? type, name
93
+ @violations << [type, name, line]
94
+ end
95
+ end
96
+
97
+ def bad_name?(type, name)
98
+ !CONVENTION_REGEXES[EXPECTED_CONVENTIONS[type]].match(name)
99
+ end
100
+
101
+ def strip_name_suffix(name)
102
+ name.sub(/[!?]\z/, '')
103
+ end
104
+
105
+ def strip_name_prefix(name)
106
+ name.sub(/\A@{1,2}/, '')
107
+ end
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,35 @@
1
+ module Skeptic
2
+ module Rules
3
+ class NoGlobalVariables
4
+ DESCRIPTION = 'Do not allow the use of global variables'
5
+
6
+ include SexpVisitor
7
+
8
+ def initialize(enabled = false)
9
+ @enabled = enabled
10
+ @violations = []
11
+ end
12
+
13
+ def apply_to(code, tokens, sexp)
14
+ visit sexp
15
+ self
16
+ end
17
+
18
+ def violations
19
+ @violations.map do |variable, line|
20
+ "You have a global variable #{variable} on line #{line}"
21
+ end
22
+ end
23
+
24
+ def name
25
+ 'No global variables'
26
+ end
27
+
28
+ private
29
+
30
+ on :@gvar do |variable, location|
31
+ @violations << [variable, location.first]
32
+ end
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,46 @@
1
+ module Skeptic
2
+ module Rules
3
+ class SpacesAroundOperators
4
+ DESCRIPTION = 'Check for spaces around operators'
5
+
6
+ OPERATORS_WITHOUT_SPACES_AROUND_THEM = ['**']
7
+
8
+ def initialize(data)
9
+ @violations = []
10
+ end
11
+
12
+ def apply_to(code, tokens, sexp)
13
+ @violations = tokens.each_cons(3).select do |_, token, _|
14
+ operator_expecting_spaces? token
15
+ end.select do |left, operator, right|
16
+ no_spaces_between?(operator, left) or
17
+ no_spaces_between?(operator, right)
18
+ end.map do |_, operator, _|
19
+ [operator.last, operator.first[0]]
20
+ end
21
+ self
22
+ end
23
+
24
+ def violations
25
+ @violations.map do |value, line_number|
26
+ "no spaces around #{value} on line #{line_number}"
27
+ end
28
+ end
29
+
30
+ def name
31
+ 'Spaces around operators'
32
+ end
33
+
34
+ private
35
+
36
+ def operator_expecting_spaces?(token)
37
+ token[1] == :on_op and
38
+ not OPERATORS_WITHOUT_SPACES_AROUND_THEM.include? token.last
39
+ end
40
+
41
+ def no_spaces_between?(operator, neighbour)
42
+ neighbour.first[0] == operator.first[0] and neighbour[1] != :on_sp
43
+ end
44
+ end
45
+ end
46
+ end
data/lib/skeptic/rules.rb CHANGED
@@ -5,11 +5,15 @@ module Skeptic
5
5
  end
6
6
 
7
7
  table.register CheckSyntax, :boolean
8
+ table.register EnglishWordsForNames, :string
8
9
  table.register LineLength, :int
9
10
  table.register LinesPerMethod, :int
10
11
  table.register MaxNestingDepth, :int
11
12
  table.register MethodsPerClass, :int
13
+ table.register NamingConventions, :boolean
12
14
  table.register NoSemicolons, :boolean
15
+ table.register NoGlobalVariables, :boolean
13
16
  table.register NoTrailingWhitespace, :boolean
17
+ table.register SpacesAroundOperators, :boolean
14
18
  end
15
19
  end
@@ -25,7 +25,7 @@ module Skeptic
25
25
  type, *args = *sexp
26
26
  handler = self.class.handlers[type]
27
27
 
28
- with_sexp_type(type) { instance_exec(*args, &handler) }
28
+ with_sexp_type(type) { instance_exec *args, &handler }
29
29
  else
30
30
  range = sexp[0].kind_of?(Symbol) ? 1..-1 : 0..-1
31
31
 
@@ -52,14 +52,42 @@ module Skeptic
52
52
  def extract_name(tree)
53
53
  type, first, second = *tree
54
54
  case type
55
- when :const_path_ref then "#{extract_name(first)}::#{extract_name(second)}"
56
- when :const_ref then extract_name(first)
57
- when :var_ref then extract_name(first)
58
- when :@const then first
59
- when :@ident then first
60
- when :@kw then first
61
- when :@op then first
62
- else '<unknown>'
55
+ when :const_path_ref
56
+ "#{extract_name(first)}::#{extract_name(second)}"
57
+ when :const_ref, :var_ref, :var_field, :field, :aref_field, :blockarg
58
+ extract_name(first)
59
+ when :@const, :@ident, :@label, :@kw, :@op, :@ivar, :@cvar, :@gvar
60
+ first
61
+ else
62
+ '<unknown>'
63
+ end
64
+ end
65
+
66
+ def extract_line_number(tree)
67
+ type, first, second = *tree
68
+ case type
69
+ when :const_path_ref, :const_ref, :var_ref, :var_field
70
+ extract_line_number(first)
71
+ when :@const, :@op, :@ident, :@ivar, :cvar, :@gvar, :@label
72
+ second.first
73
+ else
74
+ 0
75
+ end
76
+ end
77
+
78
+ def extract_param_idents(tree)
79
+ type, first, second = *tree
80
+ case type
81
+ when :params, :mlhs_add_star
82
+ tree[1..-1].compact.map { |node| extract_param_idents node }.reduce [], :+
83
+ when :mlhs_paren, :blockarg, :rest_param
84
+ extract_param_idents first
85
+ when :@ident, :@label
86
+ [tree]
87
+ when Symbol
88
+ []
89
+ else
90
+ tree.compact.map { |node| extract_param_idents node }.reduce [], :+
63
91
  end
64
92
  end
65
93
  end
data/lib/skeptic.rb CHANGED
@@ -11,6 +11,10 @@ require 'skeptic/rules/lines_per_method'
11
11
  require 'skeptic/rules/no_semicolons'
12
12
  require 'skeptic/rules/line_length'
13
13
  require 'skeptic/rules/no_trailing_whitespace'
14
+ require 'skeptic/rules/english_words_for_names'
15
+ require 'skeptic/rules/naming_conventions'
16
+ require 'skeptic/rules/no_global_variables'
17
+ require 'skeptic/rules/spaces_around_operators'
14
18
 
15
19
  require 'skeptic/rule_table'
16
20
  require 'skeptic/rules'
data/skeptic.gemspec CHANGED
@@ -5,25 +5,26 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = "skeptic"
8
- s.version = "0.0.3"
8
+ s.version = "0.0.4"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Stefan Kanev"]
12
- s.date = "2012-10-25"
12
+ s.date = "2013-11-15"
13
13
  s.description = "An experimental, half-assed, bug-ridden and highly opinionated code analyzer."
14
14
  s.email = "stefan.kanev@gmail.com"
15
15
  s.executables = ["skeptic"]
16
16
  s.extra_rdoc_files = [
17
17
  "LICENSE.txt",
18
- "README.rdoc"
18
+ "README.markdown"
19
19
  ]
20
20
  s.files = [
21
21
  ".document",
22
22
  ".rspec",
23
23
  ".rvmrc",
24
+ "CHANGELOG.md",
24
25
  "Gemfile",
25
26
  "LICENSE.txt",
26
- "README.rdoc",
27
+ "README.markdown",
27
28
  "Rakefile",
28
29
  "VERSION",
29
30
  "bin/skeptic",
@@ -34,12 +35,16 @@ Gem::Specification.new do |s|
34
35
  "lib/skeptic/rule_table.rb",
35
36
  "lib/skeptic/rules.rb",
36
37
  "lib/skeptic/rules/check_syntax.rb",
38
+ "lib/skeptic/rules/english_words_for_names.rb",
37
39
  "lib/skeptic/rules/line_length.rb",
38
40
  "lib/skeptic/rules/lines_per_method.rb",
39
41
  "lib/skeptic/rules/max_nesting_depth.rb",
40
42
  "lib/skeptic/rules/methods_per_class.rb",
43
+ "lib/skeptic/rules/naming_conventions.rb",
44
+ "lib/skeptic/rules/no_global_variables.rb",
41
45
  "lib/skeptic/rules/no_semicolons.rb",
42
46
  "lib/skeptic/rules/no_trailing_whitespace.rb",
47
+ "lib/skeptic/rules/spaces_around_operators.rb",
43
48
  "lib/skeptic/scope.rb",
44
49
  "lib/skeptic/sexp_visitor.rb",
45
50
  "skeptic.gemspec"
@@ -47,7 +52,7 @@ Gem::Specification.new do |s|
47
52
  s.homepage = "http://github.com/skanev/skeptic"
48
53
  s.licenses = ["MIT"]
49
54
  s.require_paths = ["lib"]
50
- s.rubygems_version = "1.8.24"
55
+ s.rubygems_version = "1.8.23"
51
56
  s.summary = "Skeptic points out annoying things in your code"
52
57
 
53
58
  if s.respond_to? :specification_version then
@@ -61,6 +66,7 @@ Gem::Specification.new do |s|
61
66
  s.add_development_dependency(%q<bundler>, [">= 0"])
62
67
  s.add_development_dependency(%q<jeweler>, [">= 0"])
63
68
  s.add_development_dependency(%q<simplecov>, [">= 0"])
69
+ s.add_development_dependency(%q<ffi-aspell>, [">= 0"])
64
70
  else
65
71
  s.add_dependency(%q<trollop>, [">= 1.16.2"])
66
72
  s.add_dependency(%q<rspec>, [">= 0"])
@@ -69,6 +75,7 @@ Gem::Specification.new do |s|
69
75
  s.add_dependency(%q<bundler>, [">= 0"])
70
76
  s.add_dependency(%q<jeweler>, [">= 0"])
71
77
  s.add_dependency(%q<simplecov>, [">= 0"])
78
+ s.add_dependency(%q<ffi-aspell>, [">= 0"])
72
79
  end
73
80
  else
74
81
  s.add_dependency(%q<trollop>, [">= 1.16.2"])
@@ -78,6 +85,7 @@ Gem::Specification.new do |s|
78
85
  s.add_dependency(%q<bundler>, [">= 0"])
79
86
  s.add_dependency(%q<jeweler>, [">= 0"])
80
87
  s.add_dependency(%q<simplecov>, [">= 0"])
88
+ s.add_dependency(%q<ffi-aspell>, [">= 0"])
81
89
  end
82
90
  end
83
91
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: skeptic
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-25 00:00:00.000000000 Z
12
+ date: 2013-11-15 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: trollop
@@ -123,6 +123,22 @@ dependencies:
123
123
  - - ! '>='
124
124
  - !ruby/object:Gem::Version
125
125
  version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: ffi-aspell
128
+ requirement: !ruby/object:Gem::Requirement
129
+ none: false
130
+ requirements:
131
+ - - ! '>='
132
+ - !ruby/object:Gem::Version
133
+ version: '0'
134
+ type: :development
135
+ prerelease: false
136
+ version_requirements: !ruby/object:Gem::Requirement
137
+ none: false
138
+ requirements:
139
+ - - ! '>='
140
+ - !ruby/object:Gem::Version
141
+ version: '0'
126
142
  description: An experimental, half-assed, bug-ridden and highly opinionated code analyzer.
127
143
  email: stefan.kanev@gmail.com
128
144
  executables:
@@ -130,14 +146,15 @@ executables:
130
146
  extensions: []
131
147
  extra_rdoc_files:
132
148
  - LICENSE.txt
133
- - README.rdoc
149
+ - README.markdown
134
150
  files:
135
151
  - .document
136
152
  - .rspec
137
153
  - .rvmrc
154
+ - CHANGELOG.md
138
155
  - Gemfile
139
156
  - LICENSE.txt
140
- - README.rdoc
157
+ - README.markdown
141
158
  - Rakefile
142
159
  - VERSION
143
160
  - bin/skeptic
@@ -148,12 +165,16 @@ files:
148
165
  - lib/skeptic/rule_table.rb
149
166
  - lib/skeptic/rules.rb
150
167
  - lib/skeptic/rules/check_syntax.rb
168
+ - lib/skeptic/rules/english_words_for_names.rb
151
169
  - lib/skeptic/rules/line_length.rb
152
170
  - lib/skeptic/rules/lines_per_method.rb
153
171
  - lib/skeptic/rules/max_nesting_depth.rb
154
172
  - lib/skeptic/rules/methods_per_class.rb
173
+ - lib/skeptic/rules/naming_conventions.rb
174
+ - lib/skeptic/rules/no_global_variables.rb
155
175
  - lib/skeptic/rules/no_semicolons.rb
156
176
  - lib/skeptic/rules/no_trailing_whitespace.rb
177
+ - lib/skeptic/rules/spaces_around_operators.rb
157
178
  - lib/skeptic/scope.rb
158
179
  - lib/skeptic/sexp_visitor.rb
159
180
  - skeptic.gemspec
@@ -172,7 +193,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
172
193
  version: '0'
173
194
  segments:
174
195
  - 0
175
- hash: -47347213712121151
196
+ hash: -3902018802479636628
176
197
  required_rubygems_version: !ruby/object:Gem::Requirement
177
198
  none: false
178
199
  requirements:
@@ -181,7 +202,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
181
202
  version: '0'
182
203
  requirements: []
183
204
  rubyforge_project:
184
- rubygems_version: 1.8.24
205
+ rubygems_version: 1.8.23
185
206
  signing_key:
186
207
  specification_version: 3
187
208
  summary: Skeptic points out annoying things in your code
data/README.rdoc DELETED
@@ -1,21 +0,0 @@
1
- = skeptic
2
-
3
- An **very** experimental static Ruby 1.9 analyzer to point out annoying things in your code. You
4
- should probably not use it for anything.
5
-
6
- Writing it is fun, though.
7
-
8
- == Contributing to skeptic
9
-
10
- * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
11
- * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
12
- * Fork the project
13
- * Start a feature/bugfix branch
14
- * Commit and push until you are happy with your contribution
15
- * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
16
- * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
17
-
18
- == Copyright
19
-
20
- Copyright (c) 2011 Stefan Kanev. See LICENSE.txt for
21
- further details.