From 92a2ff9834bcdeae6426ab777ef2939527915e38 Mon Sep 17 00:00:00 2001 From: nodejs-github-bot <18269663+nodejs-github-bot@users.noreply.github.com> Date: Sun, 8 Mar 2026 00:49:12 +0000 Subject: [PATCH 1/6] deps: update merve to 1.2.0 --- deps/merve/BUILD.gn | 14 -- deps/merve/merve.cpp | 504 ++++++++++++++++++++++++++++++++++---- deps/merve/merve.h | 66 ++++- deps/merve/merve_c.h | 196 +++++++++++++++ deps/merve/unofficial.gni | 20 -- 5 files changed, 708 insertions(+), 92 deletions(-) delete mode 100644 deps/merve/BUILD.gn create mode 100644 deps/merve/merve_c.h delete mode 100644 deps/merve/unofficial.gni diff --git a/deps/merve/BUILD.gn b/deps/merve/BUILD.gn deleted file mode 100644 index 7bb318f8835dba..00000000000000 --- a/deps/merve/BUILD.gn +++ /dev/null @@ -1,14 +0,0 @@ -############################################################################## -# # -# DO NOT EDIT THIS FILE! # -# # -############################################################################## - -# This file is used by GN for building, which is NOT the build system used for -# building official binaries. -# Please modify the gyp files if you are making changes to build system. - -import("unofficial.gni") - -merve_gn_build("merve") { -} diff --git a/deps/merve/merve.cpp b/deps/merve/merve.cpp index f25aafc225ac43..9d8b7717c68f5a 100644 --- a/deps/merve/merve.cpp +++ b/deps/merve/merve.cpp @@ -1,4 +1,4 @@ -/* auto-generated on 2026-01-21 14:02:13 -0500. Do not edit! */ +/* auto-generated on 2026-03-06 11:46:19 -0500. Do not edit! */ #include "merve.h" /* begin file src/parser.cpp */ @@ -313,8 +313,42 @@ struct StarExportBinding { std::string_view id; }; -// Global state for error tracking -std::optional last_error; +// Thread-local state for error tracking (safe for concurrent parse calls). +thread_local std::optional last_error; +thread_local std::optional last_error_location; + +static error_location makeErrorLocation(const char* source, const char* end, const char* at) { + const char* target = at; + if (target < source) target = source; + if (target > end) target = end; + + uint32_t line = 1; + uint32_t column = 1; + const char* cur = source; + + while (cur < target) { + const char ch = *cur++; + if (ch == '\n') { + line++; + column = 1; + continue; + } + if (ch == '\r') { + line++; + column = 1; + if (cur < target && *cur == '\n') { + cur++; + } + continue; + } + column++; + } + + error_location loc{}; + loc.line = line; + loc.column = column; + return loc; +} // Lexer state class class CJSLexer { @@ -328,18 +362,29 @@ class CJSLexer { uint16_t openTokenDepth; uint16_t templateDepth; + uint32_t line; + bool lastSlashWasDivision; bool nextBraceIsClass; std::array templateStack_; std::array openTokenPosStack_; + std::array openTokenTypeStack_; std::array openClassPosStack; std::array starExportStack_; StarExportBinding* starExportStack; const StarExportBinding* STAR_EXPORT_STACK_END; - std::vector& exports; - std::vector& re_exports; + std::vector& exports; + std::vector& re_exports; + + // Increments `line` when consuming a line terminator. + // - Counts '\n' as a newline. + // - Counts '\r' as a newline only when it is not part of a CRLF sequence. + // (i.e., the next character is not '\n' or we're at end-of-input.) + void countNewline(char ch) { + line += (ch == '\n') || (ch == '\r' && (pos + 1 >= end || *(pos + 1) != '\n')); + } // Character classification helpers using lookup tables static bool isBr(char c) { @@ -476,9 +521,11 @@ class CJSLexer { } // Parsing utilities - void syntaxError(lexer_error code) { + void syntaxError(lexer_error code, const char* at = nullptr) { if (!last_error) { last_error = code; + const char* error_pos = at ? at : pos; + last_error_location = makeErrorLocation(source, end, error_pos); } pos = end + 1; } @@ -498,6 +545,8 @@ class CJSLexer { return ch; } else if (!isBrOrWs(ch)) { return ch; + } else { + countNewline(ch); } } while (pos++ < end); return ch; @@ -506,8 +555,10 @@ class CJSLexer { void lineComment() { while (pos++ < end) { char ch = *pos; - if (ch == '\n' || ch == '\r') + if (ch == '\n' || ch == '\r') { + countNewline(ch); return; + } } } @@ -519,6 +570,7 @@ class CJSLexer { pos++; return; } + countNewline(ch); } } @@ -530,8 +582,13 @@ class CJSLexer { if (ch == '\\') { if (pos + 1 >= end) break; ch = *++pos; - if (ch == '\r' && *(pos + 1) == '\n') - pos++; + if (ch == '\r') { + ++line; + if (*(pos + 1) == '\n') + pos++; + } else if (ch == '\n') { + ++line; + } } else if (isBr(ch)) break; } @@ -583,8 +640,12 @@ class CJSLexer { } if (ch == '`') return; - if (ch == '\\' && pos + 1 < end) + if (ch == '\\' && pos + 1 < end) { pos++; + countNewline(*pos); + } else { + countNewline(ch); + } } syntaxError(lexer_error::UNTERMINATED_TEMPLATE_STRING); } @@ -617,7 +678,7 @@ class CJSLexer { #endif } - void addExport(std::string_view export_name) { + void addExport(std::string_view export_name, uint32_t at_line) { // Skip surrounding quotes if present if (!export_name.empty() && (export_name.front() == '\'' || export_name.front() == '"')) { export_name.remove_prefix(1); @@ -628,11 +689,11 @@ class CJSLexer { if (!needsUnescaping(export_name)) { // Check if this export already exists (avoid duplicates) for (const auto& existing : exports) { - if (get_string_view(existing) == export_name) { + if (get_string_view(existing.name) == export_name) { return; // Already exists, skip } } - exports.push_back(export_name); + exports.push_back(export_entry{export_name, at_line}); return; } @@ -647,14 +708,14 @@ class CJSLexer { // Check if this export already exists (avoid duplicates) for (const auto& existing : exports) { - if (get_string_view(existing) == name) { + if (get_string_view(existing.name) == name) { return; // Already exists, skip } } - exports.push_back(std::move(unescaped.value())); + exports.push_back(export_entry{std::move(unescaped.value()), at_line}); } - void addReexport(std::string_view reexport_name) { + void addReexport(std::string_view reexport_name, uint32_t at_line) { // Skip surrounding quotes if present if (!reexport_name.empty() && (reexport_name.front() == '\'' || reexport_name.front() == '"')) { reexport_name.remove_prefix(1); @@ -663,7 +724,7 @@ class CJSLexer { // Fast path: no escaping needed, use string_view directly if (!needsUnescaping(reexport_name)) { - re_exports.push_back(reexport_name); + re_exports.push_back(export_entry{reexport_name, at_line}); return; } @@ -673,7 +734,7 @@ class CJSLexer { return; // Skip invalid escape sequences } - re_exports.push_back(std::move(unescaped.value())); + re_exports.push_back(export_entry{std::move(unescaped.value()), at_line}); } bool readExportsOrModuleDotExports(char ch) { @@ -715,7 +776,7 @@ class CJSLexer { switch (requireType) { case RequireType::ExportStar: case RequireType::ExportAssign: - addReexport(std::string_view(reexportStart, reexportEnd - reexportStart)); + addReexport(std::string_view(reexportStart, reexportEnd - reexportStart), line); return true; default: if (starExportStack < STAR_EXPORT_STACK_END) { @@ -752,20 +813,14 @@ class CJSLexer { const char* endPos = pos; ch = commentWhitespace(); - // Check if this is a getter syntax: get identifier() - if (ch != ':' && endPos - startPos == 3 && matchesAt(startPos, end, "get")) { - // Skip getter: get identifier() { ... } - if (identifier(ch)) { - ch = commentWhitespace(); - if (ch == '(') { - // This is a getter, stop parsing here (early termination) - pos = revertPos; - return; - } + // Check if this is a getter syntax: get identifier() { ... } + if (ch != ':' && endPos - startPos == 3 && matchesAt(startPos, end, "get") && identifier(ch)) { + ch = commentWhitespace(); + if (ch == '(') { + // This is a getter, stop parsing here (early termination) + pos = revertPos; + return; } - // Not a getter, revert and fail - pos = revertPos; - return; } if (ch == ':') { @@ -776,7 +831,7 @@ class CJSLexer { return; } } - addExport(std::string_view(startPos, endPos - startPos)); + addExport(std::string_view(startPos, endPos - startPos), line); } else if (ch == '\'' || ch == '"') { const char* start = pos; stringLiteral(ch); @@ -789,7 +844,7 @@ class CJSLexer { pos = revertPos; return; } - addExport(std::string_view(start, end_pos - start)); + addExport(std::string_view(start, end_pos - start), line); } } else if (ch == '.' && matchesAt(pos + 1, end, "..")) { pos += 3; @@ -828,7 +883,7 @@ class CJSLexer { const char* endPos = pos; ch = commentWhitespace(); if (ch == '=') { - addExport(std::string_view(startPos, endPos - startPos)); + addExport(std::string_view(startPos, endPos - startPos), line); return; } } @@ -846,7 +901,7 @@ class CJSLexer { pos++; ch = commentWhitespace(); if (ch != '=') break; - addExport(std::string_view(startPos, endPos - startPos)); + addExport(std::string_view(startPos, endPos - startPos), line); } break; } @@ -977,7 +1032,7 @@ class CJSLexer { ch = commentWhitespace(); if (ch != ':') break; if (exportStart && exportEnd) - addExport(std::string_view(exportStart, exportEnd - exportStart)); + addExport(std::string_view(exportStart, exportEnd - exportStart), line); pos = revertPos; return; } else if (ch == 'g') { @@ -1045,7 +1100,7 @@ class CJSLexer { ch = commentWhitespace(); if (ch != ')') break; if (exportStart && exportEnd) - addExport(std::string_view(exportStart, exportEnd - exportStart)); + addExport(std::string_view(exportStart, exportEnd - exportStart), line); return; } break; @@ -1409,7 +1464,7 @@ class CJSLexer { StarExportBinding* curCheckBinding = &starExportStack_[0]; while (curCheckBinding != starExportStack) { if (curCheckBinding->id == id) { - addReexport(curCheckBinding->specifier); + addReexport(curCheckBinding->specifier, line); pos = revertPos; return; } @@ -1467,6 +1522,7 @@ class CJSLexer { char ch = commentWhitespace(); switch (ch) { case '(': + openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = startPos; return; case '.': @@ -1480,7 +1536,7 @@ class CJSLexer { // It's something like import.metaData, not import.meta return; } - syntaxError(lexer_error::UNEXPECTED_ESM_IMPORT_META); + syntaxError(lexer_error::UNEXPECTED_ESM_IMPORT_META, startPos); } return; default: @@ -1495,25 +1551,27 @@ class CJSLexer { pos--; return; } - syntaxError(lexer_error::UNEXPECTED_ESM_IMPORT); + syntaxError(lexer_error::UNEXPECTED_ESM_IMPORT, startPos); } } void throwIfExportStatement() { + const char* startPos = pos; pos += 6; const char* curPos = pos; char ch = commentWhitespace(); if (pos == curPos && !isPunctuator(ch)) return; - syntaxError(lexer_error::UNEXPECTED_ESM_EXPORT); + syntaxError(lexer_error::UNEXPECTED_ESM_EXPORT, startPos); } public: - CJSLexer(std::vector& out_exports, std::vector& out_re_exports) + CJSLexer(std::vector& out_exports, std::vector& out_re_exports) : source(nullptr), pos(nullptr), end(nullptr), lastTokenPos(nullptr), templateStackDepth(0), openTokenDepth(0), templateDepth(0), + line(1), lastSlashWasDivision(false), nextBraceIsClass(false), - templateStack_{}, openTokenPosStack_{}, openClassPosStack{}, + templateStack_{}, openTokenPosStack_{}, openTokenTypeStack_{}, openClassPosStack{}, starExportStack_{}, starExportStack(nullptr), STAR_EXPORT_STACK_END(nullptr), exports(out_exports), re_exports(out_re_exports) {} @@ -1528,6 +1586,7 @@ class CJSLexer { templateStackDepth = 0; openTokenDepth = 0; templateDepth = std::numeric_limits::max(); + line = 1; lastSlashWasDivision = false; starExportStack = &starExportStack_[0]; STAR_EXPORT_STACK_END = &starExportStack_[MAX_STAR_EXPORTS - 1]; @@ -1552,8 +1611,10 @@ class CJSLexer { while (pos++ < end) { ch = *pos; - if (ch == ' ' || (ch < 14 && ch > 8)) + if (ch == ' ' || (ch < 14 && ch > 8)) { + countNewline(ch); continue; + } if (openTokenDepth == 0) { switch (ch) { @@ -1575,6 +1636,7 @@ class CJSLexer { pos += 23; if (*pos == '(') { pos++; + openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = lastTokenPos; if (tryParseRequire(RequireType::Import) && keywordStart(startPos)) tryBacktrackAddStarExportBinding(startPos - 1); @@ -1584,6 +1646,7 @@ class CJSLexer { if (pos + 4 < end && matchesAt(pos, end, "Star")) pos += 4; if (*pos == '(') { + openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = lastTokenPos; if (*(pos + 1) == 'r') { pos++; @@ -1618,6 +1681,7 @@ class CJSLexer { tryParseObjectDefineOrKeys(openTokenDepth == 0); break; case '(': + openTokenTypeStack_[openTokenDepth] = '('; openTokenPosStack_[openTokenDepth++] = lastTokenPos; break; case ')': @@ -1630,6 +1694,7 @@ class CJSLexer { case '{': openClassPosStack[openTokenDepth] = nextBraceIsClass; nextBraceIsClass = false; + openTokenTypeStack_[openTokenDepth] = '{'; openTokenPosStack_[openTokenDepth++] = lastTokenPos; break; case '}': @@ -1692,6 +1757,19 @@ class CJSLexer { lastTokenPos = pos; } + if (!last_error) { + if (templateDepth != std::numeric_limits::max()) { + syntaxError(lexer_error::UNTERMINATED_TEMPLATE_STRING, end); + } else if (openTokenDepth != 0) { + const char open_ch = openTokenTypeStack_[openTokenDepth - 1]; + if (open_ch == '{') { + syntaxError(lexer_error::UNTERMINATED_BRACE, end); + } else { + syntaxError(lexer_error::UNTERMINATED_PAREN, end); + } + } + } + if (templateDepth != std::numeric_limits::max() || openTokenDepth || last_error) { return false; } @@ -1702,6 +1780,7 @@ class CJSLexer { std::optional parse_commonjs(std::string_view file_contents) { last_error.reset(); + last_error_location.reset(); lexer_analysis result; CJSLexer lexer(result.exports, result.re_exports); @@ -1717,5 +1796,340 @@ const std::optional& get_last_error() { return last_error; } +const std::optional& get_last_error_location() { + return last_error_location; +} + } // namespace lexer /* end file src/parser.cpp */ +/* begin file src/merve_c.cpp */ +/* begin file include/merve_c.h */ +/** + * @file merve_c.h + * @brief Includes the C definitions for merve. This is a C file, not C++. + */ +#ifndef MERVE_C_H +#define MERVE_C_H + +#include +#include +#include + +/** + * @brief Non-owning string reference. + * + * The data pointer is NOT null-terminated. Always use the length field. + * + * The data is valid as long as: + * - The merve_analysis handle that produced it has not been freed. + * - For string_view-backed exports: the original source buffer is alive. + */ +typedef struct { + const char* data; + size_t length; +} merve_string; + +/** + * @brief Opaque handle to a CommonJS parse result. + * + * Created by merve_parse_commonjs(). Must be freed with merve_free(). + */ +typedef void* merve_analysis; + +/** + * @brief Version number components. + */ +typedef struct { + int major; + int minor; + int revision; +} merve_version_components; + +/** + * @brief Source location for a parse error. + * + * - line and column are 1-based. + * - column is byte-oriented. + * + * A zeroed location (`{0, 0}`) means the location is unavailable. + */ +typedef struct { + uint32_t line; + uint32_t column; +} merve_error_loc; + +/* Error codes corresponding to lexer::lexer_error values. */ +#define MERVE_ERROR_TODO 0 +#define MERVE_ERROR_UNEXPECTED_PAREN 1 +#define MERVE_ERROR_UNEXPECTED_BRACE 2 +#define MERVE_ERROR_UNTERMINATED_PAREN 3 +#define MERVE_ERROR_UNTERMINATED_BRACE 4 +#define MERVE_ERROR_UNTERMINATED_TEMPLATE_STRING 5 +#define MERVE_ERROR_UNTERMINATED_STRING_LITERAL 6 +#define MERVE_ERROR_UNTERMINATED_REGEX_CHARACTER_CLASS 7 +#define MERVE_ERROR_UNTERMINATED_REGEX 8 +#define MERVE_ERROR_UNEXPECTED_ESM_IMPORT_META 9 +#define MERVE_ERROR_UNEXPECTED_ESM_IMPORT 10 +#define MERVE_ERROR_UNEXPECTED_ESM_EXPORT 11 +#define MERVE_ERROR_TEMPLATE_NEST_OVERFLOW 12 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Parse CommonJS source code and optionally return error location. + * + * The source buffer must remain valid while accessing string_view-backed + * export names from the returned handle. + * + * If @p out_err is non-NULL, it is always written: + * - On success: set to {0, 0}. + * - On parse failure with known location: set to that location. + * - On parse failure without available location: set to {0, 0}. + * + * You must call merve_free() on the returned handle when done. + * + * @param input Pointer to the JavaScript source (need not be + * null-terminated). NULL is treated as an empty string. + * @param length Length of the input in bytes. + * @param out_err Optional output pointer for parse error location. + * @return A handle to the parse result, or NULL on out-of-memory. + * Use merve_is_valid() to check if parsing succeeded. + */ +#ifdef __cplusplus +merve_analysis merve_parse_commonjs(const char* input, size_t length, + merve_error_loc* out_err = nullptr); +#else +merve_analysis merve_parse_commonjs(const char* input, size_t length, + merve_error_loc* out_err); +#endif + +/** + * Check whether the parse result is valid (parsing succeeded). + * + * @param result Handle returned by merve_parse_commonjs(). NULL returns false. + * @return true if parsing succeeded, false otherwise. + */ +bool merve_is_valid(merve_analysis result); + +/** + * Free a parse result and all associated memory. + * + * @param result Handle returned by merve_parse_commonjs(). NULL is a no-op. + */ +void merve_free(merve_analysis result); + +/** + * Get the number of named exports found. + * + * @param result A parse result handle. NULL returns 0. + * @return Number of exports, or 0 if result is NULL or invalid. + */ +size_t merve_get_exports_count(merve_analysis result); + +/** + * Get the number of re-export module specifiers found. + * + * @param result A parse result handle. NULL returns 0. + * @return Number of re-exports, or 0 if result is NULL or invalid. + */ +size_t merve_get_reexports_count(merve_analysis result); + +/** + * Get the name of an export at the given index. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_exports_count()). + * @return Non-owning string reference. Returns {NULL, 0} on error. + */ +merve_string merve_get_export_name(merve_analysis result, size_t index); + +/** + * Get the 1-based source line number of an export. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_exports_count()). + * @return 1-based line number, or 0 on error. + */ +uint32_t merve_get_export_line(merve_analysis result, size_t index); + +/** + * Get the module specifier of a re-export at the given index. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_reexports_count()). + * @return Non-owning string reference. Returns {NULL, 0} on error. + */ +merve_string merve_get_reexport_name(merve_analysis result, size_t index); + +/** + * Get the 1-based source line number of a re-export. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_reexports_count()). + * @return 1-based line number, or 0 on error. + */ +uint32_t merve_get_reexport_line(merve_analysis result, size_t index); + +/** + * Get the error code from the last merve_parse_commonjs() call. + * + * @return One of the MERVE_ERROR_* constants, or -1 if the last parse + * succeeded. + * @note This is global state, overwritten by each merve_parse_commonjs() call. + */ +int merve_get_last_error(void); + +/** + * Get the merve library version string. + * + * @return Null-terminated version string (e.g. "1.0.1"). Never NULL. + */ +const char* merve_get_version(void); + +/** + * Get the merve library version as individual components. + * + * @return Struct with major, minor, and revision fields. + */ +merve_version_components merve_get_version_components(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MERVE_C_H */ +/* end file include/merve_c.h */ + +#include + +struct merve_analysis_impl { + std::optional result{}; +}; + +static merve_string merve_string_create(const char* data, size_t length) { + merve_string out{}; + out.data = data; + out.length = length; + return out; +} + +static void merve_error_loc_clear(merve_error_loc* out_err) { + if (!out_err) return; + out_err->line = 0; + out_err->column = 0; +} + +static void merve_error_loc_set(merve_error_loc* out_err, + const lexer::error_location& loc) { + if (!out_err) return; + out_err->line = loc.line; + out_err->column = loc.column; +} + +extern "C" { + +merve_analysis merve_parse_commonjs(const char* input, size_t length, + merve_error_loc* out_err) { + merve_error_loc_clear(out_err); + + merve_analysis_impl* impl = new (std::nothrow) merve_analysis_impl(); + if (!impl) return nullptr; + if (input != nullptr) { + impl->result = lexer::parse_commonjs(std::string_view(input, length)); + } else { + impl->result = lexer::parse_commonjs(std::string_view("", 0)); + } + + if (!impl->result.has_value() && out_err) { + const std::optional& err_loc = + lexer::get_last_error_location(); + if (err_loc.has_value()) { + merve_error_loc_set(out_err, err_loc.value()); + } + } + + return static_cast(impl); +} + +bool merve_is_valid(merve_analysis result) { + if (!result) return false; + return static_cast(result)->result.has_value(); +} + +void merve_free(merve_analysis result) { + if (!result) return; + delete static_cast(result); +} + +size_t merve_get_exports_count(merve_analysis result) { + if (!result) return 0; + merve_analysis_impl* impl = static_cast(result); + if (!impl->result.has_value()) return 0; + return impl->result->exports.size(); +} + +size_t merve_get_reexports_count(merve_analysis result) { + if (!result) return 0; + merve_analysis_impl* impl = static_cast(result); + if (!impl->result.has_value()) return 0; + return impl->result->re_exports.size(); +} + +merve_string merve_get_export_name(merve_analysis result, size_t index) { + if (!result) return merve_string_create(nullptr, 0); + merve_analysis_impl* impl = static_cast(result); + if (!impl->result.has_value()) return merve_string_create(nullptr, 0); + if (index >= impl->result->exports.size()) + return merve_string_create(nullptr, 0); + std::string_view sv = + lexer::get_string_view(impl->result->exports[index]); + return merve_string_create(sv.data(), sv.size()); +} + +uint32_t merve_get_export_line(merve_analysis result, size_t index) { + if (!result) return 0; + merve_analysis_impl* impl = static_cast(result); + if (!impl->result.has_value()) return 0; + if (index >= impl->result->exports.size()) return 0; + return impl->result->exports[index].line; +} + +merve_string merve_get_reexport_name(merve_analysis result, size_t index) { + if (!result) return merve_string_create(nullptr, 0); + merve_analysis_impl* impl = static_cast(result); + if (!impl->result.has_value()) return merve_string_create(nullptr, 0); + if (index >= impl->result->re_exports.size()) + return merve_string_create(nullptr, 0); + std::string_view sv = + lexer::get_string_view(impl->result->re_exports[index]); + return merve_string_create(sv.data(), sv.size()); +} + +uint32_t merve_get_reexport_line(merve_analysis result, size_t index) { + if (!result) return 0; + merve_analysis_impl* impl = static_cast(result); + if (!impl->result.has_value()) return 0; + if (index >= impl->result->re_exports.size()) return 0; + return impl->result->re_exports[index].line; +} + +int merve_get_last_error(void) { + const std::optional& err = lexer::get_last_error(); + if (!err.has_value()) return -1; + return static_cast(err.value()); +} + +const char* merve_get_version(void) { return MERVE_VERSION; } + +merve_version_components merve_get_version_components(void) { + merve_version_components vc{}; + vc.major = lexer::MERVE_VERSION_MAJOR; + vc.minor = lexer::MERVE_VERSION_MINOR; + vc.revision = lexer::MERVE_VERSION_REVISION; + return vc; +} + +} /* extern "C" */ +/* end file src/merve_c.cpp */ diff --git a/deps/merve/merve.h b/deps/merve/merve.h index 3bde8fa0fa00a1..a783ed2094b209 100644 --- a/deps/merve/merve.h +++ b/deps/merve/merve.h @@ -1,13 +1,13 @@ -/* auto-generated on 2026-01-21 14:02:13 -0500. Do not edit! */ -/* begin file include/lexer.h */ +/* auto-generated on 2026-03-06 11:46:19 -0500. Do not edit! */ +/* begin file include/merve.h */ #ifndef MERVE_H #define MERVE_H -/* begin file include/lexer/parser.h */ +/* begin file include/merve/parser.h */ #ifndef MERVE_PARSER_H #define MERVE_PARSER_H -/* begin file include/lexer/version.h */ +/* begin file include/merve/version.h */ /** * @file version.h * @brief Definitions for merve's version number. @@ -15,21 +15,23 @@ #ifndef MERVE_VERSION_H #define MERVE_VERSION_H -#define MERVE_VERSION "1.0.0" +#define MERVE_VERSION "1.2.0" // x-release-please-version namespace lexer { enum { - MERVE_VERSION_MAJOR = 1, - MERVE_VERSION_MINOR = 0, - MERVE_VERSION_REVISION = 0, + MERVE_VERSION_MAJOR = 1, // x-release-please-major + MERVE_VERSION_MINOR = 2, // x-release-please-minor + MERVE_VERSION_REVISION = 0, // x-release-please-patch }; } // namespace lexer #endif // MERVE_VERSION_H -/* end file include/lexer/version.h */ +/* end file include/merve/version.h */ +#include +#include #include #include #include @@ -63,6 +65,17 @@ enum lexer_error { TEMPLATE_NEST_OVERFLOW, ///< Template literal nesting too deep }; +/** + * @brief Source location information for a parse error. + * + * - line and column are 1-based. + * - column is byte-oriented. + */ +struct error_location { + uint32_t line; + uint32_t column; +}; + /** * @brief Type alias for export names. * @@ -74,6 +87,14 @@ enum lexer_error { */ using export_string = std::variant; +/** + * @brief An export name together with its 1-based source line number. + */ +struct export_entry { + export_string name; + uint32_t line; // 1-based line number +}; + /** * @brief Result of parsing a CommonJS module. */ @@ -88,7 +109,7 @@ struct lexer_analysis { * - module.exports = { a, b, c } * - Object.defineProperty(exports, 'name', {...}) */ - std::vector exports{}; + std::vector exports{}; /** * @brief Module specifiers from re-export patterns. @@ -99,7 +120,7 @@ struct lexer_analysis { * - __export(require('other')) * - Object.keys(require('other')).forEach(...) */ - std::vector re_exports{}; + std::vector re_exports{}; }; /** @@ -116,6 +137,13 @@ inline std::string_view get_string_view(const export_string& s) { return std::visit([](const auto& v) -> std::string_view { return v; }, s); } +/** + * @brief Get a string_view from an export_entry (delegates to the name field). + */ +inline std::string_view get_string_view(const export_entry& e) { + return get_string_view(e.name); +} + /** * @brief Parse CommonJS source code and extract export information. * @@ -157,10 +185,22 @@ std::optional parse_commonjs(std::string_view file_contents); */ const std::optional& get_last_error(); +/** + * @brief Get the location of the last failed parse operation. + * + * @return const std::optional& The last error location, or + * std::nullopt if unavailable. + * + * @note This is global state and may be overwritten by subsequent calls + * to parse_commonjs(). + * @note Location tracking is best-effort and may be unavailable. + */ +const std::optional& get_last_error_location(); + } // namespace lexer #endif // MERVE_PARSER_H -/* end file include/lexer/parser.h */ +/* end file include/merve/parser.h */ #endif // MERVE_H -/* end file include/lexer.h */ +/* end file include/merve.h */ diff --git a/deps/merve/merve_c.h b/deps/merve/merve_c.h new file mode 100644 index 00000000000000..035ce21f75736f --- /dev/null +++ b/deps/merve/merve_c.h @@ -0,0 +1,196 @@ +/** + * @file merve_c.h + * @brief Includes the C definitions for merve. This is a C file, not C++. + */ +#ifndef MERVE_C_H +#define MERVE_C_H + +#include +#include +#include + +/** + * @brief Non-owning string reference. + * + * The data pointer is NOT null-terminated. Always use the length field. + * + * The data is valid as long as: + * - The merve_analysis handle that produced it has not been freed. + * - For string_view-backed exports: the original source buffer is alive. + */ +typedef struct { + const char* data; + size_t length; +} merve_string; + +/** + * @brief Opaque handle to a CommonJS parse result. + * + * Created by merve_parse_commonjs(). Must be freed with merve_free(). + */ +typedef void* merve_analysis; + +/** + * @brief Version number components. + */ +typedef struct { + int major; + int minor; + int revision; +} merve_version_components; + +/** + * @brief Source location for a parse error. + * + * - line and column are 1-based. + * - column is byte-oriented. + * + * A zeroed location (`{0, 0}`) means the location is unavailable. + */ +typedef struct { + uint32_t line; + uint32_t column; +} merve_error_loc; + +/* Error codes corresponding to lexer::lexer_error values. */ +#define MERVE_ERROR_TODO 0 +#define MERVE_ERROR_UNEXPECTED_PAREN 1 +#define MERVE_ERROR_UNEXPECTED_BRACE 2 +#define MERVE_ERROR_UNTERMINATED_PAREN 3 +#define MERVE_ERROR_UNTERMINATED_BRACE 4 +#define MERVE_ERROR_UNTERMINATED_TEMPLATE_STRING 5 +#define MERVE_ERROR_UNTERMINATED_STRING_LITERAL 6 +#define MERVE_ERROR_UNTERMINATED_REGEX_CHARACTER_CLASS 7 +#define MERVE_ERROR_UNTERMINATED_REGEX 8 +#define MERVE_ERROR_UNEXPECTED_ESM_IMPORT_META 9 +#define MERVE_ERROR_UNEXPECTED_ESM_IMPORT 10 +#define MERVE_ERROR_UNEXPECTED_ESM_EXPORT 11 +#define MERVE_ERROR_TEMPLATE_NEST_OVERFLOW 12 + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Parse CommonJS source code and optionally return error location. + * + * The source buffer must remain valid while accessing string_view-backed + * export names from the returned handle. + * + * If @p out_err is non-NULL, it is always written: + * - On success: set to {0, 0}. + * - On parse failure with known location: set to that location. + * - On parse failure without available location: set to {0, 0}. + * + * You must call merve_free() on the returned handle when done. + * + * @param input Pointer to the JavaScript source (need not be + * null-terminated). NULL is treated as an empty string. + * @param length Length of the input in bytes. + * @param out_err Optional output pointer for parse error location. + * @return A handle to the parse result, or NULL on out-of-memory. + * Use merve_is_valid() to check if parsing succeeded. + */ +#ifdef __cplusplus +merve_analysis merve_parse_commonjs(const char* input, size_t length, + merve_error_loc* out_err = nullptr); +#else +merve_analysis merve_parse_commonjs(const char* input, size_t length, + merve_error_loc* out_err); +#endif + +/** + * Check whether the parse result is valid (parsing succeeded). + * + * @param result Handle returned by merve_parse_commonjs(). NULL returns false. + * @return true if parsing succeeded, false otherwise. + */ +bool merve_is_valid(merve_analysis result); + +/** + * Free a parse result and all associated memory. + * + * @param result Handle returned by merve_parse_commonjs(). NULL is a no-op. + */ +void merve_free(merve_analysis result); + +/** + * Get the number of named exports found. + * + * @param result A parse result handle. NULL returns 0. + * @return Number of exports, or 0 if result is NULL or invalid. + */ +size_t merve_get_exports_count(merve_analysis result); + +/** + * Get the number of re-export module specifiers found. + * + * @param result A parse result handle. NULL returns 0. + * @return Number of re-exports, or 0 if result is NULL or invalid. + */ +size_t merve_get_reexports_count(merve_analysis result); + +/** + * Get the name of an export at the given index. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_exports_count()). + * @return Non-owning string reference. Returns {NULL, 0} on error. + */ +merve_string merve_get_export_name(merve_analysis result, size_t index); + +/** + * Get the 1-based source line number of an export. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_exports_count()). + * @return 1-based line number, or 0 on error. + */ +uint32_t merve_get_export_line(merve_analysis result, size_t index); + +/** + * Get the module specifier of a re-export at the given index. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_reexports_count()). + * @return Non-owning string reference. Returns {NULL, 0} on error. + */ +merve_string merve_get_reexport_name(merve_analysis result, size_t index); + +/** + * Get the 1-based source line number of a re-export. + * + * @param result A valid parse result handle. + * @param index Zero-based index (must be < merve_get_reexports_count()). + * @return 1-based line number, or 0 on error. + */ +uint32_t merve_get_reexport_line(merve_analysis result, size_t index); + +/** + * Get the error code from the last merve_parse_commonjs() call. + * + * @return One of the MERVE_ERROR_* constants, or -1 if the last parse + * succeeded. + * @note This is global state, overwritten by each merve_parse_commonjs() call. + */ +int merve_get_last_error(void); + +/** + * Get the merve library version string. + * + * @return Null-terminated version string (e.g. "1.0.1"). Never NULL. + */ +const char* merve_get_version(void); + +/** + * Get the merve library version as individual components. + * + * @return Struct with major, minor, and revision fields. + */ +merve_version_components merve_get_version_components(void); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + +#endif /* MERVE_C_H */ diff --git a/deps/merve/unofficial.gni b/deps/merve/unofficial.gni deleted file mode 100644 index dfb508d1d22f84..00000000000000 --- a/deps/merve/unofficial.gni +++ /dev/null @@ -1,20 +0,0 @@ -# This file is used by GN for building, which is NOT the build system used for -# building official binaries. -# Please edit the gyp files if you are making changes to build system. - -# The actual configurations are put inside a template in unofficial.gni to -# prevent accidental edits from contributors. -template("merve_gn_build") { - config("merve_config") { - include_dirs = [ "." ] - } - gypi_values = exec_script("../../tools/gypi_to_gn.py", - [ rebase_path("merve.gyp") ], - "scope", - [ "merve.gyp" ]) - source_set(target_name) { - forward_variables_from(invoker, "*") - public_configs = [ ":merve_config" ] - sources = gypi_values.merve_sources - } -} From 3c78a769bee841095f73fc8d85c530955641963e Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 9 Mar 2026 08:57:02 -0400 Subject: [PATCH 2/6] fixup! deps: update merve to 1.2.0 --- src/node_cjs_lexer.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/node_cjs_lexer.cc b/src/node_cjs_lexer.cc index 1b44dec9174841..edee8d23b0dbd2 100644 --- a/src/node_cjs_lexer.cc +++ b/src/node_cjs_lexer.cc @@ -71,14 +71,14 @@ void Parse(const FunctionCallbackInfo& args) { // Convert exports to JS Set Local exports_set = Set::New(isolate); for (const auto& exp : analysis.exports) { - exports_set->Add(context, CreateString(isolate, exp)).ToLocalChecked(); + exports_set->Add(context, CreateString(isolate, exp.name)).ToLocalChecked(); } // Convert reexports to JS array using batch creation LocalVector reexports_vec(isolate); reexports_vec.reserve(analysis.re_exports.size()); for (const auto& reexp : analysis.re_exports) { - reexports_vec.push_back(CreateString(isolate, reexp)); + reexports_vec.push_back(CreateString(isolate, reexp.name)); } // Create result array [exports (Set), reexports (Array)] From fbd4685484bacee297c492af4499b29b9f5226d7 Mon Sep 17 00:00:00 2001 From: Yagiz Nizipli Date: Mon, 9 Mar 2026 08:57:02 -0400 Subject: [PATCH 3/6] fixup! deps: update merve to 1.2.0 --- deps/merve/BUILD.gn | 14 ++++++++++++++ deps/merve/unofficial.gni | 20 ++++++++++++++++++++ src/node_cjs_lexer.cc | 10 ++++++++-- 3 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 deps/merve/BUILD.gn create mode 100644 deps/merve/unofficial.gni diff --git a/deps/merve/BUILD.gn b/deps/merve/BUILD.gn new file mode 100644 index 00000000000000..7bb318f8835dba --- /dev/null +++ b/deps/merve/BUILD.gn @@ -0,0 +1,14 @@ +############################################################################## +# # +# DO NOT EDIT THIS FILE! # +# # +############################################################################## + +# This file is used by GN for building, which is NOT the build system used for +# building official binaries. +# Please modify the gyp files if you are making changes to build system. + +import("unofficial.gni") + +merve_gn_build("merve") { +} diff --git a/deps/merve/unofficial.gni b/deps/merve/unofficial.gni new file mode 100644 index 00000000000000..dfb508d1d22f84 --- /dev/null +++ b/deps/merve/unofficial.gni @@ -0,0 +1,20 @@ +# This file is used by GN for building, which is NOT the build system used for +# building official binaries. +# Please edit the gyp files if you are making changes to build system. + +# The actual configurations are put inside a template in unofficial.gni to +# prevent accidental edits from contributors. +template("merve_gn_build") { + config("merve_config") { + include_dirs = [ "." ] + } + gypi_values = exec_script("../../tools/gypi_to_gn.py", + [ rebase_path("merve.gyp") ], + "scope", + [ "merve.gyp" ]) + source_set(target_name) { + forward_variables_from(invoker, "*") + public_configs = [ ":merve_config" ] + sources = gypi_values.merve_sources + } +} diff --git a/src/node_cjs_lexer.cc b/src/node_cjs_lexer.cc index edee8d23b0dbd2..b245f74e63c0e1 100644 --- a/src/node_cjs_lexer.cc +++ b/src/node_cjs_lexer.cc @@ -6,6 +6,12 @@ #include "merve.h" +#ifdef MERVE_ERROR_TODO +#define DOT_NAME(exp) (exp.name) +#else +#define DOT_NAME(exp) (exp) +#endif + namespace node { namespace cjs_lexer { @@ -71,14 +77,14 @@ void Parse(const FunctionCallbackInfo& args) { // Convert exports to JS Set Local exports_set = Set::New(isolate); for (const auto& exp : analysis.exports) { - exports_set->Add(context, CreateString(isolate, exp.name)).ToLocalChecked(); + exports_set->Add(context, CreateString(isolate, DOT_NAME(exp))).ToLocalChecked(); } // Convert reexports to JS array using batch creation LocalVector reexports_vec(isolate); reexports_vec.reserve(analysis.re_exports.size()); for (const auto& reexp : analysis.re_exports) { - reexports_vec.push_back(CreateString(isolate, reexp.name)); + reexports_vec.push_back(CreateString(isolate, DOT_NAME(reexp))); } // Create result array [exports (Set), reexports (Array)] From afcff170a467dc987a0f6592d26cf0f3851d7aff Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 9 Mar 2026 14:41:59 +0100 Subject: [PATCH 4/6] fixup! deps: update merve to 1.2.0 lint --- src/node_cjs_lexer.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/node_cjs_lexer.cc b/src/node_cjs_lexer.cc index b245f74e63c0e1..af9389d41f5b9f 100644 --- a/src/node_cjs_lexer.cc +++ b/src/node_cjs_lexer.cc @@ -77,7 +77,8 @@ void Parse(const FunctionCallbackInfo& args) { // Convert exports to JS Set Local exports_set = Set::New(isolate); for (const auto& exp : analysis.exports) { - exports_set->Add(context, CreateString(isolate, DOT_NAME(exp))).ToLocalChecked(); + exports_set->Add(context, CreateString(isolate, DOT_NAME(exp))) + .ToLocalChecked(); } // Convert reexports to JS array using batch creation From a4eb3ce7382cf9f36f5d7df464c8100d39332a92 Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 9 Mar 2026 16:10:54 +0100 Subject: [PATCH 5/6] fixup! deps: update merve to 1.2.0 --- src/node_cjs_lexer.cc | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/src/node_cjs_lexer.cc b/src/node_cjs_lexer.cc index af9389d41f5b9f..68290dc6772726 100644 --- a/src/node_cjs_lexer.cc +++ b/src/node_cjs_lexer.cc @@ -6,12 +6,6 @@ #include "merve.h" -#ifdef MERVE_ERROR_TODO -#define DOT_NAME(exp) (exp.name) -#else -#define DOT_NAME(exp) (exp) -#endif - namespace node { namespace cjs_lexer { @@ -28,8 +22,9 @@ using v8::String; using v8::Value; // Create a V8 string from an export_string variant, using fast path for ASCII +template inline Local CreateString(Isolate* isolate, - const lexer::export_string& str) { + const T& str) { std::string_view sv = lexer::get_string_view(str); if (simdutf::validate_ascii(sv.data(), sv.size())) { @@ -77,15 +72,14 @@ void Parse(const FunctionCallbackInfo& args) { // Convert exports to JS Set Local exports_set = Set::New(isolate); for (const auto& exp : analysis.exports) { - exports_set->Add(context, CreateString(isolate, DOT_NAME(exp))) - .ToLocalChecked(); + exports_set->Add(context, CreateString(isolate, exp)).ToLocalChecked(); } // Convert reexports to JS array using batch creation LocalVector reexports_vec(isolate); reexports_vec.reserve(analysis.re_exports.size()); for (const auto& reexp : analysis.re_exports) { - reexports_vec.push_back(CreateString(isolate, DOT_NAME(reexp))); + reexports_vec.push_back(CreateString(isolate, reexp)); } // Create result array [exports (Set), reexports (Array)] From 951a9877b6b9565a5629f300266dc64b169ad8dd Mon Sep 17 00:00:00 2001 From: Antoine du Hamel Date: Mon, 9 Mar 2026 16:17:05 +0100 Subject: [PATCH 6/6] fixup! fixup! deps: update merve to 1.2.0 --- src/node_cjs_lexer.cc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/node_cjs_lexer.cc b/src/node_cjs_lexer.cc index 68290dc6772726..1aac8d4b4b1b2b 100644 --- a/src/node_cjs_lexer.cc +++ b/src/node_cjs_lexer.cc @@ -23,8 +23,7 @@ using v8::Value; // Create a V8 string from an export_string variant, using fast path for ASCII template -inline Local CreateString(Isolate* isolate, - const T& str) { +inline Local CreateString(Isolate* isolate, const T& str) { std::string_view sv = lexer::get_string_view(str); if (simdutf::validate_ascii(sv.data(), sv.size())) {