midnight-mcp 0.1.41 → 0.2.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.
- package/README.md +32 -1
- package/dist/bin.d.ts +1 -0
- package/dist/bin.js +60 -0
- package/dist/chunk-HOWO4K5A.js +2197 -0
- package/dist/chunk-S7G4OHA4.js +8306 -0
- package/dist/db-YDGUWI5K.js +7 -0
- package/dist/index.d.ts +205 -3
- package/dist/index.js +28 -16
- package/package.json +16 -6
- package/dist/config/compact-version.d.ts +0 -183
- package/dist/config/compact-version.js +0 -423
- package/dist/db/index.d.ts +0 -3
- package/dist/db/index.js +0 -2
- package/dist/db/vectorStore.d.ts +0 -69
- package/dist/db/vectorStore.js +0 -196
- package/dist/pipeline/embeddings.d.ts +0 -25
- package/dist/pipeline/embeddings.js +0 -103
- package/dist/pipeline/github.d.ts +0 -84
- package/dist/pipeline/github.js +0 -399
- package/dist/pipeline/index.d.ts +0 -11
- package/dist/pipeline/index.js +0 -6
- package/dist/pipeline/indexer.d.ts +0 -41
- package/dist/pipeline/indexer.js +0 -254
- package/dist/pipeline/parser.d.ts +0 -46
- package/dist/pipeline/parser.js +0 -436
- package/dist/pipeline/releases.d.ts +0 -112
- package/dist/pipeline/releases.js +0 -298
- package/dist/pipeline/repository.d.ts +0 -372
- package/dist/pipeline/repository.js +0 -520
- package/dist/prompts/index.d.ts +0 -3
- package/dist/prompts/index.js +0 -2
- package/dist/prompts/templates.d.ts +0 -26
- package/dist/prompts/templates.js +0 -443
- package/dist/resources/code.d.ts +0 -15
- package/dist/resources/code.js +0 -122
- package/dist/resources/content/code-content.d.ts +0 -6
- package/dist/resources/content/code-content.js +0 -802
- package/dist/resources/content/docs-content.d.ts +0 -14
- package/dist/resources/content/docs-content.js +0 -1202
- package/dist/resources/content/index.d.ts +0 -6
- package/dist/resources/content/index.js +0 -6
- package/dist/resources/docs.d.ts +0 -15
- package/dist/resources/docs.js +0 -98
- package/dist/resources/index.d.ts +0 -6
- package/dist/resources/index.js +0 -13
- package/dist/resources/schemas.d.ts +0 -16
- package/dist/resources/schemas.js +0 -407
- package/dist/scripts/index-repos.d.ts +0 -12
- package/dist/scripts/index-repos.js +0 -53
- package/dist/server.d.ts +0 -43
- package/dist/server.js +0 -696
- package/dist/services/index.d.ts +0 -6
- package/dist/services/index.js +0 -6
- package/dist/services/sampling.d.ts +0 -62
- package/dist/services/sampling.js +0 -277
- package/dist/tools/analyze.d.ts +0 -106
- package/dist/tools/analyze.js +0 -431
- package/dist/tools/generation.d.ts +0 -9
- package/dist/tools/generation.js +0 -285
- package/dist/tools/health.d.ts +0 -120
- package/dist/tools/health.js +0 -365
- package/dist/tools/index.d.ts +0 -14
- package/dist/tools/index.js +0 -22
- package/dist/tools/meta.d.ts +0 -61
- package/dist/tools/meta.js +0 -282
- package/dist/tools/repository/constants.d.ts +0 -19
- package/dist/tools/repository/constants.js +0 -324
- package/dist/tools/repository/handlers.d.ts +0 -373
- package/dist/tools/repository/handlers.js +0 -724
- package/dist/tools/repository/index.d.ts +0 -9
- package/dist/tools/repository/index.js +0 -13
- package/dist/tools/repository/schemas.d.ts +0 -153
- package/dist/tools/repository/schemas.js +0 -106
- package/dist/tools/repository/tools.d.ts +0 -7
- package/dist/tools/repository/tools.js +0 -484
- package/dist/tools/repository/validation.d.ts +0 -106
- package/dist/tools/repository/validation.js +0 -820
- package/dist/tools/repository.d.ts +0 -6
- package/dist/tools/repository.js +0 -7
- package/dist/tools/search.d.ts +0 -76
- package/dist/tools/search.js +0 -423
- package/dist/types/index.d.ts +0 -2
- package/dist/types/index.js +0 -2
- package/dist/types/mcp.d.ts +0 -187
- package/dist/types/mcp.js +0 -6
- package/dist/utils/cache.d.ts +0 -77
- package/dist/utils/cache.js +0 -172
- package/dist/utils/config.d.ts +0 -70
- package/dist/utils/config.js +0 -294
- package/dist/utils/errors.d.ts +0 -111
- package/dist/utils/errors.js +0 -165
- package/dist/utils/health.d.ts +0 -29
- package/dist/utils/health.js +0 -132
- package/dist/utils/hosted-api.d.ts +0 -67
- package/dist/utils/hosted-api.js +0 -119
- package/dist/utils/index.d.ts +0 -16
- package/dist/utils/index.js +0 -15
- package/dist/utils/logger.d.ts +0 -48
- package/dist/utils/logger.js +0 -124
- package/dist/utils/rate-limit.d.ts +0 -61
- package/dist/utils/rate-limit.js +0 -148
- package/dist/utils/validation.d.ts +0 -52
- package/dist/utils/validation.js +0 -255
|
@@ -1,820 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Contract validation handlers
|
|
3
|
-
* Static analysis for Compact contracts
|
|
4
|
-
*/
|
|
5
|
-
import { readFile } from "fs/promises";
|
|
6
|
-
import { basename, isAbsolute, resolve } from "path";
|
|
7
|
-
import { platform } from "process";
|
|
8
|
-
import { logger } from "../../utils/index.js";
|
|
9
|
-
// ============================================================================
|
|
10
|
-
// SECURITY & VALIDATION HELPERS
|
|
11
|
-
// ============================================================================
|
|
12
|
-
/**
|
|
13
|
-
* Validate file path for security - prevent path traversal attacks
|
|
14
|
-
*/
|
|
15
|
-
function validateFilePath(filePath) {
|
|
16
|
-
// Must be absolute path
|
|
17
|
-
if (!isAbsolute(filePath)) {
|
|
18
|
-
return {
|
|
19
|
-
valid: false,
|
|
20
|
-
error: "File path must be absolute (e.g., /Users/you/contract.compact)",
|
|
21
|
-
};
|
|
22
|
-
}
|
|
23
|
-
// Resolve to catch ../ traversal
|
|
24
|
-
const normalized = resolve(filePath);
|
|
25
|
-
// Check for path traversal attempts
|
|
26
|
-
// Simply check for ".." in the path - this is always suspicious in absolute paths
|
|
27
|
-
if (filePath.includes("..")) {
|
|
28
|
-
return {
|
|
29
|
-
valid: false,
|
|
30
|
-
error: "Path traversal detected - use absolute paths without ../",
|
|
31
|
-
};
|
|
32
|
-
}
|
|
33
|
-
// Must end with .compact
|
|
34
|
-
if (!normalized.endsWith(".compact")) {
|
|
35
|
-
return {
|
|
36
|
-
valid: false,
|
|
37
|
-
error: "File must have .compact extension",
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
// Block sensitive paths (Unix and Windows)
|
|
41
|
-
const blockedPathsUnix = ["/etc", "/var", "/usr", "/bin", "/sbin", "/root"];
|
|
42
|
-
const blockedPathsWindows = [
|
|
43
|
-
"C:\\Windows",
|
|
44
|
-
"C:\\Program Files",
|
|
45
|
-
"C:\\Program Files (x86)",
|
|
46
|
-
"C:\\System32",
|
|
47
|
-
"C:\\ProgramData",
|
|
48
|
-
];
|
|
49
|
-
const blockedPaths = platform === "win32" ? blockedPathsWindows : blockedPathsUnix;
|
|
50
|
-
const normalizedLower = normalized.toLowerCase();
|
|
51
|
-
if (blockedPaths.some((blocked) => normalizedLower.startsWith(blocked.toLowerCase()))) {
|
|
52
|
-
return {
|
|
53
|
-
valid: false,
|
|
54
|
-
error: "Cannot access system directories",
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
return { valid: true, normalizedPath: normalized };
|
|
58
|
-
}
|
|
59
|
-
/**
|
|
60
|
-
* Check if content is valid UTF-8 text (not binary)
|
|
61
|
-
*/
|
|
62
|
-
function isValidUtf8Text(content) {
|
|
63
|
-
// Check for null bytes (common in binary files)
|
|
64
|
-
if (content.includes("\x00")) {
|
|
65
|
-
return false;
|
|
66
|
-
}
|
|
67
|
-
// Check for excessive non-printable characters
|
|
68
|
-
const nonPrintable = content.match(/[\x00-\x08\x0B\x0C\x0E-\x1F]/g);
|
|
69
|
-
if (nonPrintable && nonPrintable.length > content.length * 0.01) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
return true;
|
|
73
|
-
}
|
|
74
|
-
// ============================================================================
|
|
75
|
-
// CONTRACT STRUCTURE EXTRACTION
|
|
76
|
-
// ============================================================================
|
|
77
|
-
/**
|
|
78
|
-
* Extract the structure of a Compact contract (circuits, witnesses, ledger, etc.)
|
|
79
|
-
* This helps agents understand what a contract does without parsing it themselves
|
|
80
|
-
*/
|
|
81
|
-
export async function extractContractStructure(input) {
|
|
82
|
-
logger.debug("Extracting contract structure", {
|
|
83
|
-
hasCode: !!input.code,
|
|
84
|
-
filePath: input.filePath,
|
|
85
|
-
});
|
|
86
|
-
// Resolve code source
|
|
87
|
-
let code;
|
|
88
|
-
let filename;
|
|
89
|
-
if (input.filePath) {
|
|
90
|
-
// SECURITY: Validate file path
|
|
91
|
-
const pathValidation = validateFilePath(input.filePath);
|
|
92
|
-
if (!pathValidation.valid) {
|
|
93
|
-
return {
|
|
94
|
-
success: false,
|
|
95
|
-
error: "Invalid file path",
|
|
96
|
-
message: pathValidation.error,
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
try {
|
|
100
|
-
code = await readFile(pathValidation.normalizedPath, "utf-8");
|
|
101
|
-
filename = basename(pathValidation.normalizedPath);
|
|
102
|
-
// Check for binary content
|
|
103
|
-
if (!isValidUtf8Text(code)) {
|
|
104
|
-
return {
|
|
105
|
-
success: false,
|
|
106
|
-
error: "Invalid file content",
|
|
107
|
-
message: "File appears to be binary or contains invalid characters",
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
catch (fsError) {
|
|
112
|
-
const err = fsError;
|
|
113
|
-
return {
|
|
114
|
-
success: false,
|
|
115
|
-
error: "Failed to read file",
|
|
116
|
-
message: `Cannot read file: ${input.filePath}`,
|
|
117
|
-
details: err.code === "ENOENT" ? "File does not exist" : err.message,
|
|
118
|
-
};
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
else if (input.code) {
|
|
122
|
-
code = input.code;
|
|
123
|
-
filename = "contract.compact";
|
|
124
|
-
// Check for binary content
|
|
125
|
-
if (!isValidUtf8Text(code)) {
|
|
126
|
-
return {
|
|
127
|
-
success: false,
|
|
128
|
-
error: "Invalid code content",
|
|
129
|
-
message: "Code contains invalid characters",
|
|
130
|
-
};
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
return {
|
|
135
|
-
success: false,
|
|
136
|
-
error: "No contract provided",
|
|
137
|
-
message: "Must provide either 'code' or 'filePath'",
|
|
138
|
-
};
|
|
139
|
-
}
|
|
140
|
-
// Extract pragma version (supports >=, >, <=, <, ==, ~; >=? and <=? are ordered
|
|
141
|
-
// so that >= and <= are matched before > and <)
|
|
142
|
-
const pragmaMatch = code.match(/pragma\s+language_version\s*(?:>=?|<=?|==|~)\s*([\d.]+)/);
|
|
143
|
-
const languageVersion = pragmaMatch ? pragmaMatch[1] : null;
|
|
144
|
-
// Extract imports
|
|
145
|
-
const imports = [];
|
|
146
|
-
const importMatches = code.matchAll(/import\s+(\w+)|include\s+"([^"]+)"/g);
|
|
147
|
-
for (const match of importMatches) {
|
|
148
|
-
imports.push(match[1] || match[2]);
|
|
149
|
-
}
|
|
150
|
-
// Extract exported circuits
|
|
151
|
-
const circuits = [];
|
|
152
|
-
// Helper to split parameters handling nested angle brackets, square brackets, parentheses,
|
|
153
|
-
// and string literals (e.g., Map<A, B>, [Field, Boolean], (x: Field) => Boolean, Opaque<"a, b">)
|
|
154
|
-
const splitParams = (paramsStr) => {
|
|
155
|
-
const result = [];
|
|
156
|
-
let current = "";
|
|
157
|
-
let angleDepth = 0;
|
|
158
|
-
let squareDepth = 0;
|
|
159
|
-
let parenDepth = 0;
|
|
160
|
-
let inString = false;
|
|
161
|
-
let stringChar = "";
|
|
162
|
-
for (let i = 0; i < paramsStr.length; i++) {
|
|
163
|
-
const ch = paramsStr[i];
|
|
164
|
-
// Handle string literals
|
|
165
|
-
if ((ch === '"' || ch === "'") &&
|
|
166
|
-
(i === 0 || paramsStr[i - 1] !== "\\")) {
|
|
167
|
-
if (!inString) {
|
|
168
|
-
inString = true;
|
|
169
|
-
stringChar = ch;
|
|
170
|
-
}
|
|
171
|
-
else if (ch === stringChar) {
|
|
172
|
-
inString = false;
|
|
173
|
-
stringChar = "";
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
// Only track depth when not inside a string
|
|
177
|
-
if (!inString) {
|
|
178
|
-
if (ch === "<")
|
|
179
|
-
angleDepth++;
|
|
180
|
-
else if (ch === ">")
|
|
181
|
-
angleDepth = Math.max(0, angleDepth - 1);
|
|
182
|
-
else if (ch === "[")
|
|
183
|
-
squareDepth++;
|
|
184
|
-
else if (ch === "]")
|
|
185
|
-
squareDepth = Math.max(0, squareDepth - 1);
|
|
186
|
-
else if (ch === "(")
|
|
187
|
-
parenDepth++;
|
|
188
|
-
else if (ch === ")")
|
|
189
|
-
parenDepth = Math.max(0, parenDepth - 1);
|
|
190
|
-
}
|
|
191
|
-
if (ch === "," &&
|
|
192
|
-
!inString &&
|
|
193
|
-
angleDepth === 0 &&
|
|
194
|
-
squareDepth === 0 &&
|
|
195
|
-
parenDepth === 0) {
|
|
196
|
-
if (current.trim())
|
|
197
|
-
result.push(current.trim());
|
|
198
|
-
current = "";
|
|
199
|
-
}
|
|
200
|
-
else {
|
|
201
|
-
current += ch;
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
if (current.trim())
|
|
205
|
-
result.push(current.trim());
|
|
206
|
-
return result;
|
|
207
|
-
};
|
|
208
|
-
// Use a more permissive pattern for return types to handle complex nested types
|
|
209
|
-
// Note: [^)]* doesn't work for nested parens, so we use a manual extraction approach
|
|
210
|
-
const circuitStartPattern = /(?:(export)\s+)?circuit\s+(\w+)\s*\(/g;
|
|
211
|
-
const lines = code.split("\n");
|
|
212
|
-
// Precompute a mapping from character index to 1-based line number to avoid
|
|
213
|
-
// repeatedly scanning from the start of the string for each match.
|
|
214
|
-
const lineByIndex = new Array(code.length);
|
|
215
|
-
{
|
|
216
|
-
let currentLine = 1;
|
|
217
|
-
for (let i = 0; i < code.length; i++) {
|
|
218
|
-
lineByIndex[i] = currentLine;
|
|
219
|
-
if (code[i] === "\n") {
|
|
220
|
-
currentLine++;
|
|
221
|
-
}
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
let circuitMatch;
|
|
225
|
-
while ((circuitMatch = circuitStartPattern.exec(code)) !== null) {
|
|
226
|
-
const lineNum = lineByIndex[circuitMatch.index];
|
|
227
|
-
const isExport = circuitMatch[1] === "export";
|
|
228
|
-
const name = circuitMatch[2];
|
|
229
|
-
// Manually extract params by finding matching closing parenthesis
|
|
230
|
-
const startIdx = circuitMatch.index + circuitMatch[0].length;
|
|
231
|
-
let depth = 1;
|
|
232
|
-
let endIdx = startIdx;
|
|
233
|
-
while (endIdx < code.length && depth > 0) {
|
|
234
|
-
if (code[endIdx] === "(")
|
|
235
|
-
depth++;
|
|
236
|
-
else if (code[endIdx] === ")")
|
|
237
|
-
depth--;
|
|
238
|
-
endIdx++;
|
|
239
|
-
}
|
|
240
|
-
const paramsStr = code.substring(startIdx, endIdx - 1);
|
|
241
|
-
const params = splitParams(paramsStr);
|
|
242
|
-
// Extract return type after ): until { or newline or ;
|
|
243
|
-
const afterParams = code.substring(endIdx);
|
|
244
|
-
const returnTypeMatch = afterParams.match(/^\s*:\s*([^{\n;]+)/);
|
|
245
|
-
const returnType = returnTypeMatch ? returnTypeMatch[1].trim() : "[]";
|
|
246
|
-
circuits.push({
|
|
247
|
-
name,
|
|
248
|
-
params,
|
|
249
|
-
returnType,
|
|
250
|
-
isExport,
|
|
251
|
-
line: lineNum,
|
|
252
|
-
});
|
|
253
|
-
}
|
|
254
|
-
// Extract witnesses
|
|
255
|
-
const witnesses = [];
|
|
256
|
-
const witnessPattern = /(?:(export)\s+)?witness\s+(\w+)\s*:\s*([^;]+)/g;
|
|
257
|
-
let witnessMatch;
|
|
258
|
-
while ((witnessMatch = witnessPattern.exec(code)) !== null) {
|
|
259
|
-
const lineNum = lineByIndex[witnessMatch.index];
|
|
260
|
-
witnesses.push({
|
|
261
|
-
name: witnessMatch[2],
|
|
262
|
-
type: witnessMatch[3].trim(),
|
|
263
|
-
isExport: witnessMatch[1] === "export",
|
|
264
|
-
line: lineNum,
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
// Extract ledger items
|
|
268
|
-
const ledgerItems = [];
|
|
269
|
-
const ledgerPattern = /(?:(export)\s+)?ledger\s+(\w+)\s*:\s*([^;]+)/g;
|
|
270
|
-
let ledgerMatch;
|
|
271
|
-
while ((ledgerMatch = ledgerPattern.exec(code)) !== null) {
|
|
272
|
-
const lineNum = lineByIndex[ledgerMatch.index];
|
|
273
|
-
ledgerItems.push({
|
|
274
|
-
name: ledgerMatch[2],
|
|
275
|
-
type: ledgerMatch[3].trim(),
|
|
276
|
-
isExport: ledgerMatch[1] === "export",
|
|
277
|
-
line: lineNum,
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
// Extract type definitions
|
|
281
|
-
const types = [];
|
|
282
|
-
const typePattern = /type\s+(\w+)\s*=\s*([^;]+)/g;
|
|
283
|
-
let typeMatch;
|
|
284
|
-
while ((typeMatch = typePattern.exec(code)) !== null) {
|
|
285
|
-
const lineNum = lineByIndex[typeMatch.index];
|
|
286
|
-
types.push({
|
|
287
|
-
name: typeMatch[1],
|
|
288
|
-
definition: typeMatch[2].trim(),
|
|
289
|
-
line: lineNum,
|
|
290
|
-
});
|
|
291
|
-
}
|
|
292
|
-
// Extract struct definitions
|
|
293
|
-
const structs = [];
|
|
294
|
-
/**
|
|
295
|
-
* Extract the contents of a balanced brace block starting at `startIndex`,
|
|
296
|
-
* handling nested braces and skipping over comments and string literals.
|
|
297
|
-
*/
|
|
298
|
-
function extractBalancedBlock(source, startIndex) {
|
|
299
|
-
let depth = 0;
|
|
300
|
-
const length = source.length;
|
|
301
|
-
let i = startIndex;
|
|
302
|
-
if (source[i] !== "{") {
|
|
303
|
-
return null;
|
|
304
|
-
}
|
|
305
|
-
depth = 1;
|
|
306
|
-
i++;
|
|
307
|
-
const bodyStart = i;
|
|
308
|
-
while (i < length && depth > 0) {
|
|
309
|
-
const ch = source[i];
|
|
310
|
-
const next = i + 1 < length ? source[i + 1] : "";
|
|
311
|
-
// Handle string literals and template literals
|
|
312
|
-
if (ch === '"' || ch === "'" || ch === "`") {
|
|
313
|
-
const quote = ch;
|
|
314
|
-
i++;
|
|
315
|
-
while (i < length) {
|
|
316
|
-
const c = source[i];
|
|
317
|
-
if (c === "\\" && i + 1 < length) {
|
|
318
|
-
// Skip escaped character
|
|
319
|
-
i += 2;
|
|
320
|
-
continue;
|
|
321
|
-
}
|
|
322
|
-
if (c === quote) {
|
|
323
|
-
i++;
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
i++;
|
|
327
|
-
}
|
|
328
|
-
continue;
|
|
329
|
-
}
|
|
330
|
-
// Handle line comments
|
|
331
|
-
if (ch === "/" && next === "/") {
|
|
332
|
-
i += 2;
|
|
333
|
-
while (i < length && source[i] !== "\n") {
|
|
334
|
-
i++;
|
|
335
|
-
}
|
|
336
|
-
continue;
|
|
337
|
-
}
|
|
338
|
-
// Handle block comments
|
|
339
|
-
if (ch === "/" && next === "*") {
|
|
340
|
-
i += 2;
|
|
341
|
-
while (i < length &&
|
|
342
|
-
!(source[i] === "*" && i + 1 < length && source[i + 1] === "/")) {
|
|
343
|
-
i++;
|
|
344
|
-
}
|
|
345
|
-
if (i < length) {
|
|
346
|
-
i += 2; // Skip closing */
|
|
347
|
-
}
|
|
348
|
-
continue;
|
|
349
|
-
}
|
|
350
|
-
if (ch === "{") {
|
|
351
|
-
depth++;
|
|
352
|
-
i++;
|
|
353
|
-
continue;
|
|
354
|
-
}
|
|
355
|
-
if (ch === "}") {
|
|
356
|
-
depth--;
|
|
357
|
-
i++;
|
|
358
|
-
if (depth === 0) {
|
|
359
|
-
const body = source.slice(bodyStart, i - 1);
|
|
360
|
-
return { body, endIndex: i - 1 };
|
|
361
|
-
}
|
|
362
|
-
continue;
|
|
363
|
-
}
|
|
364
|
-
i++;
|
|
365
|
-
}
|
|
366
|
-
return null;
|
|
367
|
-
}
|
|
368
|
-
const structPattern = /struct\s+(\w+)\s*\{/g;
|
|
369
|
-
let structMatch;
|
|
370
|
-
while ((structMatch = structPattern.exec(code)) !== null) {
|
|
371
|
-
const lineNum = lineByIndex[structMatch.index];
|
|
372
|
-
const openingBraceIndex = code.indexOf("{", structMatch.index);
|
|
373
|
-
if (openingBraceIndex === -1) {
|
|
374
|
-
continue;
|
|
375
|
-
}
|
|
376
|
-
const block = extractBalancedBlock(code, openingBraceIndex);
|
|
377
|
-
if (!block) {
|
|
378
|
-
continue;
|
|
379
|
-
}
|
|
380
|
-
const fields = block.body
|
|
381
|
-
.split(",")
|
|
382
|
-
.map((f) => f.trim())
|
|
383
|
-
.filter((f) => f);
|
|
384
|
-
structs.push({
|
|
385
|
-
name: structMatch[1],
|
|
386
|
-
fields,
|
|
387
|
-
line: lineNum,
|
|
388
|
-
});
|
|
389
|
-
}
|
|
390
|
-
// Extract enum definitions using balanced block extraction
|
|
391
|
-
// (handles nested braces in comments/strings)
|
|
392
|
-
const enums = [];
|
|
393
|
-
const enumStartPattern = /enum\s+(\w+)\s*\{/g;
|
|
394
|
-
let enumMatch;
|
|
395
|
-
while ((enumMatch = enumStartPattern.exec(code)) !== null) {
|
|
396
|
-
const lineNum = lineByIndex[enumMatch.index];
|
|
397
|
-
const openingBraceIndex = code.indexOf("{", enumMatch.index);
|
|
398
|
-
if (openingBraceIndex === -1) {
|
|
399
|
-
continue;
|
|
400
|
-
}
|
|
401
|
-
const block = extractBalancedBlock(code, openingBraceIndex);
|
|
402
|
-
if (!block) {
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
const variants = block.body
|
|
406
|
-
.split(",")
|
|
407
|
-
.map((v) => v.trim())
|
|
408
|
-
.filter((v) => v);
|
|
409
|
-
enums.push({
|
|
410
|
-
name: enumMatch[1],
|
|
411
|
-
variants,
|
|
412
|
-
line: lineNum,
|
|
413
|
-
});
|
|
414
|
-
}
|
|
415
|
-
// Generate summary
|
|
416
|
-
const exports = {
|
|
417
|
-
circuits: circuits.filter((c) => c.isExport).map((c) => c.name),
|
|
418
|
-
witnesses: witnesses.filter((w) => w.isExport).map((w) => w.name),
|
|
419
|
-
ledger: ledgerItems.filter((l) => l.isExport).map((l) => l.name),
|
|
420
|
-
};
|
|
421
|
-
// ============================================================================
|
|
422
|
-
// PRE-COMPILATION ISSUE DETECTION
|
|
423
|
-
// Catch common mistakes before hitting the compiler
|
|
424
|
-
// ============================================================================
|
|
425
|
-
const potentialIssues = [];
|
|
426
|
-
// Known CompactStandardLibrary exports that shouldn't be redefined
|
|
427
|
-
const stdlibExports = [
|
|
428
|
-
"burnAddress",
|
|
429
|
-
"ownPublicKey",
|
|
430
|
-
"contractAddress",
|
|
431
|
-
"default",
|
|
432
|
-
"disclose",
|
|
433
|
-
"assert",
|
|
434
|
-
"pad",
|
|
435
|
-
"unpad",
|
|
436
|
-
"Counter",
|
|
437
|
-
"Map",
|
|
438
|
-
"Set",
|
|
439
|
-
"MerkleTree",
|
|
440
|
-
"Opaque",
|
|
441
|
-
"Vector",
|
|
442
|
-
];
|
|
443
|
-
// ========== CRITICAL SYNTAX CHECKS (P0 - causes immediate compilation failure) ==========
|
|
444
|
-
// P0-1. Detect deprecated ledger block syntax
|
|
445
|
-
const ledgerBlockPattern = /ledger\s*\{/g;
|
|
446
|
-
let ledgerBlockMatch;
|
|
447
|
-
while ((ledgerBlockMatch = ledgerBlockPattern.exec(code)) !== null) {
|
|
448
|
-
const lineNum = lineByIndex[ledgerBlockMatch.index] || 1;
|
|
449
|
-
potentialIssues.push({
|
|
450
|
-
type: "deprecated_ledger_block",
|
|
451
|
-
line: lineNum,
|
|
452
|
-
message: `Deprecated ledger block syntax 'ledger { }' - causes parse error`,
|
|
453
|
-
suggestion: `Use individual declarations: 'export ledger fieldName: Type;'`,
|
|
454
|
-
severity: "error",
|
|
455
|
-
});
|
|
456
|
-
}
|
|
457
|
-
// P0-2. Detect Void return type (doesn't exist in Compact)
|
|
458
|
-
const voidReturnPattern = /circuit\s+\w+\s*\([^)]*\)\s*:\s*Void\b/g;
|
|
459
|
-
let voidMatch;
|
|
460
|
-
while ((voidMatch = voidReturnPattern.exec(code)) !== null) {
|
|
461
|
-
const lineNum = lineByIndex[voidMatch.index] || 1;
|
|
462
|
-
potentialIssues.push({
|
|
463
|
-
type: "invalid_void_type",
|
|
464
|
-
line: lineNum,
|
|
465
|
-
message: `Invalid return type 'Void' - Void does not exist in Compact`,
|
|
466
|
-
suggestion: `Use '[]' (empty tuple) for circuits that return nothing: 'circuit fn(): []'`,
|
|
467
|
-
severity: "error",
|
|
468
|
-
});
|
|
469
|
-
}
|
|
470
|
-
// P0-3. Detect old pragma format with patch version
|
|
471
|
-
const oldPragmaPattern = /pragma\s+language_version\s*>=?\s*\d+\.\d+\.\d+/g;
|
|
472
|
-
let oldPragmaMatch;
|
|
473
|
-
while ((oldPragmaMatch = oldPragmaPattern.exec(code)) !== null) {
|
|
474
|
-
const lineNum = lineByIndex[oldPragmaMatch.index] || 1;
|
|
475
|
-
potentialIssues.push({
|
|
476
|
-
type: "invalid_pragma_format",
|
|
477
|
-
line: lineNum,
|
|
478
|
-
message: `Pragma includes patch version which may cause parse errors`,
|
|
479
|
-
suggestion: `Use bounded range format: 'pragma language_version >= 0.16 && <= 0.18;'`,
|
|
480
|
-
severity: "error",
|
|
481
|
-
});
|
|
482
|
-
}
|
|
483
|
-
// P0-4. Detect missing export on enums (won't be accessible from TypeScript)
|
|
484
|
-
const unexportedEnumPattern = /(?<!export\s+)enum\s+(\w+)\s*\{/g;
|
|
485
|
-
let unexportedEnumMatch;
|
|
486
|
-
while ((unexportedEnumMatch = unexportedEnumPattern.exec(code)) !== null) {
|
|
487
|
-
// Double check it's not preceded by export
|
|
488
|
-
const before = code.substring(Math.max(0, unexportedEnumMatch.index - 10), unexportedEnumMatch.index);
|
|
489
|
-
if (!before.includes("export")) {
|
|
490
|
-
const lineNum = lineByIndex[unexportedEnumMatch.index] || 1;
|
|
491
|
-
potentialIssues.push({
|
|
492
|
-
type: "unexported_enum",
|
|
493
|
-
line: lineNum,
|
|
494
|
-
message: `Enum '${unexportedEnumMatch[1]}' is not exported - won't be accessible from TypeScript`,
|
|
495
|
-
suggestion: `Add 'export' keyword: 'export enum ${unexportedEnumMatch[1]} { ... }'`,
|
|
496
|
-
severity: "warning",
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
// P0-5. Detect Cell<T> wrapper (deprecated since 0.15)
|
|
501
|
-
const cellPattern = /Cell\s*<\s*\w+\s*>/g;
|
|
502
|
-
let cellMatch;
|
|
503
|
-
while ((cellMatch = cellPattern.exec(code)) !== null) {
|
|
504
|
-
const lineNum = lineByIndex[cellMatch.index] || 1;
|
|
505
|
-
potentialIssues.push({
|
|
506
|
-
type: "deprecated_cell_wrapper",
|
|
507
|
-
line: lineNum,
|
|
508
|
-
message: `'Cell<T>' wrapper is deprecated since Compact 0.15`,
|
|
509
|
-
suggestion: `Use the type directly: 'Field' instead of 'Cell<Field>'`,
|
|
510
|
-
severity: "error",
|
|
511
|
-
});
|
|
512
|
-
}
|
|
513
|
-
// ========== EXISTING CHECKS ==========
|
|
514
|
-
// 1. Detect module-level const (not supported in Compact)
|
|
515
|
-
const constPattern = /^const\s+(\w+)\s*:/gm;
|
|
516
|
-
let constMatch;
|
|
517
|
-
while ((constMatch = constPattern.exec(code)) !== null) {
|
|
518
|
-
// Check if this const is inside a circuit block by looking for preceding circuit/constructor
|
|
519
|
-
const beforeConst = code.substring(0, constMatch.index);
|
|
520
|
-
const lastCircuitStart = Math.max(beforeConst.lastIndexOf("circuit "), beforeConst.lastIndexOf("constructor {"));
|
|
521
|
-
const lastCloseBrace = beforeConst.lastIndexOf("}");
|
|
522
|
-
// If no circuit before, or the last } is after the last circuit start, it's module-level
|
|
523
|
-
if (lastCircuitStart === -1 || lastCloseBrace > lastCircuitStart) {
|
|
524
|
-
const lineNum = lineByIndex[constMatch.index] || 1;
|
|
525
|
-
potentialIssues.push({
|
|
526
|
-
type: "module_level_const",
|
|
527
|
-
line: lineNum,
|
|
528
|
-
message: `Module-level 'const ${constMatch[1]}' is not supported in Compact`,
|
|
529
|
-
suggestion: `Use 'pure circuit ${constMatch[1]}(): <type> { return <value>; }' instead`,
|
|
530
|
-
severity: "error",
|
|
531
|
-
});
|
|
532
|
-
}
|
|
533
|
-
}
|
|
534
|
-
// 2. Detect standard library name collisions
|
|
535
|
-
const hasStdlibImport = imports.includes("CompactStandardLibrary") ||
|
|
536
|
-
code.includes('include "std"');
|
|
537
|
-
if (hasStdlibImport) {
|
|
538
|
-
// Check circuits for name collisions
|
|
539
|
-
for (const circuit of circuits) {
|
|
540
|
-
if (stdlibExports.includes(circuit.name)) {
|
|
541
|
-
potentialIssues.push({
|
|
542
|
-
type: "stdlib_name_collision",
|
|
543
|
-
line: circuit.line,
|
|
544
|
-
message: `Circuit '${circuit.name}' conflicts with CompactStandardLibrary.${circuit.name}()`,
|
|
545
|
-
suggestion: `Rename to avoid ambiguity, or remove to use the standard library version`,
|
|
546
|
-
severity: "error",
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
// 3. Detect sealed + export conflicts
|
|
552
|
-
const sealedFields = [];
|
|
553
|
-
const sealedPattern = /sealed\s+ledger\s+(\w+)\s*:/g;
|
|
554
|
-
let sealedMatch;
|
|
555
|
-
while ((sealedMatch = sealedPattern.exec(code)) !== null) {
|
|
556
|
-
const lineNum = lineByIndex[sealedMatch.index] || 1;
|
|
557
|
-
sealedFields.push({ name: sealedMatch[1], line: lineNum });
|
|
558
|
-
}
|
|
559
|
-
if (sealedFields.length > 0) {
|
|
560
|
-
// Check if any exported circuit writes to sealed fields
|
|
561
|
-
for (const circuit of circuits) {
|
|
562
|
-
if (circuit.isExport) {
|
|
563
|
-
// Find the circuit body and check for assignments to sealed fields
|
|
564
|
-
const circuitBodyMatch = code.match(new RegExp(`(?:export\\s+)?circuit\\s+${circuit.name}\\s*\\([^)]*\\)\\s*:[^{]*\\{([\\s\\S]*?)\\n\\}`, "m"));
|
|
565
|
-
if (circuitBodyMatch) {
|
|
566
|
-
const body = circuitBodyMatch[1];
|
|
567
|
-
for (const field of sealedFields) {
|
|
568
|
-
// Check for assignment patterns: fieldName = or fieldName.method(
|
|
569
|
-
if (new RegExp(`\\b${field.name}\\s*=`).test(body) ||
|
|
570
|
-
new RegExp(`\\b${field.name}\\s*\\.\\s*\\w+\\s*\\(`).test(body)) {
|
|
571
|
-
potentialIssues.push({
|
|
572
|
-
type: "sealed_export_conflict",
|
|
573
|
-
line: circuit.line,
|
|
574
|
-
message: `Exported circuit '${circuit.name}' modifies sealed field '${field.name}'`,
|
|
575
|
-
suggestion: `Move sealed field initialization to a 'constructor { }' block instead`,
|
|
576
|
-
severity: "error",
|
|
577
|
-
});
|
|
578
|
-
}
|
|
579
|
-
}
|
|
580
|
-
}
|
|
581
|
-
}
|
|
582
|
-
}
|
|
583
|
-
}
|
|
584
|
-
// 4. Detect missing constructor when sealed fields exist but no constructor
|
|
585
|
-
if (sealedFields.length > 0) {
|
|
586
|
-
const hasConstructor = /constructor\s*\{/.test(code);
|
|
587
|
-
if (!hasConstructor) {
|
|
588
|
-
// Check if there's an initialize-like circuit trying to set sealed fields
|
|
589
|
-
const initCircuit = circuits.find((c) => c.name.toLowerCase().includes("init") ||
|
|
590
|
-
c.name.toLowerCase() === "setup");
|
|
591
|
-
if (initCircuit && initCircuit.isExport) {
|
|
592
|
-
potentialIssues.push({
|
|
593
|
-
type: "missing_constructor",
|
|
594
|
-
line: initCircuit.line,
|
|
595
|
-
message: `Contract has sealed fields but uses '${initCircuit.name}' instead of constructor`,
|
|
596
|
-
suggestion: `Sealed fields must be initialized in 'constructor { }', not in exported circuits`,
|
|
597
|
-
severity: "warning",
|
|
598
|
-
});
|
|
599
|
-
}
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
// 5. Detect potential type mismatches with stdlib functions
|
|
603
|
-
if (hasStdlibImport) {
|
|
604
|
-
// Check for burnAddress() used where ZswapCoinPublicKey is expected
|
|
605
|
-
// burnAddress() returns Either<ZswapCoinPublicKey, ContractAddress>
|
|
606
|
-
const burnAddressUsages = code.matchAll(/burnAddress\s*\(\s*\)/g);
|
|
607
|
-
for (const usage of burnAddressUsages) {
|
|
608
|
-
// Check if it's being passed to a function or assigned
|
|
609
|
-
const afterUsage = code.substring(usage.index + usage[0].length, usage.index + usage[0].length + 50);
|
|
610
|
-
const beforeUsage = code.substring(Math.max(0, usage.index - 100), usage.index);
|
|
611
|
-
// If used in a context expecting ZswapCoinPublicKey (not .left or .right access)
|
|
612
|
-
if (!afterUsage.startsWith(".left") &&
|
|
613
|
-
!afterUsage.startsWith(".right") &&
|
|
614
|
-
!afterUsage.startsWith(".is_left")) {
|
|
615
|
-
// Check if it's in a function call or assignment that likely expects ZswapCoinPublicKey
|
|
616
|
-
if (/\(\s*$/.test(beforeUsage) || /,\s*$/.test(beforeUsage)) {
|
|
617
|
-
const lineNum = lineByIndex[usage.index] || 1;
|
|
618
|
-
potentialIssues.push({
|
|
619
|
-
type: "stdlib_type_mismatch",
|
|
620
|
-
line: lineNum,
|
|
621
|
-
message: `burnAddress() returns Either<ZswapCoinPublicKey, ContractAddress>, not ZswapCoinPublicKey`,
|
|
622
|
-
suggestion: `Use burnAddress().left for ZswapCoinPublicKey, or define 'pure circuit zeroKey(): ZswapCoinPublicKey { return default<ZswapCoinPublicKey>; }'`,
|
|
623
|
-
severity: "warning",
|
|
624
|
-
});
|
|
625
|
-
break; // Only warn once
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
}
|
|
630
|
-
// 6. Detect division operator usage (not supported in Compact)
|
|
631
|
-
const divisionPattern = /[^/]\/[^/*]/g;
|
|
632
|
-
let divMatch;
|
|
633
|
-
while ((divMatch = divisionPattern.exec(code)) !== null) {
|
|
634
|
-
// Skip if inside a comment
|
|
635
|
-
const beforeDiv = code.substring(0, divMatch.index);
|
|
636
|
-
const lastLineStart = beforeDiv.lastIndexOf("\n") + 1;
|
|
637
|
-
const lineContent = beforeDiv.substring(lastLineStart);
|
|
638
|
-
if (lineContent.includes("//"))
|
|
639
|
-
continue;
|
|
640
|
-
const lineNum = lineByIndex[divMatch.index] || 1;
|
|
641
|
-
potentialIssues.push({
|
|
642
|
-
type: "unsupported_division",
|
|
643
|
-
line: lineNum,
|
|
644
|
-
message: `Division operator '/' is not supported in Compact`,
|
|
645
|
-
suggestion: `Use a witness-based division pattern: 'witness divideWithRemainder(a, b): [quotient, remainder]' with on-chain verification`,
|
|
646
|
-
severity: "error",
|
|
647
|
-
});
|
|
648
|
-
break; // Only warn once
|
|
649
|
-
}
|
|
650
|
-
// 7. Detect Counter.value access (Counter only has .increment())
|
|
651
|
-
const counterValuePattern = /(\w+)\.value\b/g;
|
|
652
|
-
let counterMatch;
|
|
653
|
-
while ((counterMatch = counterValuePattern.exec(code)) !== null) {
|
|
654
|
-
const varName = counterMatch[1];
|
|
655
|
-
// Check if this variable is a Counter type
|
|
656
|
-
const counterLedger = ledgerItems.find((l) => l.name === varName && l.type === "Counter");
|
|
657
|
-
if (counterLedger) {
|
|
658
|
-
const lineNum = lineByIndex[counterMatch.index] || 1;
|
|
659
|
-
potentialIssues.push({
|
|
660
|
-
type: "invalid_counter_access",
|
|
661
|
-
line: lineNum,
|
|
662
|
-
message: `Counter type '${varName}' does not have a '.value' property`,
|
|
663
|
-
suggestion: `Counter only has '.increment(n)'. Use 'Uint<32>' or 'Uint<64>' instead if you need to read the value`,
|
|
664
|
-
severity: "error",
|
|
665
|
-
});
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
// 8. Detect potential Uint overflow in multiplication (suggest Field casting)
|
|
669
|
-
const multiplyPattern = /(\w+)\s*\*\s*(\w+)(?:\s*\+\s*\w+)?\s*(?:as\s+Uint|==)/g;
|
|
670
|
-
let multMatch;
|
|
671
|
-
while ((multMatch = multiplyPattern.exec(code)) !== null) {
|
|
672
|
-
// Check if operands are likely Uint types and not already cast to Field
|
|
673
|
-
const beforeMult = code.substring(Math.max(0, multMatch.index - 200), multMatch.index);
|
|
674
|
-
const afterMult = code.substring(multMatch.index, multMatch.index + multMatch[0].length + 50);
|
|
675
|
-
// Skip if already casting to Field
|
|
676
|
-
if (afterMult.includes("as Field") || beforeMult.includes("as Field"))
|
|
677
|
-
continue;
|
|
678
|
-
// Check if this looks like a verification pattern (common in witness verification)
|
|
679
|
-
if (/assert|==/.test(afterMult)) {
|
|
680
|
-
const lineNum = lineByIndex[multMatch.index] || 1;
|
|
681
|
-
potentialIssues.push({
|
|
682
|
-
type: "potential_overflow",
|
|
683
|
-
line: lineNum,
|
|
684
|
-
message: `Multiplication '${multMatch[1]} * ${multMatch[2]}' may overflow Uint bounds`,
|
|
685
|
-
suggestion: `Cast operands to Field for safe arithmetic: '(${multMatch[1]} as Field) * (${multMatch[2]} as Field)'`,
|
|
686
|
-
severity: "warning",
|
|
687
|
-
});
|
|
688
|
-
break; // Only warn once
|
|
689
|
-
}
|
|
690
|
-
}
|
|
691
|
-
// 9. Detect witness/private values used in conditionals without disclose()
|
|
692
|
-
// Look for patterns like: if (witnessVar ...) or if (param == privateValue)
|
|
693
|
-
const witnessNames = witnesses.map((w) => w.name);
|
|
694
|
-
const ifPattern = /if\s*\(([^)]+)\)/g;
|
|
695
|
-
let ifMatch;
|
|
696
|
-
while ((ifMatch = ifPattern.exec(code)) !== null) {
|
|
697
|
-
const condition = ifMatch[1];
|
|
698
|
-
// Check if condition uses a witness value without disclose
|
|
699
|
-
for (const witnessName of witnessNames) {
|
|
700
|
-
if (condition.includes(witnessName) &&
|
|
701
|
-
!condition.includes(`disclose(${witnessName}`) &&
|
|
702
|
-
!condition.includes("disclose(")) {
|
|
703
|
-
const lineNum = lineByIndex[ifMatch.index] || 1;
|
|
704
|
-
potentialIssues.push({
|
|
705
|
-
type: "undisclosed_witness_conditional",
|
|
706
|
-
line: lineNum,
|
|
707
|
-
message: `Witness value '${witnessName}' used in conditional without disclose()`,
|
|
708
|
-
suggestion: `Wrap witness comparisons in disclose(): 'if (disclose(${witnessName} == expected))'`,
|
|
709
|
-
severity: "warning",
|
|
710
|
-
});
|
|
711
|
-
break;
|
|
712
|
-
}
|
|
713
|
-
}
|
|
714
|
-
}
|
|
715
|
-
// 10. Detect constructor parameters assigned to ledger without disclose()
|
|
716
|
-
// Constructor parameters are treated as witness values and need disclose() when written to ledger
|
|
717
|
-
const constructorMatch = code.match(/constructor\s*\(([^)]*)\)\s*\{([\s\S]*?)(?=\n\s*(?:export|circuit|witness|ledger|constructor|\}|$))/);
|
|
718
|
-
if (constructorMatch) {
|
|
719
|
-
const paramsStr = constructorMatch[1];
|
|
720
|
-
const constructorBody = constructorMatch[2];
|
|
721
|
-
// Extract constructor parameter names
|
|
722
|
-
const paramPattern = /(\w+)\s*:\s*[^,)]+/g;
|
|
723
|
-
const constructorParams = [];
|
|
724
|
-
let paramMatch;
|
|
725
|
-
while ((paramMatch = paramPattern.exec(paramsStr)) !== null) {
|
|
726
|
-
constructorParams.push(paramMatch[1]);
|
|
727
|
-
}
|
|
728
|
-
// Check each parameter for direct assignment to ledger without disclose
|
|
729
|
-
for (const param of constructorParams) {
|
|
730
|
-
// Look for direct assignment: ledgerField = param (without disclose)
|
|
731
|
-
const assignmentPattern = new RegExp(`(\\w+)\\s*=\\s*(?!disclose\\s*\\()${param}\\b`, "g");
|
|
732
|
-
let assignMatch;
|
|
733
|
-
while ((assignMatch = assignmentPattern.exec(constructorBody)) !== null) {
|
|
734
|
-
const fieldName = assignMatch[1];
|
|
735
|
-
// Check if the field is a ledger item
|
|
736
|
-
const isLedgerField = ledgerItems.some((l) => l.name === fieldName);
|
|
737
|
-
if (isLedgerField) {
|
|
738
|
-
// Find the line number
|
|
739
|
-
const beforeAssign = code.substring(0, constructorMatch.index +
|
|
740
|
-
constructorMatch[0].indexOf(assignMatch[0]));
|
|
741
|
-
const lineNum = (beforeAssign.match(/\n/g) || []).length + 1;
|
|
742
|
-
potentialIssues.push({
|
|
743
|
-
type: "undisclosed_constructor_param",
|
|
744
|
-
line: lineNum,
|
|
745
|
-
message: `Constructor parameter '${param}' assigned to ledger field '${fieldName}' without disclose()`,
|
|
746
|
-
suggestion: `Wrap in disclose(): '${fieldName} = disclose(${param});'`,
|
|
747
|
-
severity: "error",
|
|
748
|
-
});
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
// 11. Detect "if" expression used in assignment context (should use ternary)
|
|
754
|
-
// Pattern: const x = if (...) { ... } else { ... }
|
|
755
|
-
const ifAssignmentPattern = /(?:const|let)\s+\w+\s*=\s*if\s*\(/g;
|
|
756
|
-
let ifAssignMatch;
|
|
757
|
-
while ((ifAssignMatch = ifAssignmentPattern.exec(code)) !== null) {
|
|
758
|
-
const lineNum = lineByIndex[ifAssignMatch.index] || 1;
|
|
759
|
-
potentialIssues.push({
|
|
760
|
-
type: "invalid_if_expression",
|
|
761
|
-
line: lineNum,
|
|
762
|
-
message: `'if' cannot be used as an expression in assignments`,
|
|
763
|
-
suggestion: `Use ternary operator instead: 'const x = condition ? valueIfTrue : valueIfFalse;'`,
|
|
764
|
-
severity: "error",
|
|
765
|
-
});
|
|
766
|
-
}
|
|
767
|
-
// 12. (Moved to P0-2 above - Void return type check)
|
|
768
|
-
const summary = [];
|
|
769
|
-
if (circuits.length > 0) {
|
|
770
|
-
summary.push(`${circuits.length} circuit(s)`);
|
|
771
|
-
}
|
|
772
|
-
if (witnesses.length > 0) {
|
|
773
|
-
summary.push(`${witnesses.length} witness(es)`);
|
|
774
|
-
}
|
|
775
|
-
if (ledgerItems.length > 0) {
|
|
776
|
-
summary.push(`${ledgerItems.length} ledger item(s)`);
|
|
777
|
-
}
|
|
778
|
-
if (types.length > 0) {
|
|
779
|
-
summary.push(`${types.length} type alias(es)`);
|
|
780
|
-
}
|
|
781
|
-
if (structs.length > 0) {
|
|
782
|
-
summary.push(`${structs.length} struct(s)`);
|
|
783
|
-
}
|
|
784
|
-
if (enums.length > 0) {
|
|
785
|
-
summary.push(`${enums.length} enum(s)`);
|
|
786
|
-
}
|
|
787
|
-
return {
|
|
788
|
-
success: true,
|
|
789
|
-
filename,
|
|
790
|
-
languageVersion,
|
|
791
|
-
imports,
|
|
792
|
-
structure: {
|
|
793
|
-
circuits,
|
|
794
|
-
witnesses,
|
|
795
|
-
ledgerItems,
|
|
796
|
-
types,
|
|
797
|
-
structs,
|
|
798
|
-
enums,
|
|
799
|
-
},
|
|
800
|
-
exports,
|
|
801
|
-
stats: {
|
|
802
|
-
lineCount: lines.length,
|
|
803
|
-
circuitCount: circuits.length,
|
|
804
|
-
witnessCount: witnesses.length,
|
|
805
|
-
ledgerCount: ledgerItems.length,
|
|
806
|
-
typeCount: types.length,
|
|
807
|
-
structCount: structs.length,
|
|
808
|
-
enumCount: enums.length,
|
|
809
|
-
exportedCircuits: exports.circuits.length,
|
|
810
|
-
exportedWitnesses: exports.witnesses.length,
|
|
811
|
-
exportedLedger: exports.ledger.length,
|
|
812
|
-
},
|
|
813
|
-
potentialIssues: potentialIssues.length > 0 ? potentialIssues : undefined,
|
|
814
|
-
summary: summary.length > 0 ? summary.join(", ") : "Empty contract",
|
|
815
|
-
message: potentialIssues.length > 0
|
|
816
|
-
? `⚠️ Found ${potentialIssues.length} potential issue(s). Contract contains: ${summary.join(", ") || "no definitions found"}`
|
|
817
|
-
: `📋 Contract contains: ${summary.join(", ") || "no definitions found"}`,
|
|
818
|
-
};
|
|
819
|
-
}
|
|
820
|
-
//# sourceMappingURL=validation.js.map
|