@fairfox/polly 0.1.3 → 0.1.4
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/{cli/polly.ts → dist/cli/polly.js} +100 -206
- package/dist/cli/polly.js.map +10 -0
- package/dist/scripts/build-extension.js +137 -0
- package/dist/scripts/build-extension.js.map +10 -0
- package/dist/vendor/verify/src/cli.js +2089 -0
- package/dist/vendor/verify/src/cli.js.map +16 -0
- package/dist/vendor/visualize/src/cli.js +2204 -0
- package/dist/vendor/visualize/src/cli.js.map +19 -0
- package/package.json +12 -12
- package/vendor/analysis/src/extract/adr.ts +0 -212
- package/vendor/analysis/src/extract/architecture.ts +0 -160
- package/vendor/analysis/src/extract/contexts.ts +0 -298
- package/vendor/analysis/src/extract/flows.ts +0 -309
- package/vendor/analysis/src/extract/handlers.ts +0 -321
- package/vendor/analysis/src/extract/index.ts +0 -9
- package/vendor/analysis/src/extract/integrations.ts +0 -329
- package/vendor/analysis/src/extract/manifest.ts +0 -298
- package/vendor/analysis/src/extract/types.ts +0 -389
- package/vendor/analysis/src/index.ts +0 -7
- package/vendor/analysis/src/types/adr.ts +0 -53
- package/vendor/analysis/src/types/architecture.ts +0 -245
- package/vendor/analysis/src/types/core.ts +0 -210
- package/vendor/analysis/src/types/index.ts +0 -18
- package/vendor/verify/src/adapters/base.ts +0 -164
- package/vendor/verify/src/adapters/detection.ts +0 -281
- package/vendor/verify/src/adapters/event-bus/index.ts +0 -480
- package/vendor/verify/src/adapters/web-extension/index.ts +0 -508
- package/vendor/verify/src/adapters/websocket/index.ts +0 -486
- package/vendor/verify/src/cli.ts +0 -430
- package/vendor/verify/src/codegen/config.ts +0 -354
- package/vendor/verify/src/codegen/tla.ts +0 -719
- package/vendor/verify/src/config/parser.ts +0 -303
- package/vendor/verify/src/config/types.ts +0 -113
- package/vendor/verify/src/core/model.ts +0 -267
- package/vendor/verify/src/core/primitives.ts +0 -106
- package/vendor/verify/src/extract/handlers.ts +0 -2
- package/vendor/verify/src/extract/types.ts +0 -2
- package/vendor/verify/src/index.ts +0 -150
- package/vendor/verify/src/primitives/index.ts +0 -102
- package/vendor/verify/src/runner/docker.ts +0 -283
- package/vendor/verify/src/types.ts +0 -51
- package/vendor/visualize/src/cli.ts +0 -365
- package/vendor/visualize/src/codegen/structurizr.ts +0 -770
- package/vendor/visualize/src/index.ts +0 -13
- package/vendor/visualize/src/runner/export.ts +0 -235
- package/vendor/visualize/src/viewer/server.ts +0 -485
- /package/dist/{background → src/background}/index.js +0 -0
- /package/dist/{background → src/background}/index.js.map +0 -0
- /package/dist/{background → src/background}/message-router.js +0 -0
- /package/dist/{background → src/background}/message-router.js.map +0 -0
- /package/dist/{index.js → src/index.js} +0 -0
- /package/dist/{index.js.map → src/index.js.map} +0 -0
- /package/dist/{shared → src/shared}/adapters/index.js +0 -0
- /package/dist/{shared → src/shared}/adapters/index.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.js +0 -0
- /package/dist/{shared → src/shared}/lib/context-helpers.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/errors.js +0 -0
- /package/dist/{shared → src/shared}/lib/errors.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.js +0 -0
- /package/dist/{shared → src/shared}/lib/message-bus.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/state.js +0 -0
- /package/dist/{shared → src/shared}/lib/state.js.map +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.js +0 -0
- /package/dist/{shared → src/shared}/lib/test-helpers.js.map +0 -0
- /package/dist/{shared → src/shared}/state/app-state.js +0 -0
- /package/dist/{shared → src/shared}/state/app-state.js.map +0 -0
- /package/dist/{shared → src/shared}/types/messages.js +0 -0
- /package/dist/{shared → src/shared}/types/messages.js.map +0 -0
|
@@ -1,309 +0,0 @@
|
|
|
1
|
-
// Message flow analysis - trace messages between contexts
|
|
2
|
-
|
|
3
|
-
import { Project, Node } from "ts-morph";
|
|
4
|
-
import type { MessageFlow, MessageStep } from "../types/architecture";
|
|
5
|
-
import type { MessageHandler } from "../types/core";
|
|
6
|
-
|
|
7
|
-
export class FlowAnalyzer {
|
|
8
|
-
private project: Project;
|
|
9
|
-
private handlers: MessageHandler[];
|
|
10
|
-
|
|
11
|
-
constructor(tsConfigPath: string, handlers: MessageHandler[]) {
|
|
12
|
-
this.project = new Project({
|
|
13
|
-
tsConfigFilePath: tsConfigPath,
|
|
14
|
-
});
|
|
15
|
-
this.handlers = handlers;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Analyze message flows between contexts
|
|
20
|
-
*/
|
|
21
|
-
analyzeFlows(): MessageFlow[] {
|
|
22
|
-
const flows: MessageFlow[] = [];
|
|
23
|
-
|
|
24
|
-
// Group handlers by message type
|
|
25
|
-
const handlersByType = new Map<string, MessageHandler[]>();
|
|
26
|
-
for (const handler of this.handlers) {
|
|
27
|
-
if (!handlersByType.has(handler.messageType)) {
|
|
28
|
-
handlersByType.set(handler.messageType, []);
|
|
29
|
-
}
|
|
30
|
-
handlersByType.get(handler.messageType)!.push(handler);
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// For each message type, trace the flow
|
|
34
|
-
for (const [messageType, handlers] of handlersByType) {
|
|
35
|
-
// Find senders
|
|
36
|
-
const senders = this.findMessageSenders(messageType);
|
|
37
|
-
|
|
38
|
-
// For each sender, create a flow
|
|
39
|
-
for (const sender of senders) {
|
|
40
|
-
const recipients = handlers.map((h) => h.node);
|
|
41
|
-
|
|
42
|
-
// Build sequence of steps
|
|
43
|
-
const sequence = this.buildSequence(messageType, sender, handlers);
|
|
44
|
-
|
|
45
|
-
// Extract flow metadata
|
|
46
|
-
const flowMetadata = this.extractFlowMetadata(sender.file, sender.line);
|
|
47
|
-
|
|
48
|
-
flows.push({
|
|
49
|
-
messageType,
|
|
50
|
-
from: sender.context,
|
|
51
|
-
to: recipients,
|
|
52
|
-
trigger: flowMetadata.trigger,
|
|
53
|
-
flowName: flowMetadata.flowName,
|
|
54
|
-
description: flowMetadata.description,
|
|
55
|
-
sequence,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
return flows;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Find all places where a message is sent
|
|
65
|
-
*/
|
|
66
|
-
private findMessageSenders(messageType: string): Array<{
|
|
67
|
-
context: string;
|
|
68
|
-
file: string;
|
|
69
|
-
line: number;
|
|
70
|
-
}> {
|
|
71
|
-
const senders: Array<{ context: string; file: string; line: number }> = [];
|
|
72
|
-
|
|
73
|
-
for (const sourceFile of this.project.getSourceFiles()) {
|
|
74
|
-
const filePath = sourceFile.getFilePath();
|
|
75
|
-
const context = this.inferContext(filePath);
|
|
76
|
-
|
|
77
|
-
sourceFile.forEachDescendant((node) => {
|
|
78
|
-
if (Node.isCallExpression(node)) {
|
|
79
|
-
const expression = node.getExpression();
|
|
80
|
-
|
|
81
|
-
// Check for .send() or .emit() calls
|
|
82
|
-
if (Node.isPropertyAccessExpression(expression)) {
|
|
83
|
-
const methodName = expression.getName();
|
|
84
|
-
|
|
85
|
-
if (methodName === "send" || methodName === "emit") {
|
|
86
|
-
const args = node.getArguments();
|
|
87
|
-
if (args.length > 0) {
|
|
88
|
-
const firstArg = args[0];
|
|
89
|
-
|
|
90
|
-
let msgType: string | undefined;
|
|
91
|
-
|
|
92
|
-
// Check if first argument is a string literal: send("MESSAGE")
|
|
93
|
-
if (Node.isStringLiteral(firstArg)) {
|
|
94
|
-
msgType = firstArg.getLiteralValue();
|
|
95
|
-
}
|
|
96
|
-
// Check if first argument is an object literal: send({ type: "MESSAGE" })
|
|
97
|
-
else if (Node.isObjectLiteralExpression(firstArg)) {
|
|
98
|
-
const typeProperty = firstArg.getProperty("type");
|
|
99
|
-
if (typeProperty && Node.isPropertyAssignment(typeProperty)) {
|
|
100
|
-
const initializer = typeProperty.getInitializer();
|
|
101
|
-
if (initializer && Node.isStringLiteral(initializer)) {
|
|
102
|
-
msgType = initializer.getLiteralValue();
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
if (msgType === messageType) {
|
|
108
|
-
senders.push({
|
|
109
|
-
context,
|
|
110
|
-
file: filePath,
|
|
111
|
-
line: node.getStartLineNumber(),
|
|
112
|
-
});
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
return senders;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
/**
|
|
125
|
-
* Build sequence of steps for a message flow
|
|
126
|
-
*/
|
|
127
|
-
private buildSequence(
|
|
128
|
-
messageType: string,
|
|
129
|
-
sender: { context: string; file: string; line: number },
|
|
130
|
-
handlers: MessageHandler[]
|
|
131
|
-
): MessageStep[] {
|
|
132
|
-
const steps: MessageStep[] = [];
|
|
133
|
-
let stepNumber = 1;
|
|
134
|
-
|
|
135
|
-
// Step 1: Send message
|
|
136
|
-
steps.push({
|
|
137
|
-
step: stepNumber++,
|
|
138
|
-
action: `${sender.context}.send(${messageType})`,
|
|
139
|
-
context: sender.context,
|
|
140
|
-
location: {
|
|
141
|
-
file: sender.file,
|
|
142
|
-
line: sender.line,
|
|
143
|
-
},
|
|
144
|
-
});
|
|
145
|
-
|
|
146
|
-
// Step 2+: Handle in each recipient context
|
|
147
|
-
for (const handler of handlers) {
|
|
148
|
-
steps.push({
|
|
149
|
-
step: stepNumber++,
|
|
150
|
-
action: `${handler.node}.handle(${messageType})`,
|
|
151
|
-
context: handler.node,
|
|
152
|
-
location: handler.location,
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
// Add substeps for any messages sent by this handler
|
|
156
|
-
const subsends = this.findMessagesInHandler(handler);
|
|
157
|
-
for (const subsend of subsends) {
|
|
158
|
-
steps.push({
|
|
159
|
-
step: stepNumber++,
|
|
160
|
-
action: `${handler.node}.send(${subsend.messageType})`,
|
|
161
|
-
context: handler.node,
|
|
162
|
-
location: subsend.location,
|
|
163
|
-
});
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
return steps;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
/**
|
|
171
|
-
* Find messages sent within a handler
|
|
172
|
-
*/
|
|
173
|
-
private findMessagesInHandler(handler: MessageHandler): Array<{
|
|
174
|
-
messageType: string;
|
|
175
|
-
location: { file: string; line: number };
|
|
176
|
-
}> {
|
|
177
|
-
const sends: Array<{ messageType: string; location: { file: string; line: number } }> = [];
|
|
178
|
-
|
|
179
|
-
const sourceFile = this.project.getSourceFile(handler.location.file);
|
|
180
|
-
if (!sourceFile) return sends;
|
|
181
|
-
|
|
182
|
-
// Find the handler function at the given line
|
|
183
|
-
const targetLine = handler.location.line;
|
|
184
|
-
|
|
185
|
-
sourceFile.forEachDescendant((node) => {
|
|
186
|
-
if (Node.isCallExpression(node)) {
|
|
187
|
-
const line = node.getStartLineNumber();
|
|
188
|
-
|
|
189
|
-
// Rough heuristic: if it's near the handler line, it's probably in the handler
|
|
190
|
-
if (Math.abs(line - targetLine) < 20) {
|
|
191
|
-
const expression = node.getExpression();
|
|
192
|
-
|
|
193
|
-
if (Node.isPropertyAccessExpression(expression)) {
|
|
194
|
-
const methodName = expression.getName();
|
|
195
|
-
|
|
196
|
-
if (methodName === "send" || methodName === "emit") {
|
|
197
|
-
const args = node.getArguments();
|
|
198
|
-
if (args.length > 0) {
|
|
199
|
-
const firstArg = args[0];
|
|
200
|
-
let messageType: string | undefined;
|
|
201
|
-
|
|
202
|
-
// Check if first argument is a string literal: send("MESSAGE")
|
|
203
|
-
if (Node.isStringLiteral(firstArg)) {
|
|
204
|
-
messageType = firstArg.getLiteralValue();
|
|
205
|
-
}
|
|
206
|
-
// Check if first argument is an object literal: send({ type: "MESSAGE" })
|
|
207
|
-
else if (Node.isObjectLiteralExpression(firstArg)) {
|
|
208
|
-
const typeProperty = firstArg.getProperty("type");
|
|
209
|
-
if (typeProperty && Node.isPropertyAssignment(typeProperty)) {
|
|
210
|
-
const initializer = typeProperty.getInitializer();
|
|
211
|
-
if (initializer && Node.isStringLiteral(initializer)) {
|
|
212
|
-
messageType = initializer.getLiteralValue();
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (messageType) {
|
|
218
|
-
sends.push({
|
|
219
|
-
messageType,
|
|
220
|
-
location: {
|
|
221
|
-
file: handler.location.file,
|
|
222
|
-
line,
|
|
223
|
-
},
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
});
|
|
232
|
-
|
|
233
|
-
return sends;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* Extract flow metadata from JSDoc annotations
|
|
238
|
-
*/
|
|
239
|
-
private extractFlowMetadata(
|
|
240
|
-
filePath: string,
|
|
241
|
-
lineNumber: number
|
|
242
|
-
): {
|
|
243
|
-
trigger?: string;
|
|
244
|
-
flowName?: string;
|
|
245
|
-
description?: string;
|
|
246
|
-
} {
|
|
247
|
-
const sourceFile = this.project.getSourceFile(filePath);
|
|
248
|
-
if (!sourceFile) return {};
|
|
249
|
-
|
|
250
|
-
// Find the node at this line
|
|
251
|
-
let targetNode: any = null;
|
|
252
|
-
|
|
253
|
-
sourceFile.forEachDescendant((node) => {
|
|
254
|
-
if (node.getStartLineNumber() === lineNumber) {
|
|
255
|
-
targetNode = node;
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
if (!targetNode) return {};
|
|
260
|
-
|
|
261
|
-
// Look for JSDoc comments
|
|
262
|
-
const jsDocs = targetNode.getJsDocs?.() || [];
|
|
263
|
-
if (jsDocs.length === 0) return {};
|
|
264
|
-
|
|
265
|
-
const comment = jsDocs[0].getText();
|
|
266
|
-
|
|
267
|
-
// Extract @flow annotation
|
|
268
|
-
const flowMatch = comment.match(/@flow\s+([^\s]+)/);
|
|
269
|
-
const flowName = flowMatch ? flowMatch[1] : undefined;
|
|
270
|
-
|
|
271
|
-
// Extract @trigger annotation
|
|
272
|
-
const triggerMatch = comment.match(/@trigger\s+(.+?)(?:\n|$)/);
|
|
273
|
-
const trigger = triggerMatch ? triggerMatch[1].trim() : undefined;
|
|
274
|
-
|
|
275
|
-
// Extract @description
|
|
276
|
-
const descMatch = comment.match(/@description\s+(.+?)(?:\n|$)/s);
|
|
277
|
-
const description = descMatch ? descMatch[1].trim() : undefined;
|
|
278
|
-
|
|
279
|
-
return { trigger, flowName, description };
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
/**
|
|
283
|
-
* Infer context from file path
|
|
284
|
-
*/
|
|
285
|
-
private inferContext(filePath: string): string {
|
|
286
|
-
const path = filePath.toLowerCase();
|
|
287
|
-
|
|
288
|
-
if (path.includes("/background/") || path.includes("\\background\\")) {
|
|
289
|
-
return "background";
|
|
290
|
-
}
|
|
291
|
-
if (path.includes("/content/") || path.includes("\\content\\")) {
|
|
292
|
-
return "content";
|
|
293
|
-
}
|
|
294
|
-
if (path.includes("/popup/") || path.includes("\\popup\\")) {
|
|
295
|
-
return "popup";
|
|
296
|
-
}
|
|
297
|
-
if (path.includes("/devtools/") || path.includes("\\devtools\\")) {
|
|
298
|
-
return "devtools";
|
|
299
|
-
}
|
|
300
|
-
if (path.includes("/options/") || path.includes("\\options\\")) {
|
|
301
|
-
return "options";
|
|
302
|
-
}
|
|
303
|
-
if (path.includes("/offscreen/") || path.includes("\\offscreen\\")) {
|
|
304
|
-
return "offscreen";
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return "unknown";
|
|
308
|
-
}
|
|
309
|
-
}
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
// Handler extraction from TypeScript code
|
|
2
|
-
// Extracts message handlers and their state mutations
|
|
3
|
-
|
|
4
|
-
import { Project, type SourceFile, SyntaxKind, Node } from "ts-morph";
|
|
5
|
-
import type { MessageHandler, StateAssignment, VerificationCondition } from "../types";
|
|
6
|
-
|
|
7
|
-
export interface HandlerAnalysis {
|
|
8
|
-
handlers: MessageHandler[];
|
|
9
|
-
messageTypes: Set<string>;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export class HandlerExtractor {
|
|
13
|
-
private project: Project;
|
|
14
|
-
|
|
15
|
-
constructor(tsConfigPath: string) {
|
|
16
|
-
this.project = new Project({
|
|
17
|
-
tsConfigFilePath: tsConfigPath,
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Extract all message handlers from the codebase
|
|
23
|
-
*/
|
|
24
|
-
extractHandlers(): HandlerAnalysis {
|
|
25
|
-
const handlers: MessageHandler[] = [];
|
|
26
|
-
const messageTypes = new Set<string>();
|
|
27
|
-
|
|
28
|
-
// Find all source files
|
|
29
|
-
const sourceFiles = this.project.getSourceFiles();
|
|
30
|
-
|
|
31
|
-
for (const sourceFile of sourceFiles) {
|
|
32
|
-
const fileHandlers = this.extractFromFile(sourceFile);
|
|
33
|
-
handlers.push(...fileHandlers);
|
|
34
|
-
|
|
35
|
-
for (const handler of fileHandlers) {
|
|
36
|
-
messageTypes.add(handler.messageType);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return {
|
|
41
|
-
handlers,
|
|
42
|
-
messageTypes,
|
|
43
|
-
};
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Extract handlers from a single source file
|
|
48
|
-
*/
|
|
49
|
-
private extractFromFile(sourceFile: SourceFile): MessageHandler[] {
|
|
50
|
-
const handlers: MessageHandler[] = [];
|
|
51
|
-
const filePath = sourceFile.getFilePath();
|
|
52
|
-
|
|
53
|
-
// Determine context from file path
|
|
54
|
-
const context = this.inferContext(filePath);
|
|
55
|
-
|
|
56
|
-
// Find all .on() call expressions
|
|
57
|
-
sourceFile.forEachDescendant((node) => {
|
|
58
|
-
if (Node.isCallExpression(node)) {
|
|
59
|
-
const expression = node.getExpression();
|
|
60
|
-
|
|
61
|
-
// Check if this is a .on() call
|
|
62
|
-
if (Node.isPropertyAccessExpression(expression)) {
|
|
63
|
-
const methodName = expression.getName();
|
|
64
|
-
|
|
65
|
-
if (methodName === "on") {
|
|
66
|
-
const handler = this.extractHandler(node, context, filePath);
|
|
67
|
-
if (handler) {
|
|
68
|
-
handlers.push(handler);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
return handlers;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* Extract handler details from a .on() call expression
|
|
80
|
-
*/
|
|
81
|
-
private extractHandler(callExpr: any, context: string, filePath: string): MessageHandler | null {
|
|
82
|
-
const args = callExpr.getArguments();
|
|
83
|
-
|
|
84
|
-
if (args.length < 2) {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// First argument should be the message type (string literal)
|
|
89
|
-
const messageTypeArg = args[0];
|
|
90
|
-
let messageType: string | null = null;
|
|
91
|
-
|
|
92
|
-
if (Node.isStringLiteral(messageTypeArg)) {
|
|
93
|
-
messageType = messageTypeArg.getLiteralValue();
|
|
94
|
-
} else if (Node.isTemplateExpression(messageTypeArg)) {
|
|
95
|
-
// Handle template literals if needed
|
|
96
|
-
messageType = messageTypeArg.getText().replace(/[`'"]/g, "");
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
if (!messageType) {
|
|
100
|
-
return null;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// Second argument is the handler function
|
|
104
|
-
const handlerArg = args[1];
|
|
105
|
-
const assignments: StateAssignment[] = [];
|
|
106
|
-
const preconditions: VerificationCondition[] = [];
|
|
107
|
-
const postconditions: VerificationCondition[] = [];
|
|
108
|
-
|
|
109
|
-
// Parse the handler function for state assignments and verification conditions
|
|
110
|
-
if (Node.isArrowFunction(handlerArg) || Node.isFunctionExpression(handlerArg)) {
|
|
111
|
-
this.extractAssignments(handlerArg, assignments);
|
|
112
|
-
this.extractVerificationConditions(handlerArg, preconditions, postconditions);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
const line = callExpr.getStartLineNumber();
|
|
116
|
-
|
|
117
|
-
return {
|
|
118
|
-
messageType,
|
|
119
|
-
node: context, // Renamed from 'context' to 'node' for generalization
|
|
120
|
-
assignments,
|
|
121
|
-
preconditions,
|
|
122
|
-
postconditions,
|
|
123
|
-
location: {
|
|
124
|
-
file: filePath,
|
|
125
|
-
line,
|
|
126
|
-
},
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Extract state assignments from a handler function
|
|
132
|
-
*/
|
|
133
|
-
private extractAssignments(funcNode: any, assignments: StateAssignment[]): void {
|
|
134
|
-
funcNode.forEachDescendant((node: any) => {
|
|
135
|
-
// Look for assignment expressions: state.field = value
|
|
136
|
-
if (Node.isBinaryExpression(node)) {
|
|
137
|
-
const operator = node.getOperatorToken().getText();
|
|
138
|
-
|
|
139
|
-
if (operator === "=") {
|
|
140
|
-
const left = node.getLeft();
|
|
141
|
-
const right = node.getRight();
|
|
142
|
-
|
|
143
|
-
// Check if left side is a state property access
|
|
144
|
-
if (Node.isPropertyAccessExpression(left)) {
|
|
145
|
-
const fieldPath = this.getPropertyPath(left);
|
|
146
|
-
|
|
147
|
-
// Check if this is a state access
|
|
148
|
-
if (fieldPath.startsWith("state.")) {
|
|
149
|
-
const field = fieldPath.substring(6); // Remove "state." prefix
|
|
150
|
-
const value = this.extractValue(right);
|
|
151
|
-
|
|
152
|
-
if (value !== undefined) {
|
|
153
|
-
assignments.push({
|
|
154
|
-
field,
|
|
155
|
-
value,
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
/**
|
|
166
|
-
* Extract verification conditions (requires/ensures) from a handler function
|
|
167
|
-
*/
|
|
168
|
-
private extractVerificationConditions(
|
|
169
|
-
funcNode: any,
|
|
170
|
-
preconditions: VerificationCondition[],
|
|
171
|
-
postconditions: VerificationCondition[]
|
|
172
|
-
): void {
|
|
173
|
-
const body = funcNode.getBody();
|
|
174
|
-
|
|
175
|
-
// Get all statements in the function body
|
|
176
|
-
const statements = Node.isBlock(body) ? body.getStatements() : [body];
|
|
177
|
-
|
|
178
|
-
statements.forEach((statement: any, index: number) => {
|
|
179
|
-
// Look for expression statements that are function calls
|
|
180
|
-
if (Node.isExpressionStatement(statement)) {
|
|
181
|
-
const expr = statement.getExpression();
|
|
182
|
-
|
|
183
|
-
if (Node.isCallExpression(expr)) {
|
|
184
|
-
const callee = expr.getExpression();
|
|
185
|
-
|
|
186
|
-
if (Node.isIdentifier(callee)) {
|
|
187
|
-
const functionName = callee.getText();
|
|
188
|
-
|
|
189
|
-
if (functionName === "requires") {
|
|
190
|
-
// Extract precondition
|
|
191
|
-
const condition = this.extractCondition(expr);
|
|
192
|
-
if (condition) {
|
|
193
|
-
preconditions.push(condition);
|
|
194
|
-
}
|
|
195
|
-
} else if (functionName === "ensures") {
|
|
196
|
-
// Extract postcondition
|
|
197
|
-
const condition = this.extractCondition(expr);
|
|
198
|
-
if (condition) {
|
|
199
|
-
postconditions.push(condition);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
});
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/**
|
|
209
|
-
* Extract condition from a requires() or ensures() call
|
|
210
|
-
*/
|
|
211
|
-
private extractCondition(callExpr: any): VerificationCondition | null {
|
|
212
|
-
const args = callExpr.getArguments();
|
|
213
|
-
|
|
214
|
-
if (args.length === 0) {
|
|
215
|
-
return null;
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// First argument is the condition expression
|
|
219
|
-
const conditionArg = args[0];
|
|
220
|
-
const expression = conditionArg.getText();
|
|
221
|
-
|
|
222
|
-
// Second argument (optional) is the message
|
|
223
|
-
let message: string | undefined;
|
|
224
|
-
if (args.length >= 2 && Node.isStringLiteral(args[1])) {
|
|
225
|
-
message = args[1].getLiteralValue();
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
const line = callExpr.getStartLineNumber();
|
|
229
|
-
const column = callExpr.getStartLinePos();
|
|
230
|
-
|
|
231
|
-
return {
|
|
232
|
-
expression,
|
|
233
|
-
message,
|
|
234
|
-
location: {
|
|
235
|
-
line,
|
|
236
|
-
column,
|
|
237
|
-
},
|
|
238
|
-
};
|
|
239
|
-
}
|
|
240
|
-
|
|
241
|
-
/**
|
|
242
|
-
* Get the full property access path (e.g., "state.user.loggedIn")
|
|
243
|
-
*/
|
|
244
|
-
private getPropertyPath(node: any): string {
|
|
245
|
-
const parts: string[] = [];
|
|
246
|
-
|
|
247
|
-
let current = node;
|
|
248
|
-
while (Node.isPropertyAccessExpression(current)) {
|
|
249
|
-
parts.unshift(current.getName());
|
|
250
|
-
current = current.getExpression();
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Add the base identifier
|
|
254
|
-
if (Node.isIdentifier(current)) {
|
|
255
|
-
parts.unshift(current.getText());
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
return parts.join(".");
|
|
259
|
-
}
|
|
260
|
-
|
|
261
|
-
/**
|
|
262
|
-
* Extract a literal value from an expression
|
|
263
|
-
*/
|
|
264
|
-
private extractValue(node: any): string | boolean | number | null | undefined {
|
|
265
|
-
if (Node.isStringLiteral(node)) {
|
|
266
|
-
return node.getLiteralValue();
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (Node.isNumericLiteral(node)) {
|
|
270
|
-
return node.getLiteralValue();
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
if (node.getKind() === SyntaxKind.TrueKeyword) {
|
|
274
|
-
return true;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
if (node.getKind() === SyntaxKind.FalseKeyword) {
|
|
278
|
-
return false;
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
if (node.getKind() === SyntaxKind.NullKeyword) {
|
|
282
|
-
return null;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
// For complex expressions, return undefined (can't extract)
|
|
286
|
-
return undefined;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Infer the context (background, content, popup, etc.) from file path
|
|
291
|
-
*/
|
|
292
|
-
private inferContext(filePath: string): string {
|
|
293
|
-
const path = filePath.toLowerCase();
|
|
294
|
-
|
|
295
|
-
if (path.includes("/background/") || path.includes("\\background\\")) {
|
|
296
|
-
return "background";
|
|
297
|
-
}
|
|
298
|
-
if (path.includes("/content/") || path.includes("\\content\\")) {
|
|
299
|
-
return "content";
|
|
300
|
-
}
|
|
301
|
-
if (path.includes("/popup/") || path.includes("\\popup\\")) {
|
|
302
|
-
return "popup";
|
|
303
|
-
}
|
|
304
|
-
if (path.includes("/devtools/") || path.includes("\\devtools\\")) {
|
|
305
|
-
return "devtools";
|
|
306
|
-
}
|
|
307
|
-
if (path.includes("/options/") || path.includes("\\options\\")) {
|
|
308
|
-
return "options";
|
|
309
|
-
}
|
|
310
|
-
if (path.includes("/offscreen/") || path.includes("\\offscreen\\")) {
|
|
311
|
-
return "offscreen";
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return "unknown";
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
export function extractHandlers(tsConfigPath: string): HandlerAnalysis {
|
|
319
|
-
const extractor = new HandlerExtractor(tsConfigPath);
|
|
320
|
-
return extractor.extractHandlers();
|
|
321
|
-
}
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
// Export all extraction utilities
|
|
2
|
-
export * from "./types";
|
|
3
|
-
export * from "./handlers";
|
|
4
|
-
export * from "./manifest";
|
|
5
|
-
export * from "./contexts";
|
|
6
|
-
export * from "./flows";
|
|
7
|
-
export * from "./integrations";
|
|
8
|
-
export * from "./adr";
|
|
9
|
-
export * from "./architecture";
|