@futpib/parser 1.0.4 → 1.0.6
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/.claude/settings.local.json +24 -0
- package/.github/workflows/main.yml +1 -0
- package/build/androidPackageParser.js +30 -32
- package/build/arbitraryDalvikBytecode.d.ts +3 -3
- package/build/arbitraryDalvikBytecode.js +33 -27
- package/build/arbitraryDalvikExecutable.js +55 -17
- package/build/arbitraryJava.d.ts +31 -0
- package/build/arbitraryJava.js +532 -0
- package/build/arbitraryJavaScript.d.ts +3 -0
- package/build/arbitraryJavaScript.js +263 -0
- package/build/arbitraryJavascript.d.ts +3 -0
- package/build/arbitraryJavascript.js +263 -0
- package/build/arbitraryZig.d.ts +3 -0
- package/build/arbitraryZig.js +240 -0
- package/build/arbitraryZipStream.d.ts +1 -1
- package/build/arrayParser.js +72 -13
- package/build/backsmali.d.ts +4 -3
- package/build/backsmali.js +26 -6
- package/build/bash.d.ts +6 -1
- package/build/bashParser.js +131 -90
- package/build/bashParser.test.js +162 -0
- package/build/bashParserEdgeCases.test.d.ts +1 -0
- package/build/bashParserEdgeCases.test.js +117 -0
- package/build/dalvikBytecodeParser/addressConversion.d.ts +110 -0
- package/build/dalvikBytecodeParser/addressConversion.js +334 -0
- package/build/dalvikBytecodeParser/formatParsers.d.ts +7 -6
- package/build/dalvikBytecodeParser/formatParsers.js +13 -14
- package/build/dalvikBytecodeParser.d.ts +60 -31
- package/build/dalvikBytecodeParser.js +92 -35
- package/build/dalvikBytecodeParser.test-d.d.ts +1 -0
- package/build/dalvikBytecodeParser.test-d.js +268 -0
- package/build/dalvikBytecodeUnparser/formatUnparsers.d.ts +9 -8
- package/build/dalvikBytecodeUnparser/formatUnparsers.js +13 -12
- package/build/dalvikBytecodeUnparser.d.ts +2 -2
- package/build/dalvikBytecodeUnparser.js +23 -23
- package/build/dalvikBytecodeUnparser.test.js +7 -7
- package/build/dalvikExecutable.d.ts +3 -3
- package/build/dalvikExecutable.test-d.d.ts +1 -0
- package/build/dalvikExecutable.test-d.js +59 -0
- package/build/dalvikExecutableParser/typedNumbers.d.ts +18 -0
- package/build/dalvikExecutableParser/typedNumbers.js +3 -0
- package/build/dalvikExecutableParser.d.ts +2 -1
- package/build/dalvikExecutableParser.js +96 -77
- package/build/dalvikExecutableParser.test.js +24 -3
- package/build/dalvikExecutableParserAgainstSmaliParser.test.js +3 -0
- package/build/dalvikExecutableUnparser/poolScanners.d.ts +2 -2
- package/build/dalvikExecutableUnparser/sectionUnparsers.d.ts +3 -3
- package/build/dalvikExecutableUnparser/sectionUnparsers.js +26 -11
- package/build/dalvikExecutableUnparser.d.ts +2 -2
- package/build/dalvikExecutableUnparser.test.js +2 -1
- package/build/disjunctionParser.d.ts +5 -3
- package/build/disjunctionParser.js +79 -17
- package/build/disjunctionParser.test-d.d.ts +1 -0
- package/build/disjunctionParser.test-d.js +72 -0
- package/build/elementSwitchParser.d.ts +4 -0
- package/build/{exactElementSwitchParser.js → elementSwitchParser.js} +3 -4
- package/build/elementSwitchParser.test-d.d.ts +1 -0
- package/build/elementSwitchParser.test-d.js +44 -0
- package/build/exactSequenceParser.d.ts +4 -2
- package/build/exactSequenceParser.test-d.d.ts +1 -0
- package/build/exactSequenceParser.test-d.js +36 -0
- package/build/fetchCid.js +2 -66
- package/build/index.d.ts +3 -2
- package/build/index.js +2 -1
- package/build/index.test.js +16 -1
- package/build/inputReader.d.ts +10 -0
- package/build/inputReader.js +36 -0
- package/build/java.d.ts +502 -0
- package/build/java.js +2 -0
- package/build/javaKeyStoreParser.js +14 -17
- package/build/javaParser.d.ts +51 -0
- package/build/javaParser.js +1538 -0
- package/build/javaParser.test.d.ts +1 -0
- package/build/javaParser.test.js +1287 -0
- package/build/javaScript.d.ts +35 -0
- package/build/javaScript.js +1 -0
- package/build/javaScriptParser.d.ts +9 -0
- package/build/javaScriptParser.js +34 -0
- package/build/javaScriptUnparser.d.ts +3 -0
- package/build/javaScriptUnparser.js +4 -0
- package/build/javaScriptUnparser.test.d.ts +1 -0
- package/build/javaScriptUnparser.test.js +24 -0
- package/build/javaUnparser.d.ts +2 -0
- package/build/javaUnparser.js +519 -0
- package/build/javaUnparser.test.d.ts +1 -0
- package/build/javaUnparser.test.js +24 -0
- package/build/javascript.d.ts +35 -0
- package/build/javascript.js +1 -0
- package/build/javascriptParser.d.ts +9 -0
- package/build/javascriptParser.js +34 -0
- package/build/javascriptUnparser.d.ts +3 -0
- package/build/javascriptUnparser.js +4 -0
- package/build/javascriptUnparser.test.d.ts +1 -0
- package/build/javascriptUnparser.test.js +24 -0
- package/build/jsonParser.js +2 -12
- package/build/lazyMessageError.d.ts +3 -0
- package/build/lookaheadParser.js +60 -3
- package/build/negativeLookaheadParser.js +70 -11
- package/build/nonEmptyArrayParser.js +72 -13
- package/build/objectParser.d.ts +12 -0
- package/build/objectParser.js +31 -0
- package/build/objectParser.test-d.d.ts +1 -0
- package/build/objectParser.test-d.js +112 -0
- package/build/objectParser.test.d.ts +1 -0
- package/build/objectParser.test.js +55 -0
- package/build/optionalParser.js +69 -10
- package/build/parser.d.ts +4 -0
- package/build/parser.js +3 -1
- package/build/parser.test.js +114 -1
- package/build/parserConsumedSequenceParser.js +66 -7
- package/build/parserContext.d.ts +6 -0
- package/build/parserContext.js +20 -11
- package/build/parserError.d.ts +119 -27
- package/build/parserError.js +16 -8
- package/build/regexpParser.js +33 -3
- package/build/regexpParser.test.js +31 -0
- package/build/regularExpressionParser.js +35 -15
- package/build/separatedArrayParser.js +73 -14
- package/build/separatedNonEmptyArrayParser.js +73 -14
- package/build/sliceBoundedParser.js +62 -5
- package/build/smaliParser.d.ts +7 -7
- package/build/smaliParser.js +185 -268
- package/build/smaliParser.test.js +58 -0
- package/build/stringEscapes.d.ts +5 -0
- package/build/stringEscapes.js +244 -0
- package/build/symbolicExpression.d.ts +29 -0
- package/build/symbolicExpression.js +1 -0
- package/build/symbolicExpressionParser.d.ts +4 -0
- package/build/symbolicExpressionParser.js +123 -0
- package/build/symbolicExpressionParser.test.d.ts +1 -0
- package/build/symbolicExpressionParser.test.js +289 -0
- package/build/terminatedArrayParser.js +113 -38
- package/build/terminatedArrayParser.test.js +4 -2
- package/build/tupleParser.d.ts +7 -15
- package/build/tupleParser.js +1 -0
- package/build/unionParser.d.ts +5 -3
- package/build/unionParser.js +7 -2
- package/build/unionParser.test-d.d.ts +1 -0
- package/build/unionParser.test-d.js +72 -0
- package/build/unionParser.test.js +10 -11
- package/build/zig.d.ts +280 -0
- package/build/zig.js +2 -0
- package/build/zigParser.d.ts +3 -0
- package/build/zigParser.js +1119 -0
- package/build/zigParser.test.d.ts +1 -0
- package/build/zigParser.test.js +1590 -0
- package/build/zigUnparser.d.ts +2 -0
- package/build/zigUnparser.js +460 -0
- package/build/zigUnparser.test.d.ts +1 -0
- package/build/zigUnparser.test.js +24 -0
- package/build/zipParser.js +19 -32
- package/build/zipUnparser.js +19 -7
- package/build/zipUnparser.test.js +1 -1
- package/node_modules-@types/s-expression/index.d.ts +5 -0
- package/package.json +24 -6
- package/src/androidPackageParser.ts +33 -60
- package/src/arbitraryDalvikBytecode.ts +39 -31
- package/src/arbitraryDalvikExecutable.ts +65 -20
- package/src/arbitraryJava.ts +804 -0
- package/src/arbitraryJavaScript.ts +410 -0
- package/src/arbitraryZig.ts +380 -0
- package/src/arrayParser.ts +1 -3
- package/src/backsmali.ts +35 -4
- package/src/bash.ts +8 -1
- package/src/bashParser.test.ts +258 -0
- package/src/bashParser.ts +180 -143
- package/src/dalvikBytecodeParser/addressConversion.ts +496 -0
- package/src/dalvikBytecodeParser/formatParsers.ts +19 -29
- package/src/dalvikBytecodeParser.test-d.ts +310 -0
- package/src/dalvikBytecodeParser.ts +194 -69
- package/src/dalvikBytecodeUnparser/formatUnparsers.ts +27 -26
- package/src/dalvikBytecodeUnparser.test.ts +7 -7
- package/src/dalvikBytecodeUnparser.ts +31 -30
- package/src/dalvikExecutable.test-d.ts +132 -0
- package/src/dalvikExecutable.ts +3 -3
- package/src/dalvikExecutableParser/typedNumbers.ts +11 -0
- package/src/dalvikExecutableParser.test.ts +37 -3
- package/src/dalvikExecutableParser.test.ts.md +163 -2
- package/src/dalvikExecutableParser.test.ts.snap +0 -0
- package/src/dalvikExecutableParser.ts +121 -139
- package/src/dalvikExecutableParserAgainstSmaliParser.test.ts +4 -0
- package/src/dalvikExecutableUnparser/poolScanners.ts +6 -6
- package/src/dalvikExecutableUnparser/sectionUnparsers.ts +38 -14
- package/src/dalvikExecutableUnparser.test.ts +3 -2
- package/src/dalvikExecutableUnparser.ts +4 -4
- package/src/disjunctionParser.test-d.ts +105 -0
- package/src/disjunctionParser.ts +18 -15
- package/src/elementSwitchParser.test-d.ts +74 -0
- package/src/elementSwitchParser.ts +51 -0
- package/src/exactSequenceParser.test-d.ts +43 -0
- package/src/exactSequenceParser.ts +13 -8
- package/src/fetchCid.ts +2 -76
- package/src/index.test.ts +22 -1
- package/src/index.ts +7 -1
- package/src/inputReader.ts +53 -0
- package/src/java.ts +708 -0
- package/src/javaKeyStoreParser.ts +18 -32
- package/src/javaParser.test.ts +1592 -0
- package/src/javaParser.ts +2640 -0
- package/src/javaScript.ts +36 -0
- package/src/javaScriptParser.ts +57 -0
- package/src/javaScriptUnparser.test.ts +37 -0
- package/src/javaScriptUnparser.ts +7 -0
- package/src/javaUnparser.test.ts +37 -0
- package/src/javaUnparser.ts +640 -0
- package/src/jsonParser.ts +6 -27
- package/src/lookaheadParser.ts +2 -6
- package/src/negativeLookaheadParser.ts +1 -3
- package/src/nonEmptyArrayParser.ts +1 -3
- package/src/objectParser.test-d.ts +152 -0
- package/src/objectParser.test.ts +71 -0
- package/src/objectParser.ts +69 -0
- package/src/optionalParser.ts +1 -3
- package/src/parser.test.ts +151 -4
- package/src/parser.ts +11 -1
- package/src/parserConsumedSequenceParser.ts +2 -4
- package/src/parserContext.ts +26 -11
- package/src/parserError.ts +17 -3
- package/src/regexpParser.test.ts +78 -0
- package/src/regexpParser.ts +35 -3
- package/src/regularExpressionParser.ts +36 -37
- package/src/separatedArrayParser.ts +1 -3
- package/src/separatedNonEmptyArrayParser.ts +1 -3
- package/src/sliceBoundedParser.test.ts +2 -2
- package/src/sliceBoundedParser.ts +15 -19
- package/src/smaliParser.test.ts +64 -0
- package/src/smaliParser.test.ts.md +12 -12
- package/src/smaliParser.test.ts.snap +0 -0
- package/src/smaliParser.ts +246 -534
- package/src/stringEscapes.ts +253 -0
- package/src/symbolicExpression.ts +17 -0
- package/src/symbolicExpressionParser.test.ts +466 -0
- package/src/symbolicExpressionParser.ts +190 -0
- package/src/terminatedArrayParser.test.ts +9 -6
- package/src/terminatedArrayParser.ts +25 -29
- package/src/tupleParser.ts +21 -18
- package/src/unionParser.test-d.ts +105 -0
- package/src/unionParser.test.ts +18 -17
- package/src/unionParser.ts +28 -16
- package/src/zig.ts +411 -0
- package/src/zigParser.test.ts +1693 -0
- package/src/zigParser.ts +1745 -0
- package/src/zigUnparser.test.ts +37 -0
- package/src/zigUnparser.ts +615 -0
- package/src/zipParser.ts +20 -56
- package/src/zipUnparser.test.ts +1 -1
- package/src/zipUnparser.ts +22 -7
- package/tsconfig.json +2 -2
- package/build/exactElementSwitchParser.d.ts +0 -3
- package/src/exactElementSwitchParser.ts +0 -41
package/build/bashParser.js
CHANGED
|
@@ -9,55 +9,85 @@ import { createOptionalParser } from './optionalParser.js';
|
|
|
9
9
|
import { createRegExpParser } from './regexpParser.js';
|
|
10
10
|
import { createNonEmptyArrayParser } from './nonEmptyArrayParser.js';
|
|
11
11
|
import { createSeparatedNonEmptyArrayParser } from './separatedNonEmptyArrayParser.js';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const
|
|
12
|
+
import { createObjectParser } from './objectParser.js';
|
|
13
|
+
// Whitespace (spaces, tabs, and line continuations - not bare newlines which are significant)
|
|
14
|
+
const bashInlineWhitespaceParser = promiseCompose(createRegExpParser(/(?:[ \t]|\\\n)+/), match => match[0]);
|
|
15
|
+
const bashOptionalInlineWhitespaceParser = promiseCompose(createRegExpParser(/(?:[ \t]|\\\n)*/), match => match[0]);
|
|
15
16
|
// Newline
|
|
16
17
|
const bashNewlineParser = promiseCompose(createRegExpParser(/\n/), match => match[0]);
|
|
17
18
|
// Word characters (unquoted, no special chars)
|
|
18
19
|
// Note: {} are excluded so brace groups are parsed correctly
|
|
19
|
-
|
|
20
|
+
// # is excluded from the first character (starts a comment) but allowed mid-word
|
|
21
|
+
const bashUnquotedWordCharsParser = promiseCompose(createRegExpParser(/[^\s\n|&;<>(){}$`"'\\#][^\s\n|&;<>(){}$`"'\\]*/), match => match[0]);
|
|
20
22
|
// Single quoted string: '...'
|
|
21
|
-
const bashSingleQuotedParser =
|
|
22
|
-
createExactSequenceParser("'"),
|
|
23
|
-
promiseCompose(createRegExpParser(/[^']*/), match => match[0]),
|
|
24
|
-
createExactSequenceParser("'"),
|
|
25
|
-
]), ([, value]) => ({
|
|
23
|
+
const bashSingleQuotedParser = createObjectParser({
|
|
26
24
|
type: 'singleQuoted',
|
|
27
|
-
|
|
28
|
-
|
|
25
|
+
_open: createExactSequenceParser("'"),
|
|
26
|
+
value: promiseCompose(createRegExpParser(/[^']*/), match => match[0]),
|
|
27
|
+
_close: createExactSequenceParser("'"),
|
|
28
|
+
});
|
|
29
29
|
// Variable name
|
|
30
30
|
const bashVariableNameParser = promiseCompose(createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*|[0-9]+|[@*#?$!-]/), match => match[0]);
|
|
31
31
|
// Simple variable: $var
|
|
32
|
-
const bashSimpleVariableParser =
|
|
33
|
-
createExactSequenceParser('$'),
|
|
34
|
-
bashVariableNameParser,
|
|
35
|
-
]), ([, name]) => ({
|
|
32
|
+
const bashSimpleVariableParser = createObjectParser({
|
|
36
33
|
type: 'variable',
|
|
37
|
-
|
|
38
|
-
|
|
34
|
+
_dollar: createExactSequenceParser('$'),
|
|
35
|
+
name: bashVariableNameParser,
|
|
36
|
+
});
|
|
39
37
|
// Command substitution: $(...)
|
|
40
|
-
const bashCommandSubstitutionParser =
|
|
41
|
-
createExactSequenceParser('$('),
|
|
42
|
-
bashOptionalInlineWhitespaceParser,
|
|
43
|
-
createParserAccessorParser(() => bashCommandParser),
|
|
44
|
-
bashOptionalInlineWhitespaceParser,
|
|
45
|
-
createExactSequenceParser(')'),
|
|
46
|
-
]), ([, , command]) => ({
|
|
38
|
+
const bashCommandSubstitutionParser = createObjectParser({
|
|
47
39
|
type: 'commandSubstitution',
|
|
48
|
-
|
|
49
|
-
|
|
40
|
+
_open: createExactSequenceParser('$('),
|
|
41
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
42
|
+
command: createParserAccessorParser(() => bashCommandParser),
|
|
43
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
44
|
+
_close: createExactSequenceParser(')'),
|
|
45
|
+
});
|
|
50
46
|
// Backtick substitution: `...`
|
|
51
|
-
const bashBacktickSubstitutionParser =
|
|
52
|
-
createExactSequenceParser('`'),
|
|
53
|
-
createParserAccessorParser(() => bashCommandParser),
|
|
54
|
-
createExactSequenceParser('`'),
|
|
55
|
-
]), ([, command]) => ({
|
|
47
|
+
const bashBacktickSubstitutionParser = createObjectParser({
|
|
56
48
|
type: 'backtickSubstitution',
|
|
57
|
-
|
|
58
|
-
|
|
49
|
+
_open: createExactSequenceParser('`'),
|
|
50
|
+
command: createParserAccessorParser(() => bashCommandParser),
|
|
51
|
+
_close: createExactSequenceParser('`'),
|
|
52
|
+
});
|
|
53
|
+
// Braced variable expansion: ${VAR} or ${VAR:-default}
|
|
54
|
+
const bashBracedVariableParser = createObjectParser({
|
|
55
|
+
type: 'variableBraced',
|
|
56
|
+
_open: createExactSequenceParser('${'),
|
|
57
|
+
name: bashVariableNameParser,
|
|
58
|
+
operator: createOptionalParser(promiseCompose(createRegExpParser(/:-|:=|:\+|:\?|-|=|\+|\?|##|#|%%|%/), match => match[0])),
|
|
59
|
+
operand: createOptionalParser(createParserAccessorParser(() => bashWordParser)),
|
|
60
|
+
_close: createExactSequenceParser('}'),
|
|
61
|
+
});
|
|
62
|
+
// Arithmetic expansion: $((expression))
|
|
63
|
+
const bashArithmeticExpansionParser = createObjectParser({
|
|
64
|
+
type: 'arithmeticExpansion',
|
|
65
|
+
_open: createExactSequenceParser('$(('),
|
|
66
|
+
expression: promiseCompose(createRegExpParser(/(?:[^)]|\)(?!\)))*/), match => match[0]),
|
|
67
|
+
_close: createExactSequenceParser('))'),
|
|
68
|
+
});
|
|
69
|
+
// ANSI-C quoting: $'...'
|
|
70
|
+
const bashAnsiCQuotedParser = createObjectParser({
|
|
71
|
+
type: 'singleQuoted',
|
|
72
|
+
_prefix: createExactSequenceParser('$'),
|
|
73
|
+
_open: createExactSequenceParser("'"),
|
|
74
|
+
value: promiseCompose(createRegExpParser(/(?:[^'\\]|\\.)*/), match => match[0]),
|
|
75
|
+
_close: createExactSequenceParser("'"),
|
|
76
|
+
});
|
|
77
|
+
// Process substitution: <(cmd) or >(cmd)
|
|
78
|
+
const bashProcessSubstitutionParser = createObjectParser({
|
|
79
|
+
type: 'processSubstitution',
|
|
80
|
+
direction: promiseCompose(createRegExpParser(/[<>](?=\()/), match => match[0]),
|
|
81
|
+
_open: createExactSequenceParser('('),
|
|
82
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
83
|
+
command: createParserAccessorParser(() => bashCommandParser),
|
|
84
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
85
|
+
_close: createExactSequenceParser(')'),
|
|
86
|
+
});
|
|
59
87
|
// Double quoted string parts (inside "...")
|
|
60
88
|
const bashDoubleQuotedPartParser = createDisjunctionParser([
|
|
89
|
+
bashBracedVariableParser,
|
|
90
|
+
bashArithmeticExpansionParser,
|
|
61
91
|
bashSimpleVariableParser,
|
|
62
92
|
bashCommandSubstitutionParser,
|
|
63
93
|
bashBacktickSubstitutionParser,
|
|
@@ -71,21 +101,29 @@ const bashDoubleQuotedPartParser = createDisjunctionParser([
|
|
|
71
101
|
type: 'literal',
|
|
72
102
|
value: match[0],
|
|
73
103
|
})),
|
|
104
|
+
// Bare $ not followed by a valid expansion start (e.g. $" at end of double-quoted string)
|
|
105
|
+
promiseCompose(createRegExpParser(/\$/), () => ({
|
|
106
|
+
type: 'literal',
|
|
107
|
+
value: '$',
|
|
108
|
+
})),
|
|
109
|
+
// Bare \ not followed by a recognized escape character (treated as literal backslash in bash)
|
|
110
|
+
promiseCompose(createRegExpParser(/\\/), () => ({
|
|
111
|
+
type: 'literal',
|
|
112
|
+
value: '\\',
|
|
113
|
+
})),
|
|
74
114
|
]);
|
|
75
115
|
// Double quoted string: "..."
|
|
76
|
-
const bashDoubleQuotedParser =
|
|
77
|
-
createExactSequenceParser('"'),
|
|
78
|
-
createArrayParser(bashDoubleQuotedPartParser),
|
|
79
|
-
createExactSequenceParser('"'),
|
|
80
|
-
]), ([, parts]) => ({
|
|
116
|
+
const bashDoubleQuotedParser = createObjectParser({
|
|
81
117
|
type: 'doubleQuoted',
|
|
82
|
-
|
|
83
|
-
|
|
118
|
+
_open: createExactSequenceParser('"'),
|
|
119
|
+
parts: createArrayParser(bashDoubleQuotedPartParser),
|
|
120
|
+
_close: createExactSequenceParser('"'),
|
|
121
|
+
});
|
|
84
122
|
// Literal word part (unquoted)
|
|
85
|
-
const bashLiteralWordPartParser =
|
|
123
|
+
const bashLiteralWordPartParser = createObjectParser({
|
|
86
124
|
type: 'literal',
|
|
87
|
-
value,
|
|
88
|
-
})
|
|
125
|
+
value: bashUnquotedWordCharsParser,
|
|
126
|
+
});
|
|
89
127
|
// Escape sequence outside quotes
|
|
90
128
|
const bashEscapeParser = promiseCompose(createRegExpParser(/\\./), match => ({
|
|
91
129
|
type: 'literal',
|
|
@@ -93,25 +131,33 @@ const bashEscapeParser = promiseCompose(createRegExpParser(/\\./), match => ({
|
|
|
93
131
|
}));
|
|
94
132
|
// Word part (any part of a word)
|
|
95
133
|
const bashWordPartParser = createDisjunctionParser([
|
|
134
|
+
bashAnsiCQuotedParser,
|
|
96
135
|
bashSingleQuotedParser,
|
|
97
136
|
bashDoubleQuotedParser,
|
|
137
|
+
bashBracedVariableParser,
|
|
138
|
+
bashArithmeticExpansionParser,
|
|
98
139
|
bashCommandSubstitutionParser,
|
|
99
140
|
bashBacktickSubstitutionParser,
|
|
100
141
|
bashSimpleVariableParser,
|
|
142
|
+
bashProcessSubstitutionParser,
|
|
101
143
|
bashEscapeParser,
|
|
102
144
|
bashLiteralWordPartParser,
|
|
145
|
+
// Bare $ not followed by a valid expansion start
|
|
146
|
+
promiseCompose(createRegExpParser(/\$/), () => ({
|
|
147
|
+
type: 'literal',
|
|
148
|
+
value: '$',
|
|
149
|
+
})),
|
|
103
150
|
]);
|
|
104
151
|
// Word (sequence of word parts)
|
|
105
|
-
export const bashWordParser =
|
|
152
|
+
export const bashWordParser = createObjectParser({
|
|
153
|
+
parts: createNonEmptyArrayParser(bashWordPartParser),
|
|
154
|
+
});
|
|
106
155
|
setParserName(bashWordParser, 'bashWordParser');
|
|
107
156
|
// Assignment: NAME=value or NAME=
|
|
108
|
-
const bashAssignmentParser =
|
|
109
|
-
promiseCompose(createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*=/), match => match[0].slice(0, -1)),
|
|
110
|
-
createOptionalParser(bashWordParser),
|
|
111
|
-
|
|
112
|
-
name,
|
|
113
|
-
value: value ?? undefined,
|
|
114
|
-
}));
|
|
157
|
+
const bashAssignmentParser = createObjectParser({
|
|
158
|
+
name: promiseCompose(createRegExpParser(/[a-zA-Z_][a-zA-Z0-9_]*=/), match => match[0].slice(0, -1)),
|
|
159
|
+
value: createOptionalParser(bashWordParser),
|
|
160
|
+
});
|
|
115
161
|
// Redirect operators
|
|
116
162
|
const bashRedirectOperatorParser = createDisjunctionParser([
|
|
117
163
|
promiseCompose(createExactSequenceParser('>>'), () => '>>'),
|
|
@@ -124,16 +170,12 @@ const bashRedirectOperatorParser = createDisjunctionParser([
|
|
|
124
170
|
promiseCompose(createExactSequenceParser('<'), () => '<'),
|
|
125
171
|
]);
|
|
126
172
|
// Redirect: [n]op word
|
|
127
|
-
const bashRedirectParser =
|
|
128
|
-
createOptionalParser(promiseCompose(createRegExpParser(/[0-9]+/), match => Number.parseInt(match[0], 10))),
|
|
129
|
-
bashRedirectOperatorParser,
|
|
130
|
-
bashOptionalInlineWhitespaceParser,
|
|
131
|
-
bashWordParser,
|
|
132
|
-
|
|
133
|
-
fd: fd ?? undefined,
|
|
134
|
-
operator,
|
|
135
|
-
target,
|
|
136
|
-
}));
|
|
173
|
+
const bashRedirectParser = createObjectParser({
|
|
174
|
+
fd: createOptionalParser(promiseCompose(createRegExpParser(/[0-9]+/), match => Number.parseInt(match[0], 10))),
|
|
175
|
+
operator: bashRedirectOperatorParser,
|
|
176
|
+
_ws: bashOptionalInlineWhitespaceParser,
|
|
177
|
+
target: bashWordParser,
|
|
178
|
+
});
|
|
137
179
|
// Word with optional trailing whitespace - for use in arrays
|
|
138
180
|
const bashWordWithWhitespaceParser = promiseCompose(createTupleParser([
|
|
139
181
|
bashWordParser,
|
|
@@ -146,8 +188,8 @@ const bashRedirectWithWhitespaceParser = promiseCompose(createTupleParser([
|
|
|
146
188
|
]), ([redirect]) => redirect);
|
|
147
189
|
// Word or redirect - for interleaved parsing in simple commands
|
|
148
190
|
const bashWordOrRedirectParser = createDisjunctionParser([
|
|
149
|
-
|
|
150
|
-
|
|
191
|
+
createObjectParser({ type: 'redirect', redirect: bashRedirectWithWhitespaceParser }),
|
|
192
|
+
createObjectParser({ type: 'word', word: bashWordWithWhitespaceParser }),
|
|
151
193
|
]);
|
|
152
194
|
// Simple command: [assignments] [name] [args] [redirects]
|
|
153
195
|
export const bashSimpleCommandParser = promiseCompose(createTupleParser([
|
|
@@ -180,30 +222,26 @@ export const bashSimpleCommandParser = promiseCompose(createTupleParser([
|
|
|
180
222
|
});
|
|
181
223
|
setParserName(bashSimpleCommandParser, 'bashSimpleCommandParser');
|
|
182
224
|
// Subshell: ( command )
|
|
183
|
-
const bashSubshellParser =
|
|
184
|
-
createExactSequenceParser('('),
|
|
185
|
-
bashOptionalInlineWhitespaceParser,
|
|
186
|
-
createParserAccessorParser(() => bashCommandParser),
|
|
187
|
-
bashOptionalInlineWhitespaceParser,
|
|
188
|
-
createExactSequenceParser(')'),
|
|
189
|
-
]), ([, , body]) => ({
|
|
225
|
+
const bashSubshellParser = createObjectParser({
|
|
190
226
|
type: 'subshell',
|
|
191
|
-
|
|
192
|
-
|
|
227
|
+
_open: createExactSequenceParser('('),
|
|
228
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
229
|
+
body: createParserAccessorParser(() => bashCommandParser),
|
|
230
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
231
|
+
_close: createExactSequenceParser(')'),
|
|
232
|
+
});
|
|
193
233
|
setParserName(bashSubshellParser, 'bashSubshellParser');
|
|
194
234
|
// Brace group: { command; }
|
|
195
|
-
const bashBraceGroupParser =
|
|
196
|
-
createExactSequenceParser('{'),
|
|
197
|
-
bashInlineWhitespaceParser,
|
|
198
|
-
createParserAccessorParser(() => bashCommandParser),
|
|
199
|
-
bashOptionalInlineWhitespaceParser,
|
|
200
|
-
createOptionalParser(createExactSequenceParser(';')),
|
|
201
|
-
bashOptionalInlineWhitespaceParser,
|
|
202
|
-
createExactSequenceParser('}'),
|
|
203
|
-
]), ([, , body]) => ({
|
|
235
|
+
const bashBraceGroupParser = createObjectParser({
|
|
204
236
|
type: 'braceGroup',
|
|
205
|
-
|
|
206
|
-
|
|
237
|
+
_open: createExactSequenceParser('{'),
|
|
238
|
+
_ws1: bashInlineWhitespaceParser,
|
|
239
|
+
body: createParserAccessorParser(() => bashCommandParser),
|
|
240
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
241
|
+
_semi: createOptionalParser(createExactSequenceParser(';')),
|
|
242
|
+
_ws3: bashOptionalInlineWhitespaceParser,
|
|
243
|
+
_close: createExactSequenceParser('}'),
|
|
244
|
+
});
|
|
207
245
|
setParserName(bashBraceGroupParser, 'bashBraceGroupParser');
|
|
208
246
|
// Command unit: simple command, subshell, or brace group
|
|
209
247
|
const bashCommandUnitParser = createDisjunctionParser([
|
|
@@ -242,12 +280,12 @@ const bashListSeparatorParser = createDisjunctionParser([
|
|
|
242
280
|
// Command list: pipeline [sep pipeline]...
|
|
243
281
|
const bashCommandListParser = promiseCompose(createTupleParser([
|
|
244
282
|
bashPipelineParser,
|
|
245
|
-
createArrayParser(
|
|
246
|
-
bashOptionalInlineWhitespaceParser,
|
|
247
|
-
bashListSeparatorParser,
|
|
248
|
-
bashOptionalInlineWhitespaceParser,
|
|
249
|
-
bashPipelineParser,
|
|
250
|
-
|
|
283
|
+
createArrayParser(createObjectParser({
|
|
284
|
+
_ws1: bashOptionalInlineWhitespaceParser,
|
|
285
|
+
separator: bashListSeparatorParser,
|
|
286
|
+
_ws2: bashOptionalInlineWhitespaceParser,
|
|
287
|
+
pipeline: bashPipelineParser,
|
|
288
|
+
})),
|
|
251
289
|
createOptionalParser(promiseCompose(createTupleParser([
|
|
252
290
|
bashOptionalInlineWhitespaceParser,
|
|
253
291
|
bashListSeparatorParser,
|
|
@@ -285,10 +323,13 @@ setParserName(bashCommandListParser, 'bashCommandListParser');
|
|
|
285
323
|
// Top-level command parser
|
|
286
324
|
export const bashCommandParser = bashCommandListParser;
|
|
287
325
|
setParserName(bashCommandParser, 'bashCommandParser');
|
|
288
|
-
//
|
|
326
|
+
// Comment: # through end of line
|
|
327
|
+
const bashOptionalCommentParser = createOptionalParser(promiseCompose(createRegExpParser(/#[^\n]*/), match => match[0]));
|
|
328
|
+
// Script parser (handles leading/trailing whitespace and comments)
|
|
289
329
|
export const bashScriptParser = promiseCompose(createTupleParser([
|
|
290
330
|
bashOptionalInlineWhitespaceParser,
|
|
291
331
|
bashCommandParser,
|
|
292
332
|
bashOptionalInlineWhitespaceParser,
|
|
333
|
+
bashOptionalCommentParser,
|
|
293
334
|
]), ([, command]) => command);
|
|
294
335
|
setParserName(bashScriptParser, 'bashScriptParser');
|
package/build/bashParser.test.js
CHANGED
|
@@ -98,6 +98,55 @@ test('double quoted string with variable', async (t) => {
|
|
|
98
98
|
});
|
|
99
99
|
}
|
|
100
100
|
});
|
|
101
|
+
test('double quoted string with trailing dollar', async (t) => {
|
|
102
|
+
const result = await runParser(bashScriptParser, 'echo "hello$"', stringParserInputCompanion);
|
|
103
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
104
|
+
if (cmd.type === 'simple') {
|
|
105
|
+
t.deepEqual(cmd.args[0], {
|
|
106
|
+
parts: [{
|
|
107
|
+
type: 'doubleQuoted',
|
|
108
|
+
parts: [
|
|
109
|
+
{ type: 'literal', value: 'hello' },
|
|
110
|
+
{ type: 'literal', value: '$' },
|
|
111
|
+
],
|
|
112
|
+
}],
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
test('double quoted string with only dollar', async (t) => {
|
|
117
|
+
const result = await runParser(bashScriptParser, 'echo "$"', stringParserInputCompanion);
|
|
118
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
119
|
+
if (cmd.type === 'simple') {
|
|
120
|
+
t.deepEqual(cmd.args[0], {
|
|
121
|
+
parts: [{
|
|
122
|
+
type: 'doubleQuoted',
|
|
123
|
+
parts: [
|
|
124
|
+
{ type: 'literal', value: '$' },
|
|
125
|
+
],
|
|
126
|
+
}],
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
test('grep with dollar anchor in double quotes', async (t) => {
|
|
131
|
+
const result = await runParser(bashScriptParser, 'grep "\\.ts$"', stringParserInputCompanion);
|
|
132
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
133
|
+
if (cmd.type === 'simple') {
|
|
134
|
+
t.deepEqual(cmd.args[0], {
|
|
135
|
+
parts: [{
|
|
136
|
+
type: 'doubleQuoted',
|
|
137
|
+
parts: [
|
|
138
|
+
{ type: 'literal', value: '\\' },
|
|
139
|
+
{ type: 'literal', value: '.ts' },
|
|
140
|
+
{ type: 'literal', value: '$' },
|
|
141
|
+
],
|
|
142
|
+
}],
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
test('pipeline with dollar anchor in double quoted grep pattern', async (t) => {
|
|
147
|
+
const result = await runParser(bashScriptParser, 'ls -la /home | grep "\\.ts$" | grep -v "\\.test\\.ts"', stringParserInputCompanion);
|
|
148
|
+
t.is(result.entries[0].pipeline.commands.length, 3);
|
|
149
|
+
});
|
|
101
150
|
test('simple variable', async (t) => {
|
|
102
151
|
const result = await runParser(bashScriptParser, 'echo $HOME', stringParserInputCompanion);
|
|
103
152
|
const cmd = result.entries[0].pipeline.commands[0];
|
|
@@ -172,6 +221,119 @@ test('[[ treated as command name', async (t) => {
|
|
|
172
221
|
t.is(cmd.args.length, 3); // -f, file, ]]
|
|
173
222
|
}
|
|
174
223
|
});
|
|
224
|
+
// Braced variable expansion: ${VAR}
|
|
225
|
+
test('braced variable expansion', async (t) => {
|
|
226
|
+
const result = await runParser(bashScriptParser, 'echo ${HOME}', stringParserInputCompanion);
|
|
227
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
228
|
+
if (cmd.type === 'simple') {
|
|
229
|
+
t.is(cmd.args[0].parts[0].type, 'variableBraced');
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
// Braced variable with default: ${VAR:-default}
|
|
233
|
+
test('braced variable with default value', async (t) => {
|
|
234
|
+
const result = await runParser(bashScriptParser, 'echo ${VAR:-default}', stringParserInputCompanion);
|
|
235
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
236
|
+
if (cmd.type === 'simple') {
|
|
237
|
+
t.is(cmd.args[0].parts[0].type, 'variableBraced');
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
// Arithmetic expansion: $((1+2))
|
|
241
|
+
test('arithmetic expansion', async (t) => {
|
|
242
|
+
const result = await runParser(bashScriptParser, 'echo $((1+2))', stringParserInputCompanion);
|
|
243
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
244
|
+
if (cmd.type === 'simple') {
|
|
245
|
+
t.is(cmd.args[0].parts[0].type, 'arithmeticExpansion');
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
// Bare $ at end of unquoted word
|
|
249
|
+
test('bare dollar at end of unquoted word', async (t) => {
|
|
250
|
+
const result = await runParser(bashScriptParser, 'echo foo$', stringParserInputCompanion);
|
|
251
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
252
|
+
if (cmd.type === 'simple') {
|
|
253
|
+
t.deepEqual(cmd.args[0], {
|
|
254
|
+
parts: [
|
|
255
|
+
{ type: 'literal', value: 'foo' },
|
|
256
|
+
{ type: 'literal', value: '$' },
|
|
257
|
+
],
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
// Bare $ as its own unquoted word
|
|
262
|
+
test('bare dollar as standalone unquoted word', async (t) => {
|
|
263
|
+
const result = await runParser(bashScriptParser, 'echo $', stringParserInputCompanion);
|
|
264
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
265
|
+
if (cmd.type === 'simple') {
|
|
266
|
+
t.deepEqual(cmd.args[0], {
|
|
267
|
+
parts: [
|
|
268
|
+
{ type: 'literal', value: '$' },
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
// Comment after command
|
|
274
|
+
test('comment after command', async (t) => {
|
|
275
|
+
const result = await runParser(bashScriptParser, 'echo hello # this is a comment', stringParserInputCompanion);
|
|
276
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
277
|
+
if (cmd.type === 'simple') {
|
|
278
|
+
t.is(cmd.args.length, 1);
|
|
279
|
+
t.deepEqual(cmd.args[0], {
|
|
280
|
+
parts: [{ type: 'literal', value: 'hello' }],
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// ANSI-C quoting: $'...'
|
|
285
|
+
test('ansi-c quoting', async (t) => {
|
|
286
|
+
const result = await runParser(bashScriptParser, "echo $'hello\\nworld'", stringParserInputCompanion);
|
|
287
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
288
|
+
if (cmd.type === 'simple') {
|
|
289
|
+
t.is(cmd.args.length, 1);
|
|
290
|
+
}
|
|
291
|
+
});
|
|
292
|
+
// Braced variable in double quotes: "${VAR}"
|
|
293
|
+
test('braced variable in double quotes', async (t) => {
|
|
294
|
+
const result = await runParser(bashScriptParser, 'echo "${HOME}"', stringParserInputCompanion);
|
|
295
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
296
|
+
if (cmd.type === 'simple') {
|
|
297
|
+
const dq = cmd.args[0].parts[0];
|
|
298
|
+
if (dq.type === 'doubleQuoted') {
|
|
299
|
+
t.is(dq.parts[0].type, 'variableBraced');
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
});
|
|
303
|
+
// Arithmetic expansion in double quotes
|
|
304
|
+
test('arithmetic expansion in double quotes', async (t) => {
|
|
305
|
+
const result = await runParser(bashScriptParser, 'echo "$((1+2))"', stringParserInputCompanion);
|
|
306
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
307
|
+
if (cmd.type === 'simple') {
|
|
308
|
+
const dq = cmd.args[0].parts[0];
|
|
309
|
+
if (dq.type === 'doubleQuoted') {
|
|
310
|
+
t.is(dq.parts[0].type, 'arithmeticExpansion');
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
// Process substitution: <(cmd)
|
|
315
|
+
test('process substitution input', async (t) => {
|
|
316
|
+
const result = await runParser(bashScriptParser, 'diff <(sort file1) <(sort file2)', stringParserInputCompanion);
|
|
317
|
+
t.truthy(result);
|
|
318
|
+
});
|
|
319
|
+
// Line continuation (backslash-newline)
|
|
320
|
+
test('line continuation', async (t) => {
|
|
321
|
+
const result = await runParser(bashScriptParser, 'echo hello \\\nworld', stringParserInputCompanion);
|
|
322
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
323
|
+
if (cmd.type === 'simple') {
|
|
324
|
+
t.is(cmd.args.length, 2);
|
|
325
|
+
}
|
|
326
|
+
});
|
|
327
|
+
// Hash in middle of unquoted word is literal, not a comment
|
|
328
|
+
test('hash in middle of unquoted word', async (t) => {
|
|
329
|
+
const result = await runParser(bashScriptParser, 'echo foo#bar', stringParserInputCompanion);
|
|
330
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
331
|
+
if (cmd.type === 'simple') {
|
|
332
|
+
t.deepEqual(cmd.args[0], {
|
|
333
|
+
parts: [{ type: 'literal', value: 'foo#bar' }],
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
});
|
|
175
337
|
test('if treated as command name', async (t) => {
|
|
176
338
|
const result = await runParser(bashScriptParser, 'if true', stringParserInputCompanion);
|
|
177
339
|
const cmd = result.entries[0].pipeline.commands[0];
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import test from 'ava';
|
|
2
|
+
import { runParser } from './parser.js';
|
|
3
|
+
import { stringParserInputCompanion } from './parserInputCompanion.js';
|
|
4
|
+
import { bashScriptParser } from './bashParser.js';
|
|
5
|
+
// Braced variable expansion: ${VAR}
|
|
6
|
+
test('braced variable expansion', async (t) => {
|
|
7
|
+
const result = await runParser(bashScriptParser, 'echo ${HOME}', stringParserInputCompanion);
|
|
8
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
9
|
+
if (cmd.type === 'simple') {
|
|
10
|
+
t.is(cmd.args[0].parts[0].type, 'variableBraced');
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
// Braced variable with default: ${VAR:-default}
|
|
14
|
+
test('braced variable with default value', async (t) => {
|
|
15
|
+
const result = await runParser(bashScriptParser, 'echo ${VAR:-default}', stringParserInputCompanion);
|
|
16
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
17
|
+
if (cmd.type === 'simple') {
|
|
18
|
+
t.is(cmd.args[0].parts[0].type, 'variableBraced');
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
// Arithmetic expansion: $((1+2))
|
|
22
|
+
test('arithmetic expansion', async (t) => {
|
|
23
|
+
const result = await runParser(bashScriptParser, 'echo $((1+2))', stringParserInputCompanion);
|
|
24
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
25
|
+
if (cmd.type === 'simple') {
|
|
26
|
+
t.is(cmd.args[0].parts[0].type, 'arithmeticExpansion');
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
// Bare $ at end of unquoted word
|
|
30
|
+
test('bare dollar at end of unquoted word', async (t) => {
|
|
31
|
+
const result = await runParser(bashScriptParser, 'echo foo$', stringParserInputCompanion);
|
|
32
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
33
|
+
if (cmd.type === 'simple') {
|
|
34
|
+
t.deepEqual(cmd.args[0], {
|
|
35
|
+
parts: [
|
|
36
|
+
{ type: 'literal', value: 'foo' },
|
|
37
|
+
{ type: 'literal', value: '$' },
|
|
38
|
+
],
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
// Bare $ as its own unquoted word
|
|
43
|
+
test('bare dollar as standalone unquoted word', async (t) => {
|
|
44
|
+
const result = await runParser(bashScriptParser, 'echo $', stringParserInputCompanion);
|
|
45
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
46
|
+
if (cmd.type === 'simple') {
|
|
47
|
+
t.deepEqual(cmd.args[0], {
|
|
48
|
+
parts: [
|
|
49
|
+
{ type: 'literal', value: '$' },
|
|
50
|
+
],
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
// Comment after command
|
|
55
|
+
test('comment after command', async (t) => {
|
|
56
|
+
const result = await runParser(bashScriptParser, 'echo hello # this is a comment', stringParserInputCompanion);
|
|
57
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
58
|
+
if (cmd.type === 'simple') {
|
|
59
|
+
t.is(cmd.args.length, 1);
|
|
60
|
+
t.deepEqual(cmd.args[0], {
|
|
61
|
+
parts: [{ type: 'literal', value: 'hello' }],
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
// ANSI-C quoting: $'...'
|
|
66
|
+
test('ansi-c quoting', async (t) => {
|
|
67
|
+
const result = await runParser(bashScriptParser, "echo $'hello\\nworld'", stringParserInputCompanion);
|
|
68
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
69
|
+
if (cmd.type === 'simple') {
|
|
70
|
+
t.is(cmd.args.length, 1);
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
// Braced variable in double quotes: "${VAR}"
|
|
74
|
+
test('braced variable in double quotes', async (t) => {
|
|
75
|
+
const result = await runParser(bashScriptParser, 'echo "${HOME}"', stringParserInputCompanion);
|
|
76
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
77
|
+
if (cmd.type === 'simple') {
|
|
78
|
+
const dq = cmd.args[0].parts[0];
|
|
79
|
+
if (dq.type === 'doubleQuoted') {
|
|
80
|
+
t.is(dq.parts[0].type, 'variableBraced');
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
// Arithmetic expansion in double quotes
|
|
85
|
+
test('arithmetic expansion in double quotes', async (t) => {
|
|
86
|
+
const result = await runParser(bashScriptParser, 'echo "$((1+2))"', stringParserInputCompanion);
|
|
87
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
88
|
+
if (cmd.type === 'simple') {
|
|
89
|
+
const dq = cmd.args[0].parts[0];
|
|
90
|
+
if (dq.type === 'doubleQuoted') {
|
|
91
|
+
t.is(dq.parts[0].type, 'arithmeticExpansion');
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
// Process substitution: <(cmd)
|
|
96
|
+
test('process substitution input', async (t) => {
|
|
97
|
+
const result = await runParser(bashScriptParser, 'diff <(sort file1) <(sort file2)', stringParserInputCompanion);
|
|
98
|
+
t.truthy(result);
|
|
99
|
+
});
|
|
100
|
+
// Line continuation (backslash-newline)
|
|
101
|
+
test('line continuation', async (t) => {
|
|
102
|
+
const result = await runParser(bashScriptParser, 'echo hello \\\nworld', stringParserInputCompanion);
|
|
103
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
104
|
+
if (cmd.type === 'simple') {
|
|
105
|
+
t.is(cmd.args.length, 2);
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
// Hash in middle of unquoted word is literal, not a comment
|
|
109
|
+
test('hash in middle of unquoted word', async (t) => {
|
|
110
|
+
const result = await runParser(bashScriptParser, 'echo foo#bar', stringParserInputCompanion);
|
|
111
|
+
const cmd = result.entries[0].pipeline.commands[0];
|
|
112
|
+
if (cmd.type === 'simple') {
|
|
113
|
+
t.deepEqual(cmd.args[0], {
|
|
114
|
+
parts: [{ type: 'literal', value: 'foo#bar' }],
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
});
|