@eventra_dev/eventra-cli 0.1.1 → 0.1.2

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.
@@ -1,31 +1,34 @@
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.resolveFunctionFromCall = resolveFunctionFromCall;
4
- const ts_morph_1 = require("ts-morph");
5
- function resolveFunctionFromCall(expr) {
6
- let symbol = expr.getSymbol();
7
- if (!symbol && ts_morph_1.Node.isPropertyAccessExpression(expr)) {
8
- symbol = expr.getNameNode().getSymbol();
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ function resolveFunctionFromCall(expr, checker) {
9
+ let symbol = checker.getSymbolAtLocation(expr);
10
+ if (!symbol && typescript_1.default.isPropertyAccessExpression(expr)) {
11
+ symbol = checker.getSymbolAtLocation(expr.name);
9
12
  }
10
13
  if (!symbol)
11
14
  return null;
12
- const decls = symbol.getDeclarations() ?? [];
13
- for (const d of decls) {
14
- if (ts_morph_1.Node.isFunctionDeclaration(d))
15
- return d;
16
- if (ts_morph_1.Node.isVariableDeclaration(d)) {
17
- const init = d.getInitializer();
18
- if (init &&
19
- (ts_morph_1.Node.isArrowFunction(init) ||
20
- ts_morph_1.Node.isFunctionExpression(init))) {
15
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
16
+ symbol = checker.getAliasedSymbol(symbol);
17
+ }
18
+ for (const decl of symbol.getDeclarations() ?? []) {
19
+ if (typescript_1.default.isFunctionDeclaration(decl) ||
20
+ typescript_1.default.isMethodDeclaration(decl) ||
21
+ typescript_1.default.isFunctionExpression(decl) ||
22
+ typescript_1.default.isArrowFunction(decl)) {
23
+ return decl;
24
+ }
25
+ if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
26
+ const init = decl.initializer;
27
+ if (typescript_1.default.isArrowFunction(init) ||
28
+ typescript_1.default.isFunctionExpression(init)) {
21
29
  return init;
22
30
  }
23
31
  }
24
- if (ts_morph_1.Node.isMethodDeclaration(d))
25
- return d;
26
- if (ts_morph_1.Node.isFunctionExpression(d) || ts_morph_1.Node.isArrowFunction(d)) {
27
- return d;
28
- }
29
32
  }
30
33
  return null;
31
34
  }
@@ -1,17 +1,30 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.EventraEngine = void 0;
4
- const project_1 = require("./project");
4
+ const languageService_1 = require("./languageService");
5
5
  const scanner_1 = require("./scanner");
6
+ const graph_1 = require("./graph");
7
+ const hash_1 = require("./hash");
6
8
  class EventraEngine {
7
- project;
9
+ ts;
10
+ graph = new graph_1.DependencyGraph();
8
11
  fileEvents = new Map();
9
- constructor(entry) {
10
- this.project = new project_1.ProjectGraph(entry);
12
+ fileHash = new Map();
13
+ constructor(root) {
14
+ this.ts = new languageService_1.TSService(root);
11
15
  }
12
16
  scanFile(file, content) {
13
- const source = this.project.getSource(file, content);
14
- const checker = this.project.getChecker();
17
+ const h = (0, hash_1.hash)(content);
18
+ if (this.fileHash.get(file) === h) {
19
+ return this.fileEvents.get(file) ?? new Set();
20
+ }
21
+ this.fileHash.set(file, h);
22
+ this.ts.updateFile(file, content);
23
+ const source = this.ts.getSourceFile(file);
24
+ const checker = this.ts.getChecker();
25
+ if (!source || !checker)
26
+ return new Set();
27
+ this.graph.update(file, source);
15
28
  const events = (0, scanner_1.scanSource)(source, checker);
16
29
  this.fileEvents.set(file, events);
17
30
  return events;
@@ -28,6 +41,7 @@ class EventraEngine {
28
41
  }
29
42
  removeFile(file) {
30
43
  this.fileEvents.delete(file);
44
+ this.fileHash.delete(file);
31
45
  }
32
46
  }
33
47
  exports.EventraEngine = EventraEngine;
@@ -1,18 +1,21 @@
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.DependencyGraph = void 0;
7
+ const typescript_1 = __importDefault(require("typescript"));
4
8
  class DependencyGraph {
5
9
  reverse = new Map();
6
10
  update(file, source) {
7
- const imports = source
8
- .getImportDeclarations()
9
- .map(i => i.getModuleSpecifierSourceFile()?.getFilePath())
10
- .filter(Boolean);
11
- for (const dep of imports) {
12
- if (!this.reverse.has(dep)) {
13
- this.reverse.set(dep, new Set());
11
+ for (const stmt of source.statements) {
12
+ if (!typescript_1.default.isImportDeclaration(stmt))
13
+ continue;
14
+ const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
15
+ if (!this.reverse.has(module)) {
16
+ this.reverse.set(module, new Set());
14
17
  }
15
- this.reverse.get(dep).add(file);
18
+ this.reverse.get(module).add(file);
16
19
  }
17
20
  }
18
21
  getDependents(file) {
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.hash = hash;
7
+ const crypto_1 = __importDefault(require("crypto"));
8
+ function hash(content) {
9
+ return crypto_1.default.createHash("md5").update(content).digest("hex");
10
+ }
@@ -0,0 +1,67 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.TSService = void 0;
7
+ const typescript_1 = __importDefault(require("typescript"));
8
+ const fs_1 = __importDefault(require("fs"));
9
+ class TSService {
10
+ root;
11
+ files = new Map();
12
+ service;
13
+ constructor(root) {
14
+ this.root = root;
15
+ const host = {
16
+ getScriptFileNames: () => [...this.files.keys()],
17
+ getScriptVersion: (file) => this.files.get(file)?.version.toString() ?? "0",
18
+ getScriptSnapshot: (file) => {
19
+ const f = this.files.get(file);
20
+ if (f) {
21
+ return typescript_1.default.ScriptSnapshot.fromString(f.content);
22
+ }
23
+ if (fs_1.default.existsSync(file)) {
24
+ return typescript_1.default.ScriptSnapshot.fromString(fs_1.default.readFileSync(file, "utf-8"));
25
+ }
26
+ return undefined;
27
+ },
28
+ getCurrentDirectory: () => this.root,
29
+ getCompilationSettings: () => ({
30
+ allowJs: true,
31
+ jsx: typescript_1.default.JsxEmit.Preserve,
32
+ moduleResolution: typescript_1.default.ModuleResolutionKind.Node16,
33
+ target: typescript_1.default.ScriptTarget.ESNext,
34
+ esModuleInterop: true,
35
+ allowSyntheticDefaultImports: true,
36
+ }),
37
+ getDefaultLibFileName: (opts) => typescript_1.default.getDefaultLibFilePath(opts),
38
+ readFile: (fileName, encoding) => {
39
+ try {
40
+ return fs_1.default.readFileSync(fileName, encoding);
41
+ }
42
+ catch (err) {
43
+ return undefined;
44
+ }
45
+ },
46
+ fileExists: fs_1.default.existsSync,
47
+ };
48
+ this.service = typescript_1.default.createLanguageService(host);
49
+ }
50
+ updateFile(file, content) {
51
+ const prev = this.files.get(file);
52
+ this.files.set(file, {
53
+ content,
54
+ version: prev ? prev.version + 1 : 1,
55
+ });
56
+ }
57
+ getProgram() {
58
+ return this.service.getProgram();
59
+ }
60
+ getChecker() {
61
+ return this.getProgram()?.getTypeChecker();
62
+ }
63
+ getSourceFile(file) {
64
+ return this.getProgram()?.getSourceFile(file);
65
+ }
66
+ }
67
+ exports.TSService = TSService;
@@ -1,36 +1,67 @@
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");
7
+ const typescript_1 = __importDefault(require("typescript"));
5
8
  function resolveNodeValue(node, checker) {
6
- if (!ts_morph_1.Node.isIdentifier(node))
7
- 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 };
17
- }
18
- if (ts_morph_1.ts.isNoSubstitutionTemplateLiteral(init)) {
19
- return { values: [init.text], dynamic: false };
9
+ if (typescript_1.default.isStringLiteral(node)) {
10
+ return { values: [node.text], dynamic: false };
11
+ }
12
+ if (typescript_1.default.isTemplateExpression(node)) {
13
+ let result = node.head.text;
14
+ for (const span of node.templateSpans) {
15
+ result += "*";
16
+ result += span.literal.text;
17
+ }
18
+ return { values: [result], dynamic: true };
19
+ }
20
+ if (typescript_1.default.isConditionalExpression(node)) {
21
+ const a = resolveNodeValue(node.whenTrue, checker);
22
+ const b = resolveNodeValue(node.whenFalse, checker);
23
+ return {
24
+ values: [...new Set([...(a?.values ?? []), ...(b?.values ?? [])])],
25
+ dynamic: true,
26
+ };
27
+ }
28
+ if (typescript_1.default.isIdentifier(node)) {
29
+ let symbol = checker.getSymbolAtLocation(node);
30
+ if (!symbol)
31
+ return null;
32
+ if (symbol.flags & typescript_1.default.SymbolFlags.Alias) {
33
+ symbol = checker.getAliasedSymbol(symbol);
34
+ }
35
+ for (const decl of symbol.getDeclarations() ?? []) {
36
+ if (typescript_1.default.isVariableDeclaration(decl) && decl.initializer) {
37
+ return resolveNodeValue(decl.initializer, checker);
20
38
  }
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;
39
+ if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
40
+ if (typescript_1.default.isStringLiteral(decl.initializer)) {
41
+ return {
42
+ values: [decl.initializer.text],
43
+ dynamic: false,
44
+ };
26
45
  }
27
- return { values: [result], dynamic: true };
28
46
  }
29
47
  }
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 };
48
+ }
49
+ if (typescript_1.default.isPropertyAccessExpression(node)) {
50
+ const symbol = checker.getSymbolAtLocation(node);
51
+ if (symbol) {
52
+ let s = symbol;
53
+ if (s.flags & typescript_1.default.SymbolFlags.Alias) {
54
+ s = checker.getAliasedSymbol(s);
55
+ }
56
+ for (const decl of s.getDeclarations() ?? []) {
57
+ if (typescript_1.default.isEnumMember(decl) && decl.initializer) {
58
+ if (typescript_1.default.isStringLiteral(decl.initializer)) {
59
+ return {
60
+ values: [decl.initializer.text],
61
+ dynamic: false,
62
+ };
63
+ }
64
+ }
34
65
  }
35
66
  }
36
67
  }
@@ -1,78 +1,50 @@
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()) {
25
11
  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];
12
+ if (visited.has(source.fileName))
13
+ return events;
14
+ visited.add(source.fileName);
15
+ function visit(node) {
16
+ if (typescript_1.default.isCallExpression(node)) {
17
+ const name = getCallName(node.expression);
18
+ if (/(track|event|capture|send)/i.test(name)) {
19
+ let target = node.arguments[0];
20
+ const fn = (0, callResolver_1.resolveFunctionFromCall)(node.expression, checker);
21
+ // 🔥 cross-file
22
+ if (fn) {
23
+ const sf = fn.getSourceFile();
24
+ if (!visited.has(sf.fileName)) {
25
+ scanSource(sf, checker, visited).forEach(e => events.add(e));
26
+ }
27
+ }
28
+ if (target) {
29
+ const res = (0, resolver_1.resolveNodeValue)(target, checker);
30
+ res?.values.forEach(v => {
31
+ if (v && v.length > 1 && !v.includes(" ")) {
32
+ events.add(v);
33
+ }
34
+ });
35
+ }
41
36
  }
42
37
  }
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
- }
38
+ typescript_1.default.forEachChild(node, visit);
73
39
  }
40
+ visit(source);
74
41
  return events;
75
42
  }
76
- function isValid(v) {
77
- return !!v && v.length > 1 && !v.includes(" ");
43
+ function getCallName(expr) {
44
+ if (typescript_1.default.isIdentifier(expr))
45
+ return expr.text;
46
+ if (typescript_1.default.isPropertyAccessExpression(expr)) {
47
+ return expr.name.text;
48
+ }
49
+ return "";
78
50
  }
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.2",
4
4
  "description": "Eventra CLI",
5
5
  "type": "commonjs",
6
6
  "bin": {
@@ -1,36 +1,42 @@
1
- import { Node } from "ts-morph";
1
+ import ts from "typescript";
2
2
 
3
- export function resolveFunctionFromCall(expr: Node) {
4
- let symbol = expr.getSymbol();
3
+ export function resolveFunctionFromCall(
4
+ expr: ts.Expression,
5
+ checker: ts.TypeChecker
6
+ ): ts.FunctionLikeDeclaration | null {
5
7
 
6
- if (!symbol && Node.isPropertyAccessExpression(expr)) {
7
- symbol = expr.getNameNode().getSymbol();
8
+ let symbol = checker.getSymbolAtLocation(expr);
9
+
10
+ if (!symbol && ts.isPropertyAccessExpression(expr)) {
11
+ symbol = checker.getSymbolAtLocation(expr.name);
8
12
  }
9
13
 
10
14
  if (!symbol) return null;
11
15
 
12
- const decls = symbol.getDeclarations() ?? [];
16
+ if (symbol.flags & ts.SymbolFlags.Alias) {
17
+ symbol = checker.getAliasedSymbol(symbol);
18
+ }
13
19
 
14
- for (const d of decls) {
15
- if (Node.isFunctionDeclaration(d)) return d;
20
+ for (const decl of symbol.getDeclarations() ?? []) {
21
+ if (
22
+ ts.isFunctionDeclaration(decl) ||
23
+ ts.isMethodDeclaration(decl) ||
24
+ ts.isFunctionExpression(decl) ||
25
+ ts.isArrowFunction(decl)
26
+ ) {
27
+ return decl;
28
+ }
16
29
 
17
- if (Node.isVariableDeclaration(d)) {
18
- const init = d.getInitializer();
30
+ if (ts.isVariableDeclaration(decl) && decl.initializer) {
31
+ const init = decl.initializer;
19
32
 
20
33
  if (
21
- init &&
22
- (Node.isArrowFunction(init) ||
23
- Node.isFunctionExpression(init))
34
+ ts.isArrowFunction(init) ||
35
+ ts.isFunctionExpression(init)
24
36
  ) {
25
37
  return init;
26
38
  }
27
39
  }
28
-
29
- if (Node.isMethodDeclaration(d)) return d;
30
-
31
- if (Node.isFunctionExpression(d) || Node.isArrowFunction(d)) {
32
- return d;
33
- }
34
40
  }
35
41
 
36
42
  return null;
@@ -1,21 +1,41 @@
1
- import { ProjectGraph } from "./project";
1
+ import { TSService } from "./languageService";
2
2
  import { scanSource } from "./scanner";
3
+ import { DependencyGraph } from "./graph";
4
+ import { hash } from "./hash";
3
5
 
4
6
  export class EventraEngine {
5
- private project: ProjectGraph;
7
+ private ts: TSService;
8
+ private graph = new DependencyGraph();
9
+
6
10
  private fileEvents = new Map<string, Set<string>>();
11
+ private fileHash = new Map<string, string>();
7
12
 
8
- constructor(entry: string) {
9
- this.project = new ProjectGraph(entry);
13
+ constructor(root: string) {
14
+ this.ts = new TSService(root);
10
15
  }
11
16
 
12
17
  scanFile(file: string, content: string) {
13
- const source = this.project.getSource(file, content);
14
- const checker = this.project.getChecker();
18
+ const h = hash(content);
19
+
20
+ if (this.fileHash.get(file) === h) {
21
+ return this.fileEvents.get(file) ?? new Set();
22
+ }
23
+
24
+ this.fileHash.set(file, h);
25
+
26
+ this.ts.updateFile(file, content);
27
+
28
+ const source = this.ts.getSourceFile(file);
29
+ const checker = this.ts.getChecker();
30
+
31
+ if (!source || !checker) return new Set();
32
+
33
+ this.graph.update(file, source);
15
34
 
16
35
  const events = scanSource(source, checker);
17
36
 
18
37
  this.fileEvents.set(file, events);
38
+
19
39
  return events;
20
40
  }
21
41
 
@@ -35,5 +55,6 @@ export class EventraEngine {
35
55
 
36
56
  removeFile(file: string) {
37
57
  this.fileEvents.delete(file);
58
+ this.fileHash.delete(file);
38
59
  }
39
60
  }
@@ -1,19 +1,19 @@
1
- import { SourceFile } from "ts-morph";
1
+ import ts from "typescript";
2
2
 
3
3
  export class DependencyGraph {
4
4
  private reverse = new Map<string, Set<string>>();
5
5
 
6
- update(file: string, source: SourceFile) {
7
- const imports = source
8
- .getImportDeclarations()
9
- .map(i => i.getModuleSpecifierSourceFile()?.getFilePath())
10
- .filter(Boolean) as string[];
6
+ update(file: string, source: ts.SourceFile) {
7
+ for (const stmt of source.statements) {
8
+ if (!ts.isImportDeclaration(stmt)) continue;
11
9
 
12
- for (const dep of imports) {
13
- if (!this.reverse.has(dep)) {
14
- this.reverse.set(dep, new Set());
10
+ const module = stmt.moduleSpecifier.getText().replace(/['"]/g, "");
11
+
12
+ if (!this.reverse.has(module)) {
13
+ this.reverse.set(module, new Set());
15
14
  }
16
- this.reverse.get(dep)!.add(file);
15
+
16
+ this.reverse.get(module)!.add(file);
17
17
  }
18
18
  }
19
19
 
@@ -0,0 +1,5 @@
1
+ import crypto from "crypto";
2
+
3
+ export function hash(content: string) {
4
+ return crypto.createHash("md5").update(content).digest("hex");
5
+ }
@@ -0,0 +1,73 @@
1
+ import ts from "typescript";
2
+ import fs from "fs";
3
+
4
+ export class TSService {
5
+ private files = new Map<string, { version: number; content: string }>();
6
+ private service: ts.LanguageService;
7
+
8
+ constructor(private root: string) {
9
+ const host: ts.LanguageServiceHost = {
10
+ getScriptFileNames: () => [...this.files.keys()],
11
+ getScriptVersion: (file) =>
12
+ this.files.get(file)?.version.toString() ?? "0",
13
+ getScriptSnapshot: (file) => {
14
+ const f = this.files.get(file);
15
+
16
+ if (f) {
17
+ return ts.ScriptSnapshot.fromString(f.content);
18
+ }
19
+
20
+ if (fs.existsSync(file)) {
21
+ return ts.ScriptSnapshot.fromString(
22
+ fs.readFileSync(file, "utf-8" as BufferEncoding)
23
+ );
24
+ }
25
+
26
+ return undefined;
27
+ },
28
+ getCurrentDirectory: () => this.root,
29
+ getCompilationSettings: () => ({
30
+ allowJs: true,
31
+ jsx: ts.JsxEmit.Preserve,
32
+ moduleResolution: ts.ModuleResolutionKind.Node16,
33
+ target: ts.ScriptTarget.ESNext,
34
+ esModuleInterop: true,
35
+ allowSyntheticDefaultImports: true,
36
+ }),
37
+ getDefaultLibFileName: (opts) => ts.getDefaultLibFilePath(opts),
38
+
39
+ readFile: (fileName: string, encoding?: string) => {
40
+ try {
41
+ return fs.readFileSync(fileName, encoding as BufferEncoding);
42
+ } catch (err) {
43
+ return undefined;
44
+ }
45
+ },
46
+
47
+ fileExists: fs.existsSync,
48
+ };
49
+
50
+ this.service = ts.createLanguageService(host);
51
+ }
52
+
53
+ updateFile(file: string, content: string) {
54
+ const prev = this.files.get(file);
55
+
56
+ this.files.set(file, {
57
+ content,
58
+ version: prev ? prev.version + 1 : 1,
59
+ });
60
+ }
61
+
62
+ getProgram() {
63
+ return this.service.getProgram();
64
+ }
65
+
66
+ getChecker() {
67
+ return this.getProgram()?.getTypeChecker();
68
+ }
69
+
70
+ getSourceFile(file: string) {
71
+ return this.getProgram()?.getSourceFile(file);
72
+ }
73
+ }