🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

unbash

Package Overview
Dependencies
Maintainers
1
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

unbash - npm Package Compare versions

Comparing version
4.0.8
to
4.0.9
+6
-0
dist/lexer.d.ts

@@ -69,2 +69,3 @@ import type { DeferredCommandExpansion, ParseError, Word, WordPart } from "./types.ts";

readonly TestMode: 2;
readonly CommandPrefix: 3;
};

@@ -96,2 +97,4 @@ export type LexContext = (typeof LexContext)[keyof typeof LexContext];

findClosingBracket(start: number, end?: number): number;
/** Find the closing bracket of `$[ … ]`, which unlike a subscript does not recurse into `${ }`. */
private findClosingArithmeticBracket;
/** Find the closing brace for a parameter expansion, ignoring braces inside nested shell syntax. */

@@ -137,2 +140,4 @@ findClosingBrace(start: number, end?: number): number;

private readHereDocBody;
private matchHereDocDelimiter;
private logicalLineEnd;
private skipHereDocBody;

@@ -161,2 +166,3 @@ private parseHereDocBody;

private readWord;
private classifyWord;
private readWordText;

@@ -163,0 +169,0 @@ private readInnerWordText;

+31
-15

@@ -243,2 +243,7 @@ import { hasEmbeddedWordStructure, LexContext, MAX_SYNTAX_NESTING, Token, Lexer, TokenValue } from "./lexer.js";

};
// A heredoc delimiter is quote-removed but never expanded, so it has no expandable structure.
const heredocDelimiterParts = (source, word) => {
const raw = source.slice(word.pos, word.end);
return raw === word.text ? undefined : [{ type: "Literal", value: word.text, text: raw }];
};
const EMPTY_PREFIX = [];

@@ -1078,4 +1083,4 @@ const EMPTY_SUFFIX = [];

const firstEnd = first.end;
// Unary test: -op word
if (UNARY_TEST_OPS[val] === 1) {
// Unary test: -op word, recognized only as written.
if (first.keywordEligible && UNARY_TEST_OPS[val] === 1) {
const nt = this.tok.peek(LexContext.TestMode).token;

@@ -1095,3 +1100,3 @@ if (nt === Token.Word) {

const nt = this.tok.peek(LexContext.TestMode);
if (nt.token === Token.Word && BINARY_TEST_OPS[nt.value] === 1) {
if (nt.token === Token.Word && nt.keywordEligible && BINARY_TEST_OPS[nt.value] === 1) {
const op = this.tok.next(LexContext.TestMode).value;

@@ -1153,14 +1158,23 @@ let right;

let lastEnd = cmdPos;
while (this.tok.peek(LexContext.CommandStart).token === Token.Assignment) {
const t = this.tok.next(LexContext.CommandStart);
lastEnd = t.end;
if (prefix === EMPTY_PREFIX)
prefix = [];
prefix.push(this.parseAssignment(t));
// Assignments and redirects interleave freely; after the first element CommandPrefix
// keeps the following command name from being read as a reserved word.
let ctx = LexContext.CommandStart;
for (;;) {
const t = this.tok.peek(ctx).token;
if (t === Token.Assignment) {
const assignment = this.tok.next(ctx);
lastEnd = assignment.end;
if (prefix === EMPTY_PREFIX)
prefix = [];
prefix.push(this.parseAssignment(assignment));
}
else if (t === Token.Redirect) {
redirects = this.collectRedirect(redirects, ctx);
lastEnd = redirects[redirects.length - 1].end;
}
else {
break;
}
ctx = LexContext.CommandPrefix;
}
// Consume prefix redirects (e.g. "2>/dev/null cmd")
while (this.tok.peek(LexContext.CommandStart).token === Token.Redirect) {
redirects = this.collectRedirect(redirects, LexContext.CommandStart);
lastEnd = redirects[redirects.length - 1].end;
}
if (this.tok.peek(LexContext.Normal).token !== Token.Word) {

@@ -1231,3 +1245,5 @@ return {

if (t.targetEnd > t.targetPos) {
r.target = new WordImpl(t.content ?? "", t.targetPos, t.targetEnd, this.source, undefined, this.depth);
const heredoc = t.value === "<<" || t.value === "<<-";
const resolver = heredoc ? heredocDelimiterParts : undefined;
r.target = new WordImpl(t.content ?? "", t.targetPos, t.targetEnd, this.source, resolver, this.depth);
}

@@ -1234,0 +1250,0 @@ else {

{
"name": "unbash",
"version": "4.0.8",
"version": "4.0.9",
"description": "Fast 0-deps bash parser written in TypeScript",

@@ -5,0 +5,0 @@ "keywords": [

@@ -222,14 +222,14 @@ # unbash

Parse throughput in MB/s, higher is better, with unbash's relative speed in
parentheses. Median of five runs on Apple M1 Pro/32GB using Node.js 22.23.2.
parentheses. Median of eleven runs on Apple M1 Pro/32GB using Node.js 24.19.0.
| Parser | short (1.2KB) | advanced (0.9KB) | medium (150KB) | large (965KB) |
| ---------------------------- | ------------: | ---------------: | -------------: | ------------: |
| **unbash** | **76.7** | **69.0** | **99.6** | **111.4** |
| tree-sitter-bash (native) | 4.60 (17x) | 6.61 (10x) | 14.97 (7x) | 11.72 (10x) |
| tree-sitter-bash (WASM) | 3.81 (20x) | 4.79 (14x) | 6.62 (15x) | 6.31 (18x) |
| sh-syntax | 0.02 (3361x) | 0.03 (2299x) | 7.13 (14x) | 12.66 (9x) |
| bash-parser | 0.24 (317x) | n/a | n/a | n/a |
| @ericcornelissen/bash-parser | 0.23 (336x) | n/a | n/a | n/a |
| **unbash** | **75.7** | **69.4** | **97.1** | **115.7** |
| tree-sitter-bash (native) | 4.59 (16x) | 6.52 (11x) | 14.90 (7x) | 11.78 (10x) |
| tree-sitter-bash (WASM) | 4.56 (17x) | 5.57 (12x) | 8.55 (11x) | 7.55 (15x) |
| sh-syntax | 0.03 (2870x) | 0.04 (1947x) | 7.59 (13x) | 13.58 (9x) |
| bash-parser | 0.28 (275x) | n/a | n/a | n/a |
| @ericcornelissen/bash-parser | 0.26 (291x) | n/a | n/a | n/a |
Run the benchmarks using Node.js v22, (native tree-sitter binding does not build on newer releases):
Run the benchmarks using Node.js v22+:

@@ -236,0 +236,0 @@ ```sh

Sorry, the diff of this file is too big to display