@shipfox/expression 1.2.1 → 2.1.0

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.
Files changed (53) hide show
  1. package/README.md +25 -12
  2. package/dist/evaluator/errors.d.ts.map +1 -1
  3. package/dist/evaluator/index.d.ts +1 -0
  4. package/dist/evaluator/index.d.ts.map +1 -1
  5. package/dist/evaluator/index.js +1 -0
  6. package/dist/evaluator/index.js.map +1 -1
  7. package/dist/evaluator/rehydrate-json-expression.d.ts +4 -0
  8. package/dist/evaluator/rehydrate-json-expression.d.ts.map +1 -0
  9. package/dist/evaluator/rehydrate-json-expression.js +44 -0
  10. package/dist/evaluator/rehydrate-json-expression.js.map +1 -0
  11. package/dist/expression/create-workflow-expression.d.ts.map +1 -1
  12. package/dist/expression/create-workflow-expression.js +23 -1
  13. package/dist/expression/create-workflow-expression.js.map +1 -1
  14. package/dist/expression/errors.d.ts.map +1 -1
  15. package/dist/index.d.ts +5 -3
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +4 -2
  18. package/dist/index.js.map +1 -1
  19. package/dist/plan/context-key-access.d.ts +2 -0
  20. package/dist/plan/context-key-access.d.ts.map +1 -1
  21. package/dist/plan/context-key-access.js +10 -4
  22. package/dist/plan/context-key-access.js.map +1 -1
  23. package/dist/plan/evaluate-planned-predicate.d.ts.map +1 -1
  24. package/dist/plan/evaluate-planned-predicate.js +2 -2
  25. package/dist/plan/evaluate-planned-predicate.js.map +1 -1
  26. package/dist/resolver/errors.d.ts.map +1 -1
  27. package/dist/run/hoist-run-command.d.ts +8 -0
  28. package/dist/run/hoist-run-command.d.ts.map +1 -1
  29. package/dist/run/hoist-run-command.js +28 -3
  30. package/dist/run/hoist-run-command.js.map +1 -1
  31. package/dist/run/shell-code-position.d.ts +21 -0
  32. package/dist/run/shell-code-position.d.ts.map +1 -0
  33. package/dist/run/shell-code-position.js +623 -0
  34. package/dist/run/shell-code-position.js.map +1 -0
  35. package/dist/run/shell-quoting.d.ts +5 -1
  36. package/dist/run/shell-quoting.d.ts.map +1 -1
  37. package/dist/run/shell-quoting.js +37 -6
  38. package/dist/run/shell-quoting.js.map +1 -1
  39. package/dist/template/errors.d.ts.map +1 -1
  40. package/dist/tsconfig.test.tsbuildinfo +1 -1
  41. package/dist/workflow-context/workflow-context-docs.d.ts +185 -0
  42. package/dist/workflow-context/workflow-context-docs.d.ts.map +1 -0
  43. package/dist/workflow-context/workflow-context-docs.js +168 -0
  44. package/dist/workflow-context/workflow-context-docs.js.map +1 -0
  45. package/dist/workflow-context/workflow-context.d.ts +234 -249
  46. package/dist/workflow-context/workflow-context.d.ts.map +1 -1
  47. package/dist/workflow-context/workflow-context.js +201 -85
  48. package/dist/workflow-context/workflow-context.js.map +1 -1
  49. package/package.json +2 -2
  50. package/dist/template/extract-cel-untrusted-path-accesses.d.ts +0 -5
  51. package/dist/template/extract-cel-untrusted-path-accesses.d.ts.map +0 -1
  52. package/dist/template/extract-cel-untrusted-path-accesses.js +0 -140
  53. package/dist/template/extract-cel-untrusted-path-accesses.js.map +0 -1
