@rcrsr/rill-cli 0.6.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 (171) hide show
  1. package/LICENSE +21 -0
  2. package/dist/check/config.d.ts +20 -0
  3. package/dist/check/config.d.ts.map +1 -0
  4. package/dist/check/config.js +151 -0
  5. package/dist/check/config.js.map +1 -0
  6. package/dist/check/fixer.d.ts +39 -0
  7. package/dist/check/fixer.d.ts.map +1 -0
  8. package/dist/check/fixer.js +119 -0
  9. package/dist/check/fixer.js.map +1 -0
  10. package/dist/check/index.d.ts +10 -0
  11. package/dist/check/index.d.ts.map +1 -0
  12. package/dist/check/index.js +21 -0
  13. package/dist/check/index.js.map +1 -0
  14. package/dist/check/rules/anti-patterns.d.ts +65 -0
  15. package/dist/check/rules/anti-patterns.d.ts.map +1 -0
  16. package/dist/check/rules/anti-patterns.js +481 -0
  17. package/dist/check/rules/anti-patterns.js.map +1 -0
  18. package/dist/check/rules/closures.d.ts +66 -0
  19. package/dist/check/rules/closures.d.ts.map +1 -0
  20. package/dist/check/rules/closures.js +370 -0
  21. package/dist/check/rules/closures.js.map +1 -0
  22. package/dist/check/rules/collections.d.ts +90 -0
  23. package/dist/check/rules/collections.d.ts.map +1 -0
  24. package/dist/check/rules/collections.js +373 -0
  25. package/dist/check/rules/collections.js.map +1 -0
  26. package/dist/check/rules/conditionals.d.ts +41 -0
  27. package/dist/check/rules/conditionals.d.ts.map +1 -0
  28. package/dist/check/rules/conditionals.js +134 -0
  29. package/dist/check/rules/conditionals.js.map +1 -0
  30. package/dist/check/rules/flow.d.ts +46 -0
  31. package/dist/check/rules/flow.d.ts.map +1 -0
  32. package/dist/check/rules/flow.js +206 -0
  33. package/dist/check/rules/flow.js.map +1 -0
  34. package/dist/check/rules/formatting.d.ts +143 -0
  35. package/dist/check/rules/formatting.d.ts.map +1 -0
  36. package/dist/check/rules/formatting.js +656 -0
  37. package/dist/check/rules/formatting.js.map +1 -0
  38. package/dist/check/rules/helpers.d.ts +26 -0
  39. package/dist/check/rules/helpers.d.ts.map +1 -0
  40. package/dist/check/rules/helpers.js +66 -0
  41. package/dist/check/rules/helpers.js.map +1 -0
  42. package/dist/check/rules/index.d.ts +21 -0
  43. package/dist/check/rules/index.d.ts.map +1 -0
  44. package/dist/check/rules/index.js +78 -0
  45. package/dist/check/rules/index.js.map +1 -0
  46. package/dist/check/rules/loops.d.ts +77 -0
  47. package/dist/check/rules/loops.d.ts.map +1 -0
  48. package/dist/check/rules/loops.js +310 -0
  49. package/dist/check/rules/loops.js.map +1 -0
  50. package/dist/check/rules/naming.d.ts +21 -0
  51. package/dist/check/rules/naming.d.ts.map +1 -0
  52. package/dist/check/rules/naming.js +174 -0
  53. package/dist/check/rules/naming.js.map +1 -0
  54. package/dist/check/rules/strings.d.ts +28 -0
  55. package/dist/check/rules/strings.d.ts.map +1 -0
  56. package/dist/check/rules/strings.js +79 -0
  57. package/dist/check/rules/strings.js.map +1 -0
  58. package/dist/check/rules/types.d.ts +41 -0
  59. package/dist/check/rules/types.d.ts.map +1 -0
  60. package/dist/check/rules/types.js +167 -0
  61. package/dist/check/rules/types.js.map +1 -0
  62. package/dist/check/types.d.ts +112 -0
  63. package/dist/check/types.d.ts.map +1 -0
  64. package/dist/check/types.js +6 -0
  65. package/dist/check/types.js.map +1 -0
  66. package/dist/check/validator.d.ts +18 -0
  67. package/dist/check/validator.d.ts.map +1 -0
  68. package/dist/check/validator.js +110 -0
  69. package/dist/check/validator.js.map +1 -0
  70. package/dist/check/visitor.d.ts +33 -0
  71. package/dist/check/visitor.d.ts.map +1 -0
  72. package/dist/check/visitor.js +259 -0
  73. package/dist/check/visitor.js.map +1 -0
  74. package/dist/cli-check.d.ts +43 -0
  75. package/dist/cli-check.d.ts.map +1 -0
  76. package/dist/cli-check.js +366 -0
  77. package/dist/cli-check.js.map +1 -0
  78. package/dist/cli-error-enrichment.d.ts +73 -0
  79. package/dist/cli-error-enrichment.d.ts.map +1 -0
  80. package/dist/cli-error-enrichment.js +205 -0
  81. package/dist/cli-error-enrichment.js.map +1 -0
  82. package/dist/cli-error-formatter.d.ts +45 -0
  83. package/dist/cli-error-formatter.d.ts.map +1 -0
  84. package/dist/cli-error-formatter.js +218 -0
  85. package/dist/cli-error-formatter.js.map +1 -0
  86. package/dist/cli-eval.d.ts +15 -0
  87. package/dist/cli-eval.d.ts.map +1 -0
  88. package/dist/cli-eval.js +116 -0
  89. package/dist/cli-eval.js.map +1 -0
  90. package/dist/cli-exec.d.ts +58 -0
  91. package/dist/cli-exec.d.ts.map +1 -0
  92. package/dist/cli-exec.js +326 -0
  93. package/dist/cli-exec.js.map +1 -0
  94. package/dist/cli-explain.d.ts +24 -0
  95. package/dist/cli-explain.d.ts.map +1 -0
  96. package/dist/cli-explain.js +68 -0
  97. package/dist/cli-explain.js.map +1 -0
  98. package/dist/cli-lsp-diagnostic.d.ts +35 -0
  99. package/dist/cli-lsp-diagnostic.d.ts.map +1 -0
  100. package/dist/cli-lsp-diagnostic.js +98 -0
  101. package/dist/cli-lsp-diagnostic.js.map +1 -0
  102. package/dist/cli-module-loader.d.ts +19 -0
  103. package/dist/cli-module-loader.d.ts.map +1 -0
  104. package/dist/cli-module-loader.js +83 -0
  105. package/dist/cli-module-loader.js.map +1 -0
  106. package/dist/cli-shared.d.ts +62 -0
  107. package/dist/cli-shared.d.ts.map +1 -0
  108. package/dist/cli-shared.js +158 -0
  109. package/dist/cli-shared.js.map +1 -0
  110. package/dist/cli.d.ts +13 -0
  111. package/dist/cli.d.ts.map +1 -0
  112. package/dist/cli.js +62 -0
  113. package/dist/cli.js.map +1 -0
  114. package/dist/test-internal-import.d.ts +2 -0
  115. package/dist/test-internal-import.d.ts.map +1 -0
  116. package/dist/test-internal-import.js +7 -0
  117. package/dist/test-internal-import.js.map +1 -0
  118. package/package.json +24 -0
  119. package/src/check/config.ts +202 -0
  120. package/src/check/fixer.ts +174 -0
  121. package/src/check/index.ts +39 -0
  122. package/src/check/rules/anti-patterns.ts +585 -0
  123. package/src/check/rules/closures.ts +445 -0
  124. package/src/check/rules/collections.ts +437 -0
  125. package/src/check/rules/conditionals.ts +155 -0
  126. package/src/check/rules/flow.ts +262 -0
  127. package/src/check/rules/formatting.ts +811 -0
  128. package/src/check/rules/helpers.ts +89 -0
  129. package/src/check/rules/index.ts +140 -0
  130. package/src/check/rules/loops.ts +372 -0
  131. package/src/check/rules/naming.ts +242 -0
  132. package/src/check/rules/strings.ts +104 -0
  133. package/src/check/rules/types.ts +214 -0
  134. package/src/check/types.ts +163 -0
  135. package/src/check/validator.ts +136 -0
  136. package/src/check/visitor.ts +338 -0
  137. package/src/cli-check.ts +456 -0
  138. package/src/cli-error-enrichment.ts +274 -0
  139. package/src/cli-error-formatter.ts +313 -0
  140. package/src/cli-eval.ts +145 -0
  141. package/src/cli-exec.ts +408 -0
  142. package/src/cli-explain.ts +76 -0
  143. package/src/cli-lsp-diagnostic.ts +132 -0
  144. package/src/cli-module-loader.ts +101 -0
  145. package/src/cli-shared.ts +187 -0
  146. package/tests/check/cli-check.test.ts +189 -0
  147. package/tests/check/config.test.ts +350 -0
  148. package/tests/check/fixer.test.ts +373 -0
  149. package/tests/check/format-diagnostics.test.ts +327 -0
  150. package/tests/check/rules/anti-patterns.test.ts +467 -0
  151. package/tests/check/rules/closures.test.ts +192 -0
  152. package/tests/check/rules/collections.test.ts +380 -0
  153. package/tests/check/rules/conditionals.test.ts +185 -0
  154. package/tests/check/rules/flow.test.ts +250 -0
  155. package/tests/check/rules/formatting.test.ts +755 -0
  156. package/tests/check/rules/loops.test.ts +334 -0
  157. package/tests/check/rules/naming.test.ts +336 -0
  158. package/tests/check/rules/strings.test.ts +129 -0
  159. package/tests/check/rules/types.test.ts +257 -0
  160. package/tests/check/validator.test.ts +444 -0
  161. package/tests/check/visitor.test.ts +171 -0
  162. package/tests/cli/check.test.ts +801 -0
  163. package/tests/cli/error-enrichment.test.ts +510 -0
  164. package/tests/cli/error-formatter.test.ts +631 -0
  165. package/tests/cli/eval.test.ts +85 -0
  166. package/tests/cli/exec.test.ts +537 -0
  167. package/tests/cli-explain.test.ts +249 -0
  168. package/tests/cli-lsp-diagnostic.test.ts +202 -0
  169. package/tests/cli-shared.test.ts +439 -0
  170. package/tsconfig.json +9 -0
  171. package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,445 @@
