This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

Analysis for loop keywords.

This analysis class checks if certain keywords are used inside blocks/loops or
not. If this is not the case an error is added.

Ruby reports this kind of usage as a syntax error. Parser doesn't handle this
and rightfully so. This requires keeping track of two things:

1. Are we in a block?
2. Are we in a loop that allows next/break?

While the code for this is not too difficult I don't feel this should be handled
on parser level, thus I've added it to a new analysis class.

This fixes #126.
  • Loading branch information...
YorickPeterse committed Aug 5, 2014
1 parent 69ae36f commit ccdf3fc918b06b47eac3fa07ee305a551ccf22d9
Showing with 162 additions and 0 deletions.
  1. +1 −0 lib/ruby-lint.rb
  2. +66 −0 lib/ruby-lint/analysis/loop_keywords.rb
  3. +95 −0 spec/ruby-lint/analysis/loop_keywords_spec.rb
@@ -67,6 +67,7 @@
require_relative 'ruby-lint/analysis/argument_amount'
require_relative 'ruby-lint/analysis/pedantics'
require_relative 'ruby-lint/analysis/useless_equality_checks'
require_relative 'ruby-lint/analysis/loop_keywords'

require_relative 'ruby-lint/report'
require_relative 'ruby-lint/report/entry'
@@ -0,0 +1,66 @@
module RubyLint
module Analysis
##
# Analysis class that checks if certain keywords are used inside a
# block/loop or not. For example, the following is not valid Ruby code:
#
# next
#
# But the following is valid:
#
# [10, 20].each do |n|
# next
# end
#
# The following isn't valid either:
#
# def foo
# next
# end
#
# See {KEYWORDS} for a list of the keywords that can only be used inside a
# loop.
#
class LoopKeywords < Base
register 'loop_keywords'

##
# List of keywords that can only be used inside a loop.
#
# @return [Array]
#
KEYWORDS = [:next, :break]

##
# List of statements that do allow the use of the various keywords.
#
# @return [Array]
#
STATEMENTS = [:while, :until, :for]

KEYWORDS.each do |kw|
define_method("on_#{kw}") { |node| verify_keyword(kw, node) }
end

STATEMENTS.each do |statement|
define_method("on_#{statement}") do
@allow_keyword = true
end

define_method("after_#{statement}") do
@allow_keyword = false
end
end

##
# @param [Symbol] keyword
# @param [RubyLint::AST::Node] node
#
def verify_keyword(keyword, node)
if current_scope.type != :block and !@allow_keyword
error("#{keyword} can only be used inside a loop/block", node)
end
end
end # BlockKeywords
end # Analysis
end # RubyLint
@@ -0,0 +1,95 @@
require 'spec_helper'

describe RubyLint::Analysis::LoopKeywords do
context 'next' do
example 'error when using next outside of a loop' do
report = build_report('next', described_class)
entries = report.entries

entries[0].message.should == 'next can only be used inside a loop/block'
entries[0].line.should == 1
entries[0].column.should == 1
end

example 'do not error when using next in a loop' do
code = '[].each { next }'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using next inside a while loop' do
code = 'while true; next; end'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using next inside an until loop' do
code = 'until true; next; end'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using next inside a for loop' do
code = 'for x in y; next; end'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using next inside a loop loop' do
code = 'loop { next }'
report = build_report(code, described_class)

report.entries.should be_empty
end
end

context 'break' do
example 'error when using break outside of a loop' do
report = build_report('break', described_class)
entries = report.entries

entries[0].message.should == 'break can only be used inside a loop/block'
entries[0].line.should == 1
entries[0].column.should == 1
end

example 'do not error when using break in a loop' do
code = '[].each { break }'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using break inside a while loop' do
code = 'while true; break; end'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using break inside an until loop' do
code = 'until true; break; end'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using break inside a for loop' do
code = 'for x in y; break; end'
report = build_report(code, described_class)

report.entries.should be_empty
end

example 'do not error when using break inside a loop loop' do
code = 'loop { break }'
report = build_report(code, described_class)

report.entries.should be_empty
end
end
end

0 comments on commit ccdf3fc

Please sign in to comment.