@eventra_dev/eventra-cli 0.0.17 → 0.0.19

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.
@@ -0,0 +1,46 @@
1
+ import {
2
+ SourceFile,
3
+ SyntaxKind,
4
+ Node
5
+ } from "ts-morph";
6
+
7
+ export function detectAliases(source: SourceFile) {
8
+ const aliases: Record<string, string> = {};
9
+
10
+ const vars = source.getDescendantsOfKind(SyntaxKind.VariableDeclaration);
11
+
12
+ for (const v of vars) {
13
+ const name = v.getName();
14
+ const init = v.getInitializer();
15
+ if (!init) continue;
16
+
17
+ // const A = "event"
18
+ if (Node.isStringLiteral(init)) {
19
+ aliases[name] = init.getLiteralText();
20
+ continue;
21
+ }
22
+
23
+ // const A = `event`
24
+ if (init.getKind() === SyntaxKind.NoSubstitutionTemplateLiteral) {
25
+ aliases[name] = init.getText().replace(/`/g, "");
26
+ continue;
27
+ }
28
+
29
+ // const EVENTS = { CLICK: "click" }
30
+ if (Node.isObjectLiteralExpression(init)) {
31
+ for (const p of init.getProperties()) {
32
+ if (!Node.isPropertyAssignment(p)) continue;
33
+
34
+ const key = p.getName();
35
+ const val = p.getInitializer();
36
+ if (!val) continue;
37
+
38
+ if (Node.isStringLiteral(val)) {
39
+ aliases[`${name}.${key}`] = val.getLiteralText();
40
+ }
41
+ }
42
+ }
43
+ }
44
+
45
+ return aliases;
46
+ }
@@ -0,0 +1,119 @@
1
+ import {
2
+ SourceFile,
3
+ SyntaxKind,
4
+ Node,
5
+ CallExpression
6
+ } from "ts-morph";
7
+
8
+ export type FunctionWrapper = {
9
+ name: string;
10
+ event?: string;
11
+ };
12
+
13
+ export type ComponentWrapper = {
14
+ name: string;
15
+ prop: string;
16
+ };
17
+
18
+ export function detectWrappers(source: SourceFile) {
19
+ return {
20
+ functions: detectFunctionWrappers(source),
21
+ components: detectComponentWrappers(source)
22
+ };
23
+ }
24
+
25
+ // FUNCTIONS
26
+ function detectFunctionWrappers(source: SourceFile): FunctionWrapper[] {
27
+ const result = new Map<string, FunctionWrapper>();
28
+
29
+ const functions = [
30
+ ...source.getDescendantsOfKind(SyntaxKind.FunctionDeclaration),
31
+ ...source.getDescendantsOfKind(SyntaxKind.ArrowFunction),
32
+ ...source.getDescendantsOfKind(SyntaxKind.FunctionExpression)
33
+ ];
34
+
35
+ for (const fn of functions) {
36
+ const body = fn.getBody();
37
+ if (!body) continue;
38
+
39
+ const calls = body.getDescendantsOfKind(SyntaxKind.CallExpression);
40
+
41
+ for (const call of calls) {
42
+ if (!isTrackCall(call)) continue;
43
+
44
+ const name = getFunctionName(fn);
45
+ if (!name) continue;
46
+
47
+ const arg = call.getArguments()[0];
48
+
49
+ let eventField: string | undefined;
50
+
51
+ if (arg && Node.isObjectLiteralExpression(arg)) {
52
+ const prop = arg.getProperties()[0];
53
+ if (prop && Node.isPropertyAssignment(prop)) {
54
+ eventField = prop.getName();
55
+ }
56
+ }
57
+
58
+ result.set(name, { name, event: eventField });
59
+ }
60
+ }
61
+
62
+ return [...result.values()];
63
+ }
64
+
65
+ function isTrackCall(call: CallExpression) {
66
+ const expr = call.getExpression();
67
+
68
+ if (Node.isIdentifier(expr)) {
69
+ return expr.getText() === "track";
70
+ }
71
+
72
+ if (Node.isPropertyAccessExpression(expr)) {
73
+ return expr.getName() === "track";
74
+ }
75
+
76
+ return false;
77
+ }
78
+
79
+ function getFunctionName(fn: Node): string | null {
80
+ if (Node.isFunctionDeclaration(fn)) {
81
+ return fn.getName() ?? null;
82
+ }
83
+
84
+ const varDecl = fn.getFirstAncestorByKind(SyntaxKind.VariableDeclaration);
85
+ return varDecl?.getName() ?? null;
86
+ }
87
+
88
+ // COMPONENTS
89
+ function detectComponentWrappers(source: SourceFile): ComponentWrapper[] {
90
+ const result = new Map<string, ComponentWrapper>();
91
+
92
+ const elements = [
93
+ ...source.getDescendantsOfKind(SyntaxKind.JsxOpeningElement),
94
+ ...source.getDescendantsOfKind(SyntaxKind.JsxSelfClosingElement)
95
+ ];
96
+
97
+ for (const el of elements) {
98
+ const name = el.getTagNameNode().getText();
99
+
100
+ for (const attr of el.getAttributes()) {
101
+ const a = attr.asKind(SyntaxKind.JsxAttribute);
102
+ if (!a) continue;
103
+
104
+ const prop = a.getNameNode().getText();
105
+ const val = a.getInitializer();
106
+
107
+ if (!val) continue;
108
+
109
+ if (
110
+ Node.isStringLiteral(val) ||
111
+ Node.isJsxExpression(val)
112
+ ) {
113
+ result.set(name, { name, prop });
114
+ }
115
+ }
116
+ }
117
+
118
+ return [...result.values()];
119
+ }
@@ -1,7 +1,4 @@
1
- import {
2
- Node,
3
- SyntaxKind
4
- } from "ts-morph";
1
+ import { Node, SyntaxKind } from "ts-morph";
5
2
 
6
3
  export type ExtractResult = {
7
4
  values: string[];
@@ -9,7 +6,8 @@ export type ExtractResult = {
9
6
  };
10
7
 
11
8
  export function extractExpression(
12
- expr: Node
9
+ expr: Node,
10
+ aliases: Record<string, string>
13
11
  ): ExtractResult | null {
14
12
 
15
13
  // "event"
@@ -21,58 +19,22 @@ export function extractExpression(
21
19
  }
22
20
 
23
21
  // `event`
24
- if (
25
- expr.getKind() ===
26
- SyntaxKind.NoSubstitutionTemplateLiteral
27
- ) {
22
+ if (expr.getKind() === SyntaxKind.NoSubstitutionTemplateLiteral) {
28
23
  return {
29
- values: [
30
- expr.getText().replace(/`/g, "")
31
- ],
24
+ values: [expr.getText().replace(/`/g, "")],
32
25
  dynamic: false
33
26
  };
34
27
  }
35
28
 
36
- // identifier (TEST)
29
+ // IDENTIFIER
37
30
  if (Node.isIdentifier(expr)) {
38
31
  const name = expr.getText();
39
32
 
40
- const defs =
41
- "getDefinitions" in expr
42
- ? (expr as any).getDefinitions()
43
- : [];
44
-
45
- if (defs && defs.length > 0) {
46
- const decl = defs[0]?.getDeclarationNode();
47
-
48
- if (decl && Node.isVariableDeclaration(decl)) {
49
- const initializer = decl.getInitializer();
50
-
51
- // const TEST = "event"
52
- if (
53
- initializer &&
54
- Node.isStringLiteral(initializer)
55
- ) {
56
- return {
57
- values: [initializer.getLiteralText()],
58
- dynamic: false
59
- };
60
- }
61
-
62
- // const TEST = `event`
63
- if (
64
- initializer &&
65
- initializer.getKind() ===
66
- SyntaxKind.NoSubstitutionTemplateLiteral
67
- ) {
68
- return {
69
- values: [
70
- initializer.getText().replace(/`/g, "")
71
- ],
72
- dynamic: false
73
- };
74
- }
75
- }
33
+ if (name in aliases) {
34
+ return {
35
+ values: [aliases[name]],
36
+ dynamic: false
37
+ };
76
38
  }
