Character Parser for Template Snippets
`character-parser` is a focused JavaScript utility library designed for parsing JavaScript code snippets, particularly within template languages like EJS or Jade. Its primary function is to robustly handle bracket nesting, strings, and comments, allowing for the reliable extraction of delimited JavaScript sections without performing a full syntax validation or abstract syntax tree (AST) generation. The current stable version is 4.0.0, which introduced significant changes including native ES module support and updated package export configurations. This library differentiates itself by offering a lightweight, character-by-character parsing mechanism and state management, providing functions to parse entire strings or single characters, track nesting depth, and find code segments based on custom delimiters while intelligently ignoring content within nested structures or comments. It does not aim to be a comprehensive JavaScript parser but rather a precise tool for template-driven code extraction.
Common errors
-
ERR_PACKAGE_PATH_NOT_EXPORTED
cause Attempting to import a module from an internal path (e.g., `character-parser/lib/util`) instead of the main package export.fixChange the import path to reference only the main package: `import { someExport } from 'character-parser'`. -
TypeError: parse is not a function
cause Trying to use CommonJS `require()` for named exports in a context where `character-parser@4.0.0` is resolved as an ES Module, or attempting a default import for a named export.fixUse ES module named imports: `import { parse, parseUntil } from 'character-parser'`. Ensure your environment supports ES Modules or transpile accordingly. -
CHARACTER_PARSER:UNTERMINATED_STRING
cause The input JavaScript snippet contains an unclosed string literal.fixReview the input string for syntax errors, specifically ensuring all string delimiters (single quotes, double quotes, backticks) are properly matched and escaped where necessary.
Warnings
- breaking Version 4.0.0 removed official support for Flow types. While the library itself is written in TypeScript and ships with TypeScript types, Flow users will need to manage their own type definitions or downgrade.
- breaking The `package.json` now includes an `exports` field, which restricts available imports to only the main entry point (`'character-parser'`). Importing arbitrary internal files (e.g., `character-parser/lib/some-internal-file`) is no longer supported and will result in module resolution errors in environments that respect the `exports` field.
- gotcha All parsing methods (`parse`, `parseUntil`, `parseChar`) may throw exceptions in case of syntax errors (e.g., unclosed brackets or strings). These exceptions contain a `code` property prefixed with `CHARACTER_PARSER:` for specific error identification.
- gotcha When using `parse()` to process a string in multiple sections, it is critical to pass the `state` object returned by the previous `parse()` call to the subsequent call. Failing to do so will reset the parser's internal state (e.g., stack of open brackets, in-string status), leading to incorrect parsing results.
Install
-
npm install character-parser -
yarn add character-parser -
pnpm add character-parser
Imports
- parse
const parse = require('character-parser').parseimport { parse } from 'character-parser' - parseUntil
import parseUntil from 'character-parser/parseUntil'
import { parseUntil } from 'character-parser' - defaultState
const { defaultState } = require('character-parser')import { defaultState } from 'character-parser'
Quickstart
import { parseUntil, parse } from 'character-parser';
import assert from 'assert';
console.log('--- Custom Delimited Expressions (EJS-style) ---');
// Example 1: Parsing up to a custom delimiter (EJS-style)
const ejsSnippet = 'foo.bar("%>\").baz%> bing bong';
const section1 = parseUntil(ejsSnippet, '%>');
assert.strictEqual(section1.start, 0);
assert.strictEqual(section1.end, 18);
assert.strictEqual(section1.src, 'foo.bar("%>\").baz');
console.log(`Parsed section 1: "${section1.src}" (Start: ${section1.start}, End: ${section1.end})`);
const ejsSnippetOffset = '<%foo.bar("%>\").baz%> bing bong';
const section2 = parseUntil(ejsSnippetOffset, '%>', { start: 2 });
assert.strictEqual(section2.start, 2);
assert.strictEqual(section2.end, 20);
assert.strictEqual(section2.src, 'foo.bar("%>\").baz');
console.log(`Parsed section 2 with offset: "${section2.src}" (Start: ${section2.start}, End: ${section2.end})`);
console.log('\n--- Parsing Depth Changes ---');
// Example 2: Parsing depth changes with state management
let state = parse('foo(arg1, arg2, {\n foo: [a, b\n');
console.log('Initial stack after first parse:', state.stack);
assert.deepStrictEqual(state.stack, [')', '}', ']']);
state = parse(' c, d]\n })', state);
console.log('Final stack after second parse:', state.stack);
assert.deepStrictEqual(state.stack, []);
console.log('\nAll assertions passed!');