Skip to content

Commit

Permalink
Refactor text_error_reporter formatting
Browse files Browse the repository at this point in the history
I want to unify a bunch code between text_error_reporter and
vim_qflist_json_error_reporter. I also want to enable translation of
error messages. Take a step in both directions by refactoring how
text_error_reporter formats its messages.

Future commits will do similar things to vim_qflist_json_error_reporter,
then factor the common code.

This commit should not change behavior.
  • Loading branch information
strager committed Sep 12, 2020
1 parent eea276f commit 30d95ad
Show file tree
Hide file tree
Showing 3 changed files with 236 additions and 70 deletions.
57 changes: 55 additions & 2 deletions src/include/quick-lint-js/text-error-reporter.h
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,20 @@
#ifndef QUICK_LINT_JS_TEXT_ERROR_REPORTER_H
#define QUICK_LINT_JS_TEXT_ERROR_REPORTER_H

#include <initializer_list>
#include <iosfwd>
#include <optional>
#include <quick-lint-js/char8.h>
#include <quick-lint-js/error.h>
#include <quick-lint-js/language.h>
#include <quick-lint-js/lex.h>
#include <quick-lint-js/location.h>
#include <quick-lint-js/padded-string.h>
#include <utility>

namespace quick_lint_js {
class text_error_formatter;

class text_error_reporter final : public error_reporter {
public:
explicit text_error_reporter(std::ostream &output);
Expand Down Expand Up @@ -89,13 +94,61 @@ class text_error_reporter final : public error_reporter {
token_type, const char8 *token_begin) override;

private:
void log_location(identifier) const;
void log_location(source_code_span) const;
text_error_formatter format();

std::ostream &output_;
std::optional<locator> locator_;
const char *file_path_;
};

class text_error_formatter {
private:
enum class severity;

public:
explicit text_error_formatter(std::ostream &output, const char *file_path,
quick_lint_js::locator &locator);

template <class... Args>
text_error_formatter &error(const char8 *message, Args... parameters) {
this->add(severity::error, message, std::forward<Args>(parameters)...);
return *this;
}

template <class... Args>
text_error_formatter &note(const char8 *message, Args &&... parameters) {
this->add(severity::note, message, std::forward<Args>(parameters)...);
return *this;
}

void end();

private:
enum class severity {
error,
note,
};

template <class... Args>
void add(severity sev, const char8 *message, Args &&... parameters) {
static_assert(sizeof...(Args) > 0,
"at least origin span must be specified");
this->add(sev, message, {this->to_span(std::forward<Args>(parameters))...});
}

void add(severity, const char8 *message,
std::initializer_list<source_code_span> parameters);

static const source_code_span &to_span(const source_code_span &span) {
return span;
}

static source_code_span to_span(identifier ident) { return ident.span(); }

std::ostream &output_;
const char *file_path_;
locator &locator_;
};
}

#endif
179 changes: 111 additions & 68 deletions src/text-error-reporter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@

#include <ostream>
#include <quick-lint-js/char8.h>
#include <quick-lint-js/location.h>
#include <quick-lint-js/optional.h>
#include <quick-lint-js/padded-string.h>
#include <quick-lint-js/text-error-reporter.h>
#include <quick-lint-js/unreachable.h>