77
39
 
78
40
  return {
@@ -81,82 +43,19 @@ export function extractExpression(
81
43
  };
82
44
  }
83
45
 
84
- // property access (EVENTS.CLICK)
46
+ // PROPERTY ACCESS
85
47
  if (Node.isPropertyAccessExpression(expr)) {
86
- const fullName = expr.getText(); // EVENTS.CLICK
87
- const propName = expr.getName(); // CLICK
88
-
89
- const defs =
90
- "getDefinitions" in expr
91
- ? (expr as any).getDefinitions()
92
- : [];
93
-
94
- if (defs && defs.length > 0) {
95
- const decl = defs[0]?.getDeclarationNode();
96
-
97
- // const EVENTS = { CLICK: "event" }
98
- if (decl && Node.isVariableDeclaration(decl)) {
99
- const init = decl.getInitializer();
100
-
101
- if (init && Node.isObjectLiteralExpression(init)) {
102
- const prop = init.getProperty(propName);
103
-
104
- if (
105
- prop &&
106
- Node.isPropertyAssignment(prop)
107
- ) {
108
- const value = prop.getInitializer();
109
-
110
- if (
111
- value &&
112
- Node.isStringLiteral(value)
113
- ) {
114
- return {
115
- values: [value.getLiteralText()],
116
- dynamic: false
117
- };
118
- }
119
- }
120
- }
121
- }
122
-
123
- // fallback
124
- if (decl && Node.isPropertyAssignment(decl)) {
125
- const initializer = decl.getInitializer();
48
+ const name = expr.getText();
126
49
 
127
- if (
128
- initializer &&
129
- Node.isStringLiteral(initializer)
130
- ) {
131
- return {
132
- values: [initializer.getLiteralText()],
133
- dynamic: false
134
- };
135
- }
136
- }
50
+ if (name in aliases) {
51
+ return {
52
+ values: [aliases[name]],
53
+ dynamic: false
54
+ };
137
55
  }
138
56
 
139
57
  return {
140
- values: [fullName],
141
- dynamic: true
142
- };
143
- }
144
-
145
- // ternary (a ? b : c)
146
- if (Node.isConditionalExpression(expr)) {
147
- const values: string[] = [];
148
-
149
- const whenTrue =
150
- extractExpression(expr.getWhenTrue());
151
-
152
- const whenFalse =
153
- extractExpression(expr.getWhenFalse());
154
-
155
- if (whenTrue) values.push(...whenTrue.values);
156
- if (whenFalse) values.push(...whenFalse.values);
157
-
158
- return {
159
- values,
58
+ values: [name],
160
59
  dynamic: true
161
60
  };
162
61
  }
@@ -164,19 +63,7 @@ export function extractExpression(
164
63
  // template `${}`
165
64
  if (Node.isTemplateExpression(expr)) {
166
65
  return {
167
- values: [
168
- expr.getText().replace(/`/g, "")
169
- ],
170
- dynamic: true
171
- };
172
- }
173
-
174
- // call (fn())
175
- if (Node.isCallExpression(expr)) {
176
- return {
177
- values: [
178
- expr.getExpression().getText()
179
- ],
66
+ values: [expr.getText()],
180
67
  dynamic: true
181
68
  };
182
69
  }
@@ -13,9 +13,10 @@ import { extractExpression } from "../extract";
13
13
 
14
14
  export function scanComponentWrappers(
15
15
  source: SourceFile,
16
- wrappers: ComponentWrapper[]
16
+ wrappers: ComponentWrapper[],
17
+ aliases: Record<string, string>
17
18
  ) {
18
- const events = new Set<ExtractedEvent>();
19
+ const events = new Map<string, ExtractedEvent>();
19
20
 
20
21
  const elements = [
21
22
  ...source.getDescendantsOfKind(
@@ -36,14 +37,10 @@ export function scanComponentWrappers(
36
37
  if (name !== wrapper.name.toLowerCase())
37
38
  continue;
38
39
 
39
- const attrs = el.getAttributes();
40
+ for (const attr of el.getAttributes()) {
40
41
 
41
- for (const attr of attrs) {
42
-
43
- // ❗ NO spread props
44
- if (Node.isJsxSpreadAttribute(attr)) {
42
+ if (Node.isJsxSpreadAttribute(attr))
45
43
  continue;
46
- }
47
44
 
48
45
  const attrNode =
49
46
  attr.asKind(SyntaxKind.JsxAttribute);
@@ -63,45 +60,50 @@ export function scanComponentWrappers(
63
60
 
64
61
  // <Button event />
65
62
  if (!init) {
66
- events.add({
67
- value: key,
63
+ const value = key;
64
+ const k = `${value}:true`;
65
+
66
+ events.set(k, {
67
+ value,
68
68
  dynamic: true
69
69
  });
70
+
70
71
  continue;
71
72
  }
72
73
 
73
- // string: <Button event="click" />
74
+ // <Button event="click" />
74
75
  if (Node.isStringLiteral(init)) {
75
- events.add({
76
- value: init.getLiteralText(),
76
+ const value = init.getLiteralText();
77
+ const k = `${value}:false`;
78
+
79
+ events.set(k, {
80
+ value,
77
81
  dynamic: false
78
82
  });
83
+
79
84
  continue;
80
85
  }
81
86
 
82
- // jsx expression: <Button event={"click"} />
87
+ // <Button event={...} />
83
88
  if (Node.isJsxExpression(init)) {
84
89
  const expr = init.getExpression();
85
-
86
90
  if (!expr) continue;
87
91
 
88
- const result =
89
- extractExpression(expr);
90
-
92
+ const result = extractExpression(expr, aliases);
91
93
  if (!result) continue;
92
94
 
93
- result.values.forEach((value) =>
94
- events.add({
95
+ for (const value of result.values) {
96
+ const k = `${value}:${result.dynamic}`;
97
+
98
+ events.set(k, {
95
99
  value,
96
100
  dynamic: result.dynamic
97
- })
98
- );
99
-
100
- continue;
101
+ });
102
+ }
101
103
  }
102
104
  }
103
105
  }
104
106
  }
105
107
 
106
- return events;
108
+ return [...events.values()];
107
109
  }
@@ -15,10 +15,10 @@ import { extractExpression } from "../extract";
15
15
 
16
16
  export function scanFunctionWrappers(
17
17
  source: SourceFile,
18
- wrappers: FunctionWrapper[]
18
+ wrappers: FunctionWrapper[],
19
+ aliases: Record<string, string>
19
20
  ) {
20
- const events =
21
- new Set<ExtractedEvent>();
21
+ const events = new Map<string, ExtractedEvent>();
22
22
 
23
23
  const calls =
24
24
  source.getDescendantsOfKind(
@@ -26,55 +26,42 @@ export function scanFunctionWrappers(
26
26
  );
27
27
 
28
28
  for (const call of calls) {
29
- const name =
30
- getFunctionName(call);
31
-
29
+ const name = getFunctionName(call);
32
30
  if (!name) continue;
33
31
 
34
32
  for (const wrapper of wrappers) {
35
- if (wrapper.name !== name)
36
- continue;
33
+ if (wrapper.name !== name) continue;
37
34
 
38
- const results =
39
- extractEventFromArgs(
40
- call,
41
- wrapper.event
42
- );
35
+ const results = extractEventFromArgs(
36
+ call,
37
+ wrapper.event,
38
+ aliases
39
+ );
43
40
 
44
41
  if (!results) continue;
45
42
 
46
- results.forEach(
47
- (r) => events.add(r)
48
- );
43
+ for (const r of results) {
44
+ const key = `${r.value}:${r.dynamic}`;
45
+ events.set(key, r);
46
+ }
49
47
  }
50
48
  }
51
49
 
52
- return events;
50
+ return [...events.values()];
53
51
  }
54
52
 
55
53
  function getFunctionName(
56
54
  call: CallExpression
57
55
  ): string | null {
58
56
 
59
- const expression =
60
- call.getExpression();
57
+ const expression = call.getExpression();
61
58
 
62
- if (
63
- Node.isIdentifier(
64
- expression
65
- )
66
- ) {
59
+ if (Node.isIdentifier(expression)) {
67
60
  return expression.getText();
68
61
  }
69
62
 
70
- if (
71
- Node.isPropertyAccessExpression(
72
- expression
73
- )
74
- ) {
75
- return getDeepName(
76
- expression
77
- );
63
+ if (Node.isPropertyAccessExpression(expression)) {
64
+ return getDeepName(expression);
78
65
  }
79
66
 
80
67
  return null;
@@ -84,22 +71,12 @@ function getDeepName(
84
71
  node: PropertyAccessExpression
85
72
  ): string {
86
73
 
87
- let current:
88
- | Node
89
- | undefined = node;
90
-
74
+ let current: Node | undefined = node;
91
75
  let name = "";
92
76
 
93
- while (
94
- Node.isPropertyAccessExpression(
95
- current
96
- )
97
- ) {
98
- name =
99
- current.getName();
100
-
101
- current =
102
- current.getExpression();
77
+ while (Node.isPropertyAccessExpression(current)) {
78
+ name = current.getName();
79
+ current = current.getExpression();
103
80
  }
104
81
 
105
82
  return name;
@@ -107,74 +84,54 @@ function getDeepName(
107
84
 
108
85
  function extractEventFromArgs(
109
86
  call: CallExpression,
110
- event?: string
87
+ event: string | undefined,
88
+ aliases: Record<string, string>
111
89
  ): ExtractedEvent[] | null {
112
90
 
113
- const args =
114
- call.getArguments();
115
-
91
+ const args = call.getArguments();
116
92
  const events: ExtractedEvent[] = [];
117
93
 
118
- for (let i = 0; i < args.length; i++) {
119
- const arg = args[i];
94
+ for (const arg of args) {
120
95
 
121
96
  // track("event")
122
97
  if (!event) {
123
- const result =
124
- extractExpression(arg);
125
-
98
+ const result = extractExpression(arg, aliases);
126
99
  if (!result) continue;
127
100
 
128
- result.values.forEach(
129
- (value) =>
130
- events.push({
131
- value,
132
- dynamic:
133
- result.dynamic
134
- })
101
+ result.values.forEach((value) =>
102
+ events.push({
103
+ value,
104
+ dynamic: result.dynamic
105
+ })
135
106
  );
136
107
  }
137
108
 
138
- // track({event})
109
+ // track({ event })
139
110
  if (event) {
140
- const obj =
141
- arg.asKind(
142
- SyntaxKind.ObjectLiteralExpression
143
- );
144
-
111
+ const obj = arg.asKind(
112
+ SyntaxKind.ObjectLiteralExpression
113
+ );
145
114
  if (!obj) continue;
146
115
 
147
- const prop =
148
- obj.getProperty(event);
149
-
116
+ const prop = obj.getProperty(event);
150
117
  if (!prop) continue;
151
118
 
152
- if (
153
- Node.isPropertyAssignment(prop)
154
- ) {
155
- const init =
156
- prop.getInitializer();
157
-
119
+ if (Node.isPropertyAssignment(prop)) {
120
+ const init = prop.getInitializer();
158
121
  if (!init) continue;
159
122
 
160
- const result =
161
- extractExpression(init);
162
-
123
+ const result = extractExpression(init, aliases);
163
124
  if (!result) continue;
164
125
 
165
- result.values.forEach(
166
- (value) =>
167
- events.push({
168
- value,
169
- dynamic:
170
- result.dynamic
171
- })
126
+ result.values.forEach((value) =>
127
+ events.push({
128
+ value,
129
+ dynamic: result.dynamic
130
+ })
172
131
  );
173
132
  }
174
133
  }
175
134
  }
176
135
 
177
- return events.length
178
- ? events
179
- : null;
136
+ return events.length ? events : null;
180
137
  }