@@ -0,0 +1,623 @@
1
+ import { classifyShellSite, initialShellScanState, scanShellLiteral } from './shell-quoting.js';
2
+ const shellIdentifierPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
3
+ const shellIdentifierStartPattern = /[A-Za-z_]/;
4
+ const shellIdentifierCharacterPattern = /[A-Za-z0-9_]/;
5
+ const shellAssignmentPattern = /^[A-Za-z_][A-Za-z0-9_]*=/;
6
+ const shellDigitPattern = /[0-9]/;
7
+ const shellRedirectionCharacterPattern = /[<>]/;
8
+ const shellSedFlagPattern = /^-[neiErzbsu]+$/;
9
+ const shellWhitespacePattern = /\s/;
10
+ const shellCompoundArithmeticAssignmentPattern = /^[A-Za-z_][A-Za-z0-9_]*\s*(?:\+=|-=|\*=|\/=|%=|<<=|>>=|&=|\|=|\^=)/;
11
+ const shellReservedPrefixWords = new Set([
12
+ 'case',
13
+ 'do',
14
+ 'done',
15
+ 'elif',
16
+ 'else',
17
+ 'fi',
18
+ 'for',
19
+ 'if',
20
+ 'in',
21
+ 'then',
22
+ 'time',
23
+ 'until',
24
+ 'while',
25
+ '!'
26
+ ]);
27
+ const shellCommandWrapperWords = new Set([
28
+ 'command',
29
+ 'env',
30
+ 'nohup',
31
+ 'sudo'
32
+ ]);
33
+ /**
34
+ * Finds workflow-controlled values that are passed to shell constructs which
35
+ * deliberately parse or evaluate their arguments again.
36
+ *
37
+ * The scanner follows direct references in a single shell word only. It does
38
+ * not attempt shell data-flow analysis, and intentionally prefers a missed
39
+ * warning over a false positive.
40
+ */ export function classifyShellCodePosition(params) {
41
+ const workflowDataNames = new Set(params.workflowDataNames);
42
+ const matches = [];
43
+ if (workflowDataNames.size === 0) return {
44
+ matches: []
45
+ };
46
+ const tokens = tokenizeShell(params.command);
47
+ let command = [];
48
+ const report = (reference, construct)=>{
49
+ if (!workflowDataNames.has(reference.name)) return;
50
+ matches.push({
51
+ construct,
52
+ name: reference.name
53
+ });
54
+ };
55
+ const reportReferences = (words, construct, kinds = [
56
+ 'direct'
57
+ ], includeSingleQuotedLiterals = false)=>{
58
+ for (const word of words){
59
+ for (const reference of word.references){
60
+ if (kinds.includes(reference.kind) && (includeSingleQuotedLiterals || !reference.isSingleQuotedLiteral)) {
61
+ report(reference, construct);
62
+ }
63
+ }
64
+ }
65
+ };
66
+ const analyzeCommand = (words)=>{
67
+ const commandWords = words.filter((word)=>!word.isRedirectionTarget);
68
+ const firstWord = commandWords[0];
69
+ if (firstWord !== undefined && shellWordIsStatic(firstWord) && firstWord.value === 'for') {
70
+ return;
71
+ }
72
+ const headIndex = commandHeadIndex(commandWords);
73
+ const head = commandWords[headIndex];
74
+ if (head === undefined || !shellWordIsStatic(head)) return;
75
+ const commandName = shellCommandName(head.value);
76
+ if (commandName === undefined) return;
77
+ const argumentWords = commandWords.slice(headIndex + 1);
78
+ if (commandName === 'eval') {
79
+ reportReferences(argumentWords, 'eval', [
80
+ 'direct'
81
+ ], true);
82
+ return;
83
+ }
84
+ if (commandName === 'sh' || commandName === 'bash') {
85
+ const program = shellProgramAfterC(argumentWords);
86
+ if (program !== undefined) {
87
+ reportReferences([
88
+ program
89
+ ], commandName === 'sh' ? 'sh-c' : 'bash-c');
90
+ }
91
+ return;
92
+ }
93
+ if (commandName === 'source' || commandName === '.') {
94
+ reportReferences(argumentWords.slice(0, 1), 'source');
95
+ return;
96
+ }
97
+ if (commandName === 'let') {
98
+ reportReferences(argumentWords, 'let');
99
+ reportBareArithmeticReferences(argumentWords, 'let', report, workflowDataNames, false);
100
+ return;
101
+ }
102
+ if (commandName === 'declare' && argumentWords.some((word)=>word.value === '-i')) {
103
+ reportReferences(argumentWords, 'declare-i');
104
+ reportBareArithmeticReferences(argumentWords, 'declare-i', report, workflowDataNames, true);
105
+ return;
106
+ }
107
+ if (commandName === 'awk' || commandName === 'jq' || commandName === 'sed') {
108
+ const program = interpreterProgramWord(commandName, argumentWords);
109
+ if (program !== undefined) reportReferences([
110
+ program
111
+ ], commandName);
112
+ return;
113
+ }
114
+ if (commandName === 'xargs') {
115
+ const nestedShell = xargsShellProgram(argumentWords);
116
+ if (nestedShell !== undefined) {
117
+ reportReferences([
118
+ nestedShell.program
119
+ ], 'xargs-sh-c');
120
+ }
121
+ }
122
+ };
123
+ for (const token of tokens){
124
+ if (token.kind === 'control') {
125
+ analyzeCommand(command);
126
+ command = [];
127
+ continue;
128
+ }
129
+ if (token.kind === 'word' && token.references.some((reference)=>reference.kind === 'arithmetic')) {
130
+ reportReferences([
131
+ token
132
+ ], 'arithmetic', [
133
+ 'arithmetic'
134
+ ]);
135
+ }
136
+ if (token.kind === 'word') command.push(token);
137
+ }
138
+ analyzeCommand(command);
139
+ return {
140
+ matches
141
+ };
142
+ }
143
+ function tokenizeShell(source) {
144
+ const tokens = [];
145
+ let current;
146
+ let index = 0;
147
+ let redirectionTarget = false;
148
+ let wordCanStartComment = true;
149
+ let scanState = initialShellScanState;
150
+ let arithmeticContext = false;
151
+ let parameterBracketDepth = 0;
152
+ let arithmeticBracketDepth = 0;
153
+ let arithmeticShellSyntax = false;
154
+ function ensureWord() {
155
+ if (current !== undefined) return current;
156
+ current = {
157
+ dynamic: false,
158
+ isRedirectionTarget: redirectionTarget,
159
+ references: [],
160
+ value: []
161
+ };
162
+ redirectionTarget = false;
163
+ wordCanStartComment = false;
164
+ return current;
165
+ }
166
+ function flushWord() {
167
+ if (current === undefined) return;
168
+ tokens.push({
169
+ dynamic: current.dynamic,
170
+ kind: 'word',
171
+ isRedirectionTarget: current.isRedirectionTarget,
172
+ references: current.references.filter((reference)=>!(arithmeticShellSyntax && reference.kind === 'arithmetic')),
173
+ value: current.value.join('')
174
+ });
175
+ current = undefined;
176
+ arithmeticShellSyntax = false;
177
+ }
178
+ function markDynamic() {
179
+ ensureWord().dynamic = true;
180
+ }
181
+ function addReference(name, kind, isSingleQuotedLiteral = false) {
182
+ if (kind === 'arithmetic' && arithmeticShellSyntax) return;
183
+ ensureWord().references.push({
184
+ kind,
185
+ isSingleQuotedLiteral,
186
+ name
187
+ });
188
+ }
189
+ function markArithmeticShellSyntax() {
190
+ arithmeticShellSyntax = true;
191
+ }
192
+ function advance(text) {
193
+ scanState = scanShellLiteral(text, scanState);
194
+ index += text.length;
195
+ const site = classifyShellSite(scanState);
196
+ if (arithmeticContext && !(site.kind === 'unsafe' && site.region === 'arith')) {
197
+ arithmeticContext = false;
198
+ arithmeticBracketDepth = 0;
199
+ }
200
+ }
201
+ function advanceEscape() {
202
+ const nextCharacter = source[index + 1];
203
+ if (nextCharacter === undefined) {
204
+ advance(source[index] ?? '');
205
+ return;
206
+ }
207
+ if (nextCharacter !== '\n') ensureWord().value.push(nextCharacter);
208
+ advance(source.slice(index, index + 2));
209
+ }
210
+ function advanceExpansion(kind, isSingleQuotedLiteral = false) {
211
+ if (source[index] !== '$') return false;
212
+ if (source.startsWith('$((', index)) {
213
+ markDynamic();
214
+ arithmeticContext = true;
215
+ advance('$((');
216
+ return true;
217
+ }
218
+ if (source.startsWith('$[', index)) {
219
+ markDynamic();
220
+ arithmeticContext = true;
221
+ advance('$[');
222
+ return true;
223
+ }
224
+ if (source.startsWith('$(', index)) {
225
+ markDynamic();
226
+ advance('$(');
227
+ return true;
228
+ }
229
+ if (source.startsWith('${', index)) {
230
+ markDynamic();
231
+ const firstName = readParameterName(source, index + 2);
232
+ if (source[index + 2] !== '#' && firstName !== undefined) {
233
+ addReference(firstName.value, kind, isSingleQuotedLiteral);
234
+ }
235
+ advance('${');
236
+ return true;
237
+ }
238
+ if (source.startsWith("$'", index) || source.startsWith('$"', index)) {
239
+ markDynamic();
240
+ advance(source.slice(index, index + 2));
241
+ return true;
242
+ }
243
+ const name = readShellIdentifier(source, index + 1);
244
+ if (name === undefined) return false;
245
+ addReference(name.value, kind, isSingleQuotedLiteral);
246
+ advance(source.slice(index, name.index));
247
+ return true;
248
+ }
249
+ while(index < source.length){
250
+ const character = source[index];
251
+ if (character === undefined) break;
252
+ const site = classifyShellSite(scanState);
253
+ if (site.kind === 'unsafe') {
254
+ if (site.region === 'line-comment') {
255
+ if (character === '\n') {
256
+ advance('\n');
257
+ flushWord();
258
+ tokens.push({
259
+ kind: 'control'
260
+ });
261
+ wordCanStartComment = true;
262
+ redirectionTarget = false;
263
+ } else {
264
+ advance(character);
265
+ }
266
+ continue;
267
+ }
268
+ if (site.region === 'heredoc') {
269
+ advance(character);
270
+ continue;
271
+ }
272
+ if (site.region === 'arith') {
273
+ if (character === '[') arithmeticBracketDepth += 1;
274
+ if (character === ']' && arithmeticBracketDepth > 0) arithmeticBracketDepth -= 1;
275
+ if (arithmeticBracketDepth === 0 && (character === "'" || character === '"' || character === '`')) {
276
+ markArithmeticShellSyntax();
277
+ }
278
+ if (advanceExpansion('arithmetic')) continue;
279
+ const name = readShellIdentifier(source, index);
280
+ if (name !== undefined) {
281
+ const nextIndex = skipWhitespace(source, name.index);
282
+ if (!isArithmeticAssignment(source, nextIndex)) addReference(name.value, 'arithmetic');
283
+ advance(source.slice(index, name.index));
284
+ continue;
285
+ }
286
+ } else if (site.region === 'param-brace') {
287
+ if (character === '[') parameterBracketDepth += 1;
288
+ if (character === ']' && parameterBracketDepth > 0) parameterBracketDepth -= 1;
289
+ const referenceKind = arithmeticContext || parameterBracketDepth > 0 ? 'arithmetic' : 'direct';
290
+ if (advanceExpansion(referenceKind)) continue;
291
+ } else {
292
+ markDynamic();
293
+ }
294
+ const chunk = scannerChunkAt(source, index, site);
295
+ advance(chunk);
296
+ continue;
297
+ }
298
+ if (character === '\\') {
299
+ advanceEscape();
300
+ continue;
301
+ }
302
+ if (character === '#' && wordCanStartComment) {
303
+ advance('#');
304
+ continue;
305
+ }
306
+ if (character === '$' && advanceExpansion('direct', site.kind === 'single')) continue;
307
+ if (character === '`') {
308
+ markDynamic();
309
+ advance('`');
310
+ continue;
311
+ }
312
+ if (site.kind === 'unquoted' && isShellWhitespace(character)) {
313
+ flushWord();
314
+ if (character === '\n') tokens.push({
315
+ kind: 'control'
316
+ });
317
+ wordCanStartComment = true;
318
+ if (character === '\n') redirectionTarget = false;
319
+ advance(character);
320
+ continue;
321
+ }
322
+ if (character === "'" || character === '"') {
323
+ ensureWord();
324
+ advance(character);
325
+ continue;
326
+ }
327
+ if (site.kind === 'unquoted' && startsShellRedirection(source, index)) {
328
+ flushWord();
329
+ tokens.push({
330
+ kind: 'redirection'
331
+ });
332
+ redirectionTarget = true;
333
+ wordCanStartComment = true;
334
+ const length = shellRedirectionLength(source, index);
335
+ advance(source.slice(index, index + length));
336
+ continue;
337
+ }
338
+ if (site.kind === 'unquoted' && startsShellArithmetic(source, index)) {
339
+ markDynamic();
340
+ arithmeticContext = true;
341
+ advance('((');
342
+ continue;
343
+ }
344
+ if (site.kind === 'unquoted' && startsShellControl(source, index)) {
345
+ flushWord();
346
+ tokens.push({
347
+ kind: 'control'
348
+ });
349
+ wordCanStartComment = true;
350
+ redirectionTarget = false;
351
+ advance(source.slice(index, index + shellControlLength(source, index)));
352
+ continue;
353
+ }
354
+ ensureWord().value.push(character);
355
+ advance(character);
356
+ }
357
+ flushWord();
358
+ return tokens;
359
+ }
360
+ function scannerChunkAt(source, index, site) {
361
+ if (site.region === 'arith' && source[index] === ')') {
362
+ let closingRunLength = 0;
363
+ while(source[index + closingRunLength] === ')')closingRunLength += 1;
364
+ if (closingRunLength > 2) return ')'.repeat(closingRunLength - 2);
365
+ if (closingRunLength === 2) return '))';
366
+ return ')';
367
+ }
368
+ if (source.startsWith('$((', index)) return '$((';
369
+ if (source.startsWith('((', index)) return '((';
370
+ if (source.startsWith('$[', index)) return '$[';
371
+ if (source.startsWith('$(', index) || source.startsWith('${', index)) return source.slice(index, index + 2);
372
+ if (source.startsWith('<<-', index)) return '<<-';
373
+ if (source.startsWith('<<', index)) return '<<';
374
+ if (source[index] === '\\' && index + 1 < source.length) return source.slice(index, index + 2);
375
+ return source[index] ?? '';
376
+ }
377
+ function commandHeadIndex(words) {
378
+ let index = 0;
379
+ while(index < words.length){
380
+ const word = words[index];
381
+ if (isShellAssignment(word)) {
382
+ index += 1;
383
+ continue;
384
+ }
385
+ if (word !== undefined && shellWordIsStatic(word) && shellReservedPrefixWords.has(word.value)) {
386
+ index += 1;
387
+ continue;
388
+ }
389
+ if (word !== undefined && shellWordIsStatic(word) && shellCommandWrapperWords.has(word.value)) {
390
+ index += 1;
391
+ continue;
392
+ }
393
+ break;
394
+ }
395
+ return index;
396
+ }
397
+ function shellWordIsStatic(word) {
398
+ return !word.dynamic && word.references.length === 0;
399
+ }
400
+ function shellCommandName(value) {
401
+ if (value.length === 0) return undefined;
402
+ const name = value.split('/').at(-1);
403
+ return name === undefined || !shellIdentifierPattern.test(name) && name !== '.' ? undefined : name;
404
+ }
405
+ function shellProgramAfterC(words) {
406
+ for(let index = 0; index < words.length; index += 1){
407
+ const word = words[index];
408
+ if (word?.value === '-c' || word?.value === '--command') return words[index + 1];
409
+ if (word?.value.startsWith('-') && !word.value.startsWith('--') && word.value.includes('c')) {
410
+ return words[index + 1];
411
+ }
412
+ if (word?.value === '--') return undefined;
413
+ }
414
+ return undefined;
415
+ }
416
+ function reportBareArithmeticReferences(words, construct, report, workflowDataNames, skipDeclarationNames) {
417
+ for (const word of words){
418
+ if (word.references.length > 0 || word.value.startsWith('-')) continue;
419
+ const expression = skipDeclarationNames ? declarationExpression(word.value) : word.value;
420
+ if (expression === undefined) continue;
421
+ for (const identifier of arithmeticIdentifiers(expression)){
422
+ if (workflowDataNames.has(identifier)) {
423
+ report({
424
+ kind: 'arithmetic',
425
+ name: identifier,
426
+ isSingleQuotedLiteral: false
427
+ }, construct);
428
+ }
429
+ }
430
+ }
431
+ }
432
+ function declarationExpression(value) {
433
+ if (shellIdentifierPattern.test(value)) return undefined;
434
+ if (shellCompoundArithmeticAssignmentPattern.test(value)) {
435
+ return value;
436
+ }
437
+ const assignmentIndex = value.indexOf('=');
438
+ if (assignmentIndex < 0) return undefined;
439
+ return value.slice(assignmentIndex + 1);
440
+ }
441
+ function arithmeticIdentifiers(source) {
442
+ const identifiers = [];
443
+ let index = 0;
444
+ while(index < source.length){
445
+ const name = readShellIdentifier(source, index);
446
+ if (name === undefined) {
447
+ index += 1;
448
+ continue;
449
+ }
450
+ const nextIndex = skipWhitespace(source, name.index);
451
+ if (!isArithmeticAssignment(source, nextIndex)) identifiers.push(name.value);
452
+ index = name.index;
453
+ }
454
+ return identifiers;
455
+ }
456
+ function isArithmeticAssignment(source, index) {
457
+ return source[index] === '=' && source[index + 1] !== '=';
458
+ }
459
+ function interpreterProgramWord(command, words) {
460
+ let index = 0;
461
+ while(index < words.length){
462
+ const word = words[index];
463
+ if (word === undefined) return undefined;
464
+ const value = word.value;
465
+ if (value === '--') return words[index + 1];
466
+ if (!value.startsWith('-') || value === '-') return word;
467
+ if (command === 'jq' && jqOptionConsumesArguments(value)) {
468
+ index += 3;
469
+ continue;
470
+ }
471
+ if (command === 'awk' && awkOptionConsumesArgument(value)) {
472
+ index += 2;
473
+ continue;
474
+ }
475
+ if (command === 'awk' && (value === '-e' || value === '--source')) {
476
+ return words[index + 1];
477
+ }
478
+ if (command === 'sed' && (value === '-e' || value === '--expression')) {
479
+ return words[index + 1];
480
+ }
481
+ if (command === 'sed' && sedOptionConsumesArgument(value)) {
482
+ index += 2;
483
+ continue;
484
+ }
485
+ if (command === 'sed' && value.startsWith('--expression=')) return word;
486
+ if (command === 'sed' && value.startsWith('--file=')) {
487
+ index += 1;
488
+ continue;
489
+ }
490
+ if (command === 'jq' && jqFlag(value)) {
491
+ index += 1;
492
+ continue;
493
+ }
494
+ if (command === 'awk' && awkFlag(value)) {
495
+ index += 1;
496
+ continue;
497
+ }
498
+ if (command === 'sed' && sedFlag(value)) {
499
+ index += 1;
500
+ continue;
501
+ }
502
+ return undefined;
503
+ }
504
+ return undefined;
505
+ }
506
+ function jqOptionConsumesArguments(value) {
507
+ return value === '--arg' || value === '--argjson' || value === '--argfile' || value === '--slurpfile' || value === '--rawfile';
508
+ }
509
+ function awkOptionConsumesArgument(value) {
510
+ return value === '-f' || value === '-v' || value === '-F' || value === '--assign' || value === '--field-separator';
511
+ }
512
+ function sedOptionConsumesArgument(value) {
513
+ return value === '-f' || value === '--file';
514
+ }
515
+ function jqFlag(value) {
516
+ return new Set([
517
+ '-c',
518
+ '-e',
519
+ '-j',
520
+ '-M',
521
+ '-n',
522
+ '-r',
523
+ '-s',
524
+ '-S',
525
+ '-C',
526
+ '--ascii-output',
527
+ '--compact-output',
528
+ '--exit-status',
529
+ '--join-output',
530
+ '--monochrome-output',
531
+ '--null-input',
532
+ '--raw-output',
533
+ '--sort-keys',
534
+ '--slurp',
535
+ '--tab',
536
+ '--version'
537
+ ]).has(value);
538
+ }
539
+ function awkFlag(value) {
540
+ return value === '--posix' || value === '--traditional' || value === '--lint' || value === '--sandbox';
541
+ }
542
+ function sedFlag(value) {
543
+ return shellSedFlagPattern.test(value) || value === '--posix' || value === '--regexp-extended' || value === '--in-place';
544
+ }
545
+ function xargsShellProgram(words) {
546
+ let index = 0;
547
+ while(index < words.length){
548
+ const word = words[index];
549
+ if (word === undefined) return undefined;
550
+ const value = word.value;
551
+ if (value === '--') {
552
+ index += 1;
553
+ break;
554
+ }
555
+ if (!value.startsWith('-') || value === '-') break;
556
+ if (xargsOptionConsumesArgument(value)) index += 2;
557
+ else index += 1;
558
+ }
559
+ const shell = words[index];
560
+ if (shell === undefined || !shellWordIsStatic(shell)) return undefined;
561
+ const shellName = shellCommandName(shell.value);
562
+ if (shellName !== 'sh' && shellName !== 'bash') return undefined;
563
+ const program = shellProgramAfterC(words.slice(index + 1));
564
+ return program === undefined ? undefined : {
565
+ program
566
+ };
567
+ }
568
+ function xargsOptionConsumesArgument(value) {
569
+ return value === '-d' || value === '-E' || value === '-I' || value === '-J' || value === '-L' || value === '-n' || value === '-P' || value === '-s' || value === '--delimiter' || value === '--eof' || value === '--max-args' || value === '--max-lines' || value === '--max-procs' || value === '--max-chars' || value === '--replace';
570
+ }
571
+ function isShellAssignment(word) {
572
+ return word !== undefined && shellAssignmentPattern.test(word.value);
573
+ }
574
+ function readParameterName(source, start) {
575
+ const prefix = source[start];
576
+ return readShellIdentifier(source, prefix === '!' || prefix === '#' ? start + 1 : start);
577
+ }
578
+ function readShellIdentifier(source, start) {
579
+ if (!shellIdentifierStartPattern.test(source[start] ?? '')) return undefined;
580
+ let index = start + 1;
581
+ while(shellIdentifierCharacterPattern.test(source[index] ?? ''))index += 1;
582
+ return {
583
+ index,
584
+ value: source.slice(start, index)
585
+ };
586
+ }
587
+ function skipWhitespace(source, start) {
588
+ let index = start;
589
+ while(shellWhitespacePattern.test(source[index] ?? ''))index += 1;
590
+ return index;
591
+ }
592
+ function isShellWhitespace(character) {
593
+ return character === ' ' || character === '\t' || character === '\r' || character === '\n';
594
+ }
595
+ function startsShellRedirection(source, index) {
596
+ if (source[index] === '>' || source[index] === '<') return true;
597
+ return shellDigitPattern.test(source[index] ?? '') && shellRedirectionCharacterPattern.test(source[index + 1] ?? '');
598
+ }
599
+ function shellRedirectionLength(source, index) {
600
+ let nextIndex = index;
601
+ while(shellDigitPattern.test(source[nextIndex] ?? ''))nextIndex += 1;
602
+ if (source.startsWith('<<<', nextIndex)) return nextIndex - index + 3;
603
+ if (source.startsWith('<<-', nextIndex)) return nextIndex - index + 3;
604
+ if (source.startsWith('<<', nextIndex)) return nextIndex - index + 2;
605
+ if (source.startsWith('>>', nextIndex) || source.startsWith('<>', nextIndex) || source.startsWith('>&', nextIndex) || source.startsWith('<&', nextIndex) || source.startsWith('>|', nextIndex)) {
606
+ return nextIndex - index + 2;
607
+ }
608
+ return nextIndex - index + 1;
609
+ }
610
+ function startsShellArithmetic(source, index) {
611
+ return source.startsWith('((', index);
612
+ }
613
+ function startsShellControl(source, index) {
614
+ return source[index] === ';' || source[index] === '&' || source[index] === '|' || source[index] === '(' || source[index] === ')' || source[index] === '{' || source[index] === '}';
615
+ }
616
+ function shellControlLength(source, index) {
617
+ if (source.startsWith('&&', index) || source.startsWith('||', index) || source.startsWith(';;', index) || source.startsWith(';&', index) || source.startsWith(';;&', index) || source.startsWith('|&', index)) {
618
+ return source.startsWith(';;&', index) ? 3 : 2;
619
+ }
620
+ return 1;
621
+ }
622
+
623
+ //# sourceMappingURL=shell-code-position.js.map