@eventra_dev/eventra-cli 0.1.33 → 0.2.1

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.
@@ -24,9 +24,9 @@ async function check({ fix = false }) {
24
24
  absolute: true,
25
25
  });
26
26
  const engine = new engine_1.EventraEngine(process.cwd());
27
- const toScan = new Set();
28
27
  const cache = new Map();
29
- // COLLECT + CACHE
28
+ const toScan = new Set();
29
+ // COLLECT
30
30
  for (const file of files) {
31
31
  try {
32
32
  const raw = await promises_1.default.readFile(file, "utf-8");
@@ -35,23 +35,17 @@ async function check({ fix = false }) {
35
35
  const parsed = (0, processFile_1.processFile)(file, raw);
36
36
  cache.set(file, parsed);
37
37
  toScan.add(file);
38
- for (const dep of parsed.deps) {
39
- toScan.add(path_1.default.resolve(dep));
40
- }
38
+ parsed.deps.forEach(dep => toScan.add(path_1.default.resolve(dep)));
41
39
  }
42
40
  catch { }
43
41
  }
44
42
  // PRELOAD
45
43
  for (const file of toScan) {
46
44
  try {
47
- let parsed = cache.get(file);
48
- if (!parsed) {
49
- const raw = await promises_1.default.readFile(file, "utf-8");
50
- parsed = (0, processFile_1.processFile)(file, raw);
51
- cache.set(file, parsed);
52
- }
53
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
54
- engine.preloadFile(virtual, parsed.content);
45
+ const parsed = cache.get(file);
46
+ if (!parsed)
47
+ continue;
48
+ engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content);
55
49
  }
56
50
  catch { }
57
51
  }
@@ -61,34 +55,24 @@ async function check({ fix = false }) {
61
55
  const parsed = cache.get(file);
62
56
  if (!parsed)
63
57
  continue;
64
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
65
- engine.scanFile(virtual, parsed.content, config);
58
+ engine.scanFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
66
59
  }
67
60
  catch { }
68
61
  }
69
- // DIFF
70
62
  const found = new Set(engine.getAllEvents());
71
63
  const known = new Set(config.events ?? []);
72
- const newEvents = [...found].filter(e => !known.has(e));
64
+ const added = [...found].filter(e => !known.has(e));
73
65
  const removed = [...known].filter(e => !found.has(e));
74
- // FIX MODE
75
66
  if (fix) {
76
67
  config.events = [...found].sort();
77
68
  await (0, config_1.saveConfig)(config);
78
- console.log(chalk_1.default.green(`\nSynced (${config.events.length} events)`));
69
+ console.log(chalk_1.default.green("Synced"));
79
70
  return;
80
71
  }
81
- // CHECK MODE
82
- if (newEvents.length || removed.length) {
83
- if (newEvents.length) {
84
- console.log(chalk_1.default.red("\nNew events:"));
85
- newEvents.forEach(e => console.log(chalk_1.default.red(`+ ${e}`)));
86
- }
87
- if (removed.length) {
88
- console.log(chalk_1.default.yellow("\nRemoved events:"));
89
- removed.forEach(e => console.log(chalk_1.default.yellow(`- ${e}`)));
90
- }
72
+ if (added.length || removed.length) {
73
+ added.forEach(e => console.log(chalk_1.default.red(`+ ${e}`)));
74
+ removed.forEach(e => console.log(chalk_1.default.yellow(`- ${e}`)));
91
75
  process.exit(1);
92
76
  }
93
- console.log(chalk_1.default.green("\nAll good"));
77
+ console.log(chalk_1.default.green("All good"));
94
78
  }
@@ -21,14 +21,10 @@ async function sync() {
21
21
  ignore: config.sync.exclude,
22
22
  absolute: true,
23
23
  });
24
- if (!files.length) {
25
- console.log(chalk_1.default.yellow("No files found"));
26
- return;
27
- }
28
24
  const engine = new engine_1.EventraEngine(process.cwd());
29
- const toScan = new Set();
30
25
  const cache = new Map();
