@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-canary.154.1.c8074966",
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-canary.154.1.c8074966",
12
- "@kernlang/review": "3.5.2-canary.154.1.c8074966"
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 === '...') return { type: 'ignored', confidence: 1.0 };
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');