@cparra/apex-reflection 0.1.1-alpha.8 → 1.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.
package/README.md ADDED
@@ -0,0 +1,96 @@
1
+ # Apex Reflection
2
+
3
+ Provides basic reflection for the Apex programming language.
4
+
5
+ ## Installation
6
+
7
+ ```
8
+ npm i @cparra/apex-reflection
9
+ ```
10
+
11
+ ## Usage
12
+
13
+ This library exposes a single function that handles parsing the body of an Apex top level type (class, interface, or
14
+ enum)
15
+ and returns the result.
16
+
17
+ ```typescript
18
+ import {reflect} from '@cparra/apex-reflection';
19
+
20
+ const classBody = 'public with sharing class ExampleClass {}';
21
+ const response = reflect(classBody);
22
+ ```
23
+
24
+ If you wish to parse an Apex type that comes from a file, you can read the file contents and use that as the source to
25
+ reflect
26
+
27
+ ```typescript
28
+ import * as fs from 'fs';
29
+ import {reflect} from '@cparra/apex-reflection';
30
+
31
+ const path = './MyClass.cls';
32
+ const rawFile = fs.readFileSync(path);
33
+ const response = reflect(rawFile.toString());
34
+ ```
35
+
36
+ The `reflect` function returns a `ReflectionResult` which contains either the results of the parsed `Type`
37
+ (which will either be a `ClassMirror`, an `InterfaceMirror` or an `EnumMirror`) or a `ParsingError` if the passed in
38
+ body was not parsed successfully, with a message indicating where the error occurred.
39
+
40
+ ## Contributing
41
+
42
+ Even though this library is exposed as a Node.js library, the project's source code is written in Dart. The source can
43
+ be found in the `lib/src` directory.
44
+
45
+ The Dart source code is transpiled to JS by `dart2js` through the default [grinder](https://pub.dev/packages/grinder)
46
+ workflow within `tool/grind.dart`.
47
+
48
+ To generate the JS files first set up `grinder` locally by following that package's instructions through its pub.dev
49
+ listing, and then you can simply run `grind`. That build takes care of combining the output with `preamble/preamble.js`
50
+ to achieve compatibility with Node.js. The resulting file is `js/apex-reflection-node/out.js`.
51
+
52
+ ### Tests
53
+
54
+ Both the Dart source code and the JS output must be tested.
55
+
56
+ The Dart tests live in the `test` directory. The Dart source code must have unit tests testing each individual Dart file
57
+ as well as end-to-end tests that verify the overall parsing functionality.
58
+
59
+ The JS tests live in `js/apex-reflection-node/__tests__`. These are end-to-end tests that ensure that the transpiled JS
60
+ code is working as intended.
61
+
62
+ ### JSON serialization
63
+
64
+ The reflection operation outputs a JSON representation of the Apex type, which is then deserialized on the JS side to
65
+ return typed objects.
66
+
67
+ Serialization is handled through the [json_serializable](https://pub.dev/packages/json_serializable) package, which
68
+ helps automatically create the round-trip code for serialization and de-serialization.
69
+
70
+ When changing any of the model classes with serialization support, to re-build the serialization code run
71
+
72
+ ```
73
+ pub run build_runner build
74
+ ```
75
+
76
+ ### Parsing
77
+
78
+ The parsing algorithm relies on using ANTLR4 and its Dart target. Currently `dart2js` is not able to transpile the
79
+ source from the `antrl4` library hosted in pub.dev, so we rely on a local copy that fixes the transpilation issues,
80
+ which lives in `lib/antrl4-4.9.2`.
81
+
82
+ To generate the Antlr4 Apex output run:
83
+
84
+ ```
85
+ antlr4 -Dlanguage=Dart lib/src/antlr/grammars/apex/ApexLexer.g4 lib/src/antlr/grammars/apex/ApexParser.g4 -o lib/src/antlr/lib/apex/
86
+ ```
87
+
88
+ To generate the Antlr4 Apexdoc output run:
89
+
90
+ ```
91
+ antlr4 -Dlanguage=Dart lib/src/antlr/grammars/apexdoc/ApexdocLexer.g4 lib/src/antlr/grammars/apexdoc/ApexdocParser.g4 -o lib/src/antlr/lib/apexdoc/
92
+ ```
93
+
94
+ ## Typescript
95
+
96
+ This library provides its own TS type definition.
@@ -0,0 +1,256 @@
1
+ import {ClassMirror, InterfaceMirror, reflect} from '../index';
2
+
3
+ describe('Enum Reflection', () => {
4
+ test('Simple, single line declaration', () => {
5
+ const enumBody = 'enum MyEnumName {}';
6
+ const result = reflect(enumBody).typeMirror;
7
+ expect(result.type_name).toBe('enum');
8
+ expect(result.name).toBe('MyEnumName');
9
+ });
10
+
11
+ test('Multi-line declaration with values', () => {
12
+ const enumBody = `
13
+ enum MyEnumName {
14
+ VALUE_1,
15
+ VALUE2
16
+ }
17
+ `;
18
+ const result = reflect(enumBody).typeMirror;
19
+ expect(result.type_name).toBe('enum');
20
+ expect(result.name).toBe('MyEnumName');
21
+ });
22
+
23
+ test('With doc comments', () => {
24
+ const enumBody = `
25
+ /**
26
+ * My enum description
27
+ */
28
+ enum MyEnumName {
29
+ VALUE_1,
30
+ VALUE2
31
+ }
32
+ `;
33
+ const result = reflect(enumBody).typeMirror;
34
+ expect(result.docComment.description).toBe('My enum description');
35
+ });
36
+ });
37
+
38
+ describe('Interface Reflection', () => {
39
+ test('Single line interface definition', () => {
40
+ const interfaceBody = 'interface MyInterface{}';
41
+ const result = reflect(interfaceBody).typeMirror;
42
+ expect(result.type_name).toBe('interface');
43
+ expect(result.name).toBe('MyInterface');
44
+ });
45
+
46
+ test('When no access modifier is defined it is private', () => {
47
+ const interfaceBody = 'interface MyInterface{}';
48
+ const result = reflect(interfaceBody).typeMirror;
49
+ expect(result.access_modifier).toBe('private');
50
+ });
51
+
52
+ test('Can have access modifier', () => {
53
+ const interfaceBody = 'public interface MyInterface{}';
54
+ const result = reflect(interfaceBody).typeMirror;
55
+ expect(result.access_modifier).toBe('public');
56
+ });
57
+
58
+ test('Can have a sharing modifier', () => {
59
+ const interfaceBody = 'public with sharing interface MyInterface{}';
60
+ const result = reflect(interfaceBody) as InterfaceMirror;
61
+ expect(result.sharingModifier).toBe('withSharing');
62
+ });
63
+
64
+ test('Can have methods', () => {
65
+ const interfaceBody = `
66
+ public with sharing interface MyInterface{
67
+ void method1();
68
+ }
69
+ `;
70
+ const result = reflect(interfaceBody) as InterfaceMirror;
71
+ expect(result.methods.length).toBe(1);
72
+ expect(result.methods[0].name).toBe('method1');
73
+ });
74
+
75
+ test('Can have extend other interfaces', () => {
76
+ const interfaceBody = `
77
+ public with sharing interface MyInterface extends Interface2 {
78
+ void method1();
79
+ }
80
+ `;
81
+ const result = reflect(interfaceBody) as InterfaceMirror;
82
+ expect(result.extended_interfaces.length).toBe(1);
83
+ expect(result.extended_interfaces[0]).toBe('Interface2');
84
+ });
85
+
86
+ test('Can have annotations', () => {
87
+ const interfaceBody = `
88
+ @NamespaceAccessible
89
+ public with sharing interface MyInterface{
90
+ void method1();
91
+ }
92
+ `;
93
+ const result = reflect(interfaceBody) as InterfaceMirror;
94
+ expect(result.annotations.length).toBe(1);
95
+ expect(result.annotations[0].name).toBe('namespaceaccessible');
96
+ });
97
+ });
98
+
99
+ describe('Class reflection', () => {
100
+ test('Single line class definition', () => {
101
+ const classBody = 'class MyClass{}';
102
+ const result = reflect(classBody).typeMirror;
103
+ expect(result.type_name).toBe('class');
104
+ expect(result.name).toBe('MyClass');
105
+ });
106
+
107
+ test('When no access modifier is defined it is private', () => {
108
+ const classBody = 'class MyClass{}';
109
+ const result = reflect(classBody).typeMirror;
110
+ expect(result.access_modifier).toBe('private');
111
+ });
112
+
113
+ test('Can have access modifier', () => {
114
+ const interfaceBody = 'public class MyClass{}';
115
+ const result = reflect(interfaceBody).typeMirror;
116
+ expect(result.access_modifier).toBe('public');
117
+ });
118
+
119
+ test('Can have a sharing modifier', () => {
120
+ const classBody = 'public with sharing class MyClass{}';
121
+ const result = reflect(classBody) as ClassMirror;
122
+ expect(result.sharingModifier).toBe('withSharing');
123
+ });
124
+
125
+ test('Can have a class modifier', () => {
126
+ const classBody = 'public with sharing abstract class MyClass{}';
127
+ const result = reflect(classBody) as ClassMirror;
128
+ expect(result.classModifier).toBe('abstract');
129
+ });
130
+
131
+ test('Can extend a class', () => {
132
+ const classBody = 'public with sharing class MyClass extends Class2 {}';
133
+ const result = reflect(classBody) as ClassMirror;
134
+ expect(result.extended_class).toBe('Class2');
135
+ });
136
+
137
+ test('Can implement interfaces', () => {
138
+ const classBody = 'public with sharing class MyClass implements Interface1, Interface2 {}';
139
+ const result = reflect(classBody) as ClassMirror;
140
+ expect(result.implemented_interfaces.length).toBe(2);
141
+ expect(result.implemented_interfaces[0]).toBe('Interface1');
142
+ expect(result.implemented_interfaces[1]).toBe('Interface2');
143
+ });
144
+
145
+ test('Can have properties', () => {
146
+ const classBody = `
147
+ public with sharing class MyClass {
148
+ public String Prop1 { get; set; }
149
+ public Integer Prop2 { get; set; }
150
+ }
151
+ `;
152
+ const result = reflect(classBody) as ClassMirror;
153
+ expect(result.properties.length).toBe(2);
154
+ expect(result.properties[0].type).toBe('String');
155
+ expect(result.properties[0].name).toBe('Prop1');
156
+ expect(result.properties[1].type).toBe('Integer');
157
+ expect(result.properties[1].name).toBe('Prop2');
158
+ });
159
+
160
+ test('Can have fields', () => {
161
+ const classBody = `
162
+ public with sharing class MyClass {
163
+ private String var1, var2;
164
+ }
165
+ `;
166
+ const result = reflect(classBody) as ClassMirror;
167
+ expect(result.fields.length).toBe(2);
168
+ expect(result.fields[0].type).toBe('String');
169
+ expect(result.fields[1].type).toBe('String');
170
+ expect(result.fields[0].name).toBe('var1');
171
+ expect(result.fields[1].name).toBe('var2');
172
+ });
173
+
174
+ test('Can have constructors', () => {
175
+ const classBody = `
176
+ public with sharing class MyClass {
177
+ public MyClass() {}
178
+ public MyClass(String var1) {}
179
+ }
180
+ `;
181
+ const result = reflect(classBody) as ClassMirror;
182
+ expect(result.constructors.length).toBe(2);
183
+ expect(result.constructors[0].parameters.length).toBe(0);
184
+ expect(result.constructors[0].access_modifier).toBe('public');
185
+ expect(result.constructors[1].parameters.length).toBe(1);
186
+ expect(result.constructors[1].parameters[0].name).toBe('var1');
187
+ expect(result.constructors[1].parameters[0].type).toBe('String');
188
+ expect(result.constructors[1].access_modifier).toBe('public');
189
+ });
190
+
191
+ test('Can have methods', () => {
192
+ const classBody = `
193
+ public with sharing class MyClass {
194
+ public static String method1() {
195
+ return '';
196
+ }
197
+
198
+ private void method2(){}
199
+ }
200
+ `;
201
+ const result = reflect(classBody) as ClassMirror;
202
+ expect(result.methods.length).toBe(2);
203
+ expect(result.methods[0].memberModifiers.length).toBe(1);
204
+ expect(result.methods[0].memberModifiers[0]).toBe('static');
205
+ expect(result.methods[0].access_modifier).toBe('public');
206
+ expect(result.methods[0].type).toBe('String');
207
+ expect(result.methods[0].name).toBe('method1');
208
+
209
+ expect(result.methods[1].memberModifiers.length).toBe(0);
210
+ expect(result.methods[1].access_modifier).toBe('private');
211
+ expect(result.methods[1].type).toBe('void');
212
+ expect(result.methods[1].name).toBe('method2');
213
+ });
214
+
215
+ test('Can have inner enums', () => {
216
+ const classBody = `
217
+ public with sharing class MyClass {
218
+ public enum MyEnum {}
219
+ }
220
+ `;
221
+ const result = reflect(classBody) as ClassMirror;
222
+ expect(result.enums.length).toBe(1);
223
+ expect(result.enums[0].access_modifier).toBe('public');
224
+ expect(result.enums[0].name).toBe('MyEnum');
225
+ });
226
+
227
+ test('Can have inner interfaces', () => {
228
+ const classBody = `
229
+ public with sharing class MyClass {
230
+ public interface MyInterface {
231
+ void method1();
232
+ void method2();
233
+ }
234
+ }
235
+ `;
236
+ const result = reflect(classBody) as ClassMirror;
237
+ expect(result.interfaces.length).toBe(1);
238
+ expect(result.interfaces[0].name).toBe('MyInterface');
239
+ expect(result.interfaces[0].methods.length).toBe(2);
240
+ });
241
+
242
+ test('Can have inner classes', () => {
243
+ const classBody = `
244
+ public with sharing class MyClass {
245
+ public class MyClass {
246
+ public void method1();
247
+ public void method2();
248
+ }
249
+ }
250
+ `;
251
+ const result = reflect(classBody) as ClassMirror;
252
+ expect(result.classes.length).toBe(1);
253
+ expect(result.classes[0].name).toBe('MyClass');
254
+ expect(result.classes[0].methods.length).toBe(2);
255
+ });
256
+ });
package/index.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- export declare function reflect(declarationBody: string): Type;
1
+ export declare function reflect(declarationBody: string): ReflectionResult;
2
2
  export interface ParamAnnotation {
3
3
  bodyLines: string[];
4
4
  paramName: string;
@@ -40,14 +40,17 @@ export interface ParameterMirror {
40
40
  docComment?: DocComment;
41
41
  }
42
42
  export interface MethodMirror {
43
+ access_modifier: string;
43
44
  annotations: Annotation[];
44
45
  name: string;
45
46
  memberModifiers: string[];
46
47
  type: string;
47
48
  parameters: ParameterMirror[];
48
49
  docComment?: DocComment;
50
+ group?: string;
49
51
  }
50
52
  export interface PropertyMirror {
53
+ access_modifier: string;
51
54
  annotations: Annotation[];
52
55
  name: string;
53
56
  memberModifiers: string[];
@@ -55,34 +58,46 @@ export interface PropertyMirror {
55
58
  docComment?: DocComment;
56
59
  }
57
60
  export interface FieldMirror {
61
+ access_modifier: string;
58
62
  annotations: Annotation[];
59
63
  name: string;
60
64
  memberModifiers: string[];
61
65
  type: string;
62
66
  docComment?: DocComment;
67
+ group?: string;
63
68
  }
64
69
  export interface ConstructorMirror {
65
70
  access_modifier: string;
66
71
  annotations: Annotation[];
67
72
  parameters: ParameterMirror[];
68
73
  docComment?: DocComment;
74
+ group?: string;
75
+ }
76
+ export interface ReflectionResult {
77
+ typeMirror?: Type;
78
+ error?: ParsingError;
79
+ }
80
+ export interface ParsingError {
81
+ message: string;
69
82
  }
83
+ declare type TypeName = 'class' | 'interface' | 'enum';
84
+ export declare type Type = InterfaceMirror | ClassMirror | EnumMirror;
70
85
  export interface EnumMirror {
71
86
  annotations: Annotation[];
72
87
  name: string;
73
- type_name: string;
88
+ type_name: TypeName;
74
89
  access_modifier: string;
75
90
  docComment?: DocComment;
76
91
  }
77
- declare type TypeName = 'class' | 'interface' | 'enum';
78
- declare type Type = InterfaceMirror | ClassMirror | EnumMirror;
79
92
  export interface InterfaceMirror {
80
93
  annotations: Annotation[];
81
94
  name: string;
82
95
  type_name: TypeName;
83
96
  methods: MethodMirror[];
84
97
  extended_interfaces: string[];
98
+ access_modifier: string;
85
99
  docComment?: DocComment;
100
+ sharingModifier?: string;
86
101
  }
87
102
  export interface ClassMirror {
88
103
  string: any;
@@ -90,8 +105,8 @@ export interface ClassMirror {
90
105
  name: string;
91
106
  type_name: TypeName;
92
107
  methods: MethodMirror[];
93
- sharingModifier: string;
94
- classModifiers: string[];
108
+ sharingModifier?: string;
109
+ classModifier?: string;
95
110
  extended_class?: string;
96
111
  implemented_interfaces: string[];
97
112
  properties: PropertyMirror[];
@@ -100,6 +115,7 @@ export interface ClassMirror {
100
115
  enums: EnumMirror[];
101
116
  interfaces: InterfaceMirror[];
102
117
  classes: ClassMirror[];
118
+ access_modifier: string;
103
119
  docComment?: DocComment;
104
120
  }
105
121
  export {};
package/index.js CHANGED
@@ -3,6 +3,6 @@ exports.__esModule = true;
3
3
  exports.reflect = void 0;
4
4
  var lib = require('./out');
5
5
  function reflect(declarationBody) {
6
- return lib.reflect(declarationBody);
6
+ return JSON.parse(lib.reflect(declarationBody));
7
7
  }
8
8
  exports.reflect = reflect;
package/index.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  const lib = require('./out');
2
2
 
3
- export function reflect(declarationBody: string): Type {
4
- return lib.reflect(declarationBody) as Type;
3
+ export function reflect(declarationBody: string): ReflectionResult {
4
+ return JSON.parse(lib.reflect(declarationBody)) as ReflectionResult;
5
5
  }
6
6
 
7
7
  export interface ParamAnnotation {
@@ -53,6 +53,7 @@ export interface ParameterMirror {
53
53
  }
54
54
 
55
55
  export interface MethodMirror {
56
+ access_modifier: string;
56
57
  annotations: Annotation[];
57
58
  name: string;
58
59
  memberModifiers: string[];
@@ -62,14 +63,17 @@ export interface MethodMirror {
62
63
  }
63
64
 
64
65
  export interface PropertyMirror {
66
+ access_modifier: string;
65
67
  annotations: Annotation[];
66
68
  name: string;
67
69
  memberModifiers: string[];
68
70
  type: string;
69
71
  docComment?: DocComment;
72
+ group?: string;
70
73
  }
71
74
 
72
75
  export interface FieldMirror {
76
+ access_modifier: string;
73
77
  annotations: Annotation[];
74
78
  name: string;
75
79
  memberModifiers: string[];
@@ -84,34 +88,48 @@ export interface ConstructorMirror {
84
88
  docComment?: DocComment;
85
89
  }
86
90
 
91
+ export interface ReflectionResult {
92
+ typeMirror?: Type;
93
+ error?: ParsingError;
94
+ }
95
+
96
+ export interface ParsingError {
97
+ message: string;
98
+ }
99
+
100
+ // Types
101
+
102
+ type TypeName = 'class' | 'interface' | 'enum';
103
+ export type Type = InterfaceMirror | ClassMirror | EnumMirror;
104
+
87
105
  export interface EnumMirror {
88
106
  annotations: Annotation[];
89
107
  name: string;
90
- type_name: string;
108
+ type_name: TypeName;
91
109
  access_modifier: string;
92
110
  docComment?: DocComment;
111
+ group?: string;
93
112
  }
94
113
 
95
- type TypeName = 'class' | 'interface' | 'enum';
96
- type Type = InterfaceMirror | ClassMirror | EnumMirror;
97
-
98
114
  export interface InterfaceMirror {
99
115
  annotations: Annotation[];
100
116
  name: string;
101
117
  type_name: TypeName;
102
118
  methods: MethodMirror[];
103
119
  extended_interfaces: string[];
120
+ access_modifier: string;
104
121
  docComment?: DocComment;
122
+ sharingModifier?: string;
123
+ group?: string;
105
124
  }
106
125
 
107
126
  export interface ClassMirror {
108
- string;
109
127
  annotations: Annotation[];
110
128
  name: string;
111
129
  type_name: TypeName;
112
130
  methods: MethodMirror[];
113
- sharingModifier: string;
114
- classModifiers: string[];
131
+ sharingModifier?: string;
132
+ classModifier?: string;
115
133
  extended_class?: string;
116
134
  implemented_interfaces: string[];
117
135
  properties: PropertyMirror[];
@@ -120,5 +138,7 @@ export interface ClassMirror {
120
138
  enums: EnumMirror[];
121
139
  interfaces: InterfaceMirror[];
122
140
  classes: ClassMirror[];
141
+ access_modifier: string;
123
142
  docComment?: DocComment;
143
+ group?: string;
124
144
  }
package/jest.config.js ADDED
@@ -0,0 +1,11 @@
1
+ // eslint-disable-next-line no-undef
2
+ module.exports = {
3
+ roots: ["<rootDir>/."],
4
+ testMatch: [
5
+ "**/__tests__/**/*.+(ts|tsx|js)",
6
+ "**/?(*.)+(spec|test).+(ts|tsx|js)",
7
+ ],
8
+ transform: {
9
+ "^.+\\.(ts|tsx)$": "ts-jest",
10
+ },
11
+ };