@kernlang/review-python 3.5.2-canary.154.1.c8074966 → 3.5.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.
|
@@ -25,7 +25,7 @@ export function extractErrorHandle(root, source, filePath, nodes) {
|
|
|
25
25
|
// except clauses
|
|
26
26
|
walkNodes(root, 'except_clause', (node) => {
|
|
27
27
|
const block = node.children.find((c) => c.type === 'block');
|
|
28
|
-
const disposition = classifyPythonDisposition(block, source);
|
|
28
|
+
const disposition = classifyPythonDisposition(node, block, source);
|
|
29
29
|
const errorVar = extractExceptVar(node);
|
|
30
30
|
nodes.push({
|
|
31
31
|
id: conceptId(filePath, 'error_handle', node.startIndex),
|
|
@@ -43,22 +43,29 @@ export function extractErrorHandle(root, source, filePath, nodes) {
|
|
|
43
43
|
});
|
|
44
44
|
});
|
|
45
45
|
}
|
|
46
|
-
function classifyPythonDisposition(block, source) {
|
|
46
|
+
function classifyPythonDisposition(exceptNode, block, source) {
|
|
47
47
|
if (!block)
|
|
48
48
|
return { type: 'ignored', confidence: 1.0 };
|
|
49
49
|
const children = block.namedChildren;
|
|
50
50
|
// except: pass → ignored
|
|
51
51
|
if (children.length === 1 && children[0].type === 'pass_statement') {
|
|
52
|
+
if (isIntentionalNoopExcept(exceptNode, source))
|
|
53
|
+
return { type: 'wrapped', confidence: 0.55 };
|
|
52
54
|
return { type: 'ignored', confidence: 1.0 };
|
|
53
55
|
}
|
|
54
56
|
// except: ... (ellipsis) → ignored
|
|
55
57
|
if (children.length === 1 && children[0].type === 'expression_statement') {
|
|
56
58
|
const text = source.substring(children[0].startIndex, children[0].endIndex).trim();
|
|
57
|
-
if (text === '...')
|
|
59
|
+
if (text === '...') {
|
|
60
|
+
if (isIntentionalNoopExcept(exceptNode, source))
|
|
61
|
+
return { type: 'wrapped', confidence: 0.55 };
|
|
58
62
|
return { type: 'ignored', confidence: 1.0 };
|
|
63
|
+
}
|
|
59
64
|
}
|
|
60
65
|
// Empty block
|
|
61
66
|
if (children.length === 0) {
|
|
67
|
+
if (isIntentionalNoopExcept(exceptNode, source))
|
|
68
|
+
return { type: 'wrapped', confidence: 0.55 };
|
|
62
69
|
return { type: 'ignored', confidence: 1.0 };
|
|
63
70
|
}
|
|
64
71
|
const bodyText = source.substring(block.startIndex, block.endIndex);
|
|
@@ -82,6 +89,57 @@ function classifyPythonDisposition(block, source) {
|
|
|
82
89
|
}
|
|
83
90
|
return { type: 'wrapped', confidence: 0.5 };
|
|
84
91
|
}
|
|
92
|
+
function isIntentionalNoopExcept(exceptNode, source) {
|
|
93
|
+
const block = exceptNode.children.find((child) => child.type === 'block');
|
|
94
|
+
const headerEnd = block?.startIndex ?? exceptNode.endIndex;
|
|
95
|
+
const exceptTypes = parseExceptTypes(source.substring(exceptNode.startIndex, headerEnd));
|
|
96
|
+
if (exceptTypes.length === 0)
|
|
97
|
+
return false;
|
|
98
|
+
const trySource = exceptNode.parent ? source.substring(exceptNode.parent.startIndex, exceptNode.startIndex) : '';
|
|
99
|
+
const tryBody = trySource
|
|
100
|
+
.split('\n')
|
|
101
|
+
.map((line) => line.trim())
|
|
102
|
+
.filter((line) => line && !line.startsWith('#') && !/^try\s*:/.test(line));
|
|
103
|
+
const hasOnly = (allowed) => exceptTypes.every((t) => allowed.includes(t));
|
|
104
|
+
if (hasOnly(['ProcessLookupError']) && tryBody.length === 1 && /\bos\.kill\s*\(/.test(tryBody[0]))
|
|
105
|
+
return true;
|
|
106
|
+
if (hasOnly(['ChildProcessError']) && tryBody.length === 1 && /\bos\.waitpid\s*\(/.test(tryBody[0]))
|
|
107
|
+
return true;
|
|
108
|
+
if (hasOnly(['FileNotFoundError']) &&
|
|
109
|
+
tryBody.length === 1 &&
|
|
110
|
+
/\b(os\.)?(unlink|remove|rmdir)\s*\(/.test(tryBody[0])) {
|
|
111
|
+
return true;
|
|
112
|
+
}
|
|
113
|
+
if (hasOnly(['OSError']) && tryBody.length === 1 && /\bos\.close\s*\(/.test(tryBody[0]))
|
|
114
|
+
return true;
|
|
115
|
+
if (hasOnly(['OSError']) &&
|
|
116
|
+
tryBody.length > 0 &&
|
|
117
|
+
tryBody.every((line) => /^(\w+\s*=\s*)?fcntl\.fcntl\s*\(/.test(line))) {
|
|
118
|
+
return true;
|
|
119
|
+
}
|
|
120
|
+
if (hasOnly(['BrokenPipeError']) && tryBody.length === 1 && /\.(write|flush|close)\s*\(/.test(tryBody[0]))
|
|
121
|
+
return true;
|
|
122
|
+
if (hasOnly(['AttributeError', 'ImportError', 'ValueError']) &&
|
|
123
|
+
tryBody.length === 1 &&
|
|
124
|
+
/\b(importlib|faulthandler\.register|signal\.SIG[A-Z0-9_]+|getattr|hasattr|ctypes|fcntl)\b/.test(tryBody[0])) {
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
return false;
|
|
128
|
+
}
|
|
129
|
+
function parseExceptTypes(line) {
|
|
130
|
+
const match = line.trim().match(/^except\s*(?:\(([\s\S]*?)\)|([A-Za-z_][\w.]*))?/);
|
|
131
|
+
if (!match)
|
|
132
|
+
return [];
|
|
133
|
+
const raw = match[1] ?? match[2] ?? '';
|
|
134
|
+
return raw
|
|
135
|
+
.split(',')
|
|
136
|
+
.map((part) => part
|
|
137
|
+
.trim()
|
|
138
|
+
.replace(/\s+as\s+\w+$/, '')
|
|
139
|
+
.split('.')
|
|
140
|
+
.pop() ?? '')
|
|
141
|
+
.filter(Boolean);
|
|
142
|
+
}
|
|
85
143
|
function extractRaiseType(node) {
|
|
86
144
|
// raise ValueError("...") → "ValueError"
|
|
87
145
|
const callNode = node.namedChildren.find((c) => c.type === 'call');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@kernlang/review-python",
|
|
3
|
-
"version": "3.5.2
|
|
3
|
+
"version": "3.5.2",
|
|
4
4
|
"description": "Python concept mapper for kern review — tree-sitter based",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -8,8 +8,8 @@
|
|
|
8
8
|
"dependencies": {
|
|
9
9
|
"tree-sitter": "^0.25.0",
|
|
10
10
|
"tree-sitter-python": "^0.25.0",
|
|
11
|
-
"@kernlang/core": "3.5.2
|
|
12
|
-
"@kernlang/review": "3.5.2
|
|
11
|
+
"@kernlang/core": "3.5.2",
|
|
12
|
+
"@kernlang/review": "3.5.2"
|
|
13
13
|
},
|
|
14
14
|
"devDependencies": {
|
|
15
15
|
"ts-morph": "^28.0.0",
|
|
@@ -39,7 +39,7 @@ export function extractErrorHandle(
|
|
|
39
39
|
// except clauses
|
|
40
40
|
walkNodes(root, 'except_clause', (node) => {
|
|
41
41
|
const block = node.children.find((c) => c.type === 'block');
|
|
42
|
-
const disposition = classifyPythonDisposition(block, source);
|
|
42
|
+
const disposition = classifyPythonDisposition(node, block, source);
|
|
43
43
|
const errorVar = extractExceptVar(node);
|
|
44
44
|
|
|
45
45
|
nodes.push({
|
|
@@ -60,6 +60,7 @@ export function extractErrorHandle(
|
|
|
60
60
|
}
|
|
61
61
|
|
|
62
62
|
function classifyPythonDisposition(
|
|
63
|
+
exceptNode: Parser.SyntaxNode,
|
|
63
64
|
block: Parser.SyntaxNode | undefined,
|
|
64
65
|
source: string,
|
|
65
66
|
): { type: ErrorHandlePayload['disposition']; confidence: number } {
|
|
@@ -69,17 +70,22 @@ function classifyPythonDisposition(
|
|
|
69
70
|
|
|
70
71
|
// except: pass → ignored
|
|
71
72
|
if (children.length === 1 && children[0].type === 'pass_statement') {
|
|
73
|
+
if (isIntentionalNoopExcept(exceptNode, source)) return { type: 'wrapped', confidence: 0.55 };
|
|
72
74
|
return { type: 'ignored', confidence: 1.0 };
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
// except: ... (ellipsis) → ignored
|
|
76
78
|
if (children.length === 1 && children[0].type === 'expression_statement') {
|
|
77
79
|
const text = source.substring(children[0].startIndex, children[0].endIndex).trim();
|
|
78
|
-
if (text === '...')
|
|
80
|
+
if (text === '...') {
|
|
81
|
+
if (isIntentionalNoopExcept(exceptNode, source)) return { type: 'wrapped', confidence: 0.55 };
|
|
82
|
+
return { type: 'ignored', confidence: 1.0 };
|
|
83
|
+
}
|
|
79
84
|
}
|
|
80
85
|
|
|
81
86
|
// Empty block
|
|
82
87
|
if (children.length === 0) {
|
|
88
|
+
if (isIntentionalNoopExcept(exceptNode, source)) return { type: 'wrapped', confidence: 0.55 };
|
|
83
89
|
return { type: 'ignored', confidence: 1.0 };
|
|
84
90
|
}
|
|
85
91
|
|
|
@@ -108,6 +114,66 @@ function classifyPythonDisposition(
|
|
|
108
114
|
return { type: 'wrapped', confidence: 0.5 };
|
|
109
115
|
}
|
|
110
116
|
|
|
117
|
+
function isIntentionalNoopExcept(exceptNode: Parser.SyntaxNode, source: string): boolean {
|
|
118
|
+
const block = exceptNode.children.find((child) => child.type === 'block');
|
|
119
|
+
const headerEnd = block?.startIndex ?? exceptNode.endIndex;
|
|
120
|
+
const exceptTypes = parseExceptTypes(source.substring(exceptNode.startIndex, headerEnd));
|
|
121
|
+
if (exceptTypes.length === 0) return false;
|
|
122
|
+
|
|
123
|
+
const trySource = exceptNode.parent ? source.substring(exceptNode.parent.startIndex, exceptNode.startIndex) : '';
|
|
124
|
+
const tryBody = trySource
|
|
125
|
+
.split('\n')
|
|
126
|
+
.map((line) => line.trim())
|
|
127
|
+
.filter((line) => line && !line.startsWith('#') && !/^try\s*:/.test(line));
|
|
128
|
+
const hasOnly = (allowed: readonly string[]) => exceptTypes.every((t) => allowed.includes(t));
|
|
129
|
+
|
|
130
|
+
if (hasOnly(['ProcessLookupError']) && tryBody.length === 1 && /\bos\.kill\s*\(/.test(tryBody[0])) return true;
|
|
131
|
+
if (hasOnly(['ChildProcessError']) && tryBody.length === 1 && /\bos\.waitpid\s*\(/.test(tryBody[0])) return true;
|
|
132
|
+
if (
|
|
133
|
+
hasOnly(['FileNotFoundError']) &&
|
|
134
|
+
tryBody.length === 1 &&
|
|
135
|
+
/\b(os\.)?(unlink|remove|rmdir)\s*\(/.test(tryBody[0])
|
|
136
|
+
) {
|
|
137
|
+
return true;
|
|
138
|
+
}
|
|
139
|
+
if (hasOnly(['OSError']) && tryBody.length === 1 && /\bos\.close\s*\(/.test(tryBody[0])) return true;
|
|
140
|
+
if (
|
|
141
|
+
hasOnly(['OSError']) &&
|
|
142
|
+
tryBody.length > 0 &&
|
|
143
|
+
tryBody.every((line) => /^(\w+\s*=\s*)?fcntl\.fcntl\s*\(/.test(line))
|
|
144
|
+
) {
|
|
145
|
+
return true;
|
|
146
|
+
}
|
|
147
|
+
if (hasOnly(['BrokenPipeError']) && tryBody.length === 1 && /\.(write|flush|close)\s*\(/.test(tryBody[0]))
|
|
148
|
+
return true;
|
|
149
|
+
if (
|
|
150
|
+
hasOnly(['AttributeError', 'ImportError', 'ValueError']) &&
|
|
151
|
+
tryBody.length === 1 &&
|
|
152
|
+
/\b(importlib|faulthandler\.register|signal\.SIG[A-Z0-9_]+|getattr|hasattr|ctypes|fcntl)\b/.test(tryBody[0])
|
|
153
|
+
) {
|
|
154
|
+
return true;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function parseExceptTypes(line: string): string[] {
|
|
161
|
+
const match = line.trim().match(/^except\s*(?:\(([\s\S]*?)\)|([A-Za-z_][\w.]*))?/);
|
|
162
|
+
if (!match) return [];
|
|
163
|
+
const raw = match[1] ?? match[2] ?? '';
|
|
164
|
+
return raw
|
|
165
|
+
.split(',')
|
|
166
|
+
.map(
|
|
167
|
+
(part) =>
|
|
168
|
+
part
|
|
169
|
+
.trim()
|
|
170
|
+
.replace(/\s+as\s+\w+$/, '')
|
|
171
|
+
.split('.')
|
|
172
|
+
.pop() ?? '',
|
|
173
|
+
)
|
|
174
|
+
.filter(Boolean);
|
|
175
|
+
}
|
|
176
|
+
|
|
111
177
|
function extractRaiseType(node: Parser.SyntaxNode): string | undefined {
|
|
112
178
|
// raise ValueError("...") → "ValueError"
|
|
113
179
|
const callNode = node.namedChildren.find((c) => c.type === 'call');
|