зеркало из https://github.com/github/ruby.git
[ruby/prism] Remove error formatting, put directly in CRuby
https://github.com/ruby/prism/commit/53b2866487
This commit is contained in:
Родитель
653652bcbe
Коммит
79001c8b4a
|
@ -1127,36 +1127,6 @@ inspect_node(VALUE self, VALUE source) {
|
|||
|
||||
#endif
|
||||
|
||||
/**
|
||||
* call-seq:
|
||||
* Debug::format_errors(source, colorize) -> String
|
||||
*
|
||||
* Format the errors that are found when parsing the given source string.
|
||||
*/
|
||||
static VALUE
|
||||
format_errors(VALUE self, VALUE source, VALUE colorize) {
|
||||
pm_string_t input;
|
||||
input_load_string(&input, source);
|
||||
|
||||
pm_parser_t parser;
|
||||
pm_parser_init(&parser, pm_string_source(&input), pm_string_length(&input), NULL);
|
||||
|
||||
pm_node_t *node = pm_parse(&parser);
|
||||
pm_buffer_t buffer = { 0 };
|
||||
|
||||
pm_parser_errors_format(&parser, &parser.error_list, &buffer, RTEST(colorize), true);
|
||||
|
||||
rb_encoding *encoding = rb_enc_find(parser.encoding->name);
|
||||
VALUE result = rb_enc_str_new(pm_buffer_value(&buffer), pm_buffer_length(&buffer), encoding);
|
||||
|
||||
pm_buffer_free(&buffer);
|
||||
pm_node_destroy(&parser, node);
|
||||
pm_parser_free(&parser);
|
||||
pm_string_free(&input);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* call-seq: Debug::Encoding.all -> Array[Debug::Encoding]
|
||||
*
|
||||
|
@ -1309,7 +1279,6 @@ Init_prism(void) {
|
|||
// Next, the functions that will be called by the parser to perform various
|
||||
// internal tasks. We expose these to make them easier to test.
|
||||
VALUE rb_cPrismDebug = rb_define_module_under(rb_cPrism, "Debug");
|
||||
rb_define_singleton_method(rb_cPrismDebug, "format_errors", format_errors, 2);
|
||||
|
||||
#ifndef PRISM_EXCLUDE_PRETTYPRINT
|
||||
rb_define_singleton_method(rb_cPrismDebug, "inspect_node", inspect_node, 1);
|
||||
|
|
362
prism/prism.c
362
prism/prism.c
|
@ -21675,365 +21675,3 @@ pm_serialize_parse_comments(pm_buffer_t *buffer, const uint8_t *source, size_t s
|
|||
}
|
||||
|
||||
#endif
|
||||
|
||||
/** An error that is going to be formatted into the output. */
|
||||
typedef struct {
|
||||
/** A pointer to the diagnostic that was generated during parsing. */
|
||||
pm_diagnostic_t *error;
|
||||
|
||||
/** The start line of the diagnostic message. */
|
||||
int32_t line;
|
||||
|
||||
/** The column start of the diagnostic message. */
|
||||
uint32_t column_start;
|
||||
|
||||
/** The column end of the diagnostic message. */
|
||||
uint32_t column_end;
|
||||
} pm_error_t;
|
||||
|
||||
/** The format that will be used to format the errors into the output. */
|
||||
typedef struct {
|
||||
/** The prefix that will be used for line numbers. */
|
||||
const char *number_prefix;
|
||||
|
||||
/** The prefix that will be used for blank lines. */
|
||||
const char *blank_prefix;
|
||||
|
||||
/** The divider that will be used between sections of source code. */
|
||||
const char *divider;
|
||||
|
||||
/** The length of the blank prefix. */
|
||||
size_t blank_prefix_length;
|
||||
|
||||
/** The length of the divider. */
|
||||
size_t divider_length;
|
||||
} pm_error_format_t;
|
||||
|
||||
#define PM_COLOR_GRAY "\033[38;5;102m"
|
||||
#define PM_COLOR_RED "\033[1;31m"
|
||||
#define PM_COLOR_RESET "\033[m"
|
||||
#define PM_ERROR_TRUNCATE 30
|
||||
|
||||
static inline pm_error_t *
|
||||
pm_parser_errors_format_sort(const pm_parser_t *parser, const pm_list_t *error_list, const pm_newline_list_t *newline_list) {
|
||||
pm_error_t *errors = xcalloc(error_list->size, sizeof(pm_error_t));
|
||||
if (errors == NULL) return NULL;
|
||||
|
||||
int32_t start_line = parser->start_line;
|
||||
for (pm_diagnostic_t *error = (pm_diagnostic_t *) error_list->head; error != NULL; error = (pm_diagnostic_t *) error->node.next) {
|
||||
pm_line_column_t start = pm_newline_list_line_column(newline_list, error->location.start, start_line);
|
||||
pm_line_column_t end = pm_newline_list_line_column(newline_list, error->location.end, start_line);
|
||||
|
||||
// We're going to insert this error into the array in sorted order. We
|
||||
// do this by finding the first error that has a line number greater
|
||||
// than the current error and then inserting the current error before
|
||||
// that one.
|
||||
size_t index = 0;
|
||||
while (
|
||||
(index < error_list->size) &&
|
||||
(errors[index].error != NULL) &&
|
||||
(
|
||||
(errors[index].line < start.line) ||
|
||||
((errors[index].line == start.line) && (errors[index].column_start < start.column))
|
||||
)
|
||||
) index++;
|
||||
|
||||
// Now we're going to shift all of the errors after this one down one
|
||||
// index to make room for the new error.
|
||||
if (index + 1 < error_list->size) {
|
||||
memmove(&errors[index + 1], &errors[index], sizeof(pm_error_t) * (error_list->size - index - 1));
|
||||
}
|
||||
|
||||
// Finally, we'll insert the error into the array.
|
||||
uint32_t column_end;
|
||||
if (start.line == end.line) {
|
||||
column_end = end.column;
|
||||
} else {
|
||||
column_end = (uint32_t) (newline_list->offsets[start.line - start_line + 1] - newline_list->offsets[start.line - start_line] - 1);
|
||||
}
|
||||
|
||||
// Ensure we have at least one column of error.
|
||||
if (start.column == column_end) column_end++;
|
||||
|
||||
errors[index] = (pm_error_t) {
|
||||
.error = error,
|
||||
.line = start.line,
|
||||
.column_start = start.column,
|
||||
.column_end = column_end
|
||||
};
|
||||
}
|
||||
|
||||
return errors;
|
||||
}
|
||||
|
||||
static inline void
|
||||
pm_parser_errors_format_line(const pm_parser_t *parser, const pm_newline_list_t *newline_list, const char *number_prefix, int32_t line, uint32_t column_start, uint32_t column_end, pm_buffer_t *buffer) {
|
||||
int32_t line_delta = line - parser->start_line;
|
||||
assert(line_delta >= 0);
|
||||
|
||||
size_t index = (size_t) line_delta;
|
||||
assert(index < newline_list->size);
|
||||
|
||||
const uint8_t *start = &parser->start[newline_list->offsets[index]];
|
||||
const uint8_t *end;
|
||||
|
||||
if (index >= newline_list->size - 1) {
|
||||
end = parser->end;
|
||||
} else {
|
||||
end = &parser->start[newline_list->offsets[index + 1]];
|
||||
}
|
||||
|
||||
pm_buffer_append_format(buffer, number_prefix, line);
|
||||
|
||||
// Here we determine if we should truncate the end of the line.
|
||||
bool truncate_end = false;
|
||||
if ((column_end != 0) && ((end - (start + column_end)) >= PM_ERROR_TRUNCATE)) {
|
||||
end = start + column_end + PM_ERROR_TRUNCATE;
|
||||
truncate_end = true;
|
||||
}
|
||||
|
||||
// Here we determine if we should truncate the start of the line.
|
||||
if (column_start >= PM_ERROR_TRUNCATE) {
|
||||
pm_buffer_append_string(buffer, "... ", 4);
|
||||
start += column_start;
|
||||
}
|
||||
|
||||
pm_buffer_append_string(buffer, (const char *) start, (size_t) (end - start));
|
||||
|
||||
if (truncate_end) {
|
||||
pm_buffer_append_string(buffer, " ...\n", 5);
|
||||
} else if (end == parser->end && end[-1] != '\n') {
|
||||
pm_buffer_append_string(buffer, "\n", 1);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Format the errors on the parser into the given buffer.
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION void
|
||||
pm_parser_errors_format(const pm_parser_t *parser, const pm_list_t *error_list, pm_buffer_t *buffer, bool colorize, bool inline_messages) {
|
||||
assert(error_list->size != 0);
|
||||
|
||||
// First, we're going to sort all of the errors by line number using an
|
||||
// insertion sort into a newly allocated array.
|
||||
const int32_t start_line = parser->start_line;
|
||||
const pm_newline_list_t *newline_list = &parser->newline_list;
|
||||
|
||||
pm_error_t *errors = pm_parser_errors_format_sort(parser, error_list, newline_list);
|
||||
if (errors == NULL) return;
|
||||
|
||||
// Now we're going to determine how we're going to format line numbers and
|
||||
// blank lines based on the maximum number of digits in the line numbers
|
||||
// that are going to be displaid.
|
||||
pm_error_format_t error_format;
|
||||
int32_t first_line_number = errors[0].line;
|
||||
int32_t last_line_number = errors[error_list->size - 1].line;
|
||||
|
||||
// If we have a maximum line number that is negative, then we're going to
|
||||
// use the absolute value for comparison but multiple by 10 to additionally
|
||||
// have a column for the negative sign.
|
||||
if (first_line_number < 0) first_line_number = (-first_line_number) * 10;
|
||||
if (last_line_number < 0) last_line_number = (-last_line_number) * 10;
|
||||
int32_t max_line_number = first_line_number > last_line_number ? first_line_number : last_line_number;
|
||||
|
||||
if (max_line_number < 10) {
|
||||
if (colorize) {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = PM_COLOR_GRAY "%1" PRIi32 " | " PM_COLOR_RESET,
|
||||
.blank_prefix = PM_COLOR_GRAY " | " PM_COLOR_RESET,
|
||||
.divider = PM_COLOR_GRAY " ~~~~~" PM_COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = "%1" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else if (max_line_number < 100) {
|
||||
if (colorize) {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = PM_COLOR_GRAY "%2" PRIi32 " | " PM_COLOR_RESET,
|
||||
.blank_prefix = PM_COLOR_GRAY " | " PM_COLOR_RESET,
|
||||
.divider = PM_COLOR_GRAY " ~~~~~~" PM_COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = "%2" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else if (max_line_number < 1000) {
|
||||
if (colorize) {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = PM_COLOR_GRAY "%3" PRIi32 " | " PM_COLOR_RESET,
|
||||
.blank_prefix = PM_COLOR_GRAY " | " PM_COLOR_RESET,
|
||||
.divider = PM_COLOR_GRAY " ~~~~~~~" PM_COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = "%3" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else if (max_line_number < 10000) {
|
||||
if (colorize) {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = PM_COLOR_GRAY "%4" PRIi32 " | " PM_COLOR_RESET,
|
||||
.blank_prefix = PM_COLOR_GRAY " | " PM_COLOR_RESET,
|
||||
.divider = PM_COLOR_GRAY " ~~~~~~~~" PM_COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = "%4" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~~~\n"
|
||||
};
|
||||
}
|
||||
} else {
|
||||
if (colorize) {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = PM_COLOR_GRAY "%5" PRIi32 " | " PM_COLOR_RESET,
|
||||
.blank_prefix = PM_COLOR_GRAY " | " PM_COLOR_RESET,
|
||||
.divider = PM_COLOR_GRAY " ~~~~~~~~" PM_COLOR_RESET "\n"
|
||||
};
|
||||
} else {
|
||||
error_format = (pm_error_format_t) {
|
||||
.number_prefix = "%5" PRIi32 " | ",
|
||||
.blank_prefix = " | ",
|
||||
.divider = " ~~~~~~~~\n"
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
error_format.blank_prefix_length = strlen(error_format.blank_prefix);
|
||||
error_format.divider_length = strlen(error_format.divider);
|
||||
|
||||
// Now we're going to iterate through every error in our error list and
|
||||
// display it. While we're iterating, we will display some padding lines of
|
||||
// the source before the error to give some context. We'll be careful not to
|
||||
// display the same line twice in case the errors are close enough in the
|
||||
// source.
|
||||
int32_t last_line = parser->start_line - 1;
|
||||
uint32_t last_column_start = 0;
|
||||
const pm_encoding_t *encoding = parser->encoding;
|
||||
|
||||
for (size_t index = 0; index < error_list->size; index++) {
|
||||
pm_error_t *error = &errors[index];
|
||||
|
||||
// Here we determine how many lines of padding of the source to display,
|
||||
// based on the difference from the last line that was displaid.
|
||||
if (error->line - last_line > 1) {
|
||||
if (error->line - last_line > 2) {
|
||||
if ((index != 0) && (error->line - last_line > 3)) {
|
||||
pm_buffer_append_string(buffer, error_format.divider, error_format.divider_length);
|
||||
}
|
||||
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_parser_errors_format_line(parser, newline_list, error_format.number_prefix, error->line - 2, 0, 0, buffer);
|
||||
}
|
||||
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_parser_errors_format_line(parser, newline_list, error_format.number_prefix, error->line - 1, 0, 0, buffer);
|
||||
}
|
||||
|
||||
// If this is the first error or we're on a new line, then we'll display
|
||||
// the line that has the error in it.
|
||||
if ((index == 0) || (error->line != last_line)) {
|
||||
if (colorize) {
|
||||
pm_buffer_append_string(buffer, PM_COLOR_RED "> " PM_COLOR_RESET, 12);
|
||||
} else {
|
||||
pm_buffer_append_string(buffer, "> ", 2);
|
||||
}
|
||||
|
||||
last_column_start = error->column_start;
|
||||
|
||||
// Find the maximum column end of all the errors on this line.
|
||||
uint32_t column_end = error->column_end;
|
||||
for (size_t next_index = index + 1; next_index < error_list->size; next_index++) {
|
||||
if (errors[next_index].line != error->line) break;
|
||||
if (errors[next_index].column_end > column_end) column_end = errors[next_index].column_end;
|
||||
}
|
||||
|
||||
pm_parser_errors_format_line(parser, newline_list, error_format.number_prefix, error->line, error->column_start, column_end, buffer);
|
||||
}
|
||||
|
||||
const uint8_t *start = &parser->start[newline_list->offsets[error->line - start_line]];
|
||||
if (start == parser->end) pm_buffer_append_byte(buffer, '\n');
|
||||
|
||||
// Now we'll display the actual error message. We'll do this by first
|
||||
// putting the prefix to the line, then a bunch of blank spaces
|
||||
// depending on the column, then as many carets as we need to display
|
||||
// the width of the error, then the error message itself.
|
||||
//
|
||||
// Note that this doesn't take into account the width of the actual
|
||||
// character when displaid in the terminal. For some east-asian
|
||||
// languages or emoji, this means it can be thrown off pretty badly. We
|
||||
// will need to solve this eventually.
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_buffer_append_string(buffer, error_format.blank_prefix, error_format.blank_prefix_length);
|
||||
|
||||
size_t column = 0;
|
||||
if (last_column_start >= PM_ERROR_TRUNCATE) {
|
||||
pm_buffer_append_string(buffer, " ", 4);
|
||||
column = last_column_start;
|
||||
}
|
||||
|
||||
while (column < error->column_start) {
|
||||
pm_buffer_append_byte(buffer, ' ');
|
||||
|
||||
size_t char_width = encoding->char_width(start + column, parser->end - (start + column));
|
||||
column += (char_width == 0 ? 1 : char_width);
|
||||
}
|
||||
|
||||
if (colorize) pm_buffer_append_string(buffer, PM_COLOR_RED, 7);
|
||||
pm_buffer_append_byte(buffer, '^');
|
||||
|
||||
size_t char_width = encoding->char_width(start + column, parser->end - (start + column));
|
||||
column += (char_width == 0 ? 1 : char_width);
|
||||
|
||||
while (column < error->column_end) {
|
||||
pm_buffer_append_byte(buffer, '~');
|
||||
|
||||
size_t char_width = encoding->char_width(start + column, parser->end - (start + column));
|
||||
column += (char_width == 0 ? 1 : char_width);
|
||||
}
|
||||
|
||||
if (colorize) pm_buffer_append_string(buffer, PM_COLOR_RESET, 3);
|
||||
|
||||
if (inline_messages) {
|
||||
pm_buffer_append_byte(buffer, ' ');
|
||||
assert(error->error != NULL);
|
||||
|
||||
const char *message = error->error->message;
|
||||
pm_buffer_append_string(buffer, message, strlen(message));
|
||||
}
|
||||
|
||||
pm_buffer_append_byte(buffer, '\n');
|
||||
|
||||
// Here we determine how many lines of padding to display after the
|
||||
// error, depending on where the next error is in source.
|
||||
last_line = error->line;
|
||||
int32_t next_line = (index == error_list->size - 1) ? (((int32_t) newline_list->size) + parser->start_line) : errors[index + 1].line;
|
||||
|
||||
if (next_line - last_line > 1) {
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_parser_errors_format_line(parser, newline_list, error_format.number_prefix, ++last_line, 0, 0, buffer);
|
||||
}
|
||||
|
||||
if (next_line - last_line > 1) {
|
||||
pm_buffer_append_string(buffer, " ", 2);
|
||||
pm_parser_errors_format_line(parser, newline_list, error_format.number_prefix, ++last_line, 0, 0, buffer);
|
||||
}
|
||||
}
|
||||
|
||||
// Finally, we'll free the array of errors that we allocated.
|
||||
xfree(errors);
|
||||
}
|
||||
|
||||
#undef PM_ERROR_TRUNCATE
|
||||
#undef PM_COLOR_GRAY
|
||||
#undef PM_COLOR_RED
|
||||
#undef PM_COLOR_RESET
|
||||
|
|
|
@ -219,17 +219,6 @@ PRISM_EXPORTED_FUNCTION const char * pm_token_type_name(pm_token_type_t token_ty
|
|||
*/
|
||||
const char * pm_token_type_human(pm_token_type_t token_type);
|
||||
|
||||
/**
|
||||
* Format the errors on the parser into the given buffer.
|
||||
*
|
||||
* @param parser The parser to format the errors for.
|
||||
* @param error_list The list of errors to format.
|
||||
* @param buffer The buffer to format the errors into.
|
||||
* @param colorize Whether or not to colorize the errors with ANSI escape sequences.
|
||||
* @param inline_messages Whether or not to inline the messages with the source.
|
||||
*/
|
||||
PRISM_EXPORTED_FUNCTION void pm_parser_errors_format(const pm_parser_t *parser, const pm_list_t *error_list, pm_buffer_t *buffer, bool colorize, bool inline_messages);
|
||||
|
||||
// We optionally support dumping to JSON. For systems that don't want or need
|
||||
// this functionality, it can be turned off with the PRISM_EXCLUDE_JSON define.
|
||||
#ifndef PRISM_EXCLUDE_JSON
|
||||
|
|
|
@ -1,58 +0,0 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
require_relative "test_helper"
|
||||
|
||||
return if Prism::BACKEND == :FFI
|
||||
|
||||
module Prism
|
||||
class FormatErrorsTest < TestCase
|
||||
def test_basic
|
||||
expected = <<~ERROR
|
||||
> 1 | <>
|
||||
| ^ unexpected '<', ignoring it
|
||||
| ^ unexpected '>', ignoring it
|
||||
ERROR
|
||||
|
||||
assert_equal expected, Debug.format_errors("<>", false)
|
||||
end
|
||||
|
||||
def test_multiple
|
||||
expected = <<~ERROR
|
||||
> 1 | "%W"\\u"
|
||||
| ^ unexpected backslash, ignoring it
|
||||
| ^ unexpected local variable or method, expecting end-of-input
|
||||
| ^ unterminated string meets end of file
|
||||
ERROR
|
||||
|
||||
assert_equal expected, Debug.format_errors('"%W"\u"', false)
|
||||
end
|
||||
|
||||
def test_truncate_start
|
||||
expected = <<~ERROR
|
||||
> 1 | ... <>
|
||||
| ^ unexpected '<', ignoring it
|
||||
| ^ unexpected '>', ignoring it
|
||||
ERROR
|
||||
|
||||
assert_equal expected, Debug.format_errors("#{" " * 30}<>", false)
|
||||
end
|
||||
|
||||
def test_truncate_end
|
||||
expected = <<~ERROR
|
||||
> 1 | <#{" " * 30} ...
|
||||
| ^ unexpected '<', ignoring it
|
||||
ERROR
|
||||
|
||||
assert_equal expected, Debug.format_errors("<#{" " * 30}a", false)
|
||||
end
|
||||
|
||||
def test_truncate_both
|
||||
expected = <<~ERROR
|
||||
> 1 | ... <#{" " * 30} ...
|
||||
| ^ unexpected '<', ignoring it
|
||||
ERROR
|
||||
|
||||
assert_equal expected, Debug.format_errors("#{" " * 30}<#{" " * 30}a", false)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -10,7 +10,6 @@ module Prism
|
|||
filepaths = Dir["*.rb", base: base] - %w[
|
||||
encoding_test.rb
|
||||
errors_test.rb
|
||||
format_errors_test.rb
|
||||
parser_test.rb
|
||||
regexp_test.rb
|
||||
static_literals_test.rb
|
||||
|
|
Загрузка…
Ссылка в новой задаче