namespace quick_lint_js {
text_error_reporter::text_error_reporter(std::ostream &output)
Expand All @@ -32,170 +34,153 @@ void text_error_reporter::set_source(padded_string_view input,

void text_error_reporter::report_error_assignment_before_variable_declaration(
identifier assignment, identifier declaration) {
this->log_location(assignment);
this->output_ << "error: variable assigned before its declaration\n";
this->log_location(declaration);
this->output_ << "note: variable declared here\n";
this->format()
.error(u8"variable assigned before its declaration", assignment)
.note(u8"variable declared here", declaration)
.end();
}

void text_error_reporter::report_error_assignment_to_const_global_variable(
identifier assignment) {
log_location(assignment);
this->output_ << "error: assignment to const global variable\n";
this->format()
.error(u8"assignment to const global variable", assignment)
.end();
}

void text_error_reporter::report_error_assignment_to_const_variable(
identifier declaration, identifier assignment, variable_kind) {
log_location(assignment);
this->output_ << "error: assignment to const variable\n";
log_location(declaration);
this->output_ << "note: const variable declared here\n";
this->format()
.error(u8"assignment to const variable", assignment)
.note(u8"const variable declared here", declaration)
.end();
}

void text_error_reporter::report_error_assignment_to_undeclared_variable(
identifier assignment) {
log_location(assignment);
this->output_ << "error: assignment to undeclared variable\n";
this->format().error(u8"assignment to undeclared variable", assignment).end();
}

void text_error_reporter::report_error_big_int_literal_contains_decimal_point(
source_code_span where) {
this->log_location(where);
this->output_ << "error: BigInt literal contains decimal point\n";
this->format().error(u8"BigInt literal contains decimal point", where).end();
}

void text_error_reporter::report_error_big_int_literal_contains_exponent(
source_code_span where) {
this->log_location(where);
this->output_ << "error: BigInt literal contains exponent\n";
this->format().error(u8"BigInt literal contains exponent", where).end();
}

void text_error_reporter::report_error_big_int_literal_contains_leading_zero(
source_code_span where) {
this->log_location(where);
this->output_ << "error: BigInt literal has a leading 0 digit\n";
this->format().error(u8"BigInt literal has a leading 0 digit", where).end();
}

void text_error_reporter::report_error_invalid_binding_in_let_statement(
source_code_span where) {
log_location(where);
this->output_ << "error: invalid binding in let statement\n";
this->format().error(u8"invalid binding in let statement", where).end();
}

void text_error_reporter::report_error_invalid_expression_left_of_assignment(
source_code_span where) {
log_location(where);
this->output_ << "error: invalid expression left of assignment\n";
this->format().error(u8"invalid expression left of assignment", where).end();
}

void text_error_reporter::report_error_let_with_no_bindings(
source_code_span where) {
log_location(where);
this->output_ << "error: let with no bindings\n";
this->format().error(u8"let with no bindings", where).end();
}

void text_error_reporter::
report_error_missing_comma_between_object_literal_entries(
source_code_span where) {
log_location(where);
this->output_ << "error: missing comma between object literal entries\n";
this->format()
.error(u8"missing comma between object literal entries", where)
.end();
}

void text_error_reporter::report_error_missing_operand_for_operator(
source_code_span where) {
log_location(where);
this->output_ << "error: missing operand for operator\n";
this->format().error(u8"missing operand for operator", where).end();
}

void text_error_reporter::report_error_missing_semicolon_after_expression(
source_code_span where) {
log_location(where);
this->output_ << "error: missing semicolon after expression\n";
this->format().error(u8"missing semicolon after expression", where).end();
}

void text_error_reporter::report_error_redeclaration_of_global_variable(
identifier redeclaration) {
this->log_location(redeclaration);
this->output_ << "error: redeclaration of global variable\n";
this->format()
.error(u8"redeclaration of global variable", redeclaration)
.end();
}

void text_error_reporter::report_error_redeclaration_of_variable(
identifier redeclaration, identifier original_declaration) {
log_location(redeclaration);
this->output_ << "error: redeclaration of variable: "
<< out_string8(redeclaration.string_view()) << '\n';
log_location(original_declaration);
this->output_ << "note: variable already declared here\n";
this->format()
.error(u8"redeclaration of variable: {0}", redeclaration)
.note(u8"variable already declared here", original_declaration)
.end();
}

void text_error_reporter::report_error_stray_comma_in_let_statement(
source_code_span where) {
log_location(where);
this->output_ << "error: stray comma in let statement\n";
this->format().error(u8"stray comma in let statement", where).end();
}

void text_error_reporter::report_error_unclosed_block_comment(
source_code_span where) {
log_location(where);
this->output_ << "error: unclosed block comment\n";
this->format().error(u8"unclosed block comment", where).end();
}

void text_error_reporter::report_error_unclosed_regexp_literal(
source_code_span where) {
log_location(where);
this->output_ << "error: unclosed regexp literal\n";
this->format().error(u8"unclosed regexp literal", where).end();
}

void text_error_reporter::report_error_unclosed_string_literal(
source_code_span where) {
log_location(where);
this->output_ << "error: unclosed string literal\n";
this->format().error(u8"unclosed string literal", where).end();
}

void text_error_reporter::report_error_unclosed_template(
source_code_span where) {
log_location(where);
this->output_ << "error: unclosed template\n";
this->format().error(u8"unclosed template", where).end();
}

void text_error_reporter::report_error_unexpected_characters_in_number(
source_code_span characters) {
this->log_location(characters);
this->output_ << "error: unexpected characters in number literal\n";
this->format()
.error(u8"unexpected characters in number literal", characters)
.end();
}

void text_error_reporter::report_error_unexpected_hash_character(
source_code_span where) {
this->log_location(where);
this->output_ << "error: unexpected '#'\n";
this->format().error(u8"unexpected '#'", where).end();
}

void text_error_reporter::report_error_unexpected_identifier(
source_code_span where) {
log_location(where);
this->output_ << "error: unexpected identifier\n";
this->format().error(u8"unexpected identifier", where).end();
}

void text_error_reporter::report_error_unmatched_parenthesis(
source_code_span where) {
log_location(where);
this->output_ << "error: unmatched parenthesis\n";
this->format().error(u8"unmatched parenthesis", where).end();
}

void text_error_reporter::report_error_use_of_undeclared_variable(
identifier name) {
log_location(name);
this->output_ << "error: use of undeclared variable: "
<< out_string8(name.string_view()) << '\n';
this->format().error(u8"use of undeclared variable: {0}", name).end();
}

void text_error_reporter::report_error_variable_used_before_declaration(
identifier use, identifier declaration) {
log_location(use);
this->output_ << "error: variable used before declaration: "
<< out_string8(use.string_view()) << '\n';
log_location(declaration);
this->output_ << "note: variable declared here\n";
this->format()
.error(u8"variable used before declaration: {0}", use)
.note(u8"variable declared here", declaration)
.end();
}

void text_error_reporter::report_fatal_error_unimplemented_character(
Expand Down Expand Up @@ -223,14 +208,72 @@ void text_error_reporter::report_fatal_error_unimplemented_token(
/*out=*/this->output_);
}

void text_error_reporter::log_location(identifier i) const {
log_location(i.span());
text_error_formatter text_error_reporter::format() {
QLJS_ASSERT(this->file_path_);
QLJS_ASSERT(this->locator_.has_value());
return text_error_formatter(/*output=*/this->output_,
/*file_path=*/this->file_path_,
/*locator=*/*this->locator_);
}

void text_error_reporter::log_location(source_code_span span) const {
source_range r = this->locator_->range(span);
text_error_formatter::text_error_formatter(std::ostream &output,
const char *file_path,
quick_lint_js::locator &locator)
: output_(output), file_path_(file_path), locator_(locator) {}

void text_error_formatter::add(
severity sev, const char8 *message,
std::initializer_list<source_code_span> parameters) {
static constexpr auto npos = string8_view::npos;
using string8_pos = string8_view::size_type;
QLJS_ASSERT(message);
QLJS_ASSERT(!std::empty(parameters));

const source_code_span &origin_span = *parameters.begin();
source_range r = this->locator_.range(origin_span);
source_position p = r.begin();
this->output_ << this->file_path_ << ":" << p.line_number << ":"
<< p.column_number << ": ";
}

switch (sev) {
case severity::error:
this->output_ << "error: ";
break;
case severity::note:
this->output_ << "note: ";
break;
}

string8_view remaining_message(message);
string8_pos left_curly_index;
while ((left_curly_index = remaining_message.find(u8'{')) != npos) {
this->output_ << out_string8(remaining_message.substr(0, left_curly_index));

string8_pos right_curly_index =
remaining_message.find(u8'}', left_curly_index + 1);
QLJS_ASSERT(right_curly_index != npos &&
"invalid message format: missing }");
string8_view curly_content = remaining_message.substr(
left_curly_index + 1, right_curly_index - (left_curly_index + 1));
std::size_t index;
if (curly_content == u8"0") {
index = 0;
} else if (curly_content == u8"1") {
index = 1;
} else if (curly_content == u8"2") {
index = 2;
} else {
QLJS_ASSERT(false && "invalid message format: unrecognized placeholder");
QLJS_UNREACHABLE();
}

this->output_ << out_string8((parameters.begin() + index)->string_view());
remaining_message = remaining_message.substr(right_curly_index + 1);
}
this->output_ << out_string8(remaining_message);

this->output_ << '\n';
}

void text_error_formatter::end() {}
}
Loading

0 comments on commit 30d95ad

Please sign in to comment.