🎩 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
18
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.7
to
4.0.8
+3
-0
dist/lexer.d.ts

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

}
export declare function requiresFunctionKeyword(name: string): boolean;
export declare function hasEmbeddedWordStructure(source: string, start: number, end: number): boolean;

@@ -150,2 +151,3 @@ export declare const LexContext: {

private _unbalanced;
private _notArithmetic;
private _dqText;

@@ -169,2 +171,3 @@ private _dqHasExpansions;

private readArithmeticExpansion;
private buildArithmeticExpression;
private readArithmeticCommand;

@@ -171,0 +174,0 @@ private readCommandSubstitution;

+39
-12

@@ -449,11 +449,16 @@ import { hasEmbeddedWordStructure, LexContext, MAX_SYNTAX_NESTING, Token, Lexer, TokenValue } from "./lexer.js";

}
const negated = this.tok.peek(LexContext.CommandStart).token === Token.Bang;
if (negated) {
if (!time)
// `pipeline_command: '!' pipeline_command` — each `!` toggles the invert flag
// instead of nesting, so an even number of them cancels out.
let bangs = 0;
let negated = false;
while (this.tok.peek(LexContext.CommandStart).token === Token.Bang) {
if (!time && bangs === 0)
pipelinePos = this.tok.peek(LexContext.CommandStart).pos;
this.tok.next(LexContext.CommandStart);
bangs++;
negated = !negated;
}
const first = this.command();
if (!first) {
if (time || negated) {
if (time || bangs > 0) {
const pipeline = {

@@ -472,3 +477,3 @@ type: "Pipeline",

}
if (!time && !negated)
if (!time && bangs === 0)
pipelinePos = first.pos;

@@ -496,3 +501,3 @@ const commands = [first];

}
if (commands.length === 1 && !negated && !time) {
if (commands.length === 1 && bangs === 0 && !time) {
// Pass redirects up for list() to consume

@@ -628,3 +633,6 @@ this._redirects = firstRedirects;

subshell() {
const pos = this.tok.next(LexContext.CommandStart).pos;
return this.subshellBody(this.tok.next(LexContext.CommandStart).pos);
}
// Continues a subshell whose '(' the caller already consumed.
subshellBody(pos) {
if (this.syntaxDepth === MAX_SYNTAX_NESTING) {

@@ -768,2 +776,6 @@ this.error("maximum subshell nesting depth exceeded", pos);

this.skipNewlines(LexContext.CommandStart);
if (this.tok.peek(LexContext.CommandStart).token === Token.LBrace) {
const bg = this.braceGroup();
return { type: "For", pos, end: bg.end, name, wordlist, body: bg.body };
}
if (!this.accept(Token.Do, LexContext.CommandStart))

@@ -938,2 +950,6 @@ this.error("expected 'do'", this.tok.getPos());

this.skipNewlines(LexContext.CommandStart);
if (this.tok.peek(LexContext.CommandStart).token === Token.LBrace) {
const bg = this.braceGroup();
return { type: "Select", pos, end: bg.end, name, wordlist, body: bg.body };
}
if (!this.accept(Token.Do, LexContext.CommandStart))

@@ -1111,9 +1127,20 @@ this.error("expected 'do'", this.tok.getPos());

const name = this.readWord(LexContext.Normal);
let body;
if (this.tok.peek(LexContext.CommandStart).token === Token.LParen) {
this.tok.next(LexContext.CommandStart);
if (!this.accept(Token.RParen, LexContext.CommandStart))
this.error("expected ')' after '('", this.tok.getPos());
const openPos = this.tok.next(LexContext.CommandStart).pos;
// `(` is the optional empty parameter list only when `)` follows immediately;
// anything else opens a subshell body, as in `f() ( ... )`.
if (this.tok.peek(LexContext.CommandStart).token === Token.RParen) {
this.tok.next(LexContext.CommandStart);
this.skipNewlines(LexContext.CommandStart);
body = this.commandAsBody();
}
else {
body = this.subshellBody(openPos);
}
}
this.skipNewlines(LexContext.CommandStart);
const body = this.commandAsBody();
else {
this.skipNewlines(LexContext.CommandStart);
body = this.commandAsBody();
}
const redirects = this._redirects;

@@ -1120,0 +1147,0 @@ this._redirects = EMPTY_REDIRECTS;

@@ -0,1 +1,2 @@

import { requiresFunctionKeyword } from "./lexer.js";
import { WordImpl } from "./word.js";

@@ -234,3 +235,4 @@ export function print(script) {

const pad = " ".repeat(indent);
let out = wd(n.name) + "() {\n";
const name = wd(n.name);
let out = (requiresFunctionKeyword(name) ? "function " + name + " {" : name + "() {") + "\n";
if (n.body.type === "BraceGroup") {

@@ -237,0 +239,0 @@ out += stmts(n.body.body.commands, indent + 1) + "\n";

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

@@ -58,10 +58,10 @@ "keywords": [

"mitata": "^1.0.34",
"oxfmt": "^0.58.0",
"oxlint": "^1.73.0",
"release-it": "^21.0.0",
"oxfmt": "^0.62.0",
"oxlint": "^1.77.0",
"release-it": "^21.0.1",
"remark": "^15.0.1",
"remark-preset-webpro": "^2.2.0",
"rolldown": "1.1.5",
"rolldown": "1.2.3",
"sh-syntax": "^0.6.0",
"tree-sitter": "^0.25.0",
"tree-sitter": "^0.25.1",
"tree-sitter-bash": "^0.25.1",

@@ -68,0 +68,0 @@ "typescript": "^7.0.2",

+13
-14

@@ -127,5 +127,3 @@ # unbash

```js
const nested = word.parts.find(
(part) => part.type === "CommandExpansion",
).script;
const nested = word.parts.find((part) => part.type === "CommandExpansion").script;
const command = nested.commands[0].command;

@@ -224,14 +222,15 @@

Median relative performance across three runs on Apple M1 Pro/32GB using Node.js
22.23.2. unbash is x times faster:
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.
| Parser | short | advanced | medium | large |
| ---------------------------- | ----: | -------: | -----: | ----: |
| tree-sitter-bash (native) | 17x | 10x | 7x | 10x |
| tree-sitter-bash (WASM) | 20x | 14x | 15x | 17x |
| sh-syntax | 3560x | 2370x | 15x | 9x |
| bash-parser | 317x | N/A | N/A | N/A |
| @ericcornelissen/bash-parser | 335x | N/A | N/A | N/A |
| 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 |
Run the benchmarks using Node.js v22:
Run the benchmarks using Node.js v22, (native tree-sitter binding does not build on newer releases):

@@ -245,3 +244,3 @@ ```sh

The parser bundle is 77KB minified and 18KB gzipped.
The parser bundle is 80KB minified and 19KB gzipped.

@@ -248,0 +247,0 @@ ## Playgrounds

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