@mondaydotcomorg/atp-compiler 0.18.2 → 0.18.4-rc.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.
- package/__tests__/unit/default-compiler.test.ts +259 -0
- package/__tests__/unit/plugin-system.test.ts +401 -0
- package/dist/atp-compiler/src/index.d.ts +8 -0
- package/dist/atp-compiler/src/index.d.ts.map +1 -1
- package/dist/atp-compiler/src/index.js +9 -0
- package/dist/atp-compiler/src/index.js.map +1 -1
- package/dist/atp-compiler/src/plugin-system/create-default-compiler.d.ts +40 -0
- package/dist/atp-compiler/src/plugin-system/create-default-compiler.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/create-default-compiler.js +40 -0
- package/dist/atp-compiler/src/plugin-system/create-default-compiler.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/array-transformer-plugin.d.ts +18 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/array-transformer-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/array-transformer-plugin.js +45 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/array-transformer-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/detection-plugin.d.ts +17 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/detection-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/detection-plugin.js +33 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/detection-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/index.d.ts +11 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/index.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/index.js +11 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/index.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/loop-transformer-plugin.d.ts +17 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/loop-transformer-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/loop-transformer-plugin.js +36 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/loop-transformer-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/promise-transformer-plugin.d.ts +19 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/promise-transformer-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/promise-transformer-plugin.js +49 -0
- package/dist/atp-compiler/src/plugin-system/default-plugins/promise-transformer-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/examples/loop-transformer-plugin.d.ts +31 -0
- package/dist/atp-compiler/src/plugin-system/examples/loop-transformer-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/examples/loop-transformer-plugin.js +60 -0
- package/dist/atp-compiler/src/plugin-system/examples/loop-transformer-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/examples/security-validator-plugin.d.ts +48 -0
- package/dist/atp-compiler/src/plugin-system/examples/security-validator-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/examples/security-validator-plugin.js +108 -0
- package/dist/atp-compiler/src/plugin-system/examples/security-validator-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/examples/timeout-plugin.d.ts +53 -0
- package/dist/atp-compiler/src/plugin-system/examples/timeout-plugin.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/examples/timeout-plugin.js +106 -0
- package/dist/atp-compiler/src/plugin-system/examples/timeout-plugin.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/index.d.ts +14 -0
- package/dist/atp-compiler/src/plugin-system/index.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/index.js +16 -0
- package/dist/atp-compiler/src/plugin-system/index.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/pluggable-compiler.d.ts +102 -0
- package/dist/atp-compiler/src/plugin-system/pluggable-compiler.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/pluggable-compiler.js +280 -0
- package/dist/atp-compiler/src/plugin-system/pluggable-compiler.js.map +1 -0
- package/dist/atp-compiler/src/plugin-system/plugin-api.d.ts +165 -0
- package/dist/atp-compiler/src/plugin-system/plugin-api.d.ts.map +1 -0
- package/dist/atp-compiler/src/plugin-system/plugin-api.js +180 -0
- package/dist/atp-compiler/src/plugin-system/plugin-api.js.map +1 -0
- package/dist/atp-compiler/src/transformer/index.d.ts +15 -1
- package/dist/atp-compiler/src/transformer/index.d.ts.map +1 -1
- package/dist/atp-compiler/src/transformer/index.js +17 -0
- package/dist/atp-compiler/src/transformer/index.js.map +1 -1
- package/dist/atp-compiler/src/types/compiler-interface.d.ts +61 -0
- package/dist/atp-compiler/src/types/compiler-interface.d.ts.map +1 -0
- package/dist/atp-compiler/src/types/compiler-interface.js +18 -0
- package/dist/atp-compiler/src/types/compiler-interface.js.map +1 -0
- package/dist/runtime/src/approval/index.js +2 -1
- package/dist/runtime/src/approval/index.js.map +1 -1
- package/dist/runtime/src/index.d.ts +1 -1
- package/dist/runtime/src/index.d.ts.map +1 -1
- package/dist/runtime/src/index.js +1 -1
- package/dist/runtime/src/index.js.map +1 -1
- package/dist/runtime/src/llm/index.d.ts +1 -1
- package/dist/runtime/src/llm/index.d.ts.map +1 -1
- package/dist/runtime/src/llm/index.js +1 -1
- package/dist/runtime/src/llm/index.js.map +1 -1
- package/dist/runtime/src/llm/replay.d.ts +75 -0
- package/dist/runtime/src/llm/replay.d.ts.map +1 -1
- package/dist/runtime/src/llm/replay.js +187 -5
- package/dist/runtime/src/llm/replay.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +13 -5
- package/src/index.ts +26 -0
- package/src/plugin-system/create-default-compiler.ts +57 -0
- package/src/plugin-system/default-plugins/array-transformer-plugin.ts +57 -0
- package/src/plugin-system/default-plugins/detection-plugin.ts +41 -0
- package/src/plugin-system/default-plugins/index.ts +12 -0
- package/src/plugin-system/default-plugins/loop-transformer-plugin.ts +47 -0
- package/src/plugin-system/default-plugins/promise-transformer-plugin.ts +63 -0
- package/src/plugin-system/examples/loop-transformer-plugin.ts +76 -0
- package/src/plugin-system/examples/security-validator-plugin.ts +168 -0
- package/src/plugin-system/examples/timeout-plugin.ts +158 -0
- package/src/plugin-system/index.ts +19 -0
- package/src/plugin-system/pluggable-compiler.ts +318 -0
- package/src/plugin-system/plugin-api.ts +330 -0
- package/src/transformer/index.ts +21 -1
- package/src/types/compiler-interface.ts +79 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example Plugin: Async Timeout Wrapper
|
|
3
|
+
*
|
|
4
|
+
* Automatically wraps await expressions with timeout protection
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* // Before:
|
|
8
|
+
* const result = await fetch('https://api.example.com');
|
|
9
|
+
*
|
|
10
|
+
* // After:
|
|
11
|
+
* const result = await Promise.race([
|
|
12
|
+
* fetch('https://api.example.com'),
|
|
13
|
+
* new Promise((_, reject) =>
|
|
14
|
+
* setTimeout(() => reject(new Error('Timeout')), 5000)
|
|
15
|
+
* )
|
|
16
|
+
* ]);
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import * as t from '@babel/types';
|
|
20
|
+
import type { NodePath } from '@babel/traverse';
|
|
21
|
+
import type { TransformationPlugin, BabelVisitor } from '../plugin-api.js';
|
|
22
|
+
import type { CompilerConfig, TransformMetadata } from '../../types.js';
|
|
23
|
+
|
|
24
|
+
export interface TimeoutPluginOptions {
|
|
25
|
+
/**
|
|
26
|
+
* Default timeout in milliseconds
|
|
27
|
+
*/
|
|
28
|
+
timeout?: number;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Patterns to match (namespace.method)
|
|
32
|
+
*/
|
|
33
|
+
patterns?: string[];
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Whether to add timeout to all await expressions
|
|
37
|
+
*/
|
|
38
|
+
wrapAll?: boolean;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class AsyncTimeoutPlugin implements TransformationPlugin {
|
|
42
|
+
name = 'async-timeout';
|
|
43
|
+
version = '1.0.0';
|
|
44
|
+
priority = 40; // Run after main transformations
|
|
45
|
+
|
|
46
|
+
private options: TimeoutPluginOptions;
|
|
47
|
+
private transformCount = 0;
|
|
48
|
+
|
|
49
|
+
constructor(options: TimeoutPluginOptions = {}) {
|
|
50
|
+
this.options = {
|
|
51
|
+
timeout: options.timeout || 5000,
|
|
52
|
+
patterns: options.patterns || ['fetch', 'axios.*', 'http.*'],
|
|
53
|
+
wrapAll: options.wrapAll || false,
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
getVisitor(config: CompilerConfig): BabelVisitor {
|
|
58
|
+
return {
|
|
59
|
+
AwaitExpression: (path: NodePath<t.AwaitExpression>) => {
|
|
60
|
+
// Check if should wrap this await
|
|
61
|
+
if (!this.shouldWrap(path.node.argument)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Create timeout promise
|
|
66
|
+
const timeoutPromise = t.newExpression(
|
|
67
|
+
t.identifier('Promise'),
|
|
68
|
+
[
|
|
69
|
+
t.arrowFunctionExpression(
|
|
70
|
+
[t.identifier('_'), t.identifier('reject')],
|
|
71
|
+
t.blockStatement([
|
|
72
|
+
t.expressionStatement(
|
|
73
|
+
t.callExpression(t.identifier('setTimeout'), [
|
|
74
|
+
t.arrowFunctionExpression(
|
|
75
|
+
[],
|
|
76
|
+
t.callExpression(t.identifier('reject'), [
|
|
77
|
+
t.newExpression(t.identifier('Error'), [
|
|
78
|
+
t.stringLiteral(
|
|
79
|
+
`Timeout after ${this.options.timeout}ms`
|
|
80
|
+
),
|
|
81
|
+
]),
|
|
82
|
+
])
|
|
83
|
+
),
|
|
84
|
+
t.numericLiteral(this.options.timeout!),
|
|
85
|
+
])
|
|
86
|
+
),
|
|
87
|
+
])
|
|
88
|
+
),
|
|
89
|
+
]
|
|
90
|
+
);
|
|
91
|
+
|
|
92
|
+
// Wrap in Promise.race
|
|
93
|
+
const raceCall = t.callExpression(
|
|
94
|
+
t.memberExpression(t.identifier('Promise'), t.identifier('race')),
|
|
95
|
+
[t.arrayExpression([path.node.argument, timeoutPromise])]
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
// Replace await expression
|
|
99
|
+
path.node.argument = raceCall;
|
|
100
|
+
this.transformCount++;
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
getMetadata(): Partial<TransformMetadata> {
|
|
106
|
+
return {
|
|
107
|
+
// Custom metadata
|
|
108
|
+
loopCount: 0,
|
|
109
|
+
arrayMethodCount: 0,
|
|
110
|
+
parallelCallCount: this.transformCount,
|
|
111
|
+
batchableCount: 0,
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
reset(): void {
|
|
116
|
+
this.transformCount = 0;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Check if this await should be wrapped with timeout
|
|
121
|
+
*/
|
|
122
|
+
private shouldWrap(node: t.Expression): boolean {
|
|
123
|
+
if (this.options.wrapAll) {
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Check if matches patterns
|
|
128
|
+
if (t.isCallExpression(node)) {
|
|
129
|
+
const funcName = this.getFunctionName(node);
|
|
130
|
+
if (funcName) {
|
|
131
|
+
return this.options.patterns!.some((pattern) => {
|
|
132
|
+
const regex = new RegExp(pattern.replace('*', '.*'));
|
|
133
|
+
return regex.test(funcName);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Get function name from call expression
|
|
143
|
+
*/
|
|
144
|
+
private getFunctionName(node: t.CallExpression): string | null {
|
|
145
|
+
if (t.isIdentifier(node.callee)) {
|
|
146
|
+
return node.callee.name;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (t.isMemberExpression(node.callee)) {
|
|
150
|
+
const obj = t.isIdentifier(node.callee.object) ? node.callee.object.name : '';
|
|
151
|
+
const prop = t.isIdentifier(node.callee.property) ? node.callee.property.name : '';
|
|
152
|
+
return `${obj}.${prop}`;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* check
|
|
3
|
+
*
|
|
4
|
+
* ATP Compiler Plugin System
|
|
5
|
+
*
|
|
6
|
+
* Extensible plugin architecture for custom transformations
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export * from './plugin-api.js';
|
|
10
|
+
export * from './pluggable-compiler.js';
|
|
11
|
+
export * from './create-default-compiler.js';
|
|
12
|
+
|
|
13
|
+
// Default plugins
|
|
14
|
+
export * from './default-plugins/index.js';
|
|
15
|
+
|
|
16
|
+
// Re-export examples for convenience
|
|
17
|
+
export * from './examples/timeout-plugin.js';
|
|
18
|
+
export * from './examples/security-validator-plugin.js';
|
|
19
|
+
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin-based ATP Compiler
|
|
3
|
+
*
|
|
4
|
+
* Extensible compiler that supports custom plugins for detection,
|
|
5
|
+
* transformation, optimization, and validation
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { parse } from '@babel/parser';
|
|
9
|
+
import _traverse from '@babel/traverse';
|
|
10
|
+
const traverse = (_traverse as any).default || _traverse;
|
|
11
|
+
import _generate from '@babel/generator';
|
|
12
|
+
const generate = (_generate as any).default || _generate;
|
|
13
|
+
import type * as t from '@babel/types';
|
|
14
|
+
import type { TransformResult, CompilerConfig, TransformMetadata, DetectionResult } from '../types.js';
|
|
15
|
+
import { DEFAULT_COMPILER_CONFIG } from '../types.js';
|
|
16
|
+
import { TransformationError } from '../runtime/errors.js';
|
|
17
|
+
import { resetIdCounter } from '../runtime/context.js';
|
|
18
|
+
import { PluginRegistry, type CompilerPlugin, type PluginContext } from './plugin-api.js';
|
|
19
|
+
import type { ICompiler } from '../types/compiler-interface.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Plugin-based ATP Compiler
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const compiler = new PluggableCompiler({
|
|
27
|
+
* enableBatchParallel: true
|
|
28
|
+
* });
|
|
29
|
+
*
|
|
30
|
+
* // Register custom plugin
|
|
31
|
+
* compiler.use(myCustomPlugin);
|
|
32
|
+
*
|
|
33
|
+
* // Transform code
|
|
34
|
+
* const result = compiler.transform(code);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export class PluggableCompiler implements ICompiler {
|
|
38
|
+
private config: CompilerConfig;
|
|
39
|
+
private registry: PluginRegistry;
|
|
40
|
+
private initialized: boolean = false;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* AST cache - maps code string to parsed AST
|
|
44
|
+
* This avoids re-parsing the same code multiple times
|
|
45
|
+
* (e.g., once in detect() and once in transform())
|
|
46
|
+
*
|
|
47
|
+
* Performance Impact: ~30% reduction in compile time
|
|
48
|
+
*/
|
|
49
|
+
private astCache: Map<string, t.File> = new Map();
|
|
50
|
+
|
|
51
|
+
constructor(config: Partial<CompilerConfig> = {}) {
|
|
52
|
+
this.config = { ...DEFAULT_COMPILER_CONFIG, ...config };
|
|
53
|
+
this.registry = new PluginRegistry();
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Register a plugin (chainable)
|
|
58
|
+
*/
|
|
59
|
+
use(plugin: CompilerPlugin): this {
|
|
60
|
+
this.registry.register(plugin);
|
|
61
|
+
return this;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Unregister a plugin by name
|
|
66
|
+
*/
|
|
67
|
+
remove(pluginName: string): boolean {
|
|
68
|
+
return this.registry.unregister(pluginName);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Initialize all plugins
|
|
73
|
+
*/
|
|
74
|
+
async initialize(): Promise<void> {
|
|
75
|
+
if (this.initialized) return;
|
|
76
|
+
await this.registry.initializeAll(this.config);
|
|
77
|
+
this.initialized = true;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Detect patterns using all detection plugins
|
|
82
|
+
*/
|
|
83
|
+
async detect(code: string): Promise<DetectionResult> {
|
|
84
|
+
if (!this.initialized) {
|
|
85
|
+
await this.initialize();
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
try {
|
|
89
|
+
const ast = this.parseCode(code);
|
|
90
|
+
const detectors = this.registry.getDetectors();
|
|
91
|
+
|
|
92
|
+
// Aggregate results from all detectors
|
|
93
|
+
const allPatterns = new Set<string>();
|
|
94
|
+
let batchableParallel = false;
|
|
95
|
+
|
|
96
|
+
for (const detector of detectors) {
|
|
97
|
+
const result = await detector.detect(code, ast);
|
|
98
|
+
result.patterns.forEach((p) => allPatterns.add(p));
|
|
99
|
+
if (result.batchableParallel) {
|
|
100
|
+
batchableParallel = true;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return {
|
|
105
|
+
needsTransform: allPatterns.size > 0,
|
|
106
|
+
patterns: Array.from(allPatterns) as any[],
|
|
107
|
+
batchableParallel,
|
|
108
|
+
};
|
|
109
|
+
} catch (error) {
|
|
110
|
+
return {
|
|
111
|
+
needsTransform: false,
|
|
112
|
+
patterns: [],
|
|
113
|
+
batchableParallel: false,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Transform code using all transformation plugins
|
|
120
|
+
*/
|
|
121
|
+
async transform(code: string): Promise<TransformResult> {
|
|
122
|
+
if (!this.initialized) {
|
|
123
|
+
await this.initialize();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
resetIdCounter();
|
|
127
|
+
|
|
128
|
+
// 1. Pre-validation
|
|
129
|
+
await this.runValidation(code, 'pre');
|
|
130
|
+
|
|
131
|
+
// 2. Detection
|
|
132
|
+
const detection = await this.detect(code);
|
|
133
|
+
|
|
134
|
+
if (!detection.needsTransform) {
|
|
135
|
+
return {
|
|
136
|
+
code,
|
|
137
|
+
transformed: false,
|
|
138
|
+
patterns: [],
|
|
139
|
+
metadata: {
|
|
140
|
+
loopCount: 0,
|
|
141
|
+
arrayMethodCount: 0,
|
|
142
|
+
parallelCallCount: 0,
|
|
143
|
+
batchableCount: 0,
|
|
144
|
+
},
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
try {
|
|
149
|
+
// 3. Parse AST
|
|
150
|
+
const ast = this.parseCode(code);
|
|
151
|
+
|
|
152
|
+
// 4. Reset all transformers
|
|
153
|
+
const transformers = this.registry.getTransformers();
|
|
154
|
+
for (const transformer of transformers) {
|
|
155
|
+
transformer.reset();
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// 5. Apply all transformation visitors
|
|
159
|
+
// Combine visitors from all plugins, merging handlers for the same node type
|
|
160
|
+
const visitors: any = {};
|
|
161
|
+
for (const transformer of transformers) {
|
|
162
|
+
const visitor = transformer.getVisitor(this.config);
|
|
163
|
+
|
|
164
|
+
// Merge visitors, combining multiple handlers for the same node type
|
|
165
|
+
for (const [nodeType, handler] of Object.entries(visitor)) {
|
|
166
|
+
if (!visitors[nodeType]) {
|
|
167
|
+
visitors[nodeType] = handler;
|
|
168
|
+
} else {
|
|
169
|
+
// Combine multiple handlers for the same node type
|
|
170
|
+
const existing = visitors[nodeType];
|
|
171
|
+
visitors[nodeType] = (path: any) => {
|
|
172
|
+
existing(path);
|
|
173
|
+
(handler as any)(path);
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
traverse(ast, visitors);
|
|
180
|
+
|
|
181
|
+
// 6. Optimization
|
|
182
|
+
let optimizedAst = ast;
|
|
183
|
+
const optimizers = this.registry.getOptimizers();
|
|
184
|
+
for (const optimizer of optimizers) {
|
|
185
|
+
optimizedAst = await optimizer.optimize(optimizedAst, this.config);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// 7. Generate code
|
|
189
|
+
const output = generate(optimizedAst, {
|
|
190
|
+
sourceMaps: false,
|
|
191
|
+
retainLines: true,
|
|
192
|
+
comments: true,
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// 8. Aggregate metadata
|
|
196
|
+
const metadata: TransformMetadata = {
|
|
197
|
+
loopCount: 0,
|
|
198
|
+
arrayMethodCount: 0,
|
|
199
|
+
parallelCallCount: 0,
|
|
200
|
+
batchableCount: detection.batchableParallel ? 1 : 0,
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
for (const transformer of transformers) {
|
|
204
|
+
const pluginMetadata = transformer.getMetadata();
|
|
205
|
+
metadata.loopCount += pluginMetadata.loopCount || 0;
|
|
206
|
+
metadata.arrayMethodCount += pluginMetadata.arrayMethodCount || 0;
|
|
207
|
+
metadata.parallelCallCount += pluginMetadata.parallelCallCount || 0;
|
|
208
|
+
metadata.batchableCount += pluginMetadata.batchableCount || 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// 9. Post-validation
|
|
212
|
+
await this.runValidation(output.code, 'post');
|
|
213
|
+
|
|
214
|
+
return {
|
|
215
|
+
code: output.code,
|
|
216
|
+
transformed: true,
|
|
217
|
+
patterns: detection.patterns,
|
|
218
|
+
metadata,
|
|
219
|
+
};
|
|
220
|
+
} catch (error) {
|
|
221
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
222
|
+
// Include context about which phase failed
|
|
223
|
+
const context = detection.patterns.length > 0
|
|
224
|
+
? `[Plugin transformation] ${message}`
|
|
225
|
+
: message;
|
|
226
|
+
throw new TransformationError(context, code, 'plugin-error');
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Dispose compiler and all plugins
|
|
232
|
+
*/
|
|
233
|
+
async dispose(): Promise<void> {
|
|
234
|
+
await this.registry.disposeAll();
|
|
235
|
+
this.astCache.clear(); // Clear cache on disposal
|
|
236
|
+
this.initialized = false;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get current configuration
|
|
241
|
+
*/
|
|
242
|
+
getConfig(): CompilerConfig {
|
|
243
|
+
return { ...this.config };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Update configuration
|
|
248
|
+
*/
|
|
249
|
+
setConfig(config: Partial<CompilerConfig>): void {
|
|
250
|
+
this.config = { ...this.config, ...config };
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Parse code to AST with caching
|
|
255
|
+
*
|
|
256
|
+
* Caches parsed AST to avoid re-parsing the same code multiple times.
|
|
257
|
+
* This provides ~30% performance improvement when detect() and transform()
|
|
258
|
+
* are called on the same code.
|
|
259
|
+
*/
|
|
260
|
+
private parseCode(code: string): t.File {
|
|
261
|
+
// Check cache first
|
|
262
|
+
const cached = this.astCache.get(code);
|
|
263
|
+
if (cached) {
|
|
264
|
+
return cached;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Parse and cache
|
|
268
|
+
const ast = parse(code, {
|
|
269
|
+
sourceType: 'module',
|
|
270
|
+
plugins: ['typescript'],
|
|
271
|
+
allowAwaitOutsideFunction: true,
|
|
272
|
+
allowReturnOutsideFunction: true,
|
|
273
|
+
}) as t.File;
|
|
274
|
+
|
|
275
|
+
this.astCache.set(code, ast);
|
|
276
|
+
return ast;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Clear AST cache
|
|
281
|
+
*
|
|
282
|
+
* Call this method to free memory if you've compiled many different code snippets.
|
|
283
|
+
* The cache is automatically managed and uses Map, so old entries don't leak memory.
|
|
284
|
+
*/
|
|
285
|
+
clearCache(): void {
|
|
286
|
+
this.astCache.clear();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Get the compiler type identifier (ICompiler interface requirement)
|
|
291
|
+
*/
|
|
292
|
+
getType(): string {
|
|
293
|
+
return 'PluggableCompiler';
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Get cache statistics (for debugging/monitoring)
|
|
298
|
+
*/
|
|
299
|
+
getCacheStats(): { size: number; enabled: boolean } {
|
|
300
|
+
return {
|
|
301
|
+
size: this.astCache.size,
|
|
302
|
+
enabled: true,
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
/**
|
|
307
|
+
* Run validators
|
|
308
|
+
*/
|
|
309
|
+
private async runValidation(code: string, phase: 'pre' | 'post'): Promise<void> {
|
|
310
|
+
const validators = this.registry.getValidators();
|
|
311
|
+
const ast = this.parseCode(code);
|
|
312
|
+
|
|
313
|
+
for (const validator of validators) {
|
|
314
|
+
await validator.validate(code, ast, phase);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|