1
+ /**
2
+ * Closure Convention Rules
3
+ * Enforces closure best practices from docs/guide-conventions.md:237-286.
4
+ */
5
+
6
+ import type {
7
+ ValidationRule,
8
+ Diagnostic,
9
+ ValidationContext,
10
+ } from '../types.js';
11
+ import type {
12
+ ASTNode,
13
+ BinaryExprNode,
14
+ BlockNode,
15
+ ClosureNode,
16
+ ConditionalNode,
17
+ GroupedExprNode,
18
+ InterpolationNode,
19
+ PipeChainNode,
20
+ PostfixExprNode,
21
+ StatementNode,
22
+ StringLiteralNode,
23
+ UnaryExprNode,
24
+ VariableNode,
25
+ EachExprNode,
26
+ } from '@rcrsr/rill';
27
+ import { extractContextLine } from './helpers.js';
28
+
29
+ // ============================================================
30
+ // CLOSURE_BARE_DOLLAR RULE
31
+ // ============================================================
32
+
33
+ /**
34
+ * Warns on bare $ in stored closures without parameters.
35
+ * Bare $ in stored closures has ambiguous binding - it refers to the
36
+ * pipe value at closure invocation time, not definition time.
37
+ *
38
+ * Detection:
39
+ * - Zero-parameter closures (|| { }) used outside dict context
40
+ * - Body contains bare $ references (VariableNode with name '$')
41
+ *
42
+ * Valid patterns:
43
+ * - Dict closures: [count: ||{ $.items -> .len }] ($ binds to dict)
44
+ * - Parameterized closures: |x|{ $x } (explicit params)
45
+ * - Inline blocks: -> { $ * 2 } (immediate evaluation)
46
+ *
47
+ * References:
48
+ * - docs/guide-conventions.md:251-261
49
+ * - docs/topic-closures.md: Late binding section
50
+ */
51
+ export const CLOSURE_BARE_DOLLAR: ValidationRule = {
52
+ code: 'CLOSURE_BARE_DOLLAR',
53
+ category: 'closures',
54
+ severity: 'warning',
55
+ nodeTypes: ['Closure'],
56
+
57
+ validate(node: ASTNode, context: ValidationContext): Diagnostic[] {
58
+ const closureNode = node as ClosureNode;
59
+
60
+ // Only check zero-parameter closures (|| { })
61
+ if (closureNode.params.length > 0) {
62
+ return [];
63
+ }
64
+
65
+ // Check if closure body contains bare $ references
66
+ const hasBareReference = containsBareReference(closureNode.body);
67
+
68
+ if (hasBareReference) {
69
+ return [
70
+ {
71
+ location: closureNode.span.start,
72
+ severity: 'warning',
73
+ code: 'CLOSURE_BARE_DOLLAR',
74
+ message:
75
+ 'Bare $ in stored closure has ambiguous binding. Use explicit capture: $ => $item',
76
+ context: extractContextLine(
77
+ closureNode.span.start.line,
78
+ context.source
79
+ ),
80
+ fix: null, // Cannot auto-fix safely - requires context understanding
81
+ },
82
+ ];
83
+ }
84
+
85
+ return [];
86
+ },
87
+ };
88
+
89
+ /**
90
+ * Check if a node tree contains bare $ variable references.
91
+ * Recursively walks the AST looking for VariableNode with isPipeVar=true.
92
+ */
93
+ function containsBareReference(node: ASTNode): boolean {
94
+ if (node.type === 'Variable') {
95
+ const varNode = node as VariableNode;
96
+ // $ is represented as isPipeVar: true with name: null
97
+ if (varNode.isPipeVar) {
98
+ return true;
99
+ }
100
+ }
101
+
102
+ // Recursively check child nodes based on node type
103
+ switch (node.type) {
104
+ case 'Block': {
105
+ const blockNode = node as BlockNode;
106
+ for (const stmt of blockNode.statements) {
107
+ if (containsBareReference(stmt)) return true;
108
+ }
109
+ break;
110
+ }
111
+
112
+ case 'Statement': {
113
+ const stmtNode = node as StatementNode;
114
+ if (stmtNode.expression && containsBareReference(stmtNode.expression))
115
+ return true;
116
+ break;
117
+ }
118
+
119
+ case 'PipeChain': {
120
+ const pipeNode = node as PipeChainNode;
121
+ if (pipeNode.head && containsBareReference(pipeNode.head)) return true;
122
+ if (pipeNode.pipes) {
123
+ for (const pipe of pipeNode.pipes) {
124
+ if (containsBareReference(pipe)) return true;
125
+ }
126
+ }
127
+ break;
128
+ }
129
+
130
+ case 'PostfixExpr': {
131
+ const postfixNode = node as PostfixExprNode;
132
+ if (postfixNode.primary && containsBareReference(postfixNode.primary))
133
+ return true;
134
+ if (postfixNode.methods) {
135
+ for (const method of postfixNode.methods) {
136
+ if (containsBareReference(method)) return true;
137
+ }
138
+ }
139
+ break;
140
+ }
141
+
142
+ case 'BinaryExpr': {
143
+ const binaryNode = node as BinaryExprNode;
144
+ if (binaryNode.left && containsBareReference(binaryNode.left))
145
+ return true;
146
+ if (binaryNode.right && containsBareReference(binaryNode.right))
147
+ return true;
148
+ break;
149
+ }
150
+
151
+ case 'UnaryExpr': {
152
+ const unaryNode = node as UnaryExprNode;
153
+ if (unaryNode.operand && containsBareReference(unaryNode.operand))
154
+ return true;
155
+ break;
156
+ }
157
+
158
+ case 'GroupedExpr': {
159
+ const groupedNode = node as GroupedExprNode;
160
+ if (
161
+ groupedNode.expression &&
162
+ containsBareReference(groupedNode.expression)
163
+ )
164
+ return true;
165
+ break;
166
+ }
167
+
168
+ case 'StringLiteral': {
169
+ const stringNode = node as StringLiteralNode;
170
+ if (stringNode.parts) {
171
+ for (const part of stringNode.parts) {
172
+ if (typeof part === 'object' && containsBareReference(part))
173
+ return true;
174
+ }
175
+ }
176
+ break;
177
+ }
178
+
179
+ case 'Interpolation': {
180
+ const interpNode = node as InterpolationNode;
181
+ if (interpNode.expression && containsBareReference(interpNode.expression))
182
+ return true;
183
+ break;
184
+ }
185
+
186
+ case 'Conditional': {
187
+ const condNode = node as ConditionalNode;
188
+ if (condNode.condition && containsBareReference(condNode.condition))
189
+ return true;
190
+ if (condNode.thenBranch && containsBareReference(condNode.thenBranch))
191
+ return true;
192
+ if (condNode.elseBranch && containsBareReference(condNode.elseBranch))
193
+ return true;
194
+ break;
195
+ }
196
+
197
+ case 'MethodCall':
198
+ case 'HostCall':
199
+ case 'ClosureCall':
200
+ case 'Invoke': {
201
+ const callNode = node as { args: ASTNode[] };
202
+ if (callNode.args) {
203
+ for (const arg of callNode.args) {
204
+ if (containsBareReference(arg)) return true;
205
+ }
206
+ }
207
+ break;
208
+ }
209
+ }
210
+
211
+ return false;
212
+ }
213
+
214
+ // ============================================================
215
+ // CLOSURE_BRACES RULE
216
+ // ============================================================
217
+
218
+ /**
219
+ * Enforces braces for complex closure bodies.
220
+ * Simple expressions can use parentheses, but complex bodies need braces.
221
+ *
222
+ * Complex body criteria:
223
+ * - Contains Block (multiple statements)
224
+ * - Contains Conditional
225
+ * - Contains loop constructs
226
+ *
227
+ * Simple bodies (parentheses OK):
228
+ * - Single expression: |x|($x * 2)
229
+ * - Single method chain: |s|($s.trim.lower)
230
+ *
231
+ * Complex bodies (braces required):
232
+ * - Conditionals: |n| { ($n < 1) ? 1 ! ($n * $fact($n - 1)) }
233
+ * - Multiple statements: |x| { $x => $y; $y * 2 }
234
+ *
235
+ * References:
236
+ * - docs/guide-conventions.md:239-249
237
+ */
238
+ export const CLOSURE_BRACES: ValidationRule = {
239
+ code: 'CLOSURE_BRACES',
240
+ category: 'closures',
241
+ severity: 'info',
242
+ nodeTypes: ['Closure'],
243
+
244
+ validate(node: ASTNode, context: ValidationContext): Diagnostic[] {
245
+ const closureNode = node as ClosureNode;
246
+ const body = closureNode.body;
247
+
248
+ // Check if body is GroupedExpr containing complex content
249
+ if (body.type === 'GroupedExpr') {
250
+ const grouped = body as GroupedExprNode;
251
+ const innerExpr = grouped.expression;
252
+
253
+ // Navigate through PipeChain to find the actual content
254
+ let content: ASTNode = innerExpr;
255
+ if (innerExpr && innerExpr.type === 'PipeChain') {
256
+ const head = innerExpr.head;
257
+ // Check if head is PostfixExpr
258
+ if (head && head.type === 'PostfixExpr') {
259
+ content = head.primary;
260
+ } else {
261
+ content = head;
262
+ }
263
+ }
264
+
265
+ // Check if the content is a conditional or loop
266
+ const isComplex =
267
+ content &&
268
+ (content.type === 'Conditional' ||
269
+ content.type === 'WhileLoop' ||
270
+ content.type === 'DoWhileLoop');
271
+
272
+ if (isComplex) {
273
+ return [
274
+ {
275
+ location: closureNode.span.start,
276
+ severity: 'info',
277
+ code: 'CLOSURE_BRACES',
278
+ message:
279
+ 'Use braces for complex closure bodies (conditionals, loops)',
280
+ context: extractContextLine(
281
+ closureNode.span.start.line,
282
+ context.source
283
+ ),
284
+ fix: null, // Auto-fix would require AST reconstruction
285
+ },
286
+ ];
287
+ }
288
+ }
289
+
290
+ return [];
291
+ },
292
+ };
293
+
294
+ // ============================================================
295
+ // CLOSURE_LATE_BINDING RULE
296
+ // ============================================================
297
+
298
+ /**
299
+ * Detects closures created in loops that may suffer from late binding issues.
300
+ * When creating closures inside loops, variables are captured by reference,
301
+ * not by value. This causes all closures to share the final loop value.
302
+ *
303
+ * Detection:
304
+ * - Each loop body creates a Closure node
305
+ * - Closure references loop variable ($) without explicit capture
306
+ *
307
+ * Solution: Explicit capture per iteration:
308
+ * [1, 2, 3] -> each {
309
+ * $ => $item
310
+ * || { $item }
311
+ * }
312
+ *
313
+ * References:
314
+ * - docs/guide-conventions.md:251-261
315
+ * - docs/topic-closures.md: Late binding section
316
+ */
317
+ export const CLOSURE_LATE_BINDING: ValidationRule = {
318
+ code: 'CLOSURE_LATE_BINDING',
319
+ category: 'closures',
320
+ severity: 'warning',
321
+ nodeTypes: ['EachExpr'],
322
+
323
+ validate(node: ASTNode, context: ValidationContext): Diagnostic[] {
324
+ const eachNode = node as EachExprNode;
325
+ const body = eachNode.body;
326
+
327
+ // Check if body contains a closure creation
328
+ const hasClosureCreation = containsClosureCreation(body);
329
+
330
+ if (hasClosureCreation) {
331
+ // Check if there's an explicit capture before the closure
332
+ const hasExplicitCapture = containsExplicitCapture(body);
333
+
334
+ if (!hasExplicitCapture) {
335
+ return [
336
+ {
337
+ location: eachNode.span.start,
338
+ severity: 'warning',
339
+ code: 'CLOSURE_LATE_BINDING',
340
+ message:
341
+ 'Capture loop variable explicitly for deferred closures: $ => $item',
342
+ context: extractContextLine(
343
+ eachNode.span.start.line,
344
+ context.source
345
+ ),
346
+ fix: null, // Auto-fix would require AST reconstruction
347
+ },
348
+ ];
349
+ }
350
+ }
351
+
352
+ return [];
353
+ },
354
+ };
355
+
356
+ /**
357
+ * Check if a node contains a closure creation (Closure node).
358
+ */
359
+ function containsClosureCreation(node: ASTNode): boolean {
360
+ if (node.type === 'Closure') {
361
+ return true;
362
+ }
363
+
364
+ // Recursively check child nodes
365
+ switch (node.type) {
366
+ case 'Block': {
367
+ const blockNode = node as BlockNode;
368
+ for (const stmt of blockNode.statements) {
369
+ if (containsClosureCreation(stmt)) return true;
370
+ }
371
+ break;
372
+ }
373
+
374
+ case 'Statement': {
375
+ const stmtNode = node as StatementNode;
376
+ if (stmtNode.expression && containsClosureCreation(stmtNode.expression))
377
+ return true;
378
+ break;
379
+ }
380
+
381
+ case 'PipeChain': {
382
+ const pipeNode = node as PipeChainNode;
383
+ if (pipeNode.head && containsClosureCreation(pipeNode.head)) return true;
384
+ if (pipeNode.pipes) {
385
+ for (const pipe of pipeNode.pipes) {
386
+ if (containsClosureCreation(pipe)) return true;
387
+ }
388
+ }
389
+ break;
390
+ }
391
+
392
+ case 'PostfixExpr': {
393
+ const postfixNode = node as PostfixExprNode;
394
+ if (postfixNode.primary && containsClosureCreation(postfixNode.primary))
395
+ return true;
396
+ break;
397
+ }
398
+ }
399
+
400
+ return false;
401
+ }
402
+
403
+ /**
404
+ * Check if a Block node contains an explicit capture statement ($ => $name).
405
+ */
406
+ function containsExplicitCapture(node: ASTNode): boolean {
407
+ if (node.type !== 'Block') {
408
+ return false;
409
+ }
410
+
411
+ const blockNode = node as BlockNode;
412
+ const statements = blockNode.statements;
413
+
414
+ // Look for capture of $ into a named variable
415
+ for (const stmt of statements) {
416
+ if (
417
+ stmt.type === 'Statement' &&
418
+ stmt.expression &&
419
+ stmt.expression.type === 'PipeChain'
420
+ ) {
421
+ const chain = stmt.expression;
422
+
423
+ // Check if any pipe is a Capture
424
+ if (chain.pipes && Array.isArray(chain.pipes)) {
425
+ for (const pipe of chain.pipes) {
426
+ if (pipe.type === 'Capture') {
427
+ // Check if the head is bare $
428
+ const head = chain.head;
429
+ if (head && head.type === 'PostfixExpr') {
430
+ const postfix = head as PostfixExprNode;
431
+ if (postfix.primary && postfix.primary.type === 'Variable') {
432
+ const varNode = postfix.primary as VariableNode;
433
+ if (varNode.isPipeVar) {
434
+ return true;
435
+ }
436
+ }
437
+ }
438
+ }
439
+ }
440
+ }
441
+ }
442
+ }
443
+
444
+ return false;
445
+ }