From b3449e86feadb4a93b3b0cd659c9da5ce748965a Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Mon, 30 Jun 2025 13:15:50 +0200 Subject: [PATCH 1/3] Friendly token names in Unexpected Token error messages --- config.yml | 6 +- src/include/parser_helpers.h | 17 ++- src/include/token.h | 4 + src/include/token_struct.h | 3 + src/parser.c | 53 ++++---- src/parser_helpers.c | 38 +++++- src/token.c | 118 ++++++++++++++++++ test/c/test_token.c | 55 ++++++++ ..._sign_60301c34c290d45874e5f104c39feb48.txt | 22 ++-- ...l_tag_b7b46dcd10fad620a00a95b27daab204.txt | 6 +- ...space_0ee4b96cac701f87a8b6cb0cf5ad48bc.txt | 6 +- 11 files changed, 274 insertions(+), 54 deletions(-) diff --git a/config.yml b/config.yml index e75d44705..fa95ea304 100644 --- a/config.yml +++ b/config.yml @@ -12,7 +12,7 @@ errors: types: - name: UnexpectedError message: - template: "%s. Expected: `%s`, found: `%s`." + template: "%s. Expected: %s, found: `%s`." arguments: - description - expected @@ -32,8 +32,8 @@ errors: message: template: "Found `%s` when expecting `%s` at (%zu:%zu)." arguments: - - token_type_to_string(found->type) - - token_type_to_string(expected_type) + - token_type_to_friendly_string(found->type) + - token_type_to_friendly_string(expected_type) - found->location->start->line - found->location->start->column diff --git a/src/include/parser_helpers.h b/src/include/parser_helpers.h index aa163fdf5..fece96331 100644 --- a/src/include/parser_helpers.h +++ b/src/include/parser_helpers.h @@ -12,7 +12,22 @@ void parser_push_open_tag(const parser_T* parser, token_T* tag_name); bool parser_check_matching_tag(const parser_T* parser, const char* tag_name); token_T* parser_pop_open_tag(const parser_T* parser); -void parser_append_unexpected_error(parser_T* parser, const char* description, const char* expected, array_T* errors); +void parser_append_unexpected_error_impl( + parser_T* parser, + array_T* errors, + const char* description, + token_type_T first_token, + ... +); +#define parser_append_unexpected_error(parser, errors, description, ...) \ + parser_append_unexpected_error_impl(parser, errors, description, __VA_ARGS__, TOKEN_SENTINEL) + +void parser_append_unexpected_error_string( + parser_T* parser, + array_T* errors, + const char* description, + const char* expected +); void parser_append_unexpected_token_error(parser_T* parser, token_type_T expected_type, array_T* errors); void parser_append_literal_node_from_buffer( diff --git a/src/include/token.h b/src/include/token.h index 6930439e9..09b2bffc0 100644 --- a/src/include/token.h +++ b/src/include/token.h @@ -9,6 +9,10 @@ token_T* token_init(const char* value, token_type_T type, lexer_T* lexer); char* token_to_string(const token_T* token); char* token_to_json(const token_T* token); const char* token_type_to_string(token_type_T type); +const char* token_type_to_friendly_string(token_type_T type); +char* token_types_to_friendly_string_va(token_type_T first_token, ...); + +#define token_types_to_friendly_string(...) token_types_to_friendly_string_va(__VA_ARGS__, TOKEN_SENTINEL) char* token_value(const token_T* token); int token_type(const token_T* token); diff --git a/src/include/token_struct.h b/src/include/token_struct.h index 142cd2ff6..d5e7c80f5 100644 --- a/src/include/token_struct.h +++ b/src/include/token_struct.h @@ -41,6 +41,9 @@ typedef enum { TOKEN_EOF, } token_type_T; +// Sentinel value for variadic functions +#define TOKEN_SENTINEL 99999999 + typedef struct TOKEN_STRUCT { char* value; range_T* range; diff --git a/src/parser.c b/src/parser.c index fa96c6a20..af3dbf3ea 100644 --- a/src/parser.c +++ b/src/parser.c @@ -141,17 +141,7 @@ static AST_HTML_TEXT_NODE_T* parser_parse_text_content(parser_T* parser, array_T if (token_is(parser, TOKEN_ERROR)) { buffer_free(&content); - token_T* token = parser_consume_expected(parser, TOKEN_ERROR, document_errors); - append_unexpected_error( - "Token Error", - "not TOKEN_ERROR", - token->value, - token->location->start, - token->location->end, - document_errors - ); - - token_free(token); + parser_append_unexpected_error_string(parser, document_errors, "Token Error", "not TOKEN_ERROR"); position_free(start); return NULL; @@ -300,29 +290,18 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_html_attribute_value(parser //
if (token_is(parser, TOKEN_QUOTE)) { return parser_parse_quoted_html_attribute_value(parser, children, errors); } - token_T* token = parser_advance(parser); - - append_unexpected_error( - "Unexpected Token", - "TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START", - token_type_to_string(token->type), - token->location->start, - token->location->end, - errors - ); + parser_append_unexpected_error(parser, errors, "Unexpected Token", TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START); AST_HTML_ATTRIBUTE_VALUE_NODE_T* value = ast_html_attribute_value_node_init( NULL, children, NULL, false, - token->location->start, - token->location->end, + parser->current_token->location->start, + parser->current_token->location->end, errors ); - token_free(token); - return value; } @@ -392,9 +371,12 @@ static AST_HTML_OPEN_TAG_NODE_T* parser_parse_html_open_tag(parser_T* parser) { parser_append_unexpected_error( parser, + errors, "Unexpected Token", - "TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE", - errors + TOKEN_IDENTIFIER, + TOKEN_ERB_START, + TOKEN_WHITESPACE, + TOKEN_NEWLINE ); } @@ -553,7 +535,12 @@ static AST_HTML_ELEMENT_NODE_T* parser_parse_html_element(parser_T* parser) { array_T* errors = array_init(8); - parser_append_unexpected_error(parser, "Unknown HTML open tag type", "HTMLOpenTag or HTMLSelfCloseTag", errors); + parser_append_unexpected_error_string( + parser, + errors, + "Unknown HTML open tag type", + "HTMLOpenTag or HTMLSelfCloseTag" + ); return ast_html_element_node_init( open_tag, @@ -638,10 +625,14 @@ static void parser_parse_in_data_state(parser_T* parser, array_T* children, arra parser_append_unexpected_error( parser, + errors, "Unexpected token", - "TOKEN_ERB_START, TOKEN_HTML_DOCTYPE, TOKEN_HTML_COMMENT_START, TOKEN_IDENTIFIER, TOKEN_WHITESPACE, or " - "TOKEN_NEWLINE", - errors + TOKEN_ERB_START, + TOKEN_HTML_DOCTYPE, + TOKEN_HTML_COMMENT_START, + TOKEN_IDENTIFIER, + TOKEN_WHITESPACE, + TOKEN_NEWLINE ); } } diff --git a/src/parser_helpers.c b/src/parser_helpers.c index 16b4dbe08..100c300dd 100644 --- a/src/parser_helpers.c +++ b/src/parser_helpers.c @@ -9,6 +9,7 @@ #include "include/parser.h" #include "include/token.h" +#include #include #include @@ -54,13 +55,46 @@ bool parser_in_svg_context(const parser_T* parser) { return false; } -void parser_append_unexpected_error(parser_T* parser, const char* description, const char* expected, array_T* errors) { +void parser_append_unexpected_error_impl( + parser_T* parser, + array_T* errors, + const char* description, + token_type_T first_token, + ... +) { + token_T* token = parser_advance(parser); + + // Pass the variadic arguments to token_types_to_friendly_string_va + va_list args; + va_start(args, first_token); + char* expected = token_types_to_friendly_string_va(first_token, args); + va_end(args); + + append_unexpected_error( + description, + expected, + token_type_to_friendly_string(token->type), + token->location->start, + token->location->end, + errors + ); + + free(expected); + token_free(token); +} + +void parser_append_unexpected_error_string( + parser_T* parser, + array_T* errors, + const char* description, + const char* expected +) { token_T* token = parser_advance(parser); append_unexpected_error( description, expected, - token_type_to_string(token->type), + token_type_to_friendly_string(token->type), token->location->start, token->location->end, errors diff --git a/src/token.c b/src/token.c index 8dbe140cd..947fd3242 100644 --- a/src/token.c +++ b/src/token.c @@ -5,6 +5,7 @@ #include "include/token_struct.h" #include "include/util.h" +#include #include #include #include @@ -75,6 +76,123 @@ const char* token_type_to_string(const token_type_T type) { return "Unknown token_type_T"; } +const char* token_type_to_friendly_string(const token_type_T type) { + switch (type) { + case TOKEN_WHITESPACE: return "whitespace"; + case TOKEN_NBSP: return "non-breaking space"; + case TOKEN_NEWLINE: return "newline"; + case TOKEN_IDENTIFIER: return "identifier"; + case TOKEN_HTML_DOCTYPE: return ""; + case TOKEN_HTML_TAG_START_CLOSE: return ""; + case TOKEN_HTML_COMMENT_START: return ""; + case TOKEN_EQUALS: return "="; + case TOKEN_QUOTE: return "quote"; + case TOKEN_DASH: return "-"; + case TOKEN_UNDERSCORE: return "_"; + case TOKEN_EXCLAMATION: return "!"; + case TOKEN_SLASH: return "/"; + case TOKEN_SEMICOLON: return ";"; + case TOKEN_COLON: return ":"; + case TOKEN_LT: return "<"; + case TOKEN_PERCENT: return "%"; + case TOKEN_AMPERSAND: return "&"; + case TOKEN_ERB_START: return "ERB start"; + case TOKEN_ERB_CONTENT: return "ERB content"; + case TOKEN_ERB_END: return "ERB end"; + case TOKEN_CHARACTER: return "character"; + case TOKEN_ERROR: return "error"; + case TOKEN_EOF: return "end of file"; + } + + return "unknown token"; +} + +char* token_types_to_friendly_string_va(token_type_T first_token, ...) { + // Count tokens + va_list args; + va_start(args, first_token); + + size_t count = 0; + token_type_T current = first_token; + + // First pass: count tokens + while (current != TOKEN_SENTINEL) { + count++; + current = va_arg(args, token_type_T); + } + va_end(args); + + if (count == 0) { return herb_strdup(""); } + + // Calculate total length needed + va_start(args, first_token); + size_t total_length = 0; + current = first_token; + size_t i = 0; + + while (current != TOKEN_SENTINEL) { + const char* friendly_name = token_type_to_friendly_string(current); + total_length += strlen(friendly_name) + 2; // +2 for backticks + + if (i < count - 1 && count > 1) { + if (i == count - 2) { + total_length += 4; // " or " + } else { + total_length += 2; // ", " + } + } + + current = va_arg(args, token_type_T); + i++; + } + va_end(args); + + total_length += 1; // null terminator + + // Allocate buffer + char* result = calloc(total_length, sizeof(char)); + if (!result) { return NULL; } + + // Build the string + va_start(args, first_token); + current = first_token; + size_t pos = 0; + i = 0; + + while (current != TOKEN_SENTINEL) { + const char* friendly_name = token_type_to_friendly_string(current); + + // Add backtick before token name + result[pos++] = '`'; + + // Add token name + strcpy(result + pos, friendly_name); + pos += strlen(friendly_name); + + // Add backtick after token name + result[pos++] = '`'; + + // Add separator + if (count > 2 && i < count - 2) { + strcpy(result + pos, ", "); + pos += 2; + } else if (count > 1 && i == count - 2) { + strcpy(result + pos, " or "); + pos += 4; + } + + current = va_arg(args, token_type_T); + i++; + } + + va_end(args); + return result; +} + char* token_to_string(const token_T* token) { const char* type_string = token_type_to_string(token->type); const char* template = "#"; diff --git a/test/c/test_token.c b/test/c/test_token.c index a0d06daa2..f8bf45e55 100644 --- a/test/c/test_token.c +++ b/test/c/test_token.c @@ -7,6 +7,59 @@ TEST(test_token) ck_assert_str_eq(token_type_to_string(TOKEN_IDENTIFIER), "TOKEN_IDENTIFIER"); END +TEST(test_token_type_to_friendly_string) + // Test regular tokens + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_IDENTIFIER), "identifier"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_WHITESPACE), "whitespace"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_NEWLINE), "newline"); + + // Test HTML tokens with actual characters + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_HTML_TAG_START), "<"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_HTML_TAG_END), ">"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_HTML_TAG_SELF_CLOSE), "/>"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_HTML_COMMENT_START), ""); + + // Test single character tokens + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_EQUALS), "="); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_SLASH), "/"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_COLON), ":"); + + // Test ERB tokens + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_ERB_START), "ERB start"); + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_ERB_END), "ERB end"); + + // Test special tokens + ck_assert_str_eq(token_type_to_friendly_string(TOKEN_EOF), "end of file"); +END + +TEST(test_token_types_to_friendly_string) + // Test single token + char* result1 = token_types_to_friendly_string(TOKEN_IDENTIFIER); + ck_assert_str_eq(result1, "`identifier`"); + free(result1); + + // Test two tokens + char* result2 = token_types_to_friendly_string(TOKEN_IDENTIFIER, TOKEN_QUOTE); + ck_assert_str_eq(result2, "`identifier` or `quote`"); + free(result2); + + // Test three tokens + char* result3 = token_types_to_friendly_string(TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START); + ck_assert_str_eq(result3, "`identifier`, `quote` or `ERB start`"); + free(result3); + + // Test four tokens + char* result4 = token_types_to_friendly_string(TOKEN_IDENTIFIER, TOKEN_ERB_START, TOKEN_WHITESPACE, TOKEN_NEWLINE); + ck_assert_str_eq(result4, "`identifier`, `ERB start`, `whitespace` or `newline`"); + free(result4); + + // Test with HTML tokens showing actual characters + char* result5 = token_types_to_friendly_string(TOKEN_HTML_TAG_START, TOKEN_HTML_TAG_END, TOKEN_EQUALS); + ck_assert_str_eq(result5, "`<`, `>` or `=`"); + free(result5); +END + TEST(test_token_to_string) buffer_T output = buffer_new(); herb_lex_to_buffer("hello", &output); @@ -38,6 +91,8 @@ TCase *token_tests(void) { TCase *token = tcase_create("Token"); tcase_add_test(token, test_token); + tcase_add_test(token, test_token_type_to_friendly_string); + tcase_add_test(token, test_token_types_to_friendly_string); tcase_add_test(token, test_token_to_string); tcase_add_test(token, test_token_to_json); diff --git a/test/snapshots/parser/attributes_test/test_0006_attribute_value_with_space_after_equal_sign_60301c34c290d45874e5f104c39feb48.txt b/test/snapshots/parser/attributes_test/test_0006_attribute_value_with_space_after_equal_sign_60301c34c290d45874e5f104c39feb48.txt index cd5803382..df0575816 100644 --- a/test/snapshots/parser/attributes_test/test_0006_attribute_value_with_space_after_equal_sign_60301c34c290d45874e5f104c39feb48.txt +++ b/test/snapshots/parser/attributes_test/test_0006_attribute_value_with_space_after_equal_sign_60301c34c290d45874e5f104c39feb48.txt @@ -5,35 +5,35 @@ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:24)) │ ├── errors: (2 errors) │ │ ├── @ UnexpectedError (location: (1:14)-(1:15)) - │ │ │ ├── message: "Unexpected Token. Expected: `TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE`, found: `TOKEN_QUOTE`." + │ │ │ ├── message: "Unexpected Token. Expected: `identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`, found: `quote`." │ │ │ ├── description: "Unexpected Token" - │ │ │ ├── expected: "TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE" - │ │ │ └── found: "TOKEN_QUOTE" + │ │ │ ├── expected: "`identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`" + │ │ │ └── found: "quote" │ │ │ │ │ └── @ UnexpectedError (location: (1:20)-(1:21)) - │ │ ├── message: "Unexpected Token. Expected: `TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE`, found: `TOKEN_QUOTE`." + │ │ ├── message: "Unexpected Token. Expected: `identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`, found: `quote`." │ │ ├── description: "Unexpected Token" - │ │ ├── expected: "TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE" - │ │ └── found: "TOKEN_QUOTE" + │ │ ├── expected: "`identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`" + │ │ └── found: "quote" │ │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ ├── tag_name: "input" (location: (1:1)-(1:6)) │ ├── tag_closing: "/>" (location: (1:22)-(1:24)) │ ├── children: (2 items) - │ │ ├── @ HTMLAttributeNode (location: (1:7)-(1:14)) + │ │ ├── @ HTMLAttributeNode (location: (1:7)-(1:15)) │ │ │ ├── name: │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:7)-(1:12)) │ │ │ │ └── name: "value" (location: (1:7)-(1:12)) │ │ │ │ │ │ │ ├── equals: "=" (location: (1:12)-(1:13)) │ │ │ └── value: - │ │ │ └── @ HTMLAttributeValueNode (location: (1:13)-(1:14)) + │ │ │ └── @ HTMLAttributeValueNode (location: (1:14)-(1:15)) │ │ │ ├── errors: (1 error) │ │ │ │ └── @ UnexpectedError (location: (1:13)-(1:14)) - │ │ │ │ ├── message: "Unexpected Token. Expected: `TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START`, found: `TOKEN_WHITESPACE`." + │ │ │ │ ├── message: "Unexpected Token. Expected: `identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`, found: `whitespace`." │ │ │ │ ├── description: "Unexpected Token" - │ │ │ │ ├── expected: "TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START" - │ │ │ │ └── found: "TOKEN_WHITESPACE" + │ │ │ │ ├── expected: "`identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`" + │ │ │ │ └── found: "whitespace" │ │ │ │ │ │ │ ├── open_quote: ∅ │ │ │ ├── children: [] diff --git a/test/snapshots/parser/tags_test/test_0016_colon_inside_html_tag_b7b46dcd10fad620a00a95b27daab204.txt b/test/snapshots/parser/tags_test/test_0016_colon_inside_html_tag_b7b46dcd10fad620a00a95b27daab204.txt index dd565226f..c88b27f3f 100644 --- a/test/snapshots/parser/tags_test/test_0016_colon_inside_html_tag_b7b46dcd10fad620a00a95b27daab204.txt +++ b/test/snapshots/parser/tags_test/test_0016_colon_inside_html_tag_b7b46dcd10fad620a00a95b27daab204.txt @@ -5,10 +5,10 @@ │ └── @ HTMLOpenTagNode (location: (1:0)-(1:16)) │ ├── errors: (1 error) │ │ └── @ UnexpectedError (location: (1:5)-(1:6)) - │ │ ├── message: "Unexpected Token. Expected: `TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE`, found: `TOKEN_COLON`." + │ │ ├── message: "Unexpected Token. Expected: `identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`, found: `:`." │ │ ├── description: "Unexpected Token" - │ │ ├── expected: "TOKEN_IDENTIFIER, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE" - │ │ └── found: "TOKEN_COLON" + │ │ ├── expected: "`identifier`, `unknown token`, `unknown token`, `unknown token`, `unknown token`, `newline` or `unknown token`" + │ │ └── found: ":" │ │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ ├── tag_name: "div" (location: (1:1)-(1:4)) diff --git a/test/snapshots/parser/tags_test/test_0027_stray_closing_tag_with_whitespace_0ee4b96cac701f87a8b6cb0cf5ad48bc.txt b/test/snapshots/parser/tags_test/test_0027_stray_closing_tag_with_whitespace_0ee4b96cac701f87a8b6cb0cf5ad48bc.txt index 7faf50a2a..9401c3e1c 100644 --- a/test/snapshots/parser/tags_test/test_0027_stray_closing_tag_with_whitespace_0ee4b96cac701f87a8b6cb0cf5ad48bc.txt +++ b/test/snapshots/parser/tags_test/test_0027_stray_closing_tag_with_whitespace_0ee4b96cac701f87a8b6cb0cf5ad48bc.txt @@ -1,10 +1,10 @@ @ DocumentNode (location: (1:0)-(1:24)) ├── errors: (1 error) │ └── @ UnexpectedError (location: (1:16)-(1:17)) -│ ├── message: "Unexpected token. Expected: `TOKEN_ERB_START, TOKEN_HTML_DOCTYPE, TOKEN_HTML_COMMENT_START, TOKEN_IDENTIFIER, TOKEN_WHITESPACE, or TOKEN_NEWLINE`, found: `TOKEN_LT`." +│ ├── message: "Unexpected token. Expected: `ERB start`, `unknown token`, `unknown token`, `unknown token`, `identifier`, `newline` or `unknown token`, found: `<`." │ ├── description: "Unexpected token" -│ ├── expected: "TOKEN_ERB_START, TOKEN_HTML_DOCTYPE, TOKEN_HTML_COMMENT_START, TOKEN_IDENTIFIER, TOKEN_WHITESPACE, or TOKEN_NEWLINE" -│ └── found: "TOKEN_LT" +│ ├── expected: "`ERB start`, `unknown token`, `unknown token`, `unknown token`, `identifier`, `newline` or `unknown token`" +│ └── found: "<" │ └── children: (2 items) ├── @ HTMLElementNode (location: (1:0)-(1:16)) From ac3c3410a2a154a0fc56f55ecdc9ba115277a8af Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Sun, 1 Mar 2026 05:45:32 +0100 Subject: [PATCH 2/3] Update linter snapshots --- javascript/packages/linter/test/__snapshots__/cli.test.ts.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap b/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap index 30fdf699a..4d40050ba 100644 --- a/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap +++ b/javascript/packages/linter/test/__snapshots__/cli.test.ts.snap @@ -277,7 +277,7 @@ test/fixtures/ignored.html.erb:6:14 `; exports[`CLI Output Formatting > diplays only parsers errors if one is present 1`] = ` -"[error] Unexpected Token. Expected: \`TOKEN_IDENTIFIER, TOKEN_AT, TOKEN_ERB_START,TOKEN_WHITESPACE, or TOKEN_NEWLINE\`, found: \`TOKEN_CHARACTER\`. (\`UNEXPECTED_ERROR\`) (parser-no-errors) +"[error] Unexpected Token. Expected: an identifier, \`@\`, \`<%\`, whitespace, or a newline, found: a character. (\`UNEXPECTED_ERROR\`) (parser-no-errors) test/fixtures/parser-errors.html.erb:2:16 From 2df5ca83ed8a2e3038392bf54af237f28dc4669a Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Sun, 1 Mar 2026 06:39:24 +0100 Subject: [PATCH 3/3] Use append_unexpected_error instead of parser_append_unexpected_error --- src/parser.c | 13 ++- ...start_53797bc03ba9eee20bb33d3346e15f50.txt | 96 ++++++++++--------- ...start_c4d65ef876cd7488aa38dfab522aafbd.txt | 96 ++++++++++--------- ...g_tag_c8cfc11d3772c91f3dbb9a45f21c07a0.txt | 89 ++++++++--------- 4 files changed, 158 insertions(+), 136 deletions(-) diff --git a/src/parser.c b/src/parser.c index db6be639e..f82b355a3 100644 --- a/src/parser.c +++ b/src/parser.c @@ -645,7 +645,18 @@ static AST_HTML_ATTRIBUTE_VALUE_NODE_T* parser_parse_html_attribute_value(parser return value; } - parser_append_unexpected_error(parser, errors, "Unexpected Token", TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START); + char* expected = token_types_to_friendly_string(TOKEN_IDENTIFIER, TOKEN_QUOTE, TOKEN_ERB_START); + + append_unexpected_error( + "Unexpected Token", + expected, + token_type_to_friendly_string(parser->current_token->type), + parser->current_token->location.start, + parser->current_token->location.end, + errors + ); + + free(expected); AST_HTML_ATTRIBUTE_VALUE_NODE_T* value = ast_html_attribute_value_node_init( NULL, diff --git a/test/snapshots/parser/attributes_test/test_0115_attribute_with_equals_followed_by_new_tag_start_53797bc03ba9eee20bb33d3346e15f50.txt b/test/snapshots/parser/attributes_test/test_0115_attribute_with_equals_followed_by_new_tag_start_53797bc03ba9eee20bb33d3346e15f50.txt index 59932faa7..cf5e766ef 100644 --- a/test/snapshots/parser/attributes_test/test_0115_attribute_with_equals_followed_by_new_tag_start_53797bc03ba9eee20bb33d3346e15f50.txt +++ b/test/snapshots/parser/attributes_test/test_0115_attribute_with_equals_followed_by_new_tag_start_53797bc03ba9eee20bb33d3346e15f50.txt @@ -6,63 +6,67 @@ input: "
test
" └── children: (1 item) └── @ HTMLElementNode (location: (1:0)-(1:34)) ├── open_tag: - │ └── @ HTMLOpenTagNode (location: (1:0)-(1:17)) + │ └── @ HTMLOpenTagNode (location: (1:0)-(1:11)) + │ ├── errors: (1 error) + │ │ └── @ UnclosedOpenTagError (location: (1:1)-(1:11)) + │ │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ ├── tag_name: "div" (location: (1:1)-(1:4)) - │ ├── tag_closing: ">" (location: (1:16)-(1:17)) - │ ├── children: (2 items) - │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:16)) - │ │ │ ├── name: - │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) - │ │ │ │ └── children: (1 item) - │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) - │ │ │ │ └── content: "class" - │ │ │ │ - │ │ │ │ - │ │ │ ├── equals: "=" (location: (1:10)-(1:11)) - │ │ │ └── value: - │ │ │ └── @ HTMLAttributeValueNode (location: (1:12)-(1:16)) - │ │ │ ├── errors: (1 error) - │ │ │ │ └── @ UnexpectedError (location: (1:11)-(1:12)) - │ │ │ │ ├── message: "Unexpected Token. Expected: an identifier, a quote, or `<%`, found: `<`." - │ │ │ │ ├── description: "Unexpected Token" - │ │ │ │ ├── expected: "an identifier, a quote, or `<%`" - │ │ │ │ └── found: "`<`" - │ │ │ │ - │ │ │ ├── open_quote: ∅ - │ │ │ ├── children: [] - │ │ │ ├── close_quote: ∅ - │ │ │ └── quoted: false - │ │ │ - │ │ │ - │ │ └── @ HTMLAttributeNode (location: (1:12)-(1:16)) + │ ├── tag_closing: ∅ + │ ├── children: (1 item) + │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:12)) │ │ ├── name: - │ │ │ └── @ HTMLAttributeNameNode (location: (1:12)-(1:16)) + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) │ │ │ └── children: (1 item) - │ │ │ └── @ LiteralNode (location: (1:12)-(1:16)) - │ │ │ └── content: "span" + │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ └── content: "class" │ │ │ │ │ │ - │ │ ├── equals: ∅ - │ │ └── value: ∅ + │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:12)) + │ │ ├── errors: (1 error) + │ │ │ └── @ UnexpectedError (location: (1:11)-(1:12)) + │ │ │ ├── message: "Unexpected Token. Expected: an identifier, a quote, or `<%`, found: `<`." + │ │ │ ├── description: "Unexpected Token" + │ │ │ ├── expected: "an identifier, a quote, or `<%`" + │ │ │ └── found: "`<`" + │ │ │ + │ │ ├── open_quote: ∅ + │ │ ├── children: [] + │ │ ├── close_quote: ∅ + │ │ └── quoted: false + │ │ │ │ │ └── is_void: false │ ├── tag_name: "div" (location: (1:1)-(1:4)) - ├── body: (2 items) - │ ├── @ HTMLTextNode (location: (1:17)-(1:21)) - │ │ └── content: "test" - │ │ - │ └── @ HTMLCloseTagNode (location: (1:21)-(1:28)) - │ ├── errors: (1 error) - │ │ └── @ MissingOpeningTagError (location: (1:21)-(1:28)) - │ │ ├── message: "Found closing tag `` at (1:23) without a matching opening tag in the same scope." - │ │ └── closing_tag: "span" (location: (1:23)-(1:27)) + ├── body: (1 item) + │ └── @ HTMLElementNode (location: (1:11)-(1:28)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:11)-(1:17)) + │ │ ├── tag_opening: "<" (location: (1:11)-(1:12)) + │ │ ├── tag_name: "span" (location: (1:12)-(1:16)) + │ │ ├── tag_closing: ">" (location: (1:16)-(1:17)) + │ │ ├── children: [] + │ │ └── is_void: false │ │ - │ ├── tag_opening: "" (location: (1:27)-(1:28)) + │ ├── tag_name: "span" (location: (1:12)-(1:16)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:17)-(1:21)) + │ │ └── content: "test" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (1:21)-(1:28)) + │ │ ├── tag_opening: "" (location: (1:27)-(1:28)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:28)-(1:34)) diff --git a/test/snapshots/parser/attributes_test/test_0116_attribute_with_equals_and_space_followed_by_new_tag_start_c4d65ef876cd7488aa38dfab522aafbd.txt b/test/snapshots/parser/attributes_test/test_0116_attribute_with_equals_and_space_followed_by_new_tag_start_c4d65ef876cd7488aa38dfab522aafbd.txt index 475832245..b3770c1d5 100644 --- a/test/snapshots/parser/attributes_test/test_0116_attribute_with_equals_and_space_followed_by_new_tag_start_c4d65ef876cd7488aa38dfab522aafbd.txt +++ b/test/snapshots/parser/attributes_test/test_0116_attribute_with_equals_and_space_followed_by_new_tag_start_c4d65ef876cd7488aa38dfab522aafbd.txt @@ -6,63 +6,67 @@ input: "
test
" └── children: (1 item) └── @ HTMLElementNode (location: (1:0)-(1:35)) ├── open_tag: - │ └── @ HTMLOpenTagNode (location: (1:0)-(1:18)) + │ └── @ HTMLOpenTagNode (location: (1:0)-(1:12)) + │ ├── errors: (1 error) + │ │ └── @ UnclosedOpenTagError (location: (1:1)-(1:12)) + │ │ ├── message: "Opening tag `
` at (1:1) is missing closing `>`." + │ │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ │ │ ├── tag_opening: "<" (location: (1:0)-(1:1)) │ ├── tag_name: "div" (location: (1:1)-(1:4)) - │ ├── tag_closing: ">" (location: (1:17)-(1:18)) - │ ├── children: (2 items) - │ │ ├── @ HTMLAttributeNode (location: (1:5)-(1:17)) - │ │ │ ├── name: - │ │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) - │ │ │ │ └── children: (1 item) - │ │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) - │ │ │ │ └── content: "class" - │ │ │ │ - │ │ │ │ - │ │ │ ├── equals: "=" (location: (1:10)-(1:11)) - │ │ │ └── value: - │ │ │ └── @ HTMLAttributeValueNode (location: (1:13)-(1:17)) - │ │ │ ├── errors: (1 error) - │ │ │ │ └── @ UnexpectedError (location: (1:12)-(1:13)) - │ │ │ │ ├── message: "Unexpected Token. Expected: an identifier, a quote, or `<%`, found: `<`." - │ │ │ │ ├── description: "Unexpected Token" - │ │ │ │ ├── expected: "an identifier, a quote, or `<%`" - │ │ │ │ └── found: "`<`" - │ │ │ │ - │ │ │ ├── open_quote: ∅ - │ │ │ ├── children: [] - │ │ │ ├── close_quote: ∅ - │ │ │ └── quoted: false - │ │ │ - │ │ │ - │ │ └── @ HTMLAttributeNode (location: (1:13)-(1:17)) + │ ├── tag_closing: ∅ + │ ├── children: (1 item) + │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:13)) │ │ ├── name: - │ │ │ └── @ HTMLAttributeNameNode (location: (1:13)-(1:17)) + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) │ │ │ └── children: (1 item) - │ │ │ └── @ LiteralNode (location: (1:13)-(1:17)) - │ │ │ └── content: "span" + │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ └── content: "class" │ │ │ │ │ │ - │ │ ├── equals: ∅ - │ │ └── value: ∅ + │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:12)-(1:13)) + │ │ ├── errors: (1 error) + │ │ │ └── @ UnexpectedError (location: (1:12)-(1:13)) + │ │ │ ├── message: "Unexpected Token. Expected: an identifier, a quote, or `<%`, found: `<`." + │ │ │ ├── description: "Unexpected Token" + │ │ │ ├── expected: "an identifier, a quote, or `<%`" + │ │ │ └── found: "`<`" + │ │ │ + │ │ ├── open_quote: ∅ + │ │ ├── children: [] + │ │ ├── close_quote: ∅ + │ │ └── quoted: false + │ │ │ │ │ └── is_void: false │ ├── tag_name: "div" (location: (1:1)-(1:4)) - ├── body: (2 items) - │ ├── @ HTMLTextNode (location: (1:18)-(1:22)) - │ │ └── content: "test" - │ │ - │ └── @ HTMLCloseTagNode (location: (1:22)-(1:29)) - │ ├── errors: (1 error) - │ │ └── @ MissingOpeningTagError (location: (1:22)-(1:29)) - │ │ ├── message: "Found closing tag `` at (1:24) without a matching opening tag in the same scope." - │ │ └── closing_tag: "span" (location: (1:24)-(1:28)) + ├── body: (1 item) + │ └── @ HTMLElementNode (location: (1:12)-(1:29)) + │ ├── open_tag: + │ │ └── @ HTMLOpenTagNode (location: (1:12)-(1:18)) + │ │ ├── tag_opening: "<" (location: (1:12)-(1:13)) + │ │ ├── tag_name: "span" (location: (1:13)-(1:17)) + │ │ ├── tag_closing: ">" (location: (1:17)-(1:18)) + │ │ ├── children: [] + │ │ └── is_void: false │ │ - │ ├── tag_opening: "" (location: (1:28)-(1:29)) + │ ├── tag_name: "span" (location: (1:13)-(1:17)) + │ ├── body: (1 item) + │ │ └── @ HTMLTextNode (location: (1:18)-(1:22)) + │ │ └── content: "test" + │ │ + │ ├── close_tag: + │ │ └── @ HTMLCloseTagNode (location: (1:22)-(1:29)) + │ │ ├── tag_opening: "" (location: (1:28)-(1:29)) + │ │ + │ ├── is_void: false + │ └── source: "HTML" │ ├── close_tag: │ └── @ HTMLCloseTagNode (location: (1:29)-(1:35)) diff --git a/test/snapshots/parser/attributes_test/test_0120_attribute_with_equals_followed_by_closing_tag_c8cfc11d3772c91f3dbb9a45f21c07a0.txt b/test/snapshots/parser/attributes_test/test_0120_attribute_with_equals_followed_by_closing_tag_c8cfc11d3772c91f3dbb9a45f21c07a0.txt index 93dba287a..7b3090400 100644 --- a/test/snapshots/parser/attributes_test/test_0120_attribute_with_equals_followed_by_closing_tag_c8cfc11d3772c91f3dbb9a45f21c07a0.txt +++ b/test/snapshots/parser/attributes_test/test_0120_attribute_with_equals_followed_by_closing_tag_c8cfc11d3772c91f3dbb9a45f21c07a0.txt @@ -4,49 +4,52 @@ input: "
" --- @ DocumentNode (location: (1:0)-(1:17)) └── children: (1 item) - └── @ HTMLOpenTagNode (location: (1:0)-(1:17)) - ├── errors: (1 error) - │ └── @ MissingClosingTagError (location: (1:0)-(1:17)) - │ ├── message: "Opening tag `
` at (1:1) doesn't have a matching closing tag `
` in the same scope." - │ └── opening_tag: "div" (location: (1:1)-(1:4)) - │ - ├── tag_opening: "<" (location: (1:0)-(1:1)) - ├── tag_name: "div" (location: (1:1)-(1:4)) - ├── tag_closing: ">" (location: (1:16)-(1:17)) - ├── children: (2 items) - │ ├── @ HTMLAttributeNode (location: (1:5)-(1:16)) - │ │ ├── name: - │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) - │ │ │ └── children: (1 item) - │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) - │ │ │ └── content: "class" - │ │ │ - │ │ │ - │ │ ├── equals: "=" (location: (1:10)-(1:11)) - │ │ └── value: - │ │ └── @ HTMLAttributeValueNode (location: (1:13)-(1:16)) - │ │ ├── errors: (1 error) - │ │ │ └── @ UnexpectedError (location: (1:11)-(1:13)) - │ │ │ ├── message: "Unexpected Token. Expected: an identifier, a quote, or `<%`, found: `` at (1:1) is missing closing `>`." + │ │ └── tag_name: "div" (location: (1:1)-(1:4)) + │ │ + │ ├── tag_opening: "<" (location: (1:0)-(1:1)) + │ ├── tag_name: "div" (location: (1:1)-(1:4)) + │ ├── tag_closing: ∅ + │ ├── children: (1 item) + │ │ └── @ HTMLAttributeNode (location: (1:5)-(1:13)) + │ │ ├── name: + │ │ │ └── @ HTMLAttributeNameNode (location: (1:5)-(1:10)) + │ │ │ └── children: (1 item) + │ │ │ └── @ LiteralNode (location: (1:5)-(1:10)) + │ │ │ └── content: "class" + │ │ │ + │ │ │ + │ │ ├── equals: "=" (location: (1:10)-(1:11)) + │ │ └── value: + │ │ └── @ HTMLAttributeValueNode (location: (1:11)-(1:13)) + │ │ ├── errors: (1 error) + │ │ │ └── @ UnexpectedError (location: (1:11)-(1:13)) + │ │ │ ├── message: "Unexpected Token. Expected: an identifier, a quote, or `<%`, found: `" (location: (1:16)-(1:17)) │ - └── is_void: false \ No newline at end of file + ├── is_void: false + └── source: "HTML" \ No newline at end of file