agent-docs 1.0.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 (44) hide show
  1. package/.cursor/plans/OPTIMISE.md +379 -0
  2. package/.cursor/plans/VERSIONING.md +207 -0
  3. package/.cursor/rules/IMPORTANT.mdc +97 -0
  4. package/.github/ISSUE_TEMPLATE/bug_report.md +13 -0
  5. package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
  6. package/.github/dependabot.yml +38 -0
  7. package/.github/pull_request_template.md +10 -0
  8. package/.github/workflows/format.yml +35 -0
  9. package/CODE_OF_CONDUCT.md +64 -0
  10. package/CONTRIBUTING.md +52 -0
  11. package/LICENSE.md +20 -0
  12. package/PLAN.md +707 -0
  13. package/README.md +133 -0
  14. package/SECURITY.md +21 -0
  15. package/docs/APEXANNOTATIONS.md +472 -0
  16. package/docs/APEXDOC.md +198 -0
  17. package/docs/CML.md +877 -0
  18. package/docs/CODEANALYZER.md +435 -0
  19. package/docs/CONTEXTDEFINITIONS.md +617 -0
  20. package/docs/ESLINT.md +827 -0
  21. package/docs/ESLINTJSDOC.md +520 -0
  22. package/docs/FIELDSERVICE.md +4452 -0
  23. package/docs/GRAPHBINARY.md +208 -0
  24. package/docs/GRAPHENGINE.md +616 -0
  25. package/docs/GRAPHML.md +337 -0
  26. package/docs/GRAPHSON.md +302 -0
  27. package/docs/GREMLIN.md +490 -0
  28. package/docs/GRYO.md +232 -0
  29. package/docs/HUSKY.md +106 -0
  30. package/docs/JEST.md +387 -0
  31. package/docs/JORJE.md +537 -0
  32. package/docs/JSDOC.md +621 -0
  33. package/docs/PMD.md +910 -0
  34. package/docs/PNPM.md +409 -0
  35. package/docs/PRETTIER.md +716 -0
  36. package/docs/PRETTIERAPEX.md +874 -0
  37. package/docs/REVENUETRANSACTIONMANAGEMENT.md +887 -0
  38. package/docs/TINKERPOP.md +252 -0
  39. package/docs/VITEST.md +706 -0
  40. package/docs/VSCODE.md +231 -0
  41. package/docs/XPATH31.md +213 -0
  42. package/package.json +32 -0
  43. package/postinstall.mjs +51 -0
  44. package/prettier.config.js +18 -0
package/docs/JORJE.md ADDED
@@ -0,0 +1,537 @@
1
+ # Jorje AST Reference
2
+
3
+ > **Version**: 1.0.0
4
+
5
+ > **Quick Info**: Salesforce's Java-based Apex parser. Consumed via
6
+ > `prettier-plugin-apex`. All nodes have `@class` property as type identifier.
7
+
8
+ ---
9
+
10
+ ## Node Structure
11
+
12
+ ```typescript
13
+ interface ApexNode {
14
+ '@class': string; // Required: node type (e.g., "apex.jorje.data.ast.Identifier")
15
+ [key: string]: unknown; // Additional properties vary by type
16
+ }
17
+
18
+ // Access patterns
19
+ const nodeClass = node['@class'];
20
+ const safeClass = node?.['@class'];
21
+ ```
22
+
23
+ **Package patterns:**
24
+
25
+ - `apex.jorje.data.ast.*` — Main AST nodes (expressions, declarations,
26
+ statements)
27
+ - `apex.jorje.parser.impl.*` — Parser implementation (comments, tokens)
28
+ - `$` separator — Java inner classes (e.g., `NewObject$NewListLiteral`)
29
+
30
+ ---
31
+
32
+ ## Complete Node Types
33
+
34
+ ### Collections
35
+
36
+ | Apex | `@class` | Properties |
37
+ | ---------------- | ---------------------------------------------- | ----------------- |
38
+ | `new List<T>{}` | `apex.jorje.data.ast.NewObject$NewListLiteral` | `types`, `values` |
39
+ | `new Set<T>{}` | `apex.jorje.data.ast.NewObject$NewSetLiteral` | `types`, `values` |
40
+ | `new Map<K,V>{}` | `apex.jorje.data.ast.NewObject$NewMapLiteral` | `types`, `pairs` |
41
+ | Map key-value | `apex.jorje.data.ast.MapLiteralKeyValue` | `key`, `value` |
42
+
43
+ ### Annotations
44
+
45
+ | Apex | `@class` | Properties |
46
+ | ----------------- | ------------------------------------------------------------ | --------------------------------- |
47
+ | `@Name` | `apex.jorje.data.ast.Modifier$Annotation` | `name` (Identifier), `parameters` |
48
+ | `key=value` param | `apex.jorje.data.ast.AnnotationParameter$AnnotationKeyValue` | `key`, `value` |
49
+ | String param | `apex.jorje.data.ast.AnnotationParameter$AnnotationString` | `value` (string) |
50
+ | `true` value | `apex.jorje.data.ast.AnnotationValue$TrueAnnotationValue` | — |
51
+ | `false` value | `apex.jorje.data.ast.AnnotationValue$FalseAnnotationValue` | — |
52
+ | String value | `apex.jorje.data.ast.AnnotationValue$StringAnnotationValue` | `value` (string) |
53
+
54
+ ### Identifiers & Types
55
+
56
+ | Apex | `@class` | Properties |
57
+ | ------------------ | -------------------------------------- | --------------------------- |
58
+ | Identifier `myVar` | `apex.jorje.data.ast.Identifier` | `value` (string) |
59
+ | Type ref `String` | `apex.jorje.data.ast.TypeRef` | `types` OR `names` (arrays) |
60
+ | SOQL from | `apex.jorje.data.ast.FromExpr` | (SOQL structure) |
61
+ | Variable type | `apex.jorje.data.ast.VariableTypeNode` | (type declarations) |
62
+
63
+ ### Declarations
64
+
65
+ | Apex | `@class` | Properties |
66
+ | --------- | ----------------------------------- | ------------------------------------------------------- |
67
+ | Method | `apex.jorje.data.ast.MethodDecl` | `name`, `modifiers`, `parameters`, `returnType`, `body` |
68
+ | Class | `apex.jorje.data.ast.ClassDecl` | `name`, `modifiers`, `extends`, `implements`, `body` |
69
+ | Interface | `apex.jorje.data.ast.InterfaceDecl` | `name`, `modifiers`, `extends`, `body` |
70
+ | Field | `apex.jorje.data.ast.FieldDecl` | `name`, `modifiers`, `type`, `initializer` |
71
+
72
+ ### Modifiers
73
+
74
+ | Modifier | `@class` Pattern |
75
+ | -------- | ---------------------------------------------------------- |
76
+ | Access | `Modifier$Public`, `$Private`, `$Protected`, `$Global` |
77
+ | Behavior | `$Static`, `$Final`, `$Abstract`, `$Virtual`, `$Transient` |
78
+ | Sharing | `$WithSharing`, `$WithoutSharing` |
79
+ | Other | `$Override`, `$WebService`, `$TestMethod` |
80
+
81
+ ### Comments
82
+
83
+ | Type | `@class` | Properties |
84
+ | ---------------- | -------------------------------------------------- | ---------------- |
85
+ | Block `/* */` | `apex.jorje.parser.impl.HiddenTokens$BlockComment` | `value` (string) |
86
+ | Inline `//` | Pattern: includes `InlineComment` | `value` (string) |
87
+ | ApexDoc `/** */` | Same as Block (detected by content) | `value` (string) |
88
+
89
+ ### Expressions
90
+
91
+ | Apex | `@class` | Properties |
92
+ | ------- | --------------------------------- | ---------------- |
93
+ | Literal | `apex.jorje.data.ast.LiteralExpr` | `value` (varies) |
94
+
95
+ ---
96
+
97
+ ## Property Patterns
98
+
99
+ | Category | Properties |
100
+ | ---------------- | -------------------------------------------------------------------------------- |
101
+ | **Collections** | `values` (array), `pairs` (array), `types` (array) |
102
+ | **Identifiers** | `value` (string) |
103
+ | **Annotations** | `name` (Identifier), `parameters` (array), `key`, `value` |
104
+ | **Types** | `types` (array) OR `names` (array) — not both |
105
+ | **Declarations** | `name`, `modifiers`, `body`, `parameters`, `returnType`, `extends`, `implements` |
106
+ | **Comments** | `value` (full text including delimiters) |
107
+
108
+ ---
109
+
110
+ ## Detection Patterns
111
+
112
+ ### Type Detection Functions
113
+
114
+ ```typescript
115
+ // Constants
116
+ const LIST_CLASS = 'apex.jorje.data.ast.NewObject$NewListLiteral';
117
+ const SET_CLASS = 'apex.jorje.data.ast.NewObject$NewSetLiteral';
118
+ const MAP_CLASS = 'apex.jorje.data.ast.NewObject$NewMapLiteral';
119
+ const IDENTIFIER_CLASS = 'apex.jorje.data.ast.Identifier';
120
+ const TYPEREF_CLASS = 'apex.jorje.data.ast.TypeRef';
121
+ const ANNOTATION_CLASS = 'apex.jorje.data.ast.Modifier$Annotation';
122
+ const BLOCK_COMMENT = 'apex.jorje.parser.impl.HiddenTokens$BlockComment';
123
+
124
+ // Safe class access
125
+ const getNodeClass = (node: ApexNode): string => node['@class'];
126
+ const getNodeClassOptional = (node: unknown): string | undefined =>
127
+ node && typeof node === 'object' && '@class' in node
128
+ ? (node as ApexNode)['@class']
129
+ : undefined;
130
+
131
+ // Type guards
132
+ const isIdentifier = (node: ApexNode): node is ApexIdentifier =>
133
+ node['@class'] === IDENTIFIER_CLASS ||
134
+ node['@class']?.includes('Identifier');
135
+
136
+ const isListOrSet = (node: ApexNode): boolean =>
137
+ node['@class'] === LIST_CLASS || node['@class'] === SET_CLASS;
138
+
139
+ const isCollection = (node: ApexNode): boolean =>
140
+ node['@class'] === LIST_CLASS ||
141
+ node['@class'] === SET_CLASS ||
142
+ node['@class'] === MAP_CLASS;
143
+
144
+ const isCommentNode = (node: unknown): boolean => {
145
+ const cls = getNodeClassOptional(node);
146
+ return (
147
+ cls === BLOCK_COMMENT ||
148
+ cls?.includes('BlockComment') ||
149
+ cls?.includes('InlineComment')
150
+ );
151
+ };
152
+
153
+ const isTypeRef = (node: ApexNode): boolean =>
154
+ node['@class'] === TYPEREF_CLASS || node['@class']?.includes('TypeRef');
155
+ ```
156
+
157
+ ### Pattern Matching Rules
158
+
159
+ | Detection | Method | Example |
160
+ | ----------- | -------------------------- | ------------------------------------------------------------------------------------- |
161
+ | Collections | **Exact match only** | `cls === LIST_CLASS` |
162
+ | Identifiers | Exact first, then includes | `cls === ID_CLASS \|\| cls?.includes('Identifier')` |
163
+ | Comments | Includes pattern | `cls?.includes('BlockComment')` |
164
+ | Types | Includes with exclusion | `cls?.includes('TypeRef') \|\| (cls?.includes('Type') && !cls?.includes('Variable'))` |
165
+ | Modifiers | Includes pattern | `cls?.includes('Static')` |
166
+
167
+ **Priority**: Exact match → Partial match (fallback only)
168
+
169
+ ---
170
+
171
+ ## Type Context Detection
172
+
173
+ ```typescript
174
+ const isInTypeContext = (path: AstPath<ApexNode>): boolean => {
175
+ const { key, stack } = path;
176
+
177
+ // Direct type context keys
178
+ if (
179
+ key === 'types' ||
180
+ key === 'type' ||
181
+ key === 'typeref' ||
182
+ key === 'returntype' ||
183
+ key === 'names'
184
+ ) {
185
+ return true;
186
+ }
187
+
188
+ // Check parent in stack (offset -2)
189
+ if (Array.isArray(stack) && stack.length >= 2) {
190
+ const parent = stack[stack.length - 2] as ApexNode;
191
+ const parentClass = getNodeClassOptional(parent);
192
+
193
+ if (
194
+ parentClass?.includes('TypeRef') ||
195
+ (parentClass?.includes('Type') &&
196
+ !parentClass?.includes('Variable')) ||
197
+ parentClass?.includes('FromExpr')
198
+ ) {
199
+ return true;
200
+ }
201
+
202
+ if ('types' in parent && Array.isArray(parent.types)) {
203
+ return true;
204
+ }
205
+ }
206
+ return false;
207
+ };
208
+ ```
209
+
210
+ ---
211
+
212
+ ## Path API
213
+
214
+ | Method | Purpose | Example |
215
+ | --------------------- | -------------------- | ----------------------------------------- |
216
+ | `path.getNode()` | Get current node | `const node = path.getNode() as ApexNode` |
217
+ | `path.key` | Current property key | `if (path.key === 'types')` |
218
+ | `path.stack` | Parent nodes array | `stack[stack.length - 2]` = parent |
219
+ | `path.map(fn, prop)` | Map over array prop | `path.map(print, 'values' as never)` |
220
+ | `path.call(fn, prop)` | Access nested prop | `path.call(print, 'key' as never)` |
221
+
222
+ ### Path Patterns
223
+
224
+ ```typescript
225
+ // Map collection values
226
+ const printedValues = path.map(print, 'values' as never);
227
+
228
+ // Map map pairs
229
+ const printedPairs = path.map(
230
+ (pairPath) => [
231
+ pairPath.call(print, 'key' as never),
232
+ ' => ',
233
+ pairPath.call(print, 'value' as never),
234
+ ],
235
+ 'pairs' as never,
236
+ );
237
+
238
+ // Access parent
239
+ const parent = path.stack[path.stack.length - 2] as ApexNode;
240
+
241
+ // Type assertion for unknown properties
242
+ path.map(print, 'values' as never); // Use 'as never' when prop not in type def
243
+ ```
244
+
245
+ ---
246
+
247
+ ## Property Mutation Pattern
248
+
249
+ **Always restore mutations** — nodes may be cached/reused:
250
+
251
+ ```typescript
252
+ function normalizeIdentifier(
253
+ node: ApexIdentifier,
254
+ print: PrintFn,
255
+ path: AstPath,
256
+ ): Doc {
257
+ const original = node.value;
258
+ const normalized = normalizeTypeName(original);
259
+
260
+ if (normalized === original) return print(path);
261
+
262
+ try {
263
+ (node as { value: string }).value = normalized; // Mutate
264
+ return print(path);
265
+ } finally {
266
+ (node as { value: string }).value = original; // Always restore
267
+ }
268
+ }
269
+ ```
270
+
271
+ ---
272
+
273
+ ## Common Code Patterns
274
+
275
+ ### Collection Handling
276
+
277
+ ```typescript
278
+ if (node['@class'] === LIST_CLASS || node['@class'] === SET_CLASS) {
279
+ const values = (node as ApexListInitNode).values;
280
+ // values is array of nodes
281
+ }
282
+
283
+ if (node['@class'] === MAP_CLASS) {
284
+ const pairs = (node as ApexMapInitNode).pairs;
285
+ // pairs is array of MapLiteralKeyValue nodes
286
+ }
287
+ ```
288
+
289
+ ### Annotation Handling
290
+
291
+ ```typescript
292
+ if (node['@class'] === ANNOTATION_CLASS) {
293
+ const ann = node as ApexAnnotationNode;
294
+ const name = ann.name.value; // Identifier.value
295
+ const params = ann.parameters; // Array
296
+
297
+ params.forEach((param) => {
298
+ if (
299
+ param['@class'] ===
300
+ 'apex.jorje.data.ast.AnnotationParameter$AnnotationKeyValue'
301
+ ) {
302
+ const key = (param as ApexAnnotationKeyValue).key.value;
303
+ const value = (param as ApexAnnotationKeyValue).value;
304
+ }
305
+ });
306
+ }
307
+ ```
308
+
309
+ ### TypeRef Handling
310
+
311
+ ```typescript
312
+ if (isTypeRef(node)) {
313
+ // TypeRef has EITHER 'types' OR 'names', not both
314
+ if ('types' in node && Array.isArray(node.types)) {
315
+ // Generic types: List<String>, Map<K,V>
316
+ const types = path.map(print, 'types' as never);
317
+ }
318
+ if ('names' in node && Array.isArray(node.names)) {
319
+ // Interface implementations: implements I1, I2
320
+ const names = path.map(print, 'names' as never);
321
+ }
322
+ }
323
+ ```
324
+
325
+ ### Comment & ApexDoc Handling
326
+
327
+ ```typescript
328
+ if (isCommentNode(node)) {
329
+ const comment = node as { value?: string };
330
+ const text = comment.value; // Full text including /** and */
331
+
332
+ // ApexDoc detection (multi-line with * prefix)
333
+ const isApexDoc = (text: string): boolean => {
334
+ const lines = text.split('\n');
335
+ return (
336
+ lines.length > 1 &&
337
+ lines.slice(1, -1).every((line) => line.trim()[0] === '*')
338
+ );
339
+ };
340
+
341
+ // Code block detection for embed
342
+ if (text?.includes('{@code')) {
343
+ // Extract and format code blocks
344
+ }
345
+ }
346
+ ```
347
+
348
+ ### Modifier Detection
349
+
350
+ ```typescript
351
+ const modifiers = (decl as { modifiers?: ApexNode[] }).modifiers;
352
+
353
+ const hasStatic = modifiers?.some((m) => m['@class']?.includes('Static'));
354
+ const hasVirtual = modifiers?.some((m) => m['@class']?.includes('Virtual'));
355
+ const isPublic = modifiers?.some((m) => m['@class']?.includes('Public'));
356
+ ```
357
+
358
+ ### Stack Nesting Detection
359
+
360
+ ```typescript
361
+ const isNestedInCollection = (path: AstPath<ApexNode>): boolean => {
362
+ for (const parent of path.stack) {
363
+ if (typeof parent === 'object' && parent && '@class' in parent) {
364
+ const cls = (parent as ApexNode)['@class'];
365
+ if (cls === LIST_CLASS || cls === SET_CLASS || cls === MAP_CLASS)
366
+ return true;
367
+ }
368
+ }
369
+ return false;
370
+ };
371
+ ```
372
+
373
+ ---
374
+
375
+ ## Embed Function Pattern
376
+
377
+ For async formatting (e.g., code blocks in comments):
378
+
379
+ ```typescript
380
+ const customEmbed = (path: AstPath<ApexNode>, options: ParserOptions) => {
381
+ const node = path.getNode() as ApexNode;
382
+
383
+ if (
384
+ isCommentNode(node) &&
385
+ 'value' in node &&
386
+ typeof node.value === 'string'
387
+ ) {
388
+ if (node.value.includes('{@code')) {
389
+ return async (textToDoc, print, path, options) => {
390
+ // Extract code from comment.value
391
+ // Format with textToDoc (recursive Prettier)
392
+ // Store in Map for printComment to retrieve
393
+ };
394
+ }
395
+ }
396
+ return null; // No embedding
397
+ };
398
+ ```
399
+
400
+ ---
401
+
402
+ ## Parsers
403
+
404
+ | Parser | Use Case | Example Input |
405
+ | ---------------- | ------------------------- | ----------------------------------------------- |
406
+ | `apex` | Full class files (`.cls`) | `public class Test { void method() { } }` |
407
+ | `apex-anonymous` | Standalone snippets | `List<String> items = new List<String>{ 'a' };` |
408
+
409
+ **Both produce identical AST** — same Jorje node types, same printer.
410
+
411
+ ---
412
+
413
+ ## Node Discovery
414
+
415
+ ### Methods
416
+
417
+ 1. **Playground**: https://apex.dangmai.net → Enable "Show AST"
418
+ 2. **Debug log**: `console.log(node['@class'], Object.keys(node))`
419
+ 3. **Parse directly**:
420
+ ```typescript
421
+ import * as apexPlugin from 'prettier-plugin-apex';
422
+ const ast = await apexPlugin.parsers.apex.parse(code, {});
423
+ console.log(JSON.stringify(ast, null, 2));
424
+ ```
425
+
426
+ ### Recursive Type Collection
427
+
428
+ ```typescript
429
+ function collectNodeTypes(node: ApexNode, types: Set<string>): void {
430
+ if (node && typeof node === 'object' && '@class' in node) {
431
+ types.add(node['@class']);
432
+ for (const value of Object.values(node)) {
433
+ if (Array.isArray(value)) {
434
+ value.forEach((child) => {
435
+ if (child && typeof child === 'object')
436
+ collectNodeTypes(child as ApexNode, types);
437
+ });
438
+ } else if (value && typeof value === 'object') {
439
+ collectNodeTypes(value as ApexNode, types);
440
+ }
441
+ }
442
+ }
443
+ }
444
+ ```
445
+
446
+ ---
447
+
448
+ ## ESLint Configuration
449
+
450
+ Required for Jorje AST files:
451
+
452
+ ```typescript
453
+ /* eslint-disable @typescript-eslint/naming-convention */ // '@class' property
454
+ /* eslint-disable @typescript-eslint/no-unsafe-type-assertion */ // Index signatures
455
+ /* eslint-disable @typescript-eslint/prefer-readonly-parameter-types */ // Node mutations
456
+ ```
457
+
458
+ **Why**: `@class` violates naming conventions but is required by Jorje.
459
+
460
+ ---
461
+
462
+ ## Error Handling
463
+
464
+ ```typescript
465
+ // Safe node validation
466
+ if (!node || typeof node !== 'object') return false;
467
+ const nodeClass = getNodeClassOptional(node);
468
+ if (!nodeClass) return false;
469
+
470
+ // Safe property access
471
+ const value = isIdentifier(node) ? node.value : undefined;
472
+
473
+ // Index signature fallback
474
+ const prop = (node as { [key: string]: unknown })[propertyName];
475
+ ```
476
+
477
+ ---
478
+
479
+ ## Key Implementation Notes
480
+
481
+ | Pattern | Purpose |
482
+ | ------------------------ | ---------------------------------------------------- |
483
+ | Type normalization | Identifiers in type contexts → standard casing |
484
+ | Collection formatting | 2+ entries → forced multiline |
485
+ | Annotation normalization | Names/options → case-normalized |
486
+ | ApexDoc processing | Parse tags, format `{@code}` blocks |
487
+ | Property mutation | Always restore (try/finally) |
488
+ | Path creation | Spread for temporary mutations |
489
+ | TypeRef handling | Check both `types` and `names` |
490
+ | Comment embed | Async formatting via embed function |
491
+ | Parser wrapping | Both `apex` and `apex-anonymous` wrapped identically |
492
+
493
+ ---
494
+
495
+ ## Node Hierarchy
496
+
497
+ | Category | Examples |
498
+ | --------------- | --------------------------------------------------------- |
499
+ | **Expression** | `LiteralExpr`, `BinaryExpression`, `MethodCallExpression` |
500
+ | **Statement** | `IfBlock`, `ForLoop`, `ReturnStatement` |
501
+ | **Declaration** | `MethodDecl`, `ClassDecl`, `FieldDecl` |
502
+ | **Type** | `TypeRef`, `ArrayTypeRef`, `ClassRef`, `VariableTypeNode` |
503
+ | **Modifier** | `Modifier$Annotation`, `Modifier$Public`, etc. |
504
+ | **SOQL/SOSL** | `FromExpr` |
505
+
506
+ ---
507
+
508
+ ## Performance
509
+
510
+ - Jorje runs as Java process (via `prettier-plugin-apex`)
511
+ - HTTP server mode reduces JVM startup overhead
512
+ - AST traversal: fast (in-memory)
513
+ - Node type checks: O(1) string comparison
514
+
515
+ ---
516
+
517
+ ## References
518
+
519
+ - **prettier-plugin-apex**: https://github.com/dangmai/prettier-plugin-apex
520
+ - **Playground**: https://apex.dangmai.net
521
+ - **Source files**: `src/types.ts`, `src/collections.ts`, `src/annotations.ts`,
522
+ `src/casing.ts`, `src/printer.ts`
523
+
524
+ ---
525
+
526
+ ## Glossary
527
+
528
+ | Term | Definition |
529
+ | ------------ | ----------------------------------------------------------- |
530
+ | `@class` | Required property identifying node type |
531
+ | AST | Abstract Syntax Tree |
532
+ | Jorje | Salesforce's Apex parser/compiler (Java-based) |
533
+ | Inner class | Java class inside another (uses `$` separator) |
534
+ | Type guard | Function narrowing TypeScript types via runtime checks |
535
+ | Type context | AST location where identifiers = type names (not variables) |
536
+ | Path stack | Array of parent nodes (root → current) |
537
+ | SOQL | Salesforce Object Query Language |