31
- // COLLECT + CACHE
26
+ const toScan = new Set();
27
+ // COLLECT
32
28
  for (const file of files) {
33
29
  try {
34
30
  const raw = await promises_1.default.readFile(file, "utf-8");
@@ -37,9 +33,7 @@ async function sync() {
37
33
  const parsed = (0, processFile_1.processFile)(file, raw);
38
34
  cache.set(file, parsed);
39
35
  toScan.add(file);
40
- for (const dep of parsed.deps) {
41
- toScan.add(path_1.default.resolve(dep));
42
- }
36
+ parsed.deps.forEach(dep => toScan.add(path_1.default.resolve(dep)));
43
37
  }
44
38
  catch { }
45
39
  }
@@ -52,8 +46,7 @@ async function sync() {
52
46
  parsed = (0, processFile_1.processFile)(file, raw);
53
47
  cache.set(file, parsed);
54
48
  }
55
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
56
- engine.preloadFile(virtual, parsed.content);
49
+ engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content);
57
50
  }
58
51
  catch { }
59
52
  }
@@ -65,33 +58,22 @@ async function sync() {
65
58
  const parsed = cache.get(file);
66
59
  if (!parsed)
67
60
  continue;
68
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
69
- const res = engine.scanFile(virtual, parsed.content, config);
61
+ const res = engine.updateFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
70
62
  res.detectedFunctionWrappers.forEach(w => detectedFn.add(w));
71
- for (const [k, v] of res.detectedComponentWrappers) {
72
- detectedCmp.set(k, v);
73
- }
74
- }
75
- catch {
76
- console.log(chalk_1.default.gray(`skip: ${file}`));
63
+ res.detectedComponentWrappers.forEach((v, k) => detectedCmp.set(k, v));
77
64
  }
65
+ catch { }
78
66
  }
79
67
  // RESULT
80
68
  const events = engine.getAllEvents().sort();
81
69
  config.events = events;
82
70
  config.functionWrappers = mergeUnique(config.functionWrappers ?? [], [...detectedFn].map(name => ({ name })), w => w.name);
83
- config.wrappers = mergeUnique(config.wrappers ?? [], [...detectedCmp.entries()].map(([name, prop]) => ({
84
- name,
85
- prop,
86
- })), w => `${w.name}:${w.prop}`);
71
+ config.wrappers = mergeUnique(config.wrappers ?? [], [...detectedCmp.entries()].map(([name, prop]) => ({ name, prop })), w => `${w.name}:${w.prop}`);
87
72
  await (0, config_1.saveConfig)(config);
88
73
  console.log(chalk_1.default.green(`Found ${events.length} events`));
89
74
  }
90
- // HELPERS
91
75
  function mergeUnique(a, b, key) {
92
76
  const map = new Map();
93
- [...b, ...a].forEach(item => {
94
- map.set(key(item), item);
95
- });
77
+ [...b, ...a].forEach(item => map.set(key(item), item));
96
78
  return [...map.values()];
97
79
  }
@@ -13,180 +13,97 @@ const config_1 = require("../utils/config");
13
13
  const engine_1 = require("../engine/engine");
14
14
  const processFile_1 = require("../utils/processFile");
15
15
  const getVirtualFile_1 = require("../utils/getVirtualFile");
