linguclaw 0.4.0
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/LICENSE +21 -0
- package/README.md +161 -0
- package/dist/agent-system.d.ts +196 -0
- package/dist/agent-system.d.ts.map +1 -0
- package/dist/agent-system.js +738 -0
- package/dist/agent-system.js.map +1 -0
- package/dist/alphabeta.d.ts +54 -0
- package/dist/alphabeta.d.ts.map +1 -0
- package/dist/alphabeta.js +193 -0
- package/dist/alphabeta.js.map +1 -0
- package/dist/browser.d.ts +62 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +224 -0
- package/dist/browser.js.map +1 -0
- package/dist/cli.d.ts +7 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +565 -0
- package/dist/cli.js.map +1 -0
- package/dist/code-parser.d.ts +39 -0
- package/dist/code-parser.d.ts.map +1 -0
- package/dist/code-parser.js +385 -0
- package/dist/code-parser.js.map +1 -0
- package/dist/config.d.ts +66 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +232 -0
- package/dist/config.js.map +1 -0
- package/dist/core/engine.d.ts +359 -0
- package/dist/core/engine.d.ts.map +1 -0
- package/dist/core/engine.js +127 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/daemon.d.ts +29 -0
- package/dist/daemon.d.ts.map +1 -0
- package/dist/daemon.js +212 -0
- package/dist/daemon.js.map +1 -0
- package/dist/email-receiver.d.ts +63 -0
- package/dist/email-receiver.d.ts.map +1 -0
- package/dist/email-receiver.js +553 -0
- package/dist/email-receiver.js.map +1 -0
- package/dist/git-integration.d.ts +180 -0
- package/dist/git-integration.d.ts.map +1 -0
- package/dist/git-integration.js +850 -0
- package/dist/git-integration.js.map +1 -0
- package/dist/inbox.d.ts +84 -0
- package/dist/inbox.d.ts.map +1 -0
- package/dist/inbox.js +198 -0
- package/dist/inbox.js.map +1 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +41 -0
- package/dist/index.js.map +1 -0
- package/dist/languages/cpp.d.ts +51 -0
- package/dist/languages/cpp.d.ts.map +1 -0
- package/dist/languages/cpp.js +930 -0
- package/dist/languages/cpp.js.map +1 -0
- package/dist/languages/csharp.d.ts +79 -0
- package/dist/languages/csharp.d.ts.map +1 -0
- package/dist/languages/csharp.js +1776 -0
- package/dist/languages/csharp.js.map +1 -0
- package/dist/languages/go.d.ts +50 -0
- package/dist/languages/go.d.ts.map +1 -0
- package/dist/languages/go.js +882 -0
- package/dist/languages/go.js.map +1 -0
- package/dist/languages/java.d.ts +47 -0
- package/dist/languages/java.d.ts.map +1 -0
- package/dist/languages/java.js +649 -0
- package/dist/languages/java.js.map +1 -0
- package/dist/languages/python.d.ts +47 -0
- package/dist/languages/python.d.ts.map +1 -0
- package/dist/languages/python.js +655 -0
- package/dist/languages/python.js.map +1 -0
- package/dist/languages/rust.d.ts +61 -0
- package/dist/languages/rust.d.ts.map +1 -0
- package/dist/languages/rust.js +1064 -0
- package/dist/languages/rust.js.map +1 -0
- package/dist/logger.d.ts +20 -0
- package/dist/logger.d.ts.map +1 -0
- package/dist/logger.js +133 -0
- package/dist/logger.js.map +1 -0
- package/dist/longterm-memory.d.ts +47 -0
- package/dist/longterm-memory.d.ts.map +1 -0
- package/dist/longterm-memory.js +300 -0
- package/dist/longterm-memory.js.map +1 -0
- package/dist/memory.d.ts +42 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +274 -0
- package/dist/memory.js.map +1 -0
- package/dist/messaging.d.ts +103 -0
- package/dist/messaging.d.ts.map +1 -0
- package/dist/messaging.js +645 -0
- package/dist/messaging.js.map +1 -0
- package/dist/multi-provider.d.ts +69 -0
- package/dist/multi-provider.d.ts.map +1 -0
- package/dist/multi-provider.js +484 -0
- package/dist/multi-provider.js.map +1 -0
- package/dist/orchestrator.d.ts +65 -0
- package/dist/orchestrator.d.ts.map +1 -0
- package/dist/orchestrator.js +441 -0
- package/dist/orchestrator.js.map +1 -0
- package/dist/plugins.d.ts +52 -0
- package/dist/plugins.d.ts.map +1 -0
- package/dist/plugins.js +215 -0
- package/dist/plugins.js.map +1 -0
- package/dist/prism-orchestrator.d.ts +26 -0
- package/dist/prism-orchestrator.d.ts.map +1 -0
- package/dist/prism-orchestrator.js +191 -0
- package/dist/prism-orchestrator.js.map +1 -0
- package/dist/prism.d.ts +46 -0
- package/dist/prism.d.ts.map +1 -0
- package/dist/prism.js +188 -0
- package/dist/prism.js.map +1 -0
- package/dist/privacy.d.ts +23 -0
- package/dist/privacy.d.ts.map +1 -0
- package/dist/privacy.js +220 -0
- package/dist/privacy.js.map +1 -0
- package/dist/proactive.d.ts +30 -0
- package/dist/proactive.d.ts.map +1 -0
- package/dist/proactive.js +260 -0
- package/dist/proactive.js.map +1 -0
- package/dist/refactoring-engine.d.ts +100 -0
- package/dist/refactoring-engine.d.ts.map +1 -0
- package/dist/refactoring-engine.js +717 -0
- package/dist/refactoring-engine.js.map +1 -0
- package/dist/resilience.d.ts +43 -0
- package/dist/resilience.d.ts.map +1 -0
- package/dist/resilience.js +200 -0
- package/dist/resilience.js.map +1 -0
- package/dist/safety.d.ts +40 -0
- package/dist/safety.d.ts.map +1 -0
- package/dist/safety.js +133 -0
- package/dist/safety.js.map +1 -0
- package/dist/sandbox.d.ts +33 -0
- package/dist/sandbox.d.ts.map +1 -0
- package/dist/sandbox.js +173 -0
- package/dist/sandbox.js.map +1 -0
- package/dist/scheduler.d.ts +72 -0
- package/dist/scheduler.d.ts.map +1 -0
- package/dist/scheduler.js +374 -0
- package/dist/scheduler.js.map +1 -0
- package/dist/semantic-memory.d.ts +70 -0
- package/dist/semantic-memory.d.ts.map +1 -0
- package/dist/semantic-memory.js +430 -0
- package/dist/semantic-memory.js.map +1 -0
- package/dist/skills.d.ts +97 -0
- package/dist/skills.d.ts.map +1 -0
- package/dist/skills.js +575 -0
- package/dist/skills.js.map +1 -0
- package/dist/static/dashboard.html +853 -0
- package/dist/static/hub.html +772 -0
- package/dist/static/index.html +818 -0
- package/dist/static/logo.svg +24 -0
- package/dist/static/workflow-editor.html +913 -0
- package/dist/tools.d.ts +67 -0
- package/dist/tools.d.ts.map +1 -0
- package/dist/tools.js +303 -0
- package/dist/tools.js.map +1 -0
- package/dist/types.d.ts +295 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +90 -0
- package/dist/types.js.map +1 -0
- package/dist/web.d.ts +76 -0
- package/dist/web.d.ts.map +1 -0
- package/dist/web.js +2139 -0
- package/dist/web.js.map +1 -0
- package/dist/workflow-engine.d.ts +114 -0
- package/dist/workflow-engine.d.ts.map +1 -0
- package/dist/workflow-engine.js +855 -0
- package/dist/workflow-engine.js.map +1 -0
- package/package.json +77 -0
|
@@ -0,0 +1,655 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Python Language Support for LinguClaw
|
|
4
|
+
* Advanced Python parser using AST analysis
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
exports.PythonLanguageSupport = exports.PythonAnalyzer = exports.PythonParser = void 0;
|
|
8
|
+
class PythonParser {
|
|
9
|
+
source = '';
|
|
10
|
+
lines = [];
|
|
11
|
+
parse(source, filePath) {
|
|
12
|
+
this.source = source;
|
|
13
|
+
this.lines = source.split('\n');
|
|
14
|
+
try {
|
|
15
|
+
// Use Python's ast module via child process for accurate parsing
|
|
16
|
+
const ast = this.parseWithPythonAST(source);
|
|
17
|
+
return {
|
|
18
|
+
ast: this.convertToGenericAST(ast, filePath),
|
|
19
|
+
errors: [],
|
|
20
|
+
warnings: [],
|
|
21
|
+
tokens: [],
|
|
22
|
+
comments: this.extractComments(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
// Fallback to regex-based parsing
|
|
27
|
+
return this.fallbackParse(filePath);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
async *parseStream(source) {
|
|
31
|
+
const reader = source.getReader();
|
|
32
|
+
let buffer = '';
|
|
33
|
+
while (true) {
|
|
34
|
+
const { done, value } = await reader.read();
|
|
35
|
+
if (done)
|
|
36
|
+
break;
|
|
37
|
+
buffer += value;
|
|
38
|
+
// Try to parse complete statements
|
|
39
|
+
const chunks = this.splitIntoChunks(buffer);
|
|
40
|
+
for (const chunk of chunks.complete) {
|
|
41
|
+
yield this.parse(chunk, '<stream>');
|
|
42
|
+
}
|
|
43
|
+
buffer = chunks.remaining;
|
|
44
|
+
}
|
|
45
|
+
// Parse remaining
|
|
46
|
+
if (buffer.trim()) {
|
|
47
|
+
yield this.parse(buffer, '<stream>');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
parseWithPythonAST(source) {
|
|
51
|
+
// In real implementation, this would spawn Python process
|
|
52
|
+
// python -c "import ast; print(ast.dump(ast.parse(source)))"
|
|
53
|
+
return this.createMockPythonAST(source);
|
|
54
|
+
}
|
|
55
|
+
createMockPythonAST(source) {
|
|
56
|
+
// Simplified mock AST for demonstration
|
|
57
|
+
return {
|
|
58
|
+
type: 'Module',
|
|
59
|
+
body: this.extractPythonStructure(source),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
extractPythonStructure(source) {
|
|
63
|
+
const nodes = [];
|
|
64
|
+
const lines = source.split('\n');
|
|
65
|
+
let currentIndent = 0;
|
|
66
|
+
let inClass = false;
|
|
67
|
+
let inFunction = false;
|
|
68
|
+
let className = '';
|
|
69
|
+
let functionName = '';
|
|
70
|
+
for (let i = 0; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i];
|
|
72
|
+
const stripped = line.trim();
|
|
73
|
+
const indent = line.search(/\S/);
|
|
74
|
+
if (stripped.startsWith('class ')) {
|
|
75
|
+
const match = stripped.match(/class\s+(\w+)/);
|
|
76
|
+
if (match) {
|
|
77
|
+
inClass = true;
|
|
78
|
+
className = match[1];
|
|
79
|
+
nodes.push({
|
|
80
|
+
type: 'ClassDef',
|
|
81
|
+
name: className,
|
|
82
|
+
lineno: i + 1,
|
|
83
|
+
col_offset: indent,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (stripped.startsWith('def ')) {
|
|
88
|
+
const match = stripped.match(/def\s+(\w+)/);
|
|
89
|
+
if (match) {
|
|
90
|
+
inFunction = true;
|
|
91
|
+
functionName = match[1];
|
|
92
|
+
nodes.push({
|
|
93
|
+
type: 'FunctionDef',
|
|
94
|
+
name: functionName,
|
|
95
|
+
lineno: i + 1,
|
|
96
|
+
col_offset: indent,
|
|
97
|
+
parent_class: inClass && indent > 0 ? className : undefined,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
else if (stripped.startsWith('import ') || stripped.startsWith('from ')) {
|
|
102
|
+
nodes.push({
|
|
103
|
+
type: 'Import',
|
|
104
|
+
line: stripped,
|
|
105
|
+
lineno: i + 1,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// Detect dedent
|
|
109
|
+
if (indent <= currentIndent && (inClass || inFunction)) {
|
|
110
|
+
if (indent === 0) {
|
|
111
|
+
inClass = false;
|
|
112
|
+
inFunction = false;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
currentIndent = indent;
|
|
116
|
+
}
|
|
117
|
+
return nodes;
|
|
118
|
+
}
|
|
119
|
+
convertToGenericAST(pythonAST, filePath) {
|
|
120
|
+
const convertNode = (node, parent) => {
|
|
121
|
+
const generic = {
|
|
122
|
+
type: this.mapPythonNodeType(node.type),
|
|
123
|
+
id: `${filePath}:${node.lineno || 0}:${node.col_offset || 0}`,
|
|
124
|
+
location: {
|
|
125
|
+
file: filePath,
|
|
126
|
+
startLine: node.lineno || 0,
|
|
127
|
+
startColumn: node.col_offset || 0,
|
|
128
|
+
endLine: node.end_lineno || node.lineno || 0,
|
|
129
|
+
endColumn: node.end_col_offset || 0,
|
|
130
|
+
byteOffset: 0,
|
|
131
|
+
},
|
|
132
|
+
children: [],
|
|
133
|
+
parent,
|
|
134
|
+
metadata: { ...node },
|
|
135
|
+
};
|
|
136
|
+
// Convert children
|
|
137
|
+
if (node.body && Array.isArray(node.body)) {
|
|
138
|
+
generic.children = node.body.map((child) => convertNode(child, generic));
|
|
139
|
+
}
|
|
140
|
+
return generic;
|
|
141
|
+
};
|
|
142
|
+
return convertNode(pythonAST);
|
|
143
|
+
}
|
|
144
|
+
mapPythonNodeType(pythonType) {
|
|
145
|
+
const typeMap = {
|
|
146
|
+
'Module': 'Program',
|
|
147
|
+
'FunctionDef': 'FunctionDeclaration',
|
|
148
|
+
'AsyncFunctionDef': 'FunctionDeclaration',
|
|
149
|
+
'ClassDef': 'ClassDeclaration',
|
|
150
|
+
'Import': 'ImportDeclaration',
|
|
151
|
+
'ImportFrom': 'ImportDeclaration',
|
|
152
|
+
'Assign': 'VariableDeclaration',
|
|
153
|
+
'AnnAssign': 'VariableDeclaration',
|
|
154
|
+
'If': 'IfStatement',
|
|
155
|
+
'For': 'ForStatement',
|
|
156
|
+
'While': 'WhileStatement',
|
|
157
|
+
'Try': 'TryStatement',
|
|
158
|
+
'With': 'WithStatement',
|
|
159
|
+
'Return': 'ReturnStatement',
|
|
160
|
+
'Expr': 'ExpressionStatement',
|
|
161
|
+
'Call': 'CallExpression',
|
|
162
|
+
'Attribute': 'MemberExpression',
|
|
163
|
+
'Name': 'Identifier',
|
|
164
|
+
'Constant': 'Literal',
|
|
165
|
+
'List': 'ArrayExpression',
|
|
166
|
+
'Dict': 'ObjectExpression',
|
|
167
|
+
};
|
|
168
|
+
return typeMap[pythonType] || pythonType;
|
|
169
|
+
}
|
|
170
|
+
extractComments() {
|
|
171
|
+
const comments = [];
|
|
172
|
+
const lines = this.source.split('\n');
|
|
173
|
+
for (let i = 0; i < lines.length; i++) {
|
|
174
|
+
const line = lines[i];
|
|
175
|
+
const hashIndex = line.indexOf('#');
|
|
176
|
+
if (hashIndex !== -1) {
|
|
177
|
+
comments.push({
|
|
178
|
+
type: 'Line',
|
|
179
|
+
value: line.substring(hashIndex + 1).trim(),
|
|
180
|
+
line: i + 1,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return comments;
|
|
185
|
+
}
|
|
186
|
+
fallbackParse(filePath) {
|
|
187
|
+
// Basic regex-based fallback
|
|
188
|
+
const ast = {
|
|
189
|
+
type: 'Program',
|
|
190
|
+
id: `${filePath}:0:0`,
|
|
191
|
+
location: {
|
|
192
|
+
file: filePath,
|
|
193
|
+
startLine: 0,
|
|
194
|
+
startColumn: 0,
|
|
195
|
+
endLine: this.lines.length,
|
|
196
|
+
endColumn: 0,
|
|
197
|
+
byteOffset: 0,
|
|
198
|
+
},
|
|
199
|
+
children: this.extractPythonStructure(this.source).map(node => ({
|
|
200
|
+
type: this.mapPythonNodeType(node.type),
|
|
201
|
+
id: `${filePath}:${node.lineno || 0}:0`,
|
|
202
|
+
location: {
|
|
203
|
+
file: filePath,
|
|
204
|
+
startLine: node.lineno || 0,
|
|
205
|
+
startColumn: node.col_offset || 0,
|
|
206
|
+
endLine: node.lineno || 0,
|
|
207
|
+
endColumn: 0,
|
|
208
|
+
byteOffset: 0,
|
|
209
|
+
},
|
|
210
|
+
children: [],
|
|
211
|
+
metadata: node,
|
|
212
|
+
})),
|
|
213
|
+
metadata: {},
|
|
214
|
+
};
|
|
215
|
+
return {
|
|
216
|
+
ast,
|
|
217
|
+
errors: [],
|
|
218
|
+
warnings: [{ message: 'Using fallback parser' }],
|
|
219
|
+
tokens: [],
|
|
220
|
+
comments: this.extractComments(),
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
splitIntoChunks(buffer) {
|
|
224
|
+
// Split by blank lines or dedent
|
|
225
|
+
const lines = buffer.split('\n');
|
|
226
|
+
const chunks = [];
|
|
227
|
+
let currentChunk = [];
|
|
228
|
+
for (const line of lines) {
|
|
229
|
+
if (line.trim() === '' && currentChunk.length > 0) {
|
|
230
|
+
chunks.push(currentChunk.join('\n'));
|
|
231
|
+
currentChunk = [];
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
currentChunk.push(line);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return {
|
|
238
|
+
complete: chunks,
|
|
239
|
+
remaining: currentChunk.join('\n'),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
exports.PythonParser = PythonParser;
|
|
244
|
+
class PythonAnalyzer {
|
|
245
|
+
analyze(ast, context) {
|
|
246
|
+
const symbolTable = this.buildSymbolTable(ast);
|
|
247
|
+
const metrics = this.calculateMetrics(ast);
|
|
248
|
+
const securityIssues = this.findSecurityIssues(ast);
|
|
249
|
+
return {
|
|
250
|
+
symbols: symbolTable,
|
|
251
|
+
callGraph: this.buildCallGraph(ast),
|
|
252
|
+
dataFlow: { definitions: new Map(), uses: new Map(), taintedSources: [], sinks: [] },
|
|
253
|
+
controlFlow: this.buildControlFlow(ast),
|
|
254
|
+
typeInference: new Map(),
|
|
255
|
+
metrics,
|
|
256
|
+
suggestions: this.generateSuggestions(ast, metrics, securityIssues),
|
|
257
|
+
};
|
|
258
|
+
}
|
|
259
|
+
buildSymbolTable(ast) {
|
|
260
|
+
const variables = new Map();
|
|
261
|
+
const functions = new Map();
|
|
262
|
+
const classes = new Map();
|
|
263
|
+
const traverse = (node) => {
|
|
264
|
+
switch (node.type) {
|
|
265
|
+
case 'FunctionDeclaration':
|
|
266
|
+
functions.set(node.metadata.name, {
|
|
267
|
+
name: node.metadata.name,
|
|
268
|
+
signature: this.extractSignature(node),
|
|
269
|
+
parameters: [],
|
|
270
|
+
returnType: 'Any',
|
|
271
|
+
async: false,
|
|
272
|
+
pure: this.isPureFunction(node),
|
|
273
|
+
recursive: this.isRecursive(node),
|
|
274
|
+
complexity: this.calculateComplexity(node),
|
|
275
|
+
callers: [],
|
|
276
|
+
callees: this.findCallees(node),
|
|
277
|
+
});
|
|
278
|
+
break;
|
|
279
|
+
case 'ClassDeclaration':
|
|
280
|
+
classes.set(node.metadata.name, {
|
|
281
|
+
name: node.metadata.name,
|
|
282
|
+
superClass: node.metadata.bases?.[0],
|
|
283
|
+
interfaces: [],
|
|
284
|
+
methods: [],
|
|
285
|
+
properties: [],
|
|
286
|
+
isAbstract: false,
|
|
287
|
+
isFinal: false,
|
|
288
|
+
});
|
|
289
|
+
break;
|
|
290
|
+
case 'VariableDeclaration':
|
|
291
|
+
const varName = this.extractVariableName(node);
|
|
292
|
+
if (varName) {
|
|
293
|
+
variables.set(varName, {
|
|
294
|
+
name: varName,
|
|
295
|
+
type: 'Any',
|
|
296
|
+
mutable: true,
|
|
297
|
+
scope: 'local',
|
|
298
|
+
initialized: true,
|
|
299
|
+
references: [],
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
break;
|
|
303
|
+
}
|
|
304
|
+
node.children.forEach(traverse);
|
|
305
|
+
};
|
|
306
|
+
traverse(ast);
|
|
307
|
+
return {
|
|
308
|
+
variables,
|
|
309
|
+
functions,
|
|
310
|
+
classes,
|
|
311
|
+
modules: new Map(),
|
|
312
|
+
imports: this.extractImports(ast),
|
|
313
|
+
exports: [],
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
extractSignature(node) {
|
|
317
|
+
return `def ${node.metadata.name}(...)`;
|
|
318
|
+
}
|
|
319
|
+
isPureFunction(node) {
|
|
320
|
+
// Check for side effects
|
|
321
|
+
let hasSideEffects = false;
|
|
322
|
+
const checkSideEffects = (n) => {
|
|
323
|
+
if (n.type === 'CallExpression') {
|
|
324
|
+
const funcName = n.metadata.func?.name || '';
|
|
325
|
+
if (['print', 'open', 'write', 'exec', 'eval'].includes(funcName)) {
|
|
326
|
+
hasSideEffects = true;
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
n.children.forEach(checkSideEffects);
|
|
330
|
+
};
|
|
331
|
+
checkSideEffects(node);
|
|
332
|
+
return !hasSideEffects;
|
|
333
|
+
}
|
|
334
|
+
isRecursive(node) {
|
|
335
|
+
const funcName = node.metadata.name;
|
|
336
|
+
let callsSelf = false;
|
|
337
|
+
const checkRecursion = (n) => {
|
|
338
|
+
if (n.type === 'CallExpression' && n.metadata.func?.name === funcName) {
|
|
339
|
+
callsSelf = true;
|
|
340
|
+
}
|
|
341
|
+
n.children.forEach(checkRecursion);
|
|
342
|
+
};
|
|
343
|
+
checkRecursion(node);
|
|
344
|
+
return callsSelf;
|
|
345
|
+
}
|
|
346
|
+
calculateComplexity(node) {
|
|
347
|
+
let branches = 0;
|
|
348
|
+
let nesting = 0;
|
|
349
|
+
let maxNesting = 0;
|
|
350
|
+
const traverse = (n, depth) => {
|
|
351
|
+
maxNesting = Math.max(maxNesting, depth);
|
|
352
|
+
if (['IfStatement', 'ForStatement', 'WhileStatement', 'TryStatement'].includes(n.type)) {
|
|
353
|
+
branches++;
|
|
354
|
+
nesting = depth;
|
|
355
|
+
}
|
|
356
|
+
n.children.forEach(child => traverse(child, depth + 1));
|
|
357
|
+
};
|
|
358
|
+
traverse(node, 0);
|
|
359
|
+
return {
|
|
360
|
+
cyclomatic: branches + 1,
|
|
361
|
+
cognitive: branches + maxNesting,
|
|
362
|
+
nestingDepth: maxNesting,
|
|
363
|
+
parameterCount: node.metadata.args?.length || 0,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
findCallees(node) {
|
|
367
|
+
const callees = [];
|
|
368
|
+
const traverse = (n) => {
|
|
369
|
+
if (n.type === 'CallExpression' && n.metadata.func?.name) {
|
|
370
|
+
callees.push(n.metadata.func.name);
|
|
371
|
+
}
|
|
372
|
+
n.children.forEach(traverse);
|
|
373
|
+
};
|
|
374
|
+
traverse(node);
|
|
375
|
+
return callees;
|
|
376
|
+
}
|
|
377
|
+
extractVariableName(node) {
|
|
378
|
+
// Extract variable name from assignment
|
|
379
|
+
return node.metadata.targets?.[0]?.name || null;
|
|
380
|
+
}
|
|
381
|
+
extractImports(ast) {
|
|
382
|
+
const imports = [];
|
|
383
|
+
const traverse = (node) => {
|
|
384
|
+
if (node.type === 'ImportDeclaration') {
|
|
385
|
+
imports.push({
|
|
386
|
+
source: node.metadata.module || node.metadata.line,
|
|
387
|
+
names: node.metadata.names || [],
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
node.children.forEach(traverse);
|
|
391
|
+
};
|
|
392
|
+
traverse(ast);
|
|
393
|
+
return imports;
|
|
394
|
+
}
|
|
395
|
+
calculateMetrics(ast) {
|
|
396
|
+
let linesOfCode = 0;
|
|
397
|
+
let logicalLines = 0;
|
|
398
|
+
let commentLines = 0;
|
|
399
|
+
let blankLines = 0;
|
|
400
|
+
// This would need access to source
|
|
401
|
+
// Simplified calculation
|
|
402
|
+
const countNodes = (node) => {
|
|
403
|
+
return 1 + node.children.reduce((sum, child) => sum + countNodes(child), 0);
|
|
404
|
+
};
|
|
405
|
+
const totalNodes = countNodes(ast);
|
|
406
|
+
return {
|
|
407
|
+
linesOfCode: totalNodes * 3, // Rough estimate
|
|
408
|
+
logicalLines: Math.floor(totalNodes * 1.5),
|
|
409
|
+
commentLines: 0,
|
|
410
|
+
blankLines: 0,
|
|
411
|
+
cyclomaticComplexity: this.calculateCyclomatic(ast),
|
|
412
|
+
cognitiveComplexity: 0,
|
|
413
|
+
halsteadMetrics: this.calculateHalstead(ast),
|
|
414
|
+
maintainabilityIndex: 70,
|
|
415
|
+
duplicateRate: 0,
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
calculateCyclomatic(ast) {
|
|
419
|
+
let branches = 0;
|
|
420
|
+
const traverse = (node) => {
|
|
421
|
+
if (['IfStatement', 'ForStatement', 'WhileStatement', 'TryStatement', 'ConditionalExpression'].includes(node.type)) {
|
|
422
|
+
branches++;
|
|
423
|
+
}
|
|
424
|
+
node.children.forEach(traverse);
|
|
425
|
+
};
|
|
426
|
+
traverse(ast);
|
|
427
|
+
return branches + 1;
|
|
428
|
+
}
|
|
429
|
+
calculateHalstead(ast) {
|
|
430
|
+
// Simplified Halstead metrics
|
|
431
|
+
const operators = new Set();
|
|
432
|
+
const operands = new Set();
|
|
433
|
+
const traverse = (node) => {
|
|
434
|
+
if (node.type === 'BinaryExpression' || node.type === 'UnaryExpression') {
|
|
435
|
+
operators.add(node.metadata.op);
|
|
436
|
+
}
|
|
437
|
+
if (node.type === 'Identifier' || node.type === 'Literal') {
|
|
438
|
+
operands.add(node.metadata.name || node.metadata.value);
|
|
439
|
+
}
|
|
440
|
+
node.children.forEach(traverse);
|
|
441
|
+
};
|
|
442
|
+
traverse(ast);
|
|
443
|
+
const n1 = operators.size;
|
|
444
|
+
const n2 = operands.size;
|
|
445
|
+
const N1 = operators.size * 2; // Estimated
|
|
446
|
+
const N2 = operands.size * 2;
|
|
447
|
+
const vocabulary = n1 + n2;
|
|
448
|
+
const length = N1 + N2;
|
|
449
|
+
const volume = length * Math.log2(vocabulary || 1);
|
|
450
|
+
const difficulty = (n1 / 2) * (N2 / (n2 || 1));
|
|
451
|
+
const effort = difficulty * volume;
|
|
452
|
+
return {
|
|
453
|
+
operators: n1,
|
|
454
|
+
operands: n2,
|
|
455
|
+
uniqueOperators: n1,
|
|
456
|
+
uniqueOperands: n2,
|
|
457
|
+
volume: Math.round(volume),
|
|
458
|
+
difficulty: Math.round(difficulty),
|
|
459
|
+
effort: Math.round(effort),
|
|
460
|
+
timeToProgram: Math.round(effort / 18),
|
|
461
|
+
bugsDelivered: Math.round(volume / 3000),
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
findSecurityIssues(ast) {
|
|
465
|
+
const issues = [];
|
|
466
|
+
const traverse = (node) => {
|
|
467
|
+
// Check for dangerous functions
|
|
468
|
+
if (node.type === 'CallExpression') {
|
|
469
|
+
const funcName = node.metadata.func?.name || '';
|
|
470
|
+
const funcAttr = node.metadata.func?.attr || '';
|
|
471
|
+
// SQL Injection
|
|
472
|
+
if (['execute', 'executemany', 'raw'].includes(funcName)) {
|
|
473
|
+
const hasStringConcat = this.hasStringConcat(node);
|
|
474
|
+
if (hasStringConcat) {
|
|
475
|
+
issues.push({
|
|
476
|
+
id: 'PY001',
|
|
477
|
+
severity: 'critical',
|
|
478
|
+
category: 'sql-injection',
|
|
479
|
+
location: node.location,
|
|
480
|
+
description: 'Potential SQL injection via string concatenation',
|
|
481
|
+
remediation: 'Use parameterized queries',
|
|
482
|
+
falsePositiveLikelihood: 0.3,
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
// Command Injection
|
|
487
|
+
if (['os.system', 'subprocess.call', 'subprocess.run', 'subprocess.Popen'].includes(funcAttr)) {
|
|
488
|
+
const hasUserInput = this.hasUserInput(node);
|
|
489
|
+
if (hasUserInput) {
|
|
490
|
+
issues.push({
|
|
491
|
+
id: 'PY002',
|
|
492
|
+
severity: 'critical',
|
|
493
|
+
category: 'command-injection',
|
|
494
|
+
location: node.location,
|
|
495
|
+
description: 'Potential command injection',
|
|
496
|
+
remediation: 'Use list arguments instead of shell=True',
|
|
497
|
+
falsePositiveLikelihood: 0.2,
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
// eval/exec
|
|
502
|
+
if (['eval', 'exec', 'compile'].includes(funcName)) {
|
|
503
|
+
issues.push({
|
|
504
|
+
id: 'PY003',
|
|
505
|
+
severity: 'high',
|
|
506
|
+
category: 'insecure-deserialization',
|
|
507
|
+
location: node.location,
|
|
508
|
+
description: 'Dangerous use of eval/exec',
|
|
509
|
+
remediation: 'Use ast.literal_eval for safe evaluation',
|
|
510
|
+
falsePositiveLikelihood: 0.1,
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
// Pickle
|
|
514
|
+
if (['pickle.load', 'pickle.loads', 'cPickle.load'].includes(funcAttr)) {
|
|
515
|
+
issues.push({
|
|
516
|
+
id: 'PY004',
|
|
517
|
+
severity: 'high',
|
|
518
|
+
category: 'insecure-deserialization',
|
|
519
|
+
location: node.location,
|
|
520
|
+
description: 'Unsafe deserialization with pickle',
|
|
521
|
+
remediation: 'Use json or msgpack instead',
|
|
522
|
+
falsePositiveLikelihood: 0.2,
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
// Hardcoded secrets
|
|
526
|
+
if (funcName === 'password' || funcAttr?.includes('password')) {
|
|
527
|
+
const args = node.metadata.args || [];
|
|
528
|
+
for (const arg of args) {
|
|
529
|
+
if (arg.type === 'Literal' && typeof arg.value === 'string' && arg.value.length > 0) {
|
|
530
|
+
issues.push({
|
|
531
|
+
id: 'PY005',
|
|
532
|
+
severity: 'critical',
|
|
533
|
+
category: 'sensitive-data-exposure',
|
|
534
|
+
location: node.location,
|
|
535
|
+
description: 'Potential hardcoded password',
|
|
536
|
+
remediation: 'Use environment variables or secret management',
|
|
537
|
+
falsePositiveLikelihood: 0.4,
|
|
538
|
+
});
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
// Check for debug mode
|
|
544
|
+
if (node.type === 'Assignment' && node.metadata.name === 'DEBUG') {
|
|
545
|
+
if (node.metadata.value === true) {
|
|
546
|
+
issues.push({
|
|
547
|
+
id: 'PY006',
|
|
548
|
+
severity: 'medium',
|
|
549
|
+
category: 'sensitive-data-exposure',
|
|
550
|
+
location: node.location,
|
|
551
|
+
description: 'DEBUG mode enabled',
|
|
552
|
+
remediation: 'Set DEBUG=False in production',
|
|
553
|
+
falsePositiveLikelihood: 0.1,
|
|
554
|
+
});
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
node.children.forEach(traverse);
|
|
558
|
+
};
|
|
559
|
+
traverse(ast);
|
|
560
|
+
return issues;
|
|
561
|
+
}
|
|
562
|
+
hasStringConcat(node) {
|
|
563
|
+
// Check for string concatenation or formatting
|
|
564
|
+
let found = false;
|
|
565
|
+
const traverse = (n) => {
|
|
566
|
+
if (n.type === 'BinaryExpression' && n.metadata.op === '+') {
|
|
567
|
+
found = true;
|
|
568
|
+
}
|
|
569
|
+
if (n.type === 'CallExpression' && ['format', 'f-string'].includes(n.metadata.func?.name)) {
|
|
570
|
+
found = true;
|
|
571
|
+
}
|
|
572
|
+
n.children.forEach(traverse);
|
|
573
|
+
};
|
|
574
|
+
traverse(node);
|
|
575
|
+
return found;
|
|
576
|
+
}
|
|
577
|
+
hasUserInput(node) {
|
|
578
|
+
// Check for user input sources
|
|
579
|
+
let found = false;
|
|
580
|
+
const traverse = (n) => {
|
|
581
|
+
if (n.type === 'CallExpression') {
|
|
582
|
+
const funcName = n.metadata.func?.name || '';
|
|
583
|
+
if (['input', 'request', 'argv'].includes(funcName)) {
|
|
584
|
+
found = true;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
n.children.forEach(traverse);
|
|
588
|
+
};
|
|
589
|
+
traverse(node);
|
|
590
|
+
return found;
|
|
591
|
+
}
|
|
592
|
+
buildCallGraph(ast) {
|
|
593
|
+
const nodes = [];
|
|
594
|
+
const edges = [];
|
|
595
|
+
const traverse = (node) => {
|
|
596
|
+
if (node.type === 'FunctionDeclaration') {
|
|
597
|
+
const funcName = node.metadata.name;
|
|
598
|
+
nodes.push(funcName);
|
|
599
|
+
// Find calls within this function
|
|
600
|
+
const findCalls = (n) => {
|
|
601
|
+
if (n.type === 'CallExpression') {
|
|
602
|
+
const callee = n.metadata.func?.name;
|
|
603
|
+
if (callee) {
|
|
604
|
+
edges.push({ from: funcName, to: callee });
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
n.children.forEach(findCalls);
|
|
608
|
+
};
|
|
609
|
+
findCalls(node);
|
|
610
|
+
}
|
|
611
|
+
node.children.forEach(traverse);
|
|
612
|
+
};
|
|
613
|
+
traverse(ast);
|
|
614
|
+
return { nodes, edges, entryPoints: nodes.filter(n => !edges.some(e => e.to === n)), deadCode: [] };
|
|
615
|
+
}
|
|
616
|
+
buildControlFlow(ast) {
|
|
617
|
+
// Simplified control flow graph
|
|
618
|
+
return {
|
|
619
|
+
nodes: [],
|
|
620
|
+
edges: [],
|
|
621
|
+
loops: [],
|
|
622
|
+
branches: [],
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
generateSuggestions(ast, metrics, issues) {
|
|
626
|
+
const suggestions = [];
|
|
627
|
+
// Complexity suggestions
|
|
628
|
+
if (metrics.cyclomaticComplexity > 10) {
|
|
629
|
+
suggestions.push({
|
|
630
|
+
type: 'refactor',
|
|
631
|
+
message: 'Function is too complex. Consider breaking it down.',
|
|
632
|
+
severity: 'warning',
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
// Security suggestions
|
|
636
|
+
for (const issue of issues) {
|
|
637
|
+
suggestions.push({
|
|
638
|
+
type: 'security',
|
|
639
|
+
message: issue.description,
|
|
640
|
+
severity: issue.severity,
|
|
641
|
+
remediation: issue.remediation,
|
|
642
|
+
});
|
|
643
|
+
}
|
|
644
|
+
return suggestions;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
exports.PythonAnalyzer = PythonAnalyzer;
|
|
648
|
+
exports.PythonLanguageSupport = {
|
|
649
|
+
id: 'python',
|
|
650
|
+
name: 'Python',
|
|
651
|
+
extensions: ['.py', '.pyw', '.pyi'],
|
|
652
|
+
parser: new PythonParser(),
|
|
653
|
+
analyzer: new PythonAnalyzer(),
|
|
654
|
+
};
|
|
655
|
+
//# sourceMappingURL=python.js.map
|