regex-partial-match
June 19, 2026 ยท View on GitHub
A zero-dependency regular expression transform for partial matching, enabling validation of incomplete input strings against regex patterns.
Problem statement
Unlike C/C++ (via PCRE/PCRE2, RE2, Boost.Regex), Python (via third party regex module) or Java (via hitEnd), Javascript has no canonical / innate partial-matching for regular expressions.
Overview
This library transforms regular expressions to best-effort support partial matching, allowing you to test if an incomplete string could potentially match the full pattern. This is particularly useful for real-time input validation, autocomplete systems, progressive form validation, stream chunk matching, etc.
Based on an algorithm created by Lucas Trzesniewski, re-created for NPM via ISC license, with permission.
Installation
npm install regex-partial-match
Usage
Basic Usage
import PartialMatchRegExp from "regex-partial-match";
const partial = new PartialMatchRegExp(/hello world/);
partial.test("h"); // true - could match
partial.test("hello"); // true - could match
partial.test("hello world"); // true - full match
partial.test("goodbye"); // false - cannot match
Extending RegExp.prototype
import "regex-partial-match/extend";
const partial = /hello world/.toPartialMatchRegex(); // returns a PartialMatchRegExp
partial.test("hel"); // true
How It Works
The library transforms a regular expression by wrapping each atomic element in a non-capturing group with a disjunction to end-of-input ($):
/abc/ โ /(?:a|$)(?:b|$)(?:c|$)/
This allows the pattern to match prefixes of the original pattern, enabling validation of incomplete input.
Since the library accepts only valid regular expressions 1, this enables the algorithm to make lots of unguarded assumptions about the source of the expression.
The library has been stress-tested with various regular expression features in isolation, and some in likely combination, but obviously it's an unbounded test space, and syntactically valid regular expressions nevertheless support contradictory patterns e.g.
/\b\B/- impossible to match both a word boundary and a non-word boundary/$^/- end cannot come before startx{2}?- lazy quantifiers are mutually exclusive to fixed-length assertions
Such combinations have not been tested.
Note
See Partial Match Parity for full details on how the library compares to reference implementations
Supported Features
- ๐ค Literal characters
- ๐ฃ Character escapes (
\n,\t,\x61,\u0061,\u{1F600}) - ๐งฉ Character class escapes:
/\w+/,/\d{3}/ - ๐ Unicode character class escape (
\p{Letter},\P{Letter}) - ๐ Character classes (
[abc],[^abc],[a-z]) - ๐งฎ Unicode sets (
vflag) (/[\p{Lowercase}&&\p{Script=Greek}]/v) - ๐ข Quantifiers (
*,+,?,{n},{n,},{n,m}) - ๐ Disjunction (
a|b) - ๐ฅ Groups (capturing and non-capturing) (
(?:abc),(abc),(?<named>abc)) - ๐ Lookahead assertions (
(?=...),(?!...)) - ๐ Lookbehind assertions (
(?<=...),(?<!...)) - โ Input Boundaries (
^,$) - ๐ Word Boundaries (
\b,\B) - ๐ด Flags:
g,i,m,s,u,d,y(See caveats fory) - ๐๏ธ Modifiers (
(?ims:...),(?-ims:...),(?im-s:...))
Unsupported Features
The following regex features are not currently supported:
- โ ๏ธ Backreferences (
\1,\k<name>) - Can be included, but can't partially match. See caveats. - โ ๏ธ Character class substrings (
\q{abc}) - When used independently, rather than to modify, can be included, but can't partially match. See caveats.
Browser Compatibility
The library requires ES2015 (ECMAScript 6) โ the minimum JavaScript version that supports native extension of built-in types such as RegExp, which PartialMatchRegExp relies on to override exec(). Additionally, certain regular expression features require newer environments:
- Unicode property escapes (
\p{...},\P{...}) - ES2018+ - Lookbehind assertions (
(?<=...),(?<!...)) - ES2018+ - Named capturing groups (
(?<name>...)) - ES2018+ s(dotAll) flag - ES2018+d(hasIndices) flag - ES2022+v(unicodeSets) flag - ES2024+- Modifiers (
(?ims:...),(?-ims:...),(?i-ms:...)) - ES2025+
Caveats
Backreferences
Backreferences cannot be partially matched because they are atomic. A backreference like \1 must match the complete captured text or fail entirely, and cannot be split into individual characters for partial matching like regular atoms can.
Fixed-length patterns like /(abc)\1/ could theoretically become /(?:(a)|$)(?:(b)|$)(?:(c)|$)(?:\1|$)(?:\2|$)(?:\3|$)/ (accepting polluted capture indexes as a side-effect), but this doesn't work for variable-length captures.
Positive Lookbehinds
Whilst forming a match, a positive lookbehind must match in entirety, for the pattern to match. This is inherent in the concept of non-matching groups, since they are not match-worthy themselves, but just qualify matching atoms.
e.g.
/(?<=foo)bar/;
"f" through "foo" is not a match, but "foob" is.
Surrogate Pair Matching
In unicode-aware mode (u flag), only whole astral characters are supported. Partial matching of individual surrogate pairs is not supported. For example, /๐/u will match the complete emoji character, but not the first surrogate pair in isolation. Hence, if partially matching a byte stream, be sure to pipe via a TextDecoder first.
Sticky Flag (y)
The sticky flag is fully supported for its intended use case: scanning within a single fixed string. Partial matches are found only at lastIndex; the engine does not scan forward, and lastIndex advances on success or resets to 0 on failure โ exactly as native sticky regexes behave.
import PartialMatchRegExp from "regex-partial-match";
const partial = new PartialMatchRegExp(/hello/y);
partial.lastIndex = 2;
partial.test("xyhello"); // true โ partial match at position 2
partial.test("xyworld"); // false โ no match at position 2, no forward scan
partial.lastIndex = 2;
partial.test("xyhel"); // true โ partial prefix "hel" at position 2
Limitation โ progressive input validation: Because a successful match advances lastIndex, testing a sequence of growing strings against the same instance does not work as expected:
const partial = new PartialMatchRegExp(/hello/y);
partial.test("h"); // true, lastIndex โ 1
partial.test("he"); // false โ sticky requires a match at position 1 of "he",
// but "e" is not a valid start of the pattern
partial.test("hel"); // true (lastIndex was reset to 0 by the previous failure)
There is no way for the subclass to distinguish "scanning forward in the same string" from "testing a new, longer string", so this cannot be fixed in code. For progressive input validation, use a regex without the y flag and always test against the full input so far.
The gy flag combination is also fully supported: exec()/test() behave as sticky, while match(), matchAll(), replace(), and replaceAll() iterate via exec() as global โ matching the language specification.
"String properties"
As with surrogate pair matching, grapheme clusters / string properties can only match atomically.
Hence, [\p{RGI_Emoji_Flag_Sequence}] will match ๐บ๐ณ as a whole, but not as the individual code points of which it's comprised.
In v mode expressions, where [\q{abc}] syntax is used in isolation (rather than its canonical use-case as a subtraction/intersection of another character class), this will also only match entirely or not at all. i.e. abc can match, but not partially.
Examples
Form Validation
import PartialMatchRegExp from "regex-partial-match";
const partial = new PartialMatchRegExp(
/^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}$/i
);
function validateEmail(input) {
return partial.test(input) ? "valid" : "invalid";
}
validateEmail("user"); // 'valid' - could become valid
validateEmail("user@"); // 'valid' - could become valid
validateEmail("user@example"); // 'valid' - could become valid
validateEmail("user@example.com"); // 'valid' - complete match
validateEmail("@@invalid"); // 'invalid' - cannot match
Autocomplete
import PartialMatchRegExp from "regex-partial-match";
const partial = new PartialMatchRegExp(/^(help|quit|save|load)/);
function getSuggestions(input) {
return partial.test(input) ? "valid prefix" : "no suggestions";
}
getSuggestions("h"); // 'valid prefix'
getSuggestions("hel"); // 'valid prefix'
getSuggestions("help"); // 'valid prefix'
getSuggestions("xyz"); // 'no suggestions'
Stream Processing
// Process streaming data with pattern matching at chunk boundaries
const pattern = /\{"[^"]+":"[^"]+"\}/; // Match JSON objects
const partial = new PartialMatchRegExp(pattern);
let buffer = "";
function processChunk(chunk) {
buffer += chunk;
const matches = [];
// Extract complete matches
let match;
while ((match = pattern.exec(buffer))) {
matches.push(match[0]);
buffer = buffer.slice(match.index + match[0].length);
}
// Discard buffer if it cannot possibly complete
if (buffer && !partial.test(buffer)) {
buffer = "";
}
return matches;
}
processChunk('{"na'); // [] - partial, buffer: '{"na'
processChunk('me":"Jo'); // [] - partial, buffer: '{"name":"Jo'
processChunk('hn"}{"age":'); // ['{"name":"John"}'] - buffer: '{"age":'
processChunk("25}"); // ['{"age":25}'] - buffer: ''
processChunk("invalid{"); // [] - discarded, buffer: ''
Useful for parsing log files, network streams, or any chunked data where records may be split across boundaries.
API
PartialMatchRegExp
A RegExp subclass that supports partial matching. The default export of the package.
import PartialMatchRegExp from "regex-partial-match";
const re = new PartialMatchRegExp(/^(ab)+/);
// or
const re = new PartialMatchRegExp("^(ab)+", "i");
Accepts the same constructor arguments as RegExp. For any string s that is a valid prefix of a string that would produce a complete match, exec(s) returns a non-null result whose captures are as consistent as possible with what the original RegExp would return on the completed input; for non-matching inputs it returns null.
RegExp.prototype.toPartialMatchRegex(): PartialMatchRegExp
When using import 'regex-partial-match/extend', this method is added to RegExp.prototype.
Returns:
- A new
PartialMatchRegExpcreated from theRegExpinstance the method was called on.
License
ISC License - see LICENSE file for details.
Credits
Algorithm created by Lucas Trzesniewski.
Contributing
Contributions are welcome! Please open an issue or pull request on GitHub.
Related projects
| Project | Description |
|---|---|
incr-regex-package | Incremental regex matcher |
dfa | Compiles a regular expression like syntax to fast deterministic finite automata, which could be used to partial match? |
refa | Can convert regular expressions to an Abstract Syntax Tree, which might afford partial-match capability? |
@eslint-community/regexpp | A regular expression parser for ECMAScript with AST generation and visitor implementation |
Regex+ | template literal, transforming native regular expressions |
Awesome Regex | Curated list of tools, tutorials, libraries, and other resources, covering all major regex flavours |
replace-content-transformer | A toolkit for stream content replacement, underpinned by regex-partial-match |
Footnotes
-
To remain lightweight, no runtime type validation is applied, so non-typescript consumers will be reliant on underlying errors thrown, if used incorrectly. โฉ