@eventra_dev/eventra-cli 0.1.1 → 0.1.3

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 (45) hide show
  1. package/README.md +48 -73
  2. package/dist/commands/check.js +18 -7
  3. package/dist/commands/send.js +7 -1
  4. package/dist/commands/sync.js +5 -3
  5. package/dist/commands/watch.js +13 -55
  6. package/dist/engine/callResolver.js +15 -24
  7. package/dist/engine/engine.js +32 -7
  8. package/dist/engine/graph.js +50 -9
  9. package/dist/engine/hash.js +10 -0
  10. package/dist/engine/languageService.js +74 -0
  11. package/dist/engine/resolver.js +88 -24
  12. package/dist/engine/scanner.js +57 -67
  13. package/dist/utils/config.js +7 -2
  14. package/dist/utils/parsers/astro.js +8 -1
  15. package/dist/utils/parsers/router.js +1 -0
  16. package/dist/utils/parsers/svelte.js +4 -1
  17. package/dist/utils/parsers/vue.js +13 -4
  18. package/dist/utils/processFile.js +3 -1
  19. package/package.json +1 -1
  20. package/src/commands/check.ts +21 -7
  21. package/src/commands/send.ts +8 -2
  22. package/src/commands/sync.ts +6 -3
  23. package/src/commands/watch.ts +14 -60
  24. package/src/engine/callResolver.ts +21 -32
  25. package/src/engine/engine.ts +40 -7
  26. package/src/engine/graph.ts +58 -11
  27. package/src/engine/hash.ts +5 -0
  28. package/src/engine/languageService.ts +103 -0
  29. package/src/engine/resolver.ts +104 -24
  30. package/src/engine/scanner.ts +67 -74
  31. package/src/types.ts +5 -0
  32. package/src/utils/config.ts +15 -33
  33. package/src/utils/parsers/astro.ts +11 -4
  34. package/src/utils/parsers/router.ts +5 -10
  35. package/src/utils/parsers/svelte.ts +6 -4
  36. package/src/utils/parsers/vue.ts +15 -5
  37. package/src/utils/processFile.ts +6 -3
  38. package/dist/engine/extract.js +0 -53
  39. package/dist/engine/flow.js +0 -38
  40. package/dist/engine/project.js +0 -74
  41. package/dist/engine/symbol.js +0 -31
  42. package/src/engine/extract.ts +0 -74
  43. package/src/engine/flow.ts +0 -54
  44. package/src/engine/project.ts +0 -75
  45. package/src/engine/symbol.ts +0 -42
