@esportsplus/reactivity 0.23.1 → 0.23.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.
@@ -1,10 +1,9 @@
1
- import type { TransformOptions, TransformResult } from '../types.js';
1
+ import type { TransformResult } from '../types.js';
2
2
  import { mightNeedTransform } from './detector.js';
3
3
  import ts from 'typescript';
4
- declare const createTransformer: (options?: TransformOptions) => ts.TransformerFactory<ts.SourceFile>;
5
- declare const transform: (sourceFile: ts.SourceFile, options?: TransformOptions) => TransformResult;
4
+ declare const createTransformer: () => ts.TransformerFactory<ts.SourceFile>;
5
+ declare const transform: (sourceFile: ts.SourceFile) => TransformResult;
6
6
  export { createTransformer, mightNeedTransform, transform };
7
- export { injectAutoDispose } from './transforms/auto-dispose.js';
8
- export { transformReactiveArrays } from './transforms/reactive-array.js';
9
- export { transformReactiveObjects } from './transforms/reactive-object.js';
10
- export { transformReactivePrimitives } from './transforms/reactive-primitives.js';
7
+ export { transformReactiveArrays } from './transforms/array.js';
8
+ export { transformReactiveObjects } from './transforms/object.js';
9
+ export { transformReactivePrimitives } from './transforms/primitives.js';
@@ -1,18 +1,17 @@
1
- import { injectAutoDispose } from './transforms/auto-dispose.js';
2
1
  import { mightNeedTransform } from './detector.js';
3
- import { transformReactiveArrays } from './transforms/reactive-array.js';
4
- import { transformReactiveObjects } from './transforms/reactive-object.js';
5
- import { transformReactivePrimitives } from './transforms/reactive-primitives.js';
2
+ import { transformReactiveArrays } from './transforms/array.js';
3
+ import { transformReactiveObjects } from './transforms/object.js';
4
+ import { transformReactivePrimitives } from './transforms/primitives.js';
6
5
  import ts from 'typescript';
