@futpib/parser 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/arbitraryBash.d.ts +3 -0
- package/build/arbitraryBash.js +142 -0
- package/build/arbitraryJavaScript.js +4 -4
- package/build/arbitraryZipStream.d.ts +1 -1
- package/build/bashParser.js +317 -75
- package/build/bashParser.test.js +71 -0
- package/build/bashUnparser.d.ts +3 -0
- package/build/bashUnparser.js +157 -0
- package/build/bashUnparser.test.d.ts +1 -0
- package/build/bashUnparser.test.js +24 -0
- package/build/bsonParser.js +3 -3
- package/build/dalvikExecutableParser/stringSyntaxParser.js +20 -31
- package/build/dalvikExecutableParser.js +2 -5
- package/build/hasExecutable.js +1 -1
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/jsonParser.js +2 -7
- package/build/predicateElementParser.d.ts +3 -0
- package/build/predicateElementParser.js +10 -0
- package/build/regularExpression.d.ts +12 -3
- package/build/regularExpression.js +10 -1
- package/build/regularExpressionParser.js +39 -25
- package/build/regularExpressionParser.test.js +2 -2
- package/build/smaliParser.js +5 -9
- package/build/symbolicExpressionParser.js +8 -3
- package/package.json +9 -9
- package/readme.md +468 -7
- package/src/arbitraryBash.ts +237 -0
- package/src/arbitraryJavaScript.ts +4 -4
- package/src/bashParser.test.ts +138 -0
- package/src/bashParser.ts +467 -139
- package/src/bashUnparser.test.ts +37 -0
- package/src/bashUnparser.ts +211 -0
- package/src/bsonParser.ts +4 -7
- package/src/dalvikExecutableParser/stringSyntaxParser.ts +27 -74
- package/src/dalvikExecutableParser.ts +4 -10
- package/src/hasExecutable.ts +1 -1
- package/src/index.ts +4 -0
- package/src/jsonParser.ts +2 -11
- package/src/predicateElementParser.ts +22 -0
- package/src/regularExpression.ts +11 -1
- package/src/regularExpressionParser.test.ts +3 -3
- package/src/regularExpressionParser.ts +49 -30
- package/src/smaliParser.ts +11 -23
- package/src/symbolicExpressionParser.ts +9 -3
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import * as fc from 'fast-check';
|
|
2
|
+
const arbitraryBashIdentifier = fc.stringMatching(/^[a-zA-Z_][a-zA-Z0-9_]*$/);
|
|
3
|
+
// Safe unquoted literal: no shell special chars, no leading {/} or #, no = (would be parsed as assignment)
|
|
4
|
+
const arbitraryBashWordPartLiteral = fc.record({
|
|
5
|
+
type: fc.constant('literal'),
|
|
6
|
+
value: fc.stringMatching(/^[a-zA-Z0-9][a-zA-Z0-9._@%,:^~-]*$/),
|
|
7
|
+
});
|
|
8
|
+
// Single-quoted: no single quotes, no newlines inside (keep simple)
|
|
9
|
+
const arbitraryBashWordPartSingleQuoted = fc.record({
|
|
10
|
+
type: fc.constant('singleQuoted'),
|
|
11
|
+
value: fc.stringMatching(/^[^'\n]*$/),
|
|
12
|
+
});
|
|
13
|
+
const arbitraryBashWordPartVariable = fc.record({
|
|
14
|
+
type: fc.constant('variable'),
|
|
15
|
+
name: arbitraryBashIdentifier,
|
|
16
|
+
});
|
|
17
|
+
// variableBraced without operator/operand (always include the optional keys so deepEqual matches parser output)
|
|
18
|
+
const arbitraryBashWordPartVariableBraced = fc.record({
|
|
19
|
+
type: fc.constant('variableBraced'),
|
|
20
|
+
name: arbitraryBashIdentifier,
|
|
21
|
+
operator: fc.constant(undefined),
|
|
22
|
+
operand: fc.constant(undefined),
|
|
23
|
+
});
|
|
24
|
+
const arbitraryBashWordPartArithmeticExpansion = fc.record({
|
|
25
|
+
type: fc.constant('arithmeticExpansion'),
|
|
26
|
+
expression: fc.stringMatching(/^[0-9+\- ]*$/),
|
|
27
|
+
});
|
|
28
|
+
const recursiveArbitraries = fc.letrec(tie => {
|
|
29
|
+
const arbitraryCommandList = tie('commandList');
|
|
30
|
+
// Double-quoted literal: no shell-special chars inside double quotes
|
|
31
|
+
const arbitraryDoubleQuotedLiteral = fc.record({
|
|
32
|
+
type: fc.constant('literal'),
|
|
33
|
+
value: fc.stringMatching(/^[^"\\$`\n]+$/),
|
|
34
|
+
});
|
|
35
|
+
const arbitraryBashWordPartDoubleQuoted = fc.record({
|
|
36
|
+
type: fc.constant('doubleQuoted'),
|
|
37
|
+
parts: fc.array(fc.oneof({ weight: 3, arbitrary: arbitraryDoubleQuotedLiteral }, { weight: 1, arbitrary: arbitraryBashWordPartVariable }, { weight: 1, arbitrary: arbitraryBashWordPartVariableBraced }), { minLength: 1, maxLength: 3 }),
|
|
38
|
+
}).filter(dq => dq.parts.every((part, i) => {
|
|
39
|
+
const next = dq.parts[i + 1];
|
|
40
|
+
// Prevent adjacent literal parts (they merge when re-parsed)
|
|
41
|
+
if (part.type === 'literal' && next !== undefined && next.type === 'literal') {
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
// Prevent $var followed by literal starting with ident char (would be mis-parsed as one variable)
|
|
45
|
+
if (part.type === 'variable' && next !== undefined && next.type === 'literal') {
|
|
46
|
+
return next.value.length === 0 || !isIdentChar(next.value[0]);
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}));
|
|
50
|
+
const arbitraryBashWordPartCommandSubstitution = fc.record({
|
|
51
|
+
type: fc.constant('commandSubstitution'),
|
|
52
|
+
command: arbitraryCommandList,
|
|
53
|
+
});
|
|
54
|
+
const arbitraryBashWordPart = fc.oneof({ weight: 4, arbitrary: arbitraryBashWordPartLiteral }, { weight: 2, arbitrary: arbitraryBashWordPartSingleQuoted }, { weight: 2, arbitrary: arbitraryBashWordPartDoubleQuoted }, { weight: 2, arbitrary: arbitraryBashWordPartVariable }, { weight: 1, arbitrary: arbitraryBashWordPartVariableBraced }, { weight: 1, arbitrary: arbitraryBashWordPartArithmeticExpansion }, { weight: 1, arbitrary: arbitraryBashWordPartCommandSubstitution });
|
|
55
|
+
function isIdentChar(ch) {
|
|
56
|
+
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || ch === '_';
|
|
57
|
+
}
|
|
58
|
+
const arbitraryWord = fc.record({
|
|
59
|
+
parts: fc.array(arbitraryBashWordPart, { minLength: 1, maxLength: 2 }),
|
|
60
|
+
}).filter(word => word.parts.every((part, i) => {
|
|
61
|
+
const next = word.parts[i + 1];
|
|
62
|
+
// Prevent adjacent literal parts (they merge when re-parsed)
|
|
63
|
+
if (part.type === 'literal' && next !== undefined && next.type === 'literal') {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
// Prevent $var followed by literal starting with ident char (would be mis-parsed as one variable)
|
|
67
|
+
if (part.type === 'variable' && next !== undefined && next.type === 'literal') {
|
|
68
|
+
return next.value.length === 0 || !isIdentChar(next.value[0]);
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}));
|
|
72
|
+
// Always include value key (even if undefined) to match createObjectParser behavior
|
|
73
|
+
const arbitraryBashAssignment = fc.record({
|
|
74
|
+
name: arbitraryBashIdentifier,
|
|
75
|
+
value: fc.option(arbitraryWord, { nil: undefined }),
|
|
76
|
+
});
|
|
77
|
+
// Always include fd key (even if undefined) to match createObjectParser behavior
|
|
78
|
+
const arbitraryBashRedirect = fc.record({
|
|
79
|
+
fd: fc.constant(undefined),
|
|
80
|
+
operator: fc.oneof(fc.constant('>'), fc.constant('>>'), fc.constant('<')),
|
|
81
|
+
target: arbitraryWord,
|
|
82
|
+
});
|
|
83
|
+
const arbitraryBashSimpleCommandWithName = fc.record({
|
|
84
|
+
type: fc.constant('simple'),
|
|
85
|
+
name: arbitraryWord,
|
|
86
|
+
args: fc.array(arbitraryWord, { maxLength: 2 }),
|
|
87
|
+
redirects: fc.array(arbitraryBashRedirect, { maxLength: 1 }),
|
|
88
|
+
assignments: fc.array(arbitraryBashAssignment, { maxLength: 1 }),
|
|
89
|
+
});
|
|
90
|
+
// Commands with no name: only assignments and/or redirects (no args)
|
|
91
|
+
const arbitraryBashSimpleCommandNoName = fc.record({
|
|
92
|
+
type: fc.constant('simple'),
|
|
93
|
+
name: fc.constant(undefined),
|
|
94
|
+
args: fc.constant([]),
|
|
95
|
+
redirects: fc.array(arbitraryBashRedirect, { maxLength: 1 }),
|
|
96
|
+
assignments: fc.array(arbitraryBashAssignment, { minLength: 1, maxLength: 2 }),
|
|
97
|
+
});
|
|
98
|
+
const arbitraryBashSimpleCommand = fc.oneof({ weight: 4, arbitrary: arbitraryBashSimpleCommandWithName }, { weight: 1, arbitrary: arbitraryBashSimpleCommandNoName });
|
|
99
|
+
const arbitraryBashSubshell = fc.record({
|
|
100
|
+
type: fc.constant('subshell'),
|
|
101
|
+
body: arbitraryCommandList,
|
|
102
|
+
});
|
|
103
|
+
// Brace group bodies need trailing ';' on last entry (required by "{ cmd; }" syntax)
|
|
104
|
+
const arbitraryBraceGroupBody = arbitraryCommandList.map(list => {
|
|
105
|
+
const entries = list.entries.map((entry, i) => {
|
|
106
|
+
if (i === list.entries.length - 1 && entry.separator === undefined) {
|
|
107
|
+
return { pipeline: entry.pipeline, separator: ';' };
|
|
108
|
+
}
|
|
109
|
+
return entry;
|
|
110
|
+
});
|
|
111
|
+
return { ...list, entries };
|
|
112
|
+
});
|
|
113
|
+
const arbitraryBashBraceGroup = fc.record({
|
|
114
|
+
type: fc.constant('braceGroup'),
|
|
115
|
+
body: arbitraryBraceGroupBody,
|
|
116
|
+
});
|
|
117
|
+
const arbitraryBashCommandUnit = fc.oneof({ weight: 5, arbitrary: arbitraryBashSimpleCommand }, { weight: 1, arbitrary: arbitraryBashSubshell }, { weight: 1, arbitrary: arbitraryBashBraceGroup });
|
|
118
|
+
const arbitraryBashPipeline = fc.record({
|
|
119
|
+
type: fc.constant('pipeline'),
|
|
120
|
+
negated: fc.boolean(),
|
|
121
|
+
commands: fc.array(arbitraryBashCommandUnit, { minLength: 1, maxLength: 2 }),
|
|
122
|
+
});
|
|
123
|
+
const commandListArbitrary = fc.record({
|
|
124
|
+
type: fc.constant('list'),
|
|
125
|
+
entries: fc.array(fc.record({
|
|
126
|
+
pipeline: arbitraryBashPipeline,
|
|
127
|
+
separator: fc.option(fc.oneof(fc.constant('&&'), fc.constant('||'), fc.constant(';')), { nil: undefined }),
|
|
128
|
+
}), { minLength: 1, maxLength: 2 }),
|
|
129
|
+
}).map(list => {
|
|
130
|
+
const entries = list.entries.map((entry, i) => {
|
|
131
|
+
if (i < list.entries.length - 1 && entry.separator === undefined) {
|
|
132
|
+
return { pipeline: entry.pipeline, separator: ';' };
|
|
133
|
+
}
|
|
134
|
+
return entry;
|
|
135
|
+
});
|
|
136
|
+
return { ...list, entries };
|
|
137
|
+
});
|
|
138
|
+
return {
|
|
139
|
+
commandList: commandListArbitrary,
|
|
140
|
+
};
|
|
141
|
+
});
|
|
142
|
+
export const arbitraryBashCommandList = recursiveArbitraries.commandList;
|
|
@@ -164,7 +164,7 @@ const arbitraryFunctionBodyBlockStatement = fc.record({
|
|
|
164
164
|
const arbitraryArrowFunctionExpression = fc.oneof(fc.record({
|
|
165
165
|
type: fc.constant('ArrowFunctionExpression'),
|
|
166
166
|
id: fc.constant(null),
|
|
167
|
-
params: fc.
|
|
167
|
+
params: fc.uniqueArray(arbitraryIdentifier, { minLength: 0, maxLength: 3, selector: (id) => id.name }),
|
|
168
168
|
body: arbitraryFunctionBodyBlockStatement,
|
|
169
169
|
expression: fc.constant(false),
|
|
170
170
|
generator: fc.constant(false),
|
|
@@ -172,7 +172,7 @@ const arbitraryArrowFunctionExpression = fc.oneof(fc.record({
|
|
|
172
172
|
}), fc.record({
|
|
173
173
|
type: fc.constant('ArrowFunctionExpression'),
|
|
174
174
|
id: fc.constant(null),
|
|
175
|
-
params: fc.
|
|
175
|
+
params: fc.uniqueArray(arbitraryIdentifier, { minLength: 0, maxLength: 3, selector: (id) => id.name }),
|
|
176
176
|
body: arbitraryLeafExpression,
|
|
177
177
|
expression: fc.constant(true),
|
|
178
178
|
generator: fc.constant(false),
|
|
@@ -181,7 +181,7 @@ const arbitraryArrowFunctionExpression = fc.oneof(fc.record({
|
|
|
181
181
|
const arbitraryFunctionExpression = fc.record({
|
|
182
182
|
type: fc.constant('FunctionExpression'),
|
|
183
183
|
id: fc.constant(null),
|
|
184
|
-
params: fc.
|
|
184
|
+
params: fc.uniqueArray(arbitraryIdentifier, { minLength: 0, maxLength: 3, selector: (id) => id.name }),
|
|
185
185
|
body: arbitraryFunctionBodyBlockStatement,
|
|
186
186
|
expression: fc.constant(false),
|
|
187
187
|
generator: fc.constant(false),
|
|
@@ -232,7 +232,7 @@ const arbitraryTryStatement = fc.oneof(fc.record({
|
|
|
232
232
|
const arbitraryFunctionDeclaration = fc.record({
|
|
233
233
|
type: fc.constant('FunctionDeclaration'),
|
|
234
234
|
id: arbitraryIdentifier,
|
|
235
|
-
params: fc.
|
|
235
|
+
params: fc.uniqueArray(arbitraryIdentifier, { minLength: 0, maxLength: 3, selector: (id) => id.name }),
|
|
236
236
|
body: arbitraryFunctionBodyBlockStatement,
|
|
237
237
|
expression: fc.constant(false),
|
|
238
238
|
generator: fc.constant(false),
|
|
@@ -2,4 +2,4 @@ import { type ZipEntry } from './zip.js';
|
|
|
2
2
|
export declare const arbitraryZipStream: import("fast-check").Arbitrary<readonly [{
|
|
3
3
|
comment: string;
|
|
4
4
|
entries: ZipEntry[];
|
|
5
|
-
}, import("stream/web").ReadableStream<Uint8Array<ArrayBufferLike>>]>;
|
|
5
|
+
}, import("node:stream/web").ReadableStream<Uint8Array<ArrayBufferLike>>]>;
|