diff --git a/lib/prism.rb b/lib/prism.rb index 909b71d66d..b9e615df6c 100644 --- a/lib/prism.rb +++ b/lib/prism.rb @@ -64,6 +64,22 @@ module Prism def self.load(source, serialized) Serialize.load(source, serialized) end + + # :call-seq: + # Prism::parse_failure?(source, **options) -> bool + # + # Returns true if the source is invalid Ruby code. + def self.parse_failure?(source, **options) + !parse_success?(source, **options) + end + + # :call-seq: + # Prism::parse_file_failure?(filepath, **options) -> bool + # + # Returns true if the file at filepath is invalid Ruby code. + def self.parse_file_failure?(filepath, **options) + !parse_file_success?(filepath, **options) + end end require_relative "prism/node" diff --git a/lib/prism/ffi.rb b/lib/prism/ffi.rb index 36f1c398de..8324f722a7 100644 --- a/lib/prism/ffi.rb +++ b/lib/prism/ffi.rb @@ -72,7 +72,8 @@ module Prism "pm_serialize_parse", "pm_serialize_parse_comments", "pm_serialize_lex", - "pm_serialize_parse_lex" + "pm_serialize_parse_lex", + "pm_parse_success_p" ) load_exported_functions_from( @@ -268,6 +269,18 @@ module Prism end end + # Mirror the Prism.parse_success? API by using the serialization API. + def parse_success?(code, **options) + LibRubyParser.pm_parse_success_p(code, code.bytesize, dump_options(options)) + end + + # Mirror the Prism.parse_file_success? API by using the serialization API. + def parse_file_success?(filepath, **options) + LibRubyParser::PrismString.with(filepath) do |string| + parse_success?(string.read, **options, filepath: filepath) + end + end + private # Convert the given options into a serialized options string. diff --git a/prism/extension.c b/prism/extension.c index cf3f62f6e6..b1b0d52dea 100644 --- a/prism/extension.c +++ b/prism/extension.c @@ -798,6 +798,64 @@ parse_lex_file(int argc, VALUE *argv, VALUE self) { return value; } +/** + * Parse the given input and return true if it parses without errors or + * warnings. + */ +static VALUE +parse_input_success_p(pm_string_t *input, const pm_options_t *options) { + pm_parser_t parser; + pm_parser_init(&parser, pm_string_source(input), pm_string_length(input), options); + + pm_node_t *node = pm_parse(&parser); + pm_node_destroy(&parser, node); + + VALUE result = parser.error_list.size == 0 && parser.warning_list.size == 0 ? Qtrue : Qfalse; + pm_parser_free(&parser); + + return result; +} + +/** + * call-seq: + * Prism::parse_success?(source, **options) -> Array + * + * Parse the given string and return true if it parses without errors or + * warnings. For supported options, see Prism::parse. + */ +static VALUE +parse_success_p(int argc, VALUE *argv, VALUE self) { + pm_string_t input; + pm_options_t options = { 0 }; + string_options(argc, argv, &input, &options); + + VALUE result = parse_input_success_p(&input, &options); + pm_string_free(&input); + pm_options_free(&options); + + return result; +} + +/** + * call-seq: + * Prism::parse_file_success?(filepath, **options) -> Array + * + * Parse the given file and return true if it parses without errors or warnings. + * For supported options, see Prism::parse. + */ +static VALUE +parse_file_success_p(int argc, VALUE *argv, VALUE self) { + pm_string_t input; + pm_options_t options = { 0 }; + if (!file_options(argc, argv, &input, &options)) return Qnil; + + VALUE result = parse_input_success_p(&input, &options); + pm_string_free(&input); + pm_options_free(&options); + + return result; +} + /******************************************************************************/ /* Utility functions exposed to make testing easier */ /******************************************************************************/ @@ -981,6 +1039,8 @@ Init_prism(void) { rb_define_singleton_method(rb_cPrism, "parse_file_comments", parse_file_comments, -1); rb_define_singleton_method(rb_cPrism, "parse_lex", parse_lex, -1); rb_define_singleton_method(rb_cPrism, "parse_lex_file", parse_lex_file, -1); + rb_define_singleton_method(rb_cPrism, "parse_success?", parse_success_p, -1); + rb_define_singleton_method(rb_cPrism, "parse_file_success?", parse_file_success_p, -1); // Next, the functions that will be called by the parser to perform various // internal tasks. We expose these to make them easier to test. diff --git a/prism/prism.h b/prism/prism.h index ab5811f9ac..590cd74016 100644 --- a/prism/prism.h +++ b/prism/prism.h @@ -152,6 +152,16 @@ PRISM_EXPORTED_FUNCTION void pm_serialize_lex(pm_buffer_t *buffer, const uint8_t */ PRISM_EXPORTED_FUNCTION void pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, const char *data); +/** + * Parse the source and return true if it parses without errors or warnings. + * + * @param source The source to parse. + * @param size The size of the source. + * @param data The optional data to pass to the parser. + * @return True if the source parses without errors or warnings. + */ +PRISM_EXPORTED_FUNCTION bool pm_parse_success_p(const uint8_t *source, size_t size, const char *data); + /** * Returns a string representation of the given token type. * diff --git a/prism/templates/src/serialize.c.erb b/prism/templates/src/serialize.c.erb index 60cb9ecb3d..8fa70ffb55 100644 --- a/prism/templates/src/serialize.c.erb +++ b/prism/templates/src/serialize.c.erb @@ -359,3 +359,24 @@ pm_serialize_parse_lex(pm_buffer_t *buffer, const uint8_t *source, size_t size, pm_parser_free(&parser); pm_options_free(&options); } + +/** + * Parse the source and return true if it parses without errors or warnings. + */ +PRISM_EXPORTED_FUNCTION bool +pm_parse_success_p(const uint8_t *source, size_t size, const char *data) { + pm_options_t options = { 0 }; + pm_options_read(&options, data); + + pm_parser_t parser; + pm_parser_init(&parser, source, size, &options); + + pm_node_t *node = pm_parse(&parser); + pm_node_destroy(&parser, node); + + bool result = parser.error_list.size == 0 && parser.warning_list.size == 0; + pm_parser_free(&parser); + pm_options_free(&options); + + return result; +} diff --git a/test/prism/ruby_api_test.rb b/test/prism/ruby_api_test.rb index 54c5fd28e9..b934c26ff4 100644 --- a/test/prism/ruby_api_test.rb +++ b/test/prism/ruby_api_test.rb @@ -20,6 +20,18 @@ module Prism assert_equal_nodes ast2, ast3 end + def test_parse_success? + assert Prism.parse_success?("1") + refute Prism.parse_success?("<>") + + assert Prism.parse_success?("m //", verbose: false) + refute Prism.parse_success?("m //", verbose: true) + end + + def test_parse_file_success? + assert Prism.parse_file_success?(__FILE__) + end + def test_options assert_equal "", Prism.parse("__FILE__").value.statements.body[0].filepath assert_equal "foo.rb", Prism.parse("__FILE__", filepath: "foo.rb").value.statements.body[0].filepath