Repository: nodejs/node
Pull request: deps: update merve to 1.2.0

This is an automated update of merve to 1.2.0.

Changed files:
filename: deps/merve/merve.cpp
patch: @@ -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<lexer_error> last_error;
+// Thread-local state for error tracking (safe for concurrent parse calls).
+thread_local std::optional<lexer_error> last_error;
+thread_local std::optional<error_location> 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<uint16_t, STACK_DEPTH> templateStack_;
   std::array<const char*, STACK_DEPTH> openTokenPosStack_;
+  std::array<char, STACK_DEPTH> openTokenTypeStack_;
   std::array<bool, STACK_DEPTH> openClassPosStack;
   std::array<StarExportBinding, MAX_STAR_EXPORTS> starExportStack_;
   StarExportBinding* starExportStack;
   const StarExportBinding* STAR_EXPORT_STACK_END;
 
-  std::vector<export_string>& exports;
-  std::vector<export_string>& re_exports;
+  std::vector<export_entry>& exports;
+  std::vector<export_entry>& 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<export_string>& out_exports, std::vector<export_string>& out_re_exports)
+  CJSLexer(std::vector<export_entry>& out_exports, std::vector<export_entry>& 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<uint16_t>::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<uint16_t>::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<uint16_t>::max() || openTokenDepth || last_error) {
       return false;
     }
@@ -1702,6 +1780,7 @@ class CJSLexer {
 
 std::optional<lexer_analysis> 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<lexer_error>& get_last_error() {
   return last_error;
 }
 
+const std::optional<error_location>& 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 <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * @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 <new>
+
+struct merve_analysis_impl {
+  std::optional<lexer::lexer_analysis> 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<lexer::error_location>& err_loc =
+        lexer::get_last_error_location();
+    if (err_loc.has_value()) {
+      merve_error_loc_set(out_err, err_loc.value());
+    }
+  }
+
+  return static_cast<merve_analysis>(impl);
+}
+
+bool merve_is_valid(merve_analysis result) {
+  if (!result) return false;
+  return static_cast<merve_analysis_impl*>(result)->result.has_value();
+}
+
+void merve_free(merve_analysis result) {
+  if (!result) return;
+  delete static_cast<merve_analysis_impl*>(result);
+}
+
+size_t merve_get_exports_count(merve_analysis result) {
+  if (!result) return 0;
+  merve_analysis_impl* impl = static_cast<merve_analysis_impl*>(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<merve_analysis_impl*>(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<merve_analysis_impl*>(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<merve_analysis_impl*>(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<merve_analysis_impl*>(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<merve_analysis_impl*>(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<lexer::lexer_error>& err = lexer::get_last_error();
+  if (!err.has_value()) return -1;
+  return static_cast<int>(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 */

filename: deps/merve/merve.h
patch: @@ -1,35 +1,37 @@
-/* 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.
  */
 #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 <cstddef>
+#include <cstdint>
 #include <optional>
 #include <string>
 #include <string_view>
@@ -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<std::string, std::string_view>;
 
+/**
+ * @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<export_string> exports{};
+  std::vector<export_entry> 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<export_string> re_exports{};
+  std::vector<export_entry> 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<lexer_analysis> parse_commonjs(std::string_view file_contents);
  */
 const std::optional<lexer_error>& get_last_error();
 
+/**
+ * @brief Get the location of the last failed parse operation.
+ *
+ * @return const std::optional<error_location>& 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<error_location>& 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 */

filename: deps/merve/merve_c.h
patch: @@ -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 <stdbool.h>
+#include <stddef.h>
+#include <stdint.h>
+
+/**
+ * @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 */

filename: src/node_cjs_lexer.cc
patch: @@ -22,8 +22,8 @@ using v8::String;
 using v8::Value;
 
 // Create a V8 string from an export_string variant, using fast path for ASCII
-inline Local<String> CreateString(Isolate* isolate,
-                                  const lexer::export_string& str) {
+template <typename T>
+inline Local<String> CreateString(Isolate* isolate, const T& str) {
   std::string_view sv = lexer::get_string_view(str);
 
   if (simdutf::validate_ascii(sv.data(), sv.size())) {