This repository has been archived by the owner on Mar 16, 2023. It is now read-only.
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
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
Yorick Peterse
committed
Aug 5, 2014
1 parent
69ae36f
commit ccdf3fc
Showing
3 changed files
with
162 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |