[ruby/prism] Use the diagnostic types in the parser translation layer

https://github.com/ruby/prism/commit/1a8a0063dc
This commit is contained in:
Kevin Newton 2024-03-06 14:49:25 -05:00
parent 48ca2ce5fc
commit d266b71467
5 changed files with 119 additions and 33 deletions

View File

@ -366,7 +366,8 @@ module Prism
# This represents an error that was encountered during parsing. # This represents an error that was encountered during parsing.
class ParseError class ParseError
# The type of error. # The type of error. This is an _internal_ symbol that is used for
# communicating with translation layers. It is not meant to be public API.
attr_reader :type attr_reader :type
# The message associated with this error. # The message associated with this error.
@ -399,7 +400,8 @@ module Prism
# This represents a warning that was encountered during parsing. # This represents a warning that was encountered during parsing.
class ParseWarning class ParseWarning
# The type of warning. # The type of warning. This is an _internal_ symbol that is used for
# communicating with translation layers. It is not meant to be public API.
attr_reader :type attr_reader :type
# The message associated with this warning. # The message associated with this warning.

View File

@ -9,11 +9,14 @@ module Prism
# the parser gem, and overrides the parse* methods to parse with prism and # the parser gem, and overrides the parse* methods to parse with prism and
# then translate. # then translate.
class Parser < ::Parser::Base class Parser < ::Parser::Base
Diagnostic = ::Parser::Diagnostic # :nodoc:
private_constant :Diagnostic
# The parser gem has a list of diagnostics with a hard-coded set of error # The parser gem has a list of diagnostics with a hard-coded set of error
# messages. We create our own diagnostic class in order to set our own # messages. We create our own diagnostic class in order to set our own
# error messages. # error messages.
class Diagnostic < ::Parser::Diagnostic class PrismDiagnostic < Diagnostic
# The message generated by prism. # This is the cached message coming from prism.
attr_reader :message attr_reader :message
# Initialize a new diagnostic with the given message and location. # Initialize a new diagnostic with the given message and location.
@ -112,20 +115,109 @@ module Prism
true true
end end
# Build a diagnostic from the given prism parse error.
def error_diagnostic(error, offset_cache)
location = error.location
diagnostic_location = build_range(location, offset_cache)
case error.type
when :argument_block_multi
Diagnostic.new(:error, :block_and_blockarg, {}, diagnostic_location, [])
when :argument_formal_constant
Diagnostic.new(:error, :formal_argument, {}, diagnostic_location, [])
when :argument_formal_class
Diagnostic.new(:error, :argument_cvar, {}, diagnostic_location, [])
when :argument_formal_global
Diagnostic.new(:error, :argument_gvar, {}, diagnostic_location, [])
when :argument_formal_ivar
Diagnostic.new(:error, :argument_ivar, {}, diagnostic_location, [])
when :argument_no_forwarding_amp
Diagnostic.new(:error, :no_anonymous_blockarg, {}, diagnostic_location, [])
when :argument_no_forwarding_star
Diagnostic.new(:error, :no_anonymous_restarg, {}, diagnostic_location, [])
when :begin_lonely_else
location = location.copy(length: 4)
diagnostic_location = build_range(location, offset_cache)
Diagnostic.new(:error, :useless_else, {}, diagnostic_location, [])
when :class_name, :module_name
Diagnostic.new(:error, :module_name_const, {}, diagnostic_location, [])
when :class_in_method
Diagnostic.new(:error, :class_in_def, {}, diagnostic_location, [])
when :def_endless_setter
Diagnostic.new(:error, :endless_setter, {}, diagnostic_location, [])
when :embdoc_term
Diagnostic.new(:error, :embedded_document, {}, diagnostic_location, [])
when :incomplete_variable_class, :incomplete_variable_class_3_3_0
location = location.copy(length: location.length + 1)
diagnostic_location = build_range(location, offset_cache)
Diagnostic.new(:error, :cvar_name, { name: location.slice }, diagnostic_location, [])
when :incomplete_variable_instance, :incomplete_variable_instance_3_3_0
location = location.copy(length: location.length + 1)
diagnostic_location = build_range(location, offset_cache)
Diagnostic.new(:error, :ivar_name, { name: location.slice }, diagnostic_location, [])
when :invalid_variable_global, :invalid_variable_global_3_3_0
Diagnostic.new(:error, :gvar_name, { name: location.slice }, diagnostic_location, [])
when :module_in_method
Diagnostic.new(:error, :module_in_def, {}, diagnostic_location, [])
when :numbered_parameter_ordinary
Diagnostic.new(:error, :ordinary_param_defined, {}, diagnostic_location, [])
when :numbered_parameter_outer_scope
Diagnostic.new(:error, :numparam_used_in_outer_scope, {}, diagnostic_location, [])
when :parameter_circular
Diagnostic.new(:error, :circular_argument_reference, { var_name: location.slice }, diagnostic_location, [])
when :parameter_name_repeat
Diagnostic.new(:error, :duplicate_argument, {}, diagnostic_location, [])
when :parameter_numbered_reserved
Diagnostic.new(:error, :reserved_for_numparam, { name: location.slice }, diagnostic_location, [])
when :singleton_for_literals
Diagnostic.new(:error, :singleton_literal, {}, diagnostic_location, [])
when :string_literal_eof
Diagnostic.new(:error, :string_eof, {}, diagnostic_location, [])
when :unexpected_token_ignore
Diagnostic.new(:error, :unexpected_token, { token: location.slice }, diagnostic_location, [])
when :write_target_in_method
Diagnostic.new(:error, :dynamic_const, {}, diagnostic_location, [])
else
PrismDiagnostic.new(error.message, :error, error.type, diagnostic_location)
end
end
# Build a diagnostic from the given prism parse warning.
def warning_diagnostic(warning, offset_cache)
diagnostic_location = build_range(warning.location, offset_cache)
case warning.type
when :ambiguous_first_argument_plus
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "+" }, diagnostic_location, [])
when :ambiguous_first_argument_minus
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "-" }, diagnostic_location, [])
when :ambiguous_prefix_star
Diagnostic.new(:warning, :ambiguous_prefix, { prefix: "*" }, diagnostic_location, [])
when :ambiguous_slash
Diagnostic.new(:warning, :ambiguous_regexp, {}, diagnostic_location, [])
when :dot_dot_dot_eol
Diagnostic.new(:warning, :triple_dot_at_eol, {}, diagnostic_location, [])
when :duplicated_hash_key
# skip, parser does this on its own
else
PrismDiagnostic.new(warning.message, :warning, warning.type, diagnostic_location)
end
end
# If there was a error generated during the parse, then raise an # If there was a error generated during the parse, then raise an
# appropriate syntax error. Otherwise return the result. # appropriate syntax error. Otherwise return the result.
def unwrap(result, offset_cache) def unwrap(result, offset_cache)
result.errors.each do |error| result.errors.each do |error|
next unless valid_error?(error) next unless valid_error?(error)
diagnostics.process(error_diagnostic(error, offset_cache))
location = build_range(error.location, offset_cache)
diagnostics.process(Diagnostic.new(error.message, :error, :prism_error, location))
end end
result.warnings.each do |warning| result.warnings.each do |warning|
next unless valid_warning?(warning) next unless valid_warning?(warning)
diagnostic = warning_diagnostic(warning, offset_cache)
location = build_range(warning.location, offset_cache) diagnostics.process(diagnostic) if diagnostic
diagnostics.process(Diagnostic.new(warning.message, :warning, :prism_warning, location))
end end
result result

View File

@ -213,9 +213,11 @@ module Prism
# Convert the prism tokens into the expected format for the parser gem. # Convert the prism tokens into the expected format for the parser gem.
def to_a def to_a
tokens = [] tokens = []
index = 0
while index < lexed.length index = 0
length = lexed.length
while index < length
token, state = lexed[index] token, state = lexed[index]
index += 1 index += 1
next if %i[IGNORED_NEWLINE __END__ EOF].include?(token.type) next if %i[IGNORED_NEWLINE __END__ EOF].include?(token.type)
@ -229,14 +231,18 @@ module Prism
value.delete_prefix!("?") value.delete_prefix!("?")
when :tCOMMENT when :tCOMMENT
if token.type == :EMBDOC_BEGIN if token.type == :EMBDOC_BEGIN
until (next_token = lexed[index][0]) && next_token.type == :EMBDOC_END start_index = index
while !((next_token = lexed[index][0]) && next_token.type == :EMBDOC_END) && (index < length - 1)
value += next_token.value value += next_token.value
index += 1 index += 1
end end
value += next_token.value if start_index != index
location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset]) value += next_token.value
index += 1 location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[lexed[index][0].location.end_offset])
index += 1
end
else else
value.chomp! value.chomp!
location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - 1]) location = Range.new(source_buffer, offset_cache[token.location.start_offset], offset_cache[token.location.end_offset - 1])

View File

@ -331,6 +331,9 @@ pm_diagnostic_id_human(pm_diagnostic_id_t diag_id) {
case PM_WARN_<%= warning.name %>: return "<%= warning.name.downcase %>"; case PM_WARN_<%= warning.name %>: return "<%= warning.name.downcase %>";
<%- end -%> <%- end -%>
} }
assert(false && "unreachable");
return "";
} }
static inline const char * static inline const char *

View File

@ -95,21 +95,6 @@ module Prism
end end
end end
def test_warnings
buffer = Parser::Source::Buffer.new("inline ruby with warning", 1)
buffer.source = "do_something *array"
parser = Prism::Translation::Parser33.new
parser.diagnostics.all_errors_are_fatal = false
warning = nil
parser.diagnostics.consumer = ->(received) { warning = received }
parser.parse(buffer)
assert_equal :warning, warning.level
assert_includes warning.message, "has been interpreted as"
end
private private
def assert_equal_parses(filepath, compare_tokens: true) def assert_equal_parses(filepath, compare_tokens: true)
@ -186,8 +171,6 @@ module Prism
actual_token[0] = expected_token[0] if %i[kDO_BLOCK kDO_LAMBDA].include?(expected_token[0]) actual_token[0] = expected_token[0] if %i[kDO_BLOCK kDO_LAMBDA].include?(expected_token[0])
when :tLPAREN when :tLPAREN
actual_token[0] = expected_token[0] if expected_token[0] == :tLPAREN2 actual_token[0] = expected_token[0] if expected_token[0] == :tLPAREN2
when :tLCURLY
actual_token[0] = expected_token[0] if %i[tLBRACE tLBRACE_ARG].include?(expected_token[0])
when :tPOW when :tPOW
actual_token[0] = expected_token[0] if expected_token[0] == :tDSTAR actual_token[0] = expected_token[0] if expected_token[0] == :tDSTAR
end end