7
- const createTransformer = (options) => {
6
+ const createTransformer = () => {
8
7
  return () => {
9
8
  return (sourceFile) => {
10
- let result = transform(sourceFile, options);
9
+ let result = transform(sourceFile);
11
10
  return result.transformed ? result.sourceFile : sourceFile;
12
11
  };
13
12
  };
14
13
  };
15
- const transform = (sourceFile, options) => {
14
+ const transform = (sourceFile) => {
16
15
  let bindings = new Map(), code = sourceFile.getFullText(), current = sourceFile, original = code, result;
17
16
  if (!mightNeedTransform(code)) {
18
17
  return { code, sourceFile, transformed: false };
@@ -32,13 +31,6 @@ const transform = (sourceFile, options) => {
32
31
  current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
33
32
  code = result;
34
33
  }
35
- if (options?.autoDispose) {
36
- result = injectAutoDispose(current);
37
- if (result !== code) {
38
- current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
39
- code = result;
40
- }
41
- }
42
34
  if (code === original) {
43
35
  return { code, sourceFile, transformed: false };
44
36
  }
@@ -49,7 +41,6 @@ const transform = (sourceFile, options) => {
49
41
  };
50
42
  };
51
43
  export { createTransformer, mightNeedTransform, transform };
52
- export { injectAutoDispose } from './transforms/auto-dispose.js';
53
- export { transformReactiveArrays } from './transforms/reactive-array.js';
54
- export { transformReactiveObjects } from './transforms/reactive-object.js';
55
- export { transformReactivePrimitives } from './transforms/reactive-primitives.js';
44
+ export { transformReactiveArrays } from './transforms/array.js';
45
+ export { transformReactiveObjects } from './transforms/object.js';
46
+ export { transformReactivePrimitives } from './transforms/primitives.js';
@@ -1,5 +1,3 @@
1
1
  import type { Plugin } from 'esbuild';
2
- import type { TransformOptions } from '../../types.js';
3
- declare const _default: (options?: TransformOptions) => Plugin;
2
+ declare const _default: () => Plugin;
4
3
  export default _default;
5
- export type { TransformOptions as PluginOptions };
@@ -2,7 +2,7 @@ import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
2
  import { mightNeedTransform, transform } from '../../transformer/index.js';
3
3
  import fs from 'fs';
4
4
  import ts from 'typescript';
5
- export default (options) => {
5
+ export default () => {
6
6
  return {
7
7
  name: '@esportsplus/reactivity/plugin-esbuild',
8
8
  setup(build) {
@@ -12,7 +12,7 @@ export default (options) => {
12
12
  return null;
13
13
  }
14
14
  try {
15
- let sourceFile = ts.createSourceFile(args.path, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile, options);
15
+ let sourceFile = ts.createSourceFile(args.path, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile);
16
16
  if (!result.transformed) {
17
17
  return null;
18
18
  }
@@ -1,5 +1,3 @@
1
1
  import type { Plugin } from 'vite';
2
- import type { TransformOptions } from '../../types.js';
3
- declare const _default: (options?: TransformOptions) => Plugin;
2
+ declare const _default: () => Plugin;
4
3
  export default _default;
5
- export type { TransformOptions as PluginOptions };
@@ -1,7 +1,7 @@
1
1
  import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
2
  import { mightNeedTransform, transform } from '../../transformer/index.js';
3
3
  import ts from 'typescript';
4
- export default (options) => {
4
+ export default () => {
5
5
  return {
6
6
  enforce: 'pre',
7
7
  name: '@esportsplus/reactivity/plugin-vite',
@@ -13,7 +13,7 @@ export default (options) => {
13
13
  return null;
14
14
  }
15
15
  try {
16
- let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile, options);
16
+ let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true), result = transform(sourceFile);
17
17
  if (!result.transformed) {
18
18
  return null;
19
19
  }
@@ -48,14 +48,13 @@ function buildClassCode(className, properties) {
48
48
  disposeStatements.push(`if (this.#${key}) dispose(this.#${key});`);
49
49
  }
50
50
  }
51
- let disposeBody = disposeStatements.length > 0 ? disposeStatements.join('\n') : '';
52
51
  return `
53
52
  class ${className} {
54
53
  ${fields.join('\n')}
55
54
  ${accessors.join('\n')}
56
55
 
57
56
  dispose() {
58
- ${disposeBody}
57
+ ${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
59
58
  }
60
59
  }
61
60
  `;
package/build/types.d.ts CHANGED
@@ -31,12 +31,9 @@ type Signal<T> = {
31
31
  type: typeof SIGNAL;
32
32
  value: T;
33
33
  };
34
- interface TransformOptions {
35
- autoDispose?: boolean;
36
- }
37
34
  interface TransformResult {
38
35
  code: string;
39
36
  sourceFile: ts.SourceFile;
40
37
  transformed: boolean;
41
38
  }
42
- export type { BindingType, Bindings, Computed, Link, ReactiveArray, ReactiveObject, Signal, TransformOptions, TransformResult };
39
+ export type { BindingType, Bindings, Computed, Link, ReactiveArray, ReactiveObject, Signal, TransformResult };
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "@esportsplus/utilities": "^0.27.2"
5
5
  },
6
6
  "devDependencies": {
7
- "@esportsplus/typescript": "^0.12.2",
7
+ "@esportsplus/typescript": "^0.13.0",
8
8
  "@types/node": "^25.0.3",
9
9
  "esbuild": "^0.27.2",
10
10
  "typescript": "^5.9.3",
@@ -41,7 +41,7 @@
41
41
  },
42
42
  "type": "module",
43
43
  "types": "build/index.d.ts",
44
- "version": "0.23.1",
44
+ "version": "0.23.3",
45
45
  "scripts": {
46
46
  "build": "tsc && tsc-alias",
47
47
  "build:test": "pnpm build && vite build --config test/vite.config.ts",
package/readme.md CHANGED
@@ -147,30 +147,6 @@ For direct TypeScript compilation using `ttsc` or `ts-patch`:
147
147
  }
148
148
  ```
149
149
 
150
- ## Plugin Options
151
-
152
- All plugins accept the same options:
153
-
154
- ```typescript
155
- interface TransformOptions {
156
- // Inject automatic disposal tracking (experimental)
157
- autoDispose?: boolean;
158
- }
159
- ```
160
-
161
- Example with options:
162
-
163
- ```typescript
164
- // vite.config.ts
165
- import { plugin as reactivity } from '@esportsplus/reactivity/plugins/vite';
166
-
167
- export default defineConfig({
168
- plugins: [
169
- reactivity({ autoDispose: true })
170
- ]
171
- });
172
- ```
173
-
174
150
  ## How It Works
175
151
 
176
152
  The transformer converts your code at compile time:
@@ -1,23 +1,22 @@
1
- import type { Bindings, TransformOptions, TransformResult } from '~/types';
2
- import { injectAutoDispose } from './transforms/auto-dispose';
1
+ import type { Bindings, TransformResult } from '~/types';
3
2
  import { mightNeedTransform } from './detector';
4
- import { transformReactiveArrays } from './transforms/reactive-array';
5
- import { transformReactiveObjects } from './transforms/reactive-object';
6
- import { transformReactivePrimitives } from './transforms/reactive-primitives';
3
+ import { transformReactiveArrays } from './transforms/array';
4
+ import { transformReactiveObjects } from './transforms/object';
5
+ import { transformReactivePrimitives } from './transforms/primitives';
7
6
  import ts from 'typescript';
8
7
 
9
8
 
10
- const createTransformer = (options?: TransformOptions): ts.TransformerFactory<ts.SourceFile> => {
9
+ const createTransformer = (): ts.TransformerFactory<ts.SourceFile> => {
11
10
  return () => {
12
11
  return (sourceFile: ts.SourceFile): ts.SourceFile => {
13
- let result = transform(sourceFile, options);
12
+ let result = transform(sourceFile);
14
13
 
15
14
  return result.transformed ? result.sourceFile : sourceFile;
16
15
  };
17
16
  };
18
17
  };
19
18
 
20
- const transform = (sourceFile: ts.SourceFile, options?: TransformOptions): TransformResult => {
19
+ const transform = (sourceFile: ts.SourceFile): TransformResult => {
21
20
  let bindings: Bindings = new Map(),
22
21
  code = sourceFile.getFullText(),
23
22
  current = sourceFile,
@@ -50,15 +49,6 @@ const transform = (sourceFile: ts.SourceFile, options?: TransformOptions): Trans
50
49
  code = result;
51
50
  }
52
51
 
53
- if (options?.autoDispose) {
54
- result = injectAutoDispose(current);
55
-
56
- if (result !== code) {
57
- current = ts.createSourceFile(sourceFile.fileName, result, sourceFile.languageVersion, true);
58
- code = result;
59
- }
60
- }
61
-
62
52
  if (code === original) {
63
53
  return { code, sourceFile, transformed: false };
64
54
  }
@@ -72,7 +62,6 @@ const transform = (sourceFile: ts.SourceFile, options?: TransformOptions): Trans
72
62
 
73
63
 
74
64
  export { createTransformer, mightNeedTransform, transform };
75
- export { injectAutoDispose } from './transforms/auto-dispose';
76
- export { transformReactiveArrays } from './transforms/reactive-array';
77
- export { transformReactiveObjects } from './transforms/reactive-object';
78
- export { transformReactivePrimitives } from './transforms/reactive-primitives';
65
+ export { transformReactiveArrays } from './transforms/array';
66
+ export { transformReactiveObjects } from './transforms/object';
67
+ export { transformReactivePrimitives } from './transforms/primitives';
@@ -1,12 +1,11 @@
1
1
  import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
2
  import { mightNeedTransform, transform } from '~/transformer';
3
3
  import type { OnLoadArgs, Plugin, PluginBuild } from 'esbuild';
4
- import type { TransformOptions } from '~/types';
5
4
  import fs from 'fs';
6
5
  import ts from 'typescript';
7
6
 
8
7
 
9
- export default (options?: TransformOptions): Plugin => {
8
+ export default (): Plugin => {
10
9
  return {
11
10
  name: '@esportsplus/reactivity/plugin-esbuild',
12
11
 
@@ -25,7 +24,7 @@ export default (options?: TransformOptions): Plugin => {
25
24
  ts.ScriptTarget.Latest,
26
25
  true
27
26
  ),
28
- result = transform(sourceFile, options);
27
+ result = transform(sourceFile);
29
28
 
30
29
  if (!result.transformed) {
31
30
  return null;
@@ -44,4 +43,3 @@ export default (options?: TransformOptions): Plugin => {
44
43
  }
45
44
  };
46
45
  };
47
- export type { TransformOptions as PluginOptions };
@@ -1,11 +1,10 @@
1
1
  import { TRANSFORM_PATTERN } from '@esportsplus/typescript/transformer';
2
2
  import { mightNeedTransform, transform } from '~/transformer';
3
3
  import type { Plugin } from 'vite';
4
- import type { TransformOptions } from '~/types';
5
4
  import ts from 'typescript';
6
5
 
7
6
 
8
- export default (options?: TransformOptions): Plugin => {
7
+ export default (): Plugin => {
9
8
  return {
10
9
  enforce: 'pre',
11
10
  name: '@esportsplus/reactivity/plugin-vite',
@@ -21,7 +20,7 @@ export default (options?: TransformOptions): Plugin => {
21
20
 
22
21
  try {
23
22
  let sourceFile = ts.createSourceFile(id, code, ts.ScriptTarget.Latest, true),
24
- result = transform(sourceFile, options);
23
+ result = transform(sourceFile);
25
24
 
26
25
  if (!result.transformed) {
27
26
  return null;
@@ -36,4 +35,3 @@ export default (options?: TransformOptions): Plugin => {
36
35
  }
37
36
  };
38
37
  };
39
- export type { TransformOptions as PluginOptions };
@@ -94,15 +94,13 @@ function buildClassCode(className: string, properties: AnalyzedProperty[]): stri
94
94
  }
95
95
  }
96
96
 
97
- let disposeBody = disposeStatements.length > 0 ? disposeStatements.join('\n') : '';
98
-
99
97
  return `
100
98
  class ${className} {
101
99
  ${fields.join('\n')}
102
100
  ${accessors.join('\n')}
103
101
 
104
102
  dispose() {
105
- ${disposeBody}
103
+ ${disposeStatements.length > 0 ? disposeStatements.join('\n') : ''}
106
104
  }
107
105
  }
108
106
  `;
package/src/types.ts CHANGED
@@ -43,10 +43,6 @@ type Signal<T> = {
43
43
  value: T;
44
44
  };
45
45
 
46
- interface TransformOptions {
47
- autoDispose?: boolean;
48
- }
49
-
50
46
  interface TransformResult {
51
47
  code: string;
52
48
  sourceFile: ts.SourceFile;
@@ -62,6 +58,5 @@ export type {
62
58
  ReactiveArray,
63
59
  ReactiveObject,
64
60
  Signal,
65
- TransformOptions,
66
61
  TransformResult
67
62
  };
@@ -1,3 +0,0 @@
1
- import ts from 'typescript';
2
- declare const injectAutoDispose: (sourceFile: ts.SourceFile) => string;
3
- export { injectAutoDispose };
@@ -1,119 +0,0 @@
1
- import { uid, TRAILING_SEMICOLON } from '@esportsplus/typescript/transformer';
2
- import { applyReplacements } from './utilities.js';
3
- import ts from 'typescript';
4
- function processFunction(node, sourceFile, edits) {
5
- if (!node.body || !ts.isBlock(node.body)) {
6
- return;
7
- }
8
- let ctx = {
9
- disposables: [],
10
- effectsToCapture: [],
11
- parentBody: node.body,
12
- returnStatement: null
13
- };
14
- visitBody(ctx, node.body);
15
- if (ctx.disposables.length === 0 || !ctx.returnStatement || !ctx.returnStatement.expression) {
16
- return;
17
- }
18
- let cleanupFn = ctx.returnStatement.expression;
19
- if (!cleanupFn.body) {
20
- return;
21
- }
22
- let disposeStatements = [];
23
- for (let i = ctx.disposables.length - 1; i >= 0; i--) {
24
- let d = ctx.disposables[i];
25
- if (d.type === 'reactive') {
26
- disposeStatements.push(`${d.name}.dispose();`);
27
- }
28
- else {
29
- disposeStatements.push(`${d.name}();`);
30
- }
31
- }
32
- let disposeCode = disposeStatements.join('\n');
33
- if (ts.isBlock(cleanupFn.body)) {
34
- edits.push({
35
- cleanupBodyEnd: cleanupFn.body.statements[0]?.pos ?? cleanupFn.body.end - 1,
36
- cleanupBodyStart: cleanupFn.body.pos + 1,
37
- disposeCode,
38
- effectsToCapture: ctx.effectsToCapture
39
- });
40
- }
41
- else {
42
- edits.push({
43
- cleanupBodyEnd: cleanupFn.body.end,
44
- cleanupBodyStart: cleanupFn.body.pos,
45
- disposeCode: `{ ${disposeCode}\n return ${cleanupFn.body.getText(sourceFile)}; }`,
46
- effectsToCapture: ctx.effectsToCapture
47
- });
48
- }
49
- }
50
- function visitBody(ctx, node) {
51
- if (ts.isVariableDeclaration(node) &&
52
- ts.isIdentifier(node.name) &&
53
- node.initializer &&
54
- ts.isCallExpression(node.initializer) &&
55
- ts.isIdentifier(node.initializer.expression) &&
56
- node.initializer.expression.text === 'reactive') {
57
- ctx.disposables.push({ name: node.name.text, type: 'reactive' });
58
- }
59
- if (ts.isCallExpression(node) &&
60
- ts.isIdentifier(node.expression) &&
61
- node.expression.text === 'effect') {
62
- if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
63
- ctx.disposables.push({ name: node.parent.name.text, type: 'effect' });
64
- }
65
- else if (ts.isExpressionStatement(node.parent)) {
66
- let name = uid('effect');
67
- ctx.effectsToCapture.push({
68
- end: node.parent.end,
69
- name,
70
- start: node.parent.pos
71
- });
72
- ctx.disposables.push({ name, type: 'effect' });
73
- }
74
- }
75
- if (ts.isReturnStatement(node) &&
76
- node.expression &&
77
- (ts.isArrowFunction(node.expression) || ts.isFunctionExpression(node.expression)) &&
78
- node.parent === ctx.parentBody) {
79
- ctx.returnStatement = node;
80
- }
81
- ts.forEachChild(node, n => visitBody(ctx, n));
82
- }
83
- function visitMain(ctx, node) {
84
- if (ts.isFunctionDeclaration(node) ||
85
- ts.isFunctionExpression(node) ||
86
- ts.isArrowFunction(node)) {
87
- processFunction(node, ctx.sourceFile, ctx.edits);
88
- }
89
- ts.forEachChild(node, n => visitMain(ctx, n));
90
- }
91
- const injectAutoDispose = (sourceFile) => {
92
- let code = sourceFile.getFullText(), ctx = {
93
- edits: [],
94
- sourceFile
95
- };
96
- visitMain(ctx, sourceFile);
97
- if (ctx.edits.length === 0) {
98
- return code;
99
- }
100
- let replacements = [];
101
- for (let i = 0, n = ctx.edits.length; i < n; i++) {
102
- let edit = ctx.edits[i], effects = edit.effectsToCapture;
103
- for (let j = 0, m = effects.length; j < m; j++) {
104
- let effect = effects[j], original = code.substring(effect.start, effect.end).trim();
105
- replacements.push({
106
- end: effect.end,
107
- newText: `const ${effect.name} = ${original.replace(TRAILING_SEMICOLON, '')}`,
108
- start: effect.start
109
- });
110
- }
111
- replacements.push({
112
- end: edit.cleanupBodyEnd,
113
- newText: `\n${edit.disposeCode}`,
114
- start: edit.cleanupBodyStart
115
- });
116
- }
117
- return applyReplacements(code, replacements);
118
- };
119
- export { injectAutoDispose };
@@ -1,191 +0,0 @@
1
- import { uid, TRAILING_SEMICOLON } from '@esportsplus/typescript/transformer';
2
- import { applyReplacements, Replacement } from './utilities';
3
- import ts from 'typescript';
4
-
5
-
6
- interface BodyContext {
7
- disposables: Disposable[];
8
- effectsToCapture: { end: number; name: string; start: number }[];
9
- parentBody: ts.Node;
10
- returnStatement: ts.ReturnStatement | null;
11
- }
12
-
13
- interface Disposable {
14
- name: string;
15
- type: 'effect' | 'reactive';
16
- }
17
-
18
- interface FunctionEdit {
19
- cleanupBodyEnd: number;
20
- cleanupBodyStart: number;
21
- disposeCode: string;
22
- effectsToCapture: { end: number; name: string; start: number }[];
23
- }
24
-
25
- interface MainContext {
26
- edits: FunctionEdit[];
27
- sourceFile: ts.SourceFile;
28
- }
29
-
30
-
31
- function processFunction(
32
- node: ts.ArrowFunction | ts.FunctionDeclaration | ts.FunctionExpression,
33
- sourceFile: ts.SourceFile,
34
- edits: FunctionEdit[]
35
- ): void {
36
- if (!node.body || !ts.isBlock(node.body)) {
37
- return;
38
- }
39
-
40
- let ctx: BodyContext = {
41
- disposables: [],
42
- effectsToCapture: [],
43
- parentBody: node.body,
44
- returnStatement: null
45
- };
46
-
47
- visitBody(ctx, node.body);
48
-
49
- if (ctx.disposables.length === 0 || !ctx.returnStatement || !ctx.returnStatement.expression) {
50
- return;
51
- }
52
-
53
- let cleanupFn = ctx.returnStatement.expression as ts.ArrowFunction | ts.FunctionExpression;
54
-
55
- if (!cleanupFn.body) {
56
- return;
57
- }
58
-
59
- let disposeStatements: string[] = [];
60
-
61
- for (let i = ctx.disposables.length - 1; i >= 0; i--) {
62
- let d = ctx.disposables[i];
63
-
64
- if (d.type === 'reactive') {
65
- disposeStatements.push(`${d.name}.dispose();`);
66
- }
67
- else {
68
- disposeStatements.push(`${d.name}();`);
69
- }
70
- }
71
-
72
- let disposeCode = disposeStatements.join('\n');
73
-
74
- if (ts.isBlock(cleanupFn.body)) {
75
- edits.push({
76
- cleanupBodyEnd: cleanupFn.body.statements[0]?.pos ?? cleanupFn.body.end - 1,
77
- cleanupBodyStart: cleanupFn.body.pos + 1,
78
- disposeCode,
79
- effectsToCapture: ctx.effectsToCapture
80
- });
81
- }
82
- else {
83
- edits.push({
84
- cleanupBodyEnd: cleanupFn.body.end,
85
- cleanupBodyStart: cleanupFn.body.pos,
86
- disposeCode: `{ ${disposeCode}\n return ${cleanupFn.body.getText(sourceFile)}; }`,
87
- effectsToCapture: ctx.effectsToCapture
88
- });
89
- }
90
- }
91
-
92
- function visitBody(ctx: BodyContext, node: ts.Node): void {
93
- if (
94
- ts.isVariableDeclaration(node) &&
95
- ts.isIdentifier(node.name) &&
96
- node.initializer &&
97
- ts.isCallExpression(node.initializer) &&
98
- ts.isIdentifier(node.initializer.expression) &&
99
- node.initializer.expression.text === 'reactive'
100
- ) {
101
- ctx.disposables.push({ name: node.name.text, type: 'reactive' });
102
- }
103
-
104
- if (
105
- ts.isCallExpression(node) &&
106
- ts.isIdentifier(node.expression) &&
107
- node.expression.text === 'effect'
108
- ) {
109
- if (ts.isVariableDeclaration(node.parent) && ts.isIdentifier(node.parent.name)) {
110
- ctx.disposables.push({ name: node.parent.name.text, type: 'effect' });
111
- }
112
- else if (ts.isExpressionStatement(node.parent)) {
113
- let name = uid('effect');
114
-
115
- ctx.effectsToCapture.push({
116
- end: node.parent.end,
117
- name,
118
- start: node.parent.pos
119
- });
120
-
121
- ctx.disposables.push({ name, type: 'effect' });
122
- }
123
- }
124
-
125
- if (
126
- ts.isReturnStatement(node) &&
127
- node.expression &&
128
- (ts.isArrowFunction(node.expression) || ts.isFunctionExpression(node.expression)) &&
129
- node.parent === ctx.parentBody
130
- ) {
131
- ctx.returnStatement = node;
132
- }
133
-
134
- ts.forEachChild(node, n => visitBody(ctx, n));
135
- }
136
-
137
- function visitMain(ctx: MainContext, node: ts.Node): void {
138
- if (
139
- ts.isFunctionDeclaration(node) ||
140
- ts.isFunctionExpression(node) ||
141
- ts.isArrowFunction(node)
142
- ) {
143
- processFunction(node, ctx.sourceFile, ctx.edits);
144
- }
145
-
146
- ts.forEachChild(node, n => visitMain(ctx, n));
147
- }
148
-
149
-
150
- const injectAutoDispose = (sourceFile: ts.SourceFile): string => {
151
- let code = sourceFile.getFullText(),
152
- ctx: MainContext = {
153
- edits: [],
154
- sourceFile
155
- };
156
-
157
- visitMain(ctx, sourceFile);
158
-
159
- if (ctx.edits.length === 0) {
160
- return code;
161
- }
162
-
163
- let replacements: Replacement[] = [];
164
-
165
- for (let i = 0, n = ctx.edits.length; i < n; i++) {
166
- let edit = ctx.edits[i],
167
- effects = edit.effectsToCapture;
168
-
169
- for (let j = 0, m = effects.length; j < m; j++) {
170
- let effect = effects[j],
171
- original = code.substring(effect.start, effect.end).trim();
172
-
173
- replacements.push({
174
- end: effect.end,
175
- newText: `const ${effect.name} = ${original.replace(TRAILING_SEMICOLON, '')}`,
176
- start: effect.start
177
- });
178
- }
179
-
180
- replacements.push({
181
- end: edit.cleanupBodyEnd,
182
- newText: `\n${edit.disposeCode}`,
183
- start: edit.cleanupBodyStart
184
- });
185
- }
186
-
187
- return applyReplacements(code, replacements);
188
- };
189
-
190
-
191
- export { injectAutoDispose };