From 80f1817ec850f44a80f7d10fafb79d45c184fab8 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 30 Jul 2024 18:50:55 -0400 Subject: [PATCH 1/2] Add `expect_syntax_error` spec helper --- spec_helper.rb | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/spec_helper.rb b/spec_helper.rb index af1c38587..34bc068a5 100644 --- a/spec_helper.rb +++ b/spec_helper.rb @@ -36,3 +36,7 @@ def report_on_exception=(value) ARGV.unshift $0 MSpecRun.main end + +def expect_syntax_error(ruby_src) + -> { eval(ruby_src) }.should raise_error(SyntaxError) +end From 2cc654628b1cb0cbf13c07b0153228d9a7ca4826 Mon Sep 17 00:00:00 2001 From: Alexander Momchilov Date: Tue, 30 Jul 2024 19:11:07 -0400 Subject: [PATCH 2/2] Add specs for reserved keywords --- language/reserved_keywords.rb | 155 ++++++++++++++++++++++++++++++++++ spec_helper.rb | 10 +++ 2 files changed, 165 insertions(+) create mode 100644 language/reserved_keywords.rb diff --git a/language/reserved_keywords.rb b/language/reserved_keywords.rb new file mode 100644 index 000000000..ca0f368a9 --- /dev/null +++ b/language/reserved_keywords.rb @@ -0,0 +1,155 @@ +require_relative '../spec_helper' + +describe "Ruby's reserved keywords" do + # Copied from Prism::Translation::Ripper + keywords = [ + "alias", + "and", + "begin", + "BEGIN", + "break", + "case", + "class", + "def", + "defined?", + "do", + "else", + "elsif", + "end", + "END", + "ensure", + "false", + "for", + "if", + "in", + "module", + "next", + "nil", + "not", + "or", + "redo", + "rescue", + "retry", + "return", + "self", + "super", + "then", + "true", + "undef", + "unless", + "until", + "when", + "while", + "yield", + "__ENCODING__", + "__FILE__", + "__LINE__" + ] + + keywords.each do |kw| + describe "keyword '#{kw}'" do + it "can't be used as local variable name" do + expect_syntax_error <<~RUBY + #{kw} = "a local variable named '#{kw}'" + RUBY + end + + invalid_ivar_names = ["defined?"] + + if invalid_ivar_names.include?(kw) + it "can't be used as an instance variable name" do + expect_syntax_error <<~RUBY + @#{kw} = "an instance variable named '#{kw}'" + RUBY + end + else + it "can be used as an instance variable name" do + result = sandboxed_eval <<~RUBY + @#{kw} = "an instance variable named '#{kw}'" + @#{kw} + RUBY + + result.should == "an instance variable named '#{kw}'" + end + end + + invalid_class_var_names = ["defined?"] + + if invalid_class_var_names.include?(kw) + it "can't be used as a class variable name" do + expect_syntax_error <<~RUBY + @@#{kw} = "a class variable named '#{kw}'" + RUBY + end + else + it "can be used as a class variable name" do + result = sandboxed_eval <<~RUBY + @@#{kw} = "a class variable named '#{kw}'" + @@#{kw} + RUBY + + result.should == "a class variable named '#{kw}'" + end + end + + invalid_global_var_names = ["defined?"] + + if invalid_global_var_names.include?(kw) + it "can't be used as a global variable name" do + expect_syntax_error <<~RUBY + $#{kw} = "a global variable named '#{kw}'" + RUBY + end + else + it "can be used as a global variable name" do + result = sandboxed_eval <<~RUBY + $#{kw} = "a global variable named '#{kw}'" + $#{kw} + RUBY + + result.should == "a global variable named '#{kw}'" + end + end + + it "can't be used as a positional parameter name" do + expect_syntax_error <<~RUBY + def x(#{kw}); end + RUBY + end + + invalid_kw_param_names = ["BEGIN","END","defined?"] + + if invalid_kw_param_names.include?(kw) + it "can't be used a keyword parameter name" do + expect_syntax_error <<~RUBY + def m(#{kw}:); end + RUBY + end + else + it "can be used a keyword parameter name" do + result = sandboxed_eval <<~RUBY + def m(#{kw}:) + binding.local_variable_get(:#{kw}) + end + + m(#{kw}: "an argument to '#{kw}'") + RUBY + + result.should == "an argument to '#{kw}'" + end + end + + it "can be used as a method name" do + result = sandboxed_eval <<~RUBY + def #{kw} + "a method named '#{kw}'" + end + + send(:#{kw}) + RUBY + + result.should == "a method named '#{kw}'" + end + end + end +end diff --git a/spec_helper.rb b/spec_helper.rb index 34bc068a5..21029ff8d 100644 --- a/spec_helper.rb +++ b/spec_helper.rb @@ -40,3 +40,13 @@ def report_on_exception=(value) def expect_syntax_error(ruby_src) -> { eval(ruby_src) }.should raise_error(SyntaxError) end + +# Evaluates the given Ruby source in a temporary Module, to prevent +# the surrounding context from being polluted with the new methods. +def sandboxed_eval(ruby_src) + Module.new do + # Allows instance methods defined by `ruby_src` to be called directly. + extend self + end + .class_eval(ruby_src) +end