# merve (Rust)
Fast CommonJS export lexer for Rust. Extracts named exports and re-exports from
CommonJS modules via static analysis, without executing the code.
This crate provides safe Rust bindings to the [merve](https://github.com/nodejs/merve) C++ library.
## Usage
Add to your `Cargo.toml`:
```sh
cargo add merve
```
Parse CommonJS source and iterate over exports:
```rust
use merve::parse_commonjs;
let source = r#"
exports.foo = 1;
exports.bar = function() {};
module.exports.baz = 'hello';
"#;
let analysis = parse_commonjs(source).expect("parse failed");
for export in analysis.exports() {
println!("{} (line {})", export.name, export.line);
}
```
## Features
**std** (default): Enables `std::error::Error` impl for `LexerError`. Disable for `no_std`:
```toml
merve = { version = "...", default-features = false }
```
**libcpp**: Build the underlying C++ code with `libc++` instead of `libstdc++`.
Requires `libc++` to be installed:
```toml
merve = { version = "...", features = ["libcpp"] }
```
## API
### `parse_commonjs`
```rust
pub fn parse_commonjs(source: &str) -> Result<Analysis<'_>, LocatedLexerError>
```
Parse CommonJS source code and extract export information. The returned
`Analysis` borrows from `source` because export names may point directly into
the source buffer (zero-copy).
### `Analysis<'a>`
| `exports_count()` | `usize` | Number of named exports |
| `reexports_count()` | `usize` | Number of re-export specifiers |
| `export_name(index)` | `Option<&'a str>` | Export name at index |
| `export_line(index)` | `Option<NonZeroU32>` | 1-based non-zero line number of export |
| `reexport_name(index)` | `Option<&'a str>` | Re-export specifier at index |
| `reexport_line(index)` | `Option<NonZeroU32>` | 1-based non-zero line number of re-export |
| `exports()` | `ExportIter` | Iterator over all exports |
| `reexports()` | `ExportIter` | Iterator over all re-exports |
### `Export<'a>`
```rust
pub struct Export<'a> {
pub name: &'a str,
pub line: NonZeroU32,
}
```
### `LexerError`
Returned when the input contains ESM syntax or malformed constructs:
| `UnexpectedEsmImport` | Found `import` declaration |
| `UnexpectedEsmExport` | Found `export` declaration |
| `UnexpectedEsmImportMeta` | Found `import.meta` |
| `UnterminatedStringLiteral` | Unclosed string literal |
| `UnterminatedTemplateString` | Unclosed template literal |
| `UnterminatedRegex` | Unclosed regular expression |
| `UnexpectedParen` | Unexpected `)` |
| `UnexpectedBrace` | Unexpected `}` |
| `UnterminatedParen` | Unclosed `(` |
| `UnterminatedBrace` | Unclosed `{` |
| `TemplateNestOverflow` | Template literal nesting too deep |
`LexerError` implements `Display` and, with the `std` feature, `std::error::Error`.
### `LocatedLexerError`
```rust
pub struct LocatedLexerError {
pub kind: LexerError,
pub location: Option<ErrorLocation>,
}
```
`ErrorLocation` uses 1-based `line`/`column` (byte-oriented column).
### Versioning helpers
```rust
pub fn version() -> &'static str
pub fn version_components() -> (i32, i32, i32)
```
## Lifetime semantics
`Analysis<'a>` ties its lifetime to the source `&str` passed to `parse_commonjs`.
Export names returned by `export_name()` / the iterator borrow from the original
source buffer (the C++ library uses `std::string_view` for zero-copy export names).
Keep the source string alive as long as you access export names.
```rust
let source = String::from("exports.hello = 1;");
let analysis = merve::parse_commonjs(&source).unwrap();
// `analysis` borrows `source` -- both must stay alive
assert_eq!(analysis.export_name(0), Some("hello"));
```
## Thread safety
`Analysis` implements `Send` and `Sync`.
## License
Licensed under either of
* Apache License, Version 2.0
([LICENSE-APACHE](../LICENSE-APACHE) or <http://www.apache.org/licenses/LICENSE-2.0>)
* MIT license
([LICENSE-MIT](../LICENSE-MIT) or <http://opensource.org/licenses/MIT>)
at your option.