@@ -1,36 +1,100 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.resolveNodeValue = resolveNodeValue;
4
- const ts_morph_1 = require("ts-morph");
5
- function resolveNodeValue(node, checker) {
6
- if (!ts_morph_1.Node.isIdentifier(node))
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ function resolveNodeValue(node, checker, paramMap, seen = new Set()) {
9
+ if (seen.has(node))
7
10
  return null;
8
- const symbol = checker.getSymbolAtLocation(node.compilerNode);
9
- if (!symbol)
10
- return null;
11
- const decls = symbol.getDeclarations() ?? [];
12
- for (const d of decls) {
13
- if (ts_morph_1.ts.isVariableDeclaration(d) && d.initializer) {
14
- const init = d.initializer;
15
- if (ts_morph_1.ts.isStringLiteral(init)) {
16
- return { values: [init.text], dynamic: false };
11
+ seen.add(node);
12
+ // param mapping
13
+ if (typescript_1.default.isIdentifier(node) && paramMap?.has(node.text)) {
14
+ return resolveNodeValue(paramMap.get(node.text), checker, paramMap);
15
+ }
16
+ if (typescript_1.default.isStringLiteral(node)) {
17
+ return { values: [node.text], dynamic: false };
18
+ }
19
+ if (typescript_1.default.isTemplateExpression(node)) {
20
+ let result = node.head.text;
21
+ for (const span of node.templateSpans) {
22
+ result += "*";
23
+ result += span.literal.text;
24
+ }
25
+ return { values: [result], dynamic: true };
26
+ }
27
+ if (typescript_1.default.isConditionalExpression(node)) {
28
+ const a = resolveNodeValue(node.whenTrue, checker, paramMap, seen);
29
+ const b = resolveNodeValue(node.whenFalse, checker, paramMap, seen);
30
+ return {
31
+ values: [...new Set([...(a?.values ?? []), ...(b?.values ?? [])])],
32
+ dynamic: true,
33
+ };
34
+ }
35
+ if (typescript_1.default.isIdentifier(node)) {
36
+ let symbol = checker.getSymbolAtLocation(node);
37
+ if (!symbol)
38
+ return null;
39
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
40
+ symbol = checker.getAliasedSymbol(symbol);
41
+ }
42
+ for (const decl of symbol.getDeclarations() ?? []) {
43
+ if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
44
+ return resolveNodeValue(decl.initializer, checker, paramMap, seen);
17
45
  }
18
- if (ts_morph_1.ts.isNoSubstitutionTemplateLiteral(init)) {
19
- return { values: [init.text], dynamic: false };
46
+ if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
47
+ if (typescript_1.default.isStringLiteral(decl.initializer)) {
48
+ return {
49
+ values: [decl.initializer.text],
50
+ dynamic: false,
51
+ };
52
+ }
20
53
  }
21
- if (ts_morph_1.ts.isTemplateExpression(init)) {
22
- let result = init.head.text;
23
- for (const span of init.templateSpans) {
24
- result += "*";
25
- result += span.literal.text;
54
+ }
55
+ }
56
+ if (typescript_1.default.isPropertyAccessExpression(node)) {
57
+ const symbol = checker.getSymbolAtLocation(node);
58
+ if (symbol) {
59
+ let s = symbol;
60
+ if (s.flags & typescript_1.default.SymbolFlags.Alias) {
61
+ s = checker.getAliasedSymbol(s);
62
+ }
63
+ for (const decl of s.getDeclarations() ?? []) {
64
+ if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
65
+ if (typescript_1.default.isStringLiteral(decl.initializer)) {
66
+ return {
67
+ values: [decl.initializer.text],
68
+ dynamic: false,
69
+ };
70
+ }
26
71
  }
27
- return { values: [result], dynamic: true };
28
72
  }
29
73
  }
30
- if (ts_morph_1.ts.isParameter(d) && d.initializer) {
31
- const init = d.initializer;
32
- if (ts_morph_1.ts.isStringLiteral(init)) {
33
- return { values: [init.text], dynamic: false };
74
+ }
75
+ if (typescript_1.default.isObjectLiteralExpression(node)) {
76
+ const values = [];
77
+ for (const prop of node.properties) {
78
+ if (!typescript_1.default.isPropertyAssignment(prop))
79
+ continue;
80
+ const name = prop.name.getText();
81
+ if (name === "event") {
82
+ const res = resolveNodeValue(prop.initializer, checker, paramMap, seen);
83
+ if (res)
84
+ values.push(...res.values);
85
+ }
86
+ }
87
+ return { values, dynamic: true };
88
+ }
89
+ if (typescript_1.default.isBinaryExpression(node)) {
90
+ if (node.operatorToken.kind === typescript_1.default.SyntaxKind.PlusToken) {
91
+ const left = resolveNodeValue(node.left, checker, paramMap, seen);
92
+ const right = resolveNodeValue(node.right, checker, paramMap, seen);
93
+ if (left && right) {
94
+ return {
95
+ values: left.values.flatMap(l => right.values.map(r => l + r)),
96
+ dynamic: true,
97
+ };
34
98
  }
35
99
  }
36
100
  }
@@ -1,78 +1,68 @@
1
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
2
5
  Object.defineProperty(exports, "__esModule", { value: true });
3
6
  exports.scanSource = scanSource;
4
- const ts_morph_1 = require("ts-morph");
5
- const extract_1 = require("./extract");
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ const resolver_1 = require("./resolver");
6
9
  const callResolver_1 = require("./callResolver");
7
- const flow_1 = require("./flow");
8
- function getCallName(expr) {
9
- if (ts_morph_1.Node.isIdentifier(expr))
10
- return expr.getText();
11
- if (ts_morph_1.Node.isPropertyAccessExpression(expr))
12
- return expr.getName();
13
- return expr.getText();
14
- }
15
- function isTrackLike(name) {
16
- return /(track|trackFeature)/i.test(name);
17
- }
18
- function pickArg(args) {
19
- return (args.find(ts_morph_1.Node.isStringLiteral) ??
20
- args.find(ts_morph_1.Node.isTemplateExpression) ??
21
- args.find(ts_morph_1.Node.isObjectLiteralExpression) ??
22
- args[0]);
23
- }
24
- function scanSource(source, checker) {
10
+ function scanSource(source, checker, visited = new Set(), depth = 0) {
11
+ if (depth > 5)
12
+ return new Set();
25
13
  const events = new Set();
26
- // CALLS
27
- for (const call of source.getDescendantsOfKind(ts_morph_1.SyntaxKind.CallExpression)) {
28
- const expr = call.getExpression();
29
- const name = getCallName(expr);
30
- if (!isTrackLike(name))
31
- continue;
32
- const args = call.getArguments();
33
- if (!args.length)
34
- continue;
35
- let target = pickArg(args);
36
- const fn = (0, callResolver_1.resolveFunctionFromCall)(expr);
37
- if (fn) {
38
- const index = (0, flow_1.getForwardedParamIndex)(fn);
39
- if (index !== null && args[index]) {
40
- target = args[index];
14
+ if (visited.has(source.fileName))
15
+ return events;
16
+ visited.add(source.fileName);
17
+ function visit(node) {
18
+ if (typescript_1.default.isCallExpression(node)) {
19
+ const name = getCallName(node.expression);
20
+ if (/(track|event|capture|send)/i.test(name)) {
21
+ const fn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
22
+ // param mapping
23
+ let paramMap = new Map();
24
+ if (fn && typescript_1.default.isFunctionLike(fn)) {
25
+ fn.parameters.forEach((param, index) => {
26
+ const arg = node.arguments[index];
27
+ if (!arg)
28
+ return;
29
+ if (typescript_1.default.isIdentifier(param.name)) {
30
+ paramMap.set(param.name.text, arg);
31
+ }
32
+ });
33
+ }
34
+ // cross-file traversal
35
+ if (fn) {
36
+ const sf = fn.getSourceFile();
37
+ if (!visited.has(sf.fileName)) {
38
+ scanSource(sf, checker, visited, depth + 1)
39
+ .forEach(e => events.add(e));
40
+ }
41
+ }
42
+ for (const arg of node.arguments) {
43
+ const res = (0, resolver_1.resolveNodeValue)(arg, checker, paramMap);
44
+ res?.values.forEach(v => {
45
+ if (v &&
46
+ v.length > 1 &&
47
+ v.length < 100 &&
48
+ !v.includes(" ") &&
49
+ /^[a-zA-Z0-9._:-]+$/.test(v)) {
50
+ events.add(v);
51
+ }
52
+ });
53
+ }
41
54
  }
42
55
  }
43
- (0, extract_1.extractEventsFromArgs)([target], checker).forEach(({ value }) => {
44
- if (isValid(value))
45
- events.add(value);
46
- });
47
- }
48
- // JSX
49
- const jsx = [
50
- ...source.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxOpeningElement),
51
- ...source.getDescendantsOfKind(ts_morph_1.SyntaxKind.JsxSelfClosingElement),
52
- ];
53
- for (const el of jsx) {
54
- for (const attr of el.getAttributes()) {
55
- if (!ts_morph_1.Node.isJsxAttribute(attr))
56
- continue;
57
- const name = attr.getNameNode().getText();
58
- if (!/(event|name|action)/i.test(name))
59
- continue;
60
- const init = attr.getInitializer();
61
- if (!init)
62
- continue;
63
- const expr = ts_morph_1.Node.isJsxExpression(init)
64
- ? init.getExpression()
65
- : init;
66
- if (!expr)
67
- continue;
68
- (0, extract_1.extractEventsFromArgs)([expr], checker).forEach(({ value }) => {
69
- if (isValid(value))
70
- events.add(value);
71
- });
72
- }
56
+ typescript_1.default.forEachChild(node, visit);
73
57
  }
58
+ visit(source);
74
59
  return events;
75
60
  }
76
- function isValid(v) {
77
- return !!v && v.length > 1 && !v.includes(" ");
61
+ function getCallName(expr) {
62
+ if (typescript_1.default.isIdentifier(expr))
63
+ return expr.text;
64
+ if (typescript_1.default.isPropertyAccessExpression(expr)) {
65
+ return expr.name.text;
66
+ }
67
+ return "";
78
68
  }
@@ -36,8 +36,13 @@ async function loadConfig() {
36
36
  if (!(await fs_extra_1.default.pathExists(configPath))) {
37
37
  return null;
38
38
  }
39
- const config = await fs_extra_1.default.readJSON(configPath);
40
- return normalizeConfig(config);
39
+ try {
40
+ const config = await fs_extra_1.default.readJSON(configPath);
41
+ return normalizeConfig(config);
42
+ }
43
+ catch {
44
+ return null;
45
+ }
41
46
  }
42
47
  async function saveConfig(config) {
43
48
  const configPath = path_1.default.join(process.cwd(), exports.CONFIG_NAME);
@@ -2,5 +2,12 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseAstro = parseAstro;
4
4
  function parseAstro(content) {
5
- return content;
5
+ const frontmatter = content.match(/---([\s\S]*?)---/);
6
+ const scripts = [
7
+ ...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
8
+ ];
9
+ return [
10
+ frontmatter?.[1] ?? "",
11
+ ...scripts.map(s => s[1])
12
+ ].join("\n");
6
13
  }
@@ -2,6 +2,7 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.detectParser = detectParser;
4
4
  function detectParser(file) {
5
+ file = file.toLowerCase();
5
6
  if (file.endsWith(".vue"))
6
7
  return "vue";
7
8
  if (file.endsWith(".svelte"))
@@ -2,5 +2,8 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseSvelte = parseSvelte;
4
4
  function parseSvelte(content) {
5
- return content;
5
+ const scriptMatches = [
6
+ ...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
7
+ ];
8
+ return scriptMatches.map(m => m[1]).join("\n");
6
9
  }
@@ -2,15 +2,24 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.parseVue = parseVue;
4
4
  function parseVue(content) {
5
- const scriptMatch = content.match(/<script[\s\S]*?>([\s\S]*?)<\/script>/);
5
+ const scriptMatches = [
6
+ ...content.matchAll(/<script[^>]*>([\s\S]*?)<\/script>/g)
7
+ ];
8
+ const script = scriptMatches.map(m => m[1]).join("\n");
9
+ // template
6
10
  const templateMatch = content.match(/<template>([\s\S]*?)<\/template>/);
7
- const script = scriptMatch?.[1] ?? "";
8
11
  let template = templateMatch?.[1] ?? "";
9
12
  template = template.replace(/<!--[\s\S]*?-->/g, "");
13
+ // Vue → JSX
10
14
  template = template
15
+ .replace(/\bclass=/g, "className=")
16
+ .replace(/\bfor=/g, "htmlFor=")
17
+ .replace(/@(\w+)=/g, (_, e) => {
18
+ const name = e.charAt(0).toUpperCase() + e.slice(1);
19
+ return `on${name}=`;
20
+ })
11
21
  .replace(/v-if="[^"]+"/g, "")
12
- .replace(/v-for="[^"]+"/g, "")
13
- .replace(/@click=/g, "onClick=");
22
+ .replace(/v-for="[^"]+"/g, "");
14
23
  const jsx = `
15
24
  const __VUE_JSX__ = (
16
25
  <>
@@ -13,7 +13,9 @@ function processFile(file, content) {
13
13
  content = (0, svelte_1.parseSvelte)(content);
14
14
  if (parser === "astro")
15
15
  content = (0, astro_1.parseAstro)(content);
16
- const virtualFile = file.endsWith(".vue")
16
+ const virtualFile = file.endsWith(".vue") ||
17
+ file.endsWith(".svelte") ||
18
+ file.endsWith(".astro")
17
19
  ? file + ".tsx"
18
20
  : file;
19
21
  return { content, virtualFile };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eventra_dev/eventra-cli",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -30,8 +30,12 @@ export async function check({ fix = false }: { fix?: boolean }) {
30
30
 
31
31
  engine.scanFile(virtualFile, content);
32
32
 
33
- } catch {
34
- console.log(chalk.gray(`Skipped: ${file}`));
33
+ } catch (err) {
34
+ console.log(chalk.yellow(`Skipped: ${file}`));
35
+
36
+ if (err instanceof Error) {
37
+ console.log(chalk.gray(err.message));
38
+ }
35
39
  }
36
40
  }
37
41
 
@@ -39,14 +43,17 @@ export async function check({ fix = false }: { fix?: boolean }) {
39
43
  const known = new Set(config.events ?? []);
40
44
 
41
45
  const newEvents = [...found].filter(e => !known.has(e));
46
+ const removed = [...known].filter(e => !found.has(e));
42
47
 
43
48
  if (fix) {
49
+ const merged = new Set(config.events ?? []);
50
+
44
51
  newEvents.forEach(e => {
45
- config.events.push(e);
52
+ merged.add(e);
46
53
  console.log(chalk.green(`+ ${e}`));
47
54
  });
48
55
 
49
- config.events = [...new Set(config.events)].sort();
56
+ config.events = [...merged].sort();
50
57
 
51
58
  await saveConfig(config);
52
59
 
@@ -54,9 +61,16 @@ export async function check({ fix = false }: { fix?: boolean }) {
54
61
  return;
55
62
  }
56
63
 
57
- if (newEvents.length) {
58
- console.log(chalk.red("\nNew events:"));
59
- newEvents.forEach(e => console.log(chalk.red(`+ ${e}`)));
64
+ if (newEvents.length || removed.length) {
65
+ if (newEvents.length) {
66
+ console.log(chalk.red("\nNew events:"));
67
+ newEvents.forEach(e => console.log(chalk.red(`+ ${e}`)));
68
+ }
69
+
70
+ if (removed.length) {
71
+ console.log(chalk.yellow("\nRemoved events:"));
72
+ removed.forEach(e => console.log(chalk.yellow(`- ${e}`)));
73
+ }
60
74
 
61
75
  process.exit(1);
62
76
  }
@@ -81,6 +81,9 @@ export async function send() {
81
81
  )
82
82
  );
83
83
 
84
+ const controller = new AbortController();
85
+ const timeout = setTimeout(() => controller.abort(), 10000);
86
+
84
87
  try {
85
88
  const res = await fetch(
86
89
  endpoint,
@@ -99,8 +102,9 @@ export async function send() {
99
102
  version: CLI_VERSION,
100
103
  runtime: "node"
101
104
  }
102
- })
103
- }
105
+ }),
106
+ signal: controller.signal
107
+ },
104
108
  );
105
109
 
106
110
  if (!res.ok) {
@@ -203,5 +207,7 @@ export async function send() {
203
207
  )
204
208
  );
205
209
  }
210
+ } finally {
211
+ clearTimeout(timeout);
206
212
  }
207
213
  }
@@ -31,9 +31,12 @@ export async function sync() {
31
31
 
32
32
  engine.scanFile(virtualFile, content);
33
33
 
34
- } catch (e) {
35
- console.log(chalk.red(`ERROR: ${file}`));
36
- console.error(e);
34
+ } catch (err) {
35
+ console.log(chalk.yellow(`Skipped: ${file}`));
36
+
37
+ if (err instanceof Error) {
38
+ console.log(chalk.gray(err.message));
39
+ }
37
40
  }
38
41
  }
39
42
 
@@ -5,31 +5,8 @@ import fg from "fast-glob";
5
5
 
6
6
  import { loadConfig, saveConfig } from "../utils/config";
7
7
  import { EventraEngine } from "../engine/engine";
8
+ import { processFile } from "../utils/processFile";
8
9
 
9
- import { detectParser } from "../utils/parsers/router";
10
- import { parseVue } from "../utils/parsers/vue";
11
- import { parseSvelte } from "../utils/parsers/svelte";
12
- import { parseAstro } from "../utils/parsers/astro";
13
-
14
- // PREPROCESS (CONTENT)
15
- function preprocess(file: string, content: string) {
16
- const parser = detectParser(file);
17
-
18
- if (parser === "vue") return parseVue(content);
19
- if (parser === "svelte") return parseSvelte(content);
20
- if (parser === "astro") return parseAstro(content);
21
-
22
- return content;
23
- }
24
-
25
- // VIRTUAL FILE (PATH)
26
- function toVirtualFile(file: string) {
27
- return file.endsWith(".vue")
28
- ? file + ".tsx"
29
- : file;
30
- }
31
-
32
- // WATCH
33
10
  export async function watch() {
34
11
  const config = await loadConfig();
35
12
  if (!config) return;
@@ -40,20 +17,13 @@ export async function watch() {
40
17
  ignore: config.sync.exclude,
41
18
  });
42
19
 
43
- if (!files.length) {
44
- console.log(chalk.yellow("No files found"));
45
- return;
46
- }
47
-
48
20
  const engine = new EventraEngine(process.cwd());
49
21
 
50
- // INITIAL SCAN
51
22
  for (const file of files) {
52
23
  try {
53
24
  const raw = await fs.readFile(file, "utf-8");
54
25
 
55
- const content = preprocess(file, raw);
56
- const virtualFile = toVirtualFile(file);
26
+ const { content, virtualFile } = processFile(file, raw);
57
27
 
58
28
  engine.scanFile(virtualFile, content);
59
29
 
@@ -69,9 +39,9 @@ export async function watch() {
69
39
  chalk.gray(`Initial: ${config.events.length} events\n`)
70
40
  );
71
41
 
72
- // STATE
73
42
  let locked = false;
74
43
  let queued = new Set<string>();
44
+ let timer: NodeJS.Timeout | null = null;
75
45
 
76
46
  const run = async () => {
77
47
  if (locked) return;
@@ -84,8 +54,7 @@ export async function watch() {
84
54
  try {
85
55
  const raw = await fs.readFile(file, "utf-8");
86
56
 
87
- const content = preprocess(file, raw);
88
- const virtualFile = toVirtualFile(file);
57
+ const { content, virtualFile } = processFile(file, raw);
89
58
 
90
59
  engine.updateFile(virtualFile, content);
91
60
 
@@ -116,48 +85,33 @@ export async function watch() {
116
85
 
117
86
  const schedule = (file: string) => {
118
87
  queued.add(file);
119
- setTimeout(run, 50);
88
+
89
+ if (timer) clearTimeout(timer);
90
+
91
+ timer = setTimeout(run, 100);
120
92
  };
121
93
 
122
- // WATCHER
123
94
  const watcher = chokidar.watch(config.sync.include, {
124
95
  ignored: config.sync.exclude,
125
96
  ignoreInitial: true,
126
- awaitWriteFinish: {
127
- stabilityThreshold: 50,
128
- pollInterval: 10,
129
- },
130
97
  });
131
98
 
132
99
  watcher
133
- .on("add", (file) => {
134
- console.log(chalk.gray(`add: ${file}`));
135
- schedule(file);
136
- })
137
- .on("change", (file) => {
138
- console.log(chalk.gray(`change: ${file}`));
139
- schedule(file);
140
- })
100
+ .on("add", schedule)
101
+ .on("change", schedule)
141
102
  .on("unlink", async (file) => {
142
103
  console.log(chalk.gray(`remove: ${file}`));
143
104
 
144
- const virtualFile = toVirtualFile(file);
105
+ const { virtualFile } = processFile(file, "");
145
106
 
146
- engine.removeFile?.(virtualFile);
107
+ engine.removeFile(virtualFile);
147
108
 
148
109
  const next = engine.getAllEvents();
149
110
 
150
- const removed = config.events.filter(e => !next.includes(e));
151
-
152
- if (removed.length) {
153
- removed.forEach(e => console.log(chalk.red(`- ${e}`)));
154
-
155
- config.events = next.sort();
156
- await saveConfig(config);
157
- }
111
+ config.events = next.sort();
112
+ await saveConfig(config);
158
113
  });
159
114
 
160
- // EXIT
161
115
  process.on("SIGINT", async () => {
162
116
  console.log(chalk.yellow("\nStopping watcher..."));
163
117
  await watcher.close();
@@ -1,35 +1,24 @@
1
- import { Node } from "ts-morph";
2
-
3
- export function resolveFunctionFromCall(expr: Node) {
4
- let symbol = expr.getSymbol();
5
-
6
- if (!symbol && Node.isPropertyAccessExpression(expr)) {
7
- symbol = expr.getNameNode().getSymbol();
8
- }
9
-
10
- if (!symbol) return null;
11
-
12
- const decls = symbol.getDeclarations() ?? [];
13
-
14
- for (const d of decls) {
15
- if (Node.isFunctionDeclaration(d)) return d;
16
-
17
- if (Node.isVariableDeclaration(d)) {
18
- const init = d.getInitializer();
19
-
20
- if (
21
- init &&
22
- (Node.isArrowFunction(init) ||
23
- Node.isFunctionExpression(init))
24
- ) {
25
- return init;
26
- }
27
- }
28
-
29
- if (Node.isMethodDeclaration(d)) return d;
30
-
31
- if (Node.isFunctionExpression(d) || Node.isArrowFunction(d)) {
32
- return d;
1
+ import ts from "typescript";
2
+
3
+ export function resolveFunctionFromCall(
4
+ expr: ts.Expression,
5
+ checker: ts.TypeChecker
6
+ ): ts.FunctionLikeDeclaration | null {
7
+
8
+ const type = checker.getTypeAtLocation(expr);
9
+ const signatures = type.getCallSignatures();
10
+
11
+ for (const sig of signatures) {
12
+ const decl = sig.getDeclaration();
13
+
14
+ if (
15
+ decl &&
16
+ (ts.isFunctionDeclaration(decl) ||
17
+ ts.isMethodDeclaration(decl) ||
18
+ ts.isFunctionExpression(decl) ||
19
+ ts.isArrowFunction(decl))
20
+ ) {
21
+ return decl;
33
22
  }
34
23
  }
35
24