16
- function normalize(file) {
17
- return path_1.default.resolve(file).replace(/\\/g, "/");
18
- }
19
16
  async function watch() {
20
17
  const config = await (0, config_1.loadConfig)();
21
18
  if (!config)
22
19
  return;
23
20
  console.log(chalk_1.default.blue("Watching...\n"));
21
+ const engine = new engine_1.EventraEngine(process.cwd());
22
+ const fileDeps = new Map();
23
+ const cache = new Map();
24
+ const watchedDeps = new Set();
25
+ const normalize = (f) => path_1.default.resolve(f).replace(/\\/g, "/");
26
+ // INITIAL LOAD
24
27
  const files = await (0, fast_glob_1.default)(config.sync.include, {
25
28
  ignore: config.sync.exclude,
26
29
  absolute: true,
27
30
  });
28
- const engine = new engine_1.EventraEngine(process.cwd());
29
- const watchedDeps = new Set();
30
- const cache = new Map();
31
- const toScan = new Set();
32
- // COLLECT + CACHE
33
31
  for (const file of files) {
34
32
  try {
35
33
  const abs = normalize(file);
36
34
  const raw = await promises_1.default.readFile(abs, "utf-8");
37
- if (raw.length > 2_000_000)
38
- continue;
39
35
  const parsed = (0, processFile_1.processFile)(abs, raw);
40
36
  cache.set(abs, parsed);
41
- toScan.add(abs);
42
- for (const dep of parsed.deps) {
43
- toScan.add(normalize(dep));
44
- }
45
- }
46
- catch {
47
- console.log(chalk_1.default.gray(`skip: ${file}`));
48
- }
49
- }
50
- // PRELOAD
51
- for (const file of toScan) {
52
- try {
53
- let parsed = cache.get(file);
54
- if (!parsed) {
55
- const raw = await promises_1.default.readFile(file, "utf-8");
56
- parsed = (0, processFile_1.processFile)(file, raw);
57
- cache.set(file, parsed);
58
- }
59
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
60
- engine.preloadFile?.(virtual, parsed.content);
61
- }
62
- catch { }
63
- }
64
- // INITIAL SCAN
65
- for (const file of toScan) {
66
- try {
67
- let parsed = cache.get(file);
68
- if (!parsed) {
69
- const raw = await promises_1.default.readFile(file, "utf-8");
70
- parsed = (0, processFile_1.processFile)(file, raw);
71
- }
72
- const virtual = (0, getVirtualFile_1.getVirtualFile)(file);
73
- engine.scanFile(virtual, parsed.content, config);
74
- for (const dep of parsed.deps) {
75
- watchedDeps.add(normalize(dep));
76
- }
37
+ engine.preloadFile((0, getVirtualFile_1.getVirtualFile)(abs), parsed.content);
38
+ engine.updateFile((0, getVirtualFile_1.getVirtualFile)(abs), parsed.content, config);
39
+ fileDeps.set(abs, new Set(parsed.deps.map(d => normalize(d))));
77
40
  }
78
41
  catch { }
79
42
  }
80
43
  config.events = engine.getAllEvents().sort();
81
44
  await (0, config_1.saveConfig)(config);
82
- console.log(chalk_1.default.gray(`Initial: ${config.events.length} events\n`));
83
- // QUEUE
84
- let locked = false;
85
- const queued = new Map();
45
+ console.log(chalk_1.default.gray(`Initial: ${config.events.length}\n`));
46
+ // WATCHER
47
+ const watcher = chokidar_1.default.watch([...files.map(normalize), ...watchedDeps], { ignoreInitial: true });
48
+ const queue = new Map();
86
49
  let timer = null;
87
50
  const run = async () => {
88
- if (locked)
89
- return;
90
- locked = true;
91
- const batch = [...queued.values()];
92
- queued.clear();
93
- for (const item of batch) {
51
+ const batch = [...queue.keys()];
52
+ queue.clear();
53
+ for (const file of batch) {
94
54
  try {
95
- const raw = await promises_1.default.readFile(item.real, "utf-8");
96
- if (raw.length > 2_000_000)
97
- continue;
98
- const parsed = (0, processFile_1.processFile)(item.real, raw);
99
- cache.set(item.real, parsed);
100
- engine.updateFile(item.virtual, parsed.content, config);
101
- // DEPENDENCY DIFF
102
- const nextDeps = new Set();
103
- for (const dep of parsed.deps) {
104
- nextDeps.add(normalize(dep));
105
- }
55
+ const raw = await promises_1.default.readFile(file, "utf-8");
56
+ const parsed = (0, processFile_1.processFile)(file, raw);
57
+ cache.set(file, parsed);
58
+ engine.updateFile((0, getVirtualFile_1.getVirtualFile)(file), parsed.content, config);
59
+ //dependency diff
60
+ const abs = normalize(file);
61
+ const prevDeps = fileDeps.get(abs) ?? new Set();
62
+ const nextDeps = new Set(parsed.deps.map(d => normalize(d)));
106
63
  // add new deps
107
64
  for (const dep of nextDeps) {
108
- if (!watchedDeps.has(dep)) {
65
+ if (!prevDeps.has(dep)) {
109
66
  watcher.add(dep);
110
67
  watchedDeps.add(dep);
111
68
  }
112
69
  }
113
70
  // remove old deps
114
- for (const dep of [...watchedDeps]) {
71
+ for (const dep of prevDeps) {
115
72
  if (!nextDeps.has(dep)) {
116
73
  watcher.unwatch(dep);
117
74
  watchedDeps.delete(dep);
118
75
  }
119
76
  }
120
- // preload deps
121
- for (const dep of nextDeps) {
122
- try {
123
- const raw = await promises_1.default.readFile(dep, "utf-8");
124
- const parsedDep = (0, processFile_1.processFile)(dep, raw);
125
- cache.set(dep, parsedDep);
126
- const virtual = (0, getVirtualFile_1.getVirtualFile)(dep);
127
- engine.updateFile(virtual, parsedDep.content, config);
128
- }
129
- catch { }
130
- }
77
+ fileDeps.set(abs, nextDeps);
131
78
  }
132
79
  catch {
133
- console.log(chalk_1.default.gray(`skip: ${item.real}`));
80
+ console.log(chalk_1.default.gray(`skip: ${file}`));
134
81
  }
135
82
  }
136
- // DIFF EVENTS
137
- const next = engine.getAllEvents();
138
- const added = next.filter(e => !config.events.includes(e));
139
- const removed = config.events.filter(e => !next.includes(e));
140
- if (added.length || removed.length) {
141
- added.forEach(e => console.log(chalk_1.default.green(`+ ${e}`)));
142
- removed.forEach(e => console.log(chalk_1.default.red(`- ${e}`)));
143
- config.events = next.sort();
144
- await (0, config_1.saveConfig)(config);
145
- console.log(chalk_1.default.gray(`Total: ${config.events.length} events\n`));
146
- }
147
- locked = false;
83
+ const next = engine.getAllEvents().sort();
84
+ config.events = next;
85
+ await (0, config_1.saveConfig)(config);
86
+ console.log(chalk_1.default.green(`Updated: ${next.length}`));
148
87
  };
149
- // SCHEDULER
150
88
  const schedule = (file) => {
151
89
  const abs = normalize(file);
152
- const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
153
- console.log(chalk_1.default.gray(`change: ${file}`));
154
- queued.set(abs, { real: abs, virtual });
90
+ queue.set(abs, true);
155
91
  if (timer)
156
92
  clearTimeout(timer);
157
93
  timer = setTimeout(run, 200);
158
94
  };
159
- // WATCHER
160
- const watcher = chokidar_1.default.watch(config.sync.include, {
161
- ignored: config.sync.exclude,
162
- ignoreInitial: true,
163
- persistent: true,
164
- });
165
95
  watcher
166
- .on("add", schedule)
167
96
  .on("change", schedule)
97
+ .on("add", schedule)
168
98
  .on("unlink", async (file) => {
169
99
  const abs = normalize(file);
170
- console.log(chalk_1.default.gray(`remove: ${file}`));
171
- const virtual = (0, getVirtualFile_1.getVirtualFile)(abs);
172
- engine.removeFile(virtual, config);
173
- watcher.unwatch(abs);
174
- watchedDeps.delete(abs);
175
- const next = engine.getAllEvents();
176
- config.events = next.sort();
100
+ engine.removeFile((0, getVirtualFile_1.getVirtualFile)(abs), config);
101
+ const next = engine.getAllEvents().sort();
102
+ config.events = next;
177
103
  await (0, config_1.saveConfig)(config);
178
- console.log(chalk_1.default.gray(`Total: ${config.events.length} events\n`));
179
- })
180
- .on("error", (err) => {
181
- console.error(chalk_1.default.red("Watcher error:"), err);
104
+ console.log(chalk_1.default.red(`Removed → ${next.length}`));
182
105
  });
183
- // WATCH DEPS INIT
184
- for (const dep of watchedDeps) {
185
- watcher.add(dep);
186
- }
187
- // EXIT
188
106
  process.on("SIGINT", async () => {
189
- console.log(chalk_1.default.yellow("\nStopping watcher..."));
190
107
  await watcher.close();
191
108
  process.exit(0);
192
109
  });
@@ -5,153 +5,54 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.resolveFunctionFromCall = resolveFunctionFromCall;
7
7
  const typescript_1 = __importDefault(require("typescript"));
8
- // HELPERS
9
- function isFunctionLike(node) {
8
+ function isFn(node) {
10
9
  return (typescript_1.default.isFunctionDeclaration(node) ||
11
10
  typescript_1.default.isMethodDeclaration(node) ||
12
- typescript_1.default.isConstructorDeclaration(node) ||
13
11
  typescript_1.default.isFunctionExpression(node) ||
14
12
  typescript_1.default.isArrowFunction(node));
15
13
  }
16
- // symbol → function
17
- function resolveFromSymbol(symbol, checker, visited = new Set(), depth = 0) {
18
- if (!symbol)
14
+ function resolveFromSymbol(symbol, checker, seen = new Set()) {
15
+ if (seen.has(symbol))
19
16
  return null;
20
- if (visited.has(symbol))
21
- return null;
22
- if (depth > 10)
23
- return null;
24
- visited.add(symbol);
25
- // Alias unwrap (import/export)
17
+ seen.add(symbol);
26
18
  if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
27
- try {
28
- const aliased = checker.getAliasedSymbol(symbol);
29
- return resolveFromSymbol(aliased, checker, visited, depth + 1);
30
- }
31
- catch { }
32
- }
33
- // Declarations
34
- const decls = symbol.getDeclarations() ?? [];
35
- for (const decl of decls) {
36
- /**
37
- * direct function
38
- */
39
- if (isFunctionLike(decl)) {
40
- return decl;
41
- }
42
- // VARIABLE → FUNCTION const fn = () => {}
43
- if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
44
- if (isFunctionLike(decl.initializer)) {
45
- return decl.initializer;
46
- }
47
- const innerSymbol = checker.getSymbolAtLocation(decl.initializer);
48
- if (innerSymbol && innerSymbol !== symbol) {
49
- const resolved = resolveFromSymbol(innerSymbol, checker, visited, depth + 1);
50
- if (resolved)
51
- return resolved;
19
+ return resolveFromSymbol(checker.getAliasedSymbol(symbol), checker, seen);
20
+ }
21
+ for (const d of symbol.getDeclarations() ?? []) {
22
+ if (isFn(d))
23
+ return d;
24
+ if (typescript_1.default.isVariableDeclaration(d) && d.initializer) {
25
+ if (isFn(d.initializer))
26
+ return d.initializer;
27
+ const s = checker.getSymbolAtLocation(d.initializer);
28
+ if (s) {
29
+ const r = resolveFromSymbol(s, checker, seen);
30
+ if (r)
31
+ return r;
52
32
  }
53
33
  }
54
- // CLASS METHOD
55
- if (typescript_1.default.isMethodDeclaration(decl)) {
56
- return decl;
57
- }
58
- // interface / type method
59
- if (typescript_1.default.isMethodSignature(decl)) {
60
- const parent = decl.parent;
61
- if (typescript_1.default.isInterfaceDeclaration(parent) ||
62
- typescript_1.default.isTypeLiteralNode(parent)) {
63
- const type = checker.getTypeAtLocation(parent);
64
- const props = type.getProperties();
65
- for (const prop of props) {
66
- const resolved = resolveFromSymbol(prop, checker, visited, depth + 1);
67
- if (resolved)
68
- return resolved;
69
- }
70
- }
71
- }
72
- // fallback recursion
73
- const inner = checker.getSymbolAtLocation(decl);
74
- if (inner && inner !== symbol) {
75
- const resolved = resolveFromSymbol(inner, checker, visited, depth + 1);
76
- if (resolved)
77
- return resolved;
78
- }
79
- }
80
- // Exports (re-export)
81
- const exports = symbol.exports;
82
- if (exports) {
83
- for (const exp of exports.values()) {
84
- const resolved = resolveFromSymbol(exp, checker, visited, depth + 1);
85
- if (resolved)
86
- return resolved;
87
- }
88
34
  }
89
35
  return null;
90
36
  }
91
- // MAIN FUNCTION
92
37
  function resolveFunctionFromCall(expr, checker) {
93
- // obj.method()
38
+ // foo.bar()
94
39
  if (typescript_1.default.isPropertyAccessExpression(expr)) {
95
- let symbol = checker.getSymbolAtLocation(expr.name);
96
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
97
- try {
98
- symbol = checker.getAliasedSymbol(symbol);
99
- }
100
- catch { }
101
- }
102
- if (symbol) {
103
- const resolved = resolveFromSymbol(symbol, checker);
104
- if (resolved)
105
- return resolved;
106
- }
107
- }
108
- // obj["method"]()
109
- if (typescript_1.default.isElementAccessExpression(expr)) {
110
- const symbol = checker.getSymbolAtLocation(expr.argumentExpression);
111
- if (symbol) {
112
- const resolved = resolveFromSymbol(symbol, checker);
113
- if (resolved)
114
- return resolved;
40
+ const s = checker.getSymbolAtLocation(expr.name);
41
+ if (s) {
42
+ const r = resolveFromSymbol(s, checker);
43
+ if (r)
44
+ return r;
115
45
  }
116
46
  }
117
- // Call signatures
47
+ // direct call
118
48
  const type = checker.getTypeAtLocation(expr);
119
- const signatures = type.getCallSignatures();
120
- for (const sig of signatures) {
121
- const decl = sig.getDeclaration();
122
- if (!decl)
123
- continue;
124
- if (isFunctionLike(decl)) {
125
- return decl;
126
- }
127
- if (typescript_1.default.isMethodSignature(decl)) {
128
- const symbol = checker.getSymbolAtLocation(decl.name);
129
- if (symbol) {
130
- const resolved = resolveFromSymbol(symbol, checker);
131
- if (resolved)
132
- return resolved;
133
- }
134
- }
135
- }
136
- // Identifier fallback
137
- if (typescript_1.default.isIdentifier(expr)) {
138
- let symbol = checker.getSymbolAtLocation(expr);
139
- if (symbol && (symbol.flags & typescript_1.default.SymbolFlags.Alias)) {
140
- try {
141
- symbol = checker.getAliasedSymbol(symbol);
142
- }
143
- catch { }
144
- }
145
- if (symbol) {
146
- const resolved = resolveFromSymbol(symbol, checker);
147
- if (resolved)
148
- return resolved;
149
- }
150
- }
151
- // LAST fallback
152
- const symbol = checker.getSymbolAtLocation(expr);
153
- if (symbol) {
154
- return resolveFromSymbol(symbol, checker);
155
- }
49
+ for (const sig of type.getCallSignatures()) {
50
+ const d = sig.getDeclaration();
51
+ if (d && isFn(d))
52
+ return d;
53
+ }
54
+ const s = checker.getSymbolAtLocation(expr);
55
+ if (s)
56
+ return resolveFromSymbol(s, checker);
156
57
  return null;
157
58
  }
@@ -41,7 +41,7 @@ class EventraEngine {
41
41
  this.graph.update(file, source);
42
42
  }
43
43
  }
44
- // SCAN FILE
44
+ // SCAN FILE (cached)
45
45
  scanFile(file, content, config) {
46
46
  file = this.normalize(file);
47
47
  const h = (0, hash_1.hash)(content);
@@ -55,7 +55,6 @@ class EventraEngine {
55
55
  if (!source || !checker) {
56
56
  return this.empty();
57
57
  }
58
- // update graph
59
58
  this.graph.update(file, source);
60
59
  const res = (0, scanner_1.scanSource)(source, checker, config, new Set(), undefined, this.wrapperCache);
61
60
  this.fileResults.set(file, res);
@@ -63,12 +62,19 @@ class EventraEngine {
63
62
  }
64
63
  // UPDATE FILE (incremental)
65
64
  updateFile(file, content, config) {
66
- this.wrapperCache.clear();
67
65
  file = this.normalize(file);
66
+ // EARLY EXIT
67
+ if (this.fileHash.get(file) === (0, hash_1.hash)(content)) {
68
+ return this.fileResults.get(file) ?? this.empty();
69
+ }
70
+ // invalidate wrapper cache
71
+ this.wrapperCache.clear();
68
72
  const res = this.scanFile(file, content, config);
69
73
  const dependents = this.graph.getAllDependentsDeep(file);
70
74
  for (const dep of dependents) {
71
75
  const abs = this.normalize(dep);
76
+ if (abs === file)
77
+ continue; // self-skip
72
78
  if ((0, boundary_1.isExternalFile)(abs))
73
79
  continue;
74
80
  let depContent = this.ts.getFileContent(abs);
@@ -106,15 +112,17 @@ class EventraEngine {
106
112
  }
107
113
  // REMOVE FILE
108
114
  removeFile(file, config) {
109
- this.wrapperCache.clear();
110
115
  file = this.normalize(file);
116
+ this.wrapperCache.clear();
111
117
  const dependents = this.graph.getAllDependentsDeep(file);
118
+ this.graph.remove(file);
112
119
  this.ts.removeFile(file);
113
120
  this.fileResults.delete(file);
114
121
  this.fileHash.delete(file);
115
- this.graph.remove(file);
116
122
  for (const dep of dependents) {
117
123
  const abs = this.normalize(dep);
124
+ if (abs === file)
125
+ continue;
118
126
  if ((0, boundary_1.isExternalFile)(abs))
119
127
  continue;
120
128
  let content = this.ts.getFileContent(abs);
@@ -126,10 +134,7 @@ class EventraEngine {
126
134
  continue;
127
135
  }
128
136
  }
129
- const h = (0, hash_1.hash)(content);
130
- if (this.fileHash.get(abs) === h)
131
- continue;
132
- this.fileHash.set(abs, h);
137
+ this.fileHash.delete(abs);
133
138
  this.ts.updateFile(abs, content);
134
139
  const source = this.ts.getSourceFile(abs);
135
140
  const checker = this.ts.getChecker();