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.
- package/.cursor/plans/OPTIMISE.md +379 -0
- package/.cursor/plans/VERSIONING.md +207 -0
- package/.cursor/rules/IMPORTANT.mdc +97 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +13 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +17 -0
- package/.github/dependabot.yml +38 -0
- package/.github/pull_request_template.md +10 -0
- package/.github/workflows/format.yml +35 -0
- package/CODE_OF_CONDUCT.md +64 -0
- package/CONTRIBUTING.md +52 -0
- package/LICENSE.md +20 -0
- package/PLAN.md +707 -0
- package/README.md +133 -0
- package/SECURITY.md +21 -0
- package/docs/APEXANNOTATIONS.md +472 -0
- package/docs/APEXDOC.md +198 -0
- package/docs/CML.md +877 -0
- package/docs/CODEANALYZER.md +435 -0
- package/docs/CONTEXTDEFINITIONS.md +617 -0
- package/docs/ESLINT.md +827 -0
- package/docs/ESLINTJSDOC.md +520 -0
- package/docs/FIELDSERVICE.md +4452 -0
- package/docs/GRAPHBINARY.md +208 -0
- package/docs/GRAPHENGINE.md +616 -0
- package/docs/GRAPHML.md +337 -0
- package/docs/GRAPHSON.md +302 -0
- package/docs/GREMLIN.md +490 -0
- package/docs/GRYO.md +232 -0
- package/docs/HUSKY.md +106 -0
- package/docs/JEST.md +387 -0
- package/docs/JORJE.md +537 -0
- package/docs/JSDOC.md +621 -0
- package/docs/PMD.md +910 -0
- package/docs/PNPM.md +409 -0
- package/docs/PRETTIER.md +716 -0
- package/docs/PRETTIERAPEX.md +874 -0
- package/docs/REVENUETRANSACTIONMANAGEMENT.md +887 -0
- package/docs/TINKERPOP.md +252 -0
- package/docs/VITEST.md +706 -0
- package/docs/VSCODE.md +231 -0
- package/docs/XPATH31.md +213 -0
- package/package.json +32 -0
- package/postinstall.mjs +51 -0
- 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 |
|