@kernlang/review-python 0.2.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.
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Bilingual Tests v2 — new concepts: entrypoint, guard, state_mutation, dependency
3
+ *
4
+ * Same concept, two languages, same shape.
5
+ */
6
+
7
+ import { extractPythonConcepts } from '../src/mapper.js';
8
+ import { extractTsConcepts } from '@kernlang/review';
9
+ import { Project } from 'ts-morph';
10
+
11
+ function tsSourceFile(source: string, filePath = 'test.ts') {
12
+ const project = new Project({ useInMemoryFileSystem: true, compilerOptions: { strict: true } });
13
+ return project.createSourceFile(filePath, source);
14
+ }
15
+
16
+ describe('Bilingual: entrypoint', () => {
17
+ it('TS app.get route → entrypoint(route)', () => {
18
+ const sf = tsSourceFile(`
19
+ app.get('/users', (req, res) => { res.json([]); });
20
+ `);
21
+ const concepts = extractTsConcepts(sf, 'test.ts');
22
+ const ep = concepts.nodes.find(n => n.kind === 'entrypoint');
23
+ expect(ep).toBeDefined();
24
+ expect(ep!.payload.kind).toBe('entrypoint');
25
+ if (ep!.payload.kind === 'entrypoint') {
26
+ expect(ep!.payload.subtype).toBe('route');
27
+ }
28
+ });
29
+
30
+ it('Python @app.route → same entrypoint(route)', () => {
31
+ const source = `
32
+ from flask import Flask
33
+ app = Flask(__name__)
34
+
35
+ @app.route('/users')
36
+ def get_users():
37
+ return []
38
+ `;
39
+ const concepts = extractPythonConcepts(source, 'test.py');
40
+ const ep = concepts.nodes.find(n => n.kind === 'entrypoint');
41
+ expect(ep).toBeDefined();
42
+ expect(ep!.payload.kind).toBe('entrypoint');
43
+ if (ep!.payload.kind === 'entrypoint') {
44
+ expect(ep!.payload.subtype).toBe('route');
45
+ }
46
+ });
47
+ });
48
+
49
+ describe('Bilingual: guard', () => {
50
+ it('TS auth early-return → guard(auth)', () => {
51
+ const sf = tsSourceFile(`
52
+ function handler(req, res) {
53
+ if (!req.user) return res.status(401).send('Unauthorized');
54
+ doWork();
55
+ }
56
+ `);
57
+ const concepts = extractTsConcepts(sf, 'test.ts');
58
+ const guard = concepts.nodes.find(n => n.kind === 'guard');
59
+ expect(guard).toBeDefined();
60
+ expect(guard!.payload.kind).toBe('guard');
61
+ if (guard!.payload.kind === 'guard') {
62
+ expect(guard!.payload.subtype).toBe('auth');
63
+ }
64
+ });
65
+
66
+ it('Python @login_required → same guard(auth)', () => {
67
+ const source = `
68
+ from django.contrib.auth.decorators import login_required
69
+
70
+ @login_required
71
+ def dashboard(request):
72
+ return render(request, 'dashboard.html')
73
+ `;
74
+ const concepts = extractPythonConcepts(source, 'test.py');
75
+ const guard = concepts.nodes.find(n => n.kind === 'guard');
76
+ expect(guard).toBeDefined();
77
+ expect(guard!.payload.kind).toBe('guard');
78
+ if (guard!.payload.kind === 'guard') {
79
+ expect(guard!.payload.subtype).toBe('auth');
80
+ }
81
+ });
82
+ });
83
+
84
+ describe('Bilingual: state_mutation', () => {
85
+ it('TS this.count++ → state_mutation(module)', () => {
86
+ const sf = tsSourceFile(`
87
+ class Counter {
88
+ count = 0;
89
+ increment() { this.count++; }
90
+ }
91
+ `);
92
+ const concepts = extractTsConcepts(sf, 'test.ts');
93
+ const mut = concepts.nodes.find(n => n.kind === 'state_mutation');
94
+ expect(mut).toBeDefined();
95
+ if (mut!.payload.kind === 'state_mutation') {
96
+ expect(mut!.payload.scope).toBe('module');
97
+ }
98
+ });
99
+
100
+ it('Python self.count += 1 → same state_mutation(module)', () => {
101
+ const source = `
102
+ class Counter:
103
+ def __init__(self):
104
+ self.count = 0
105
+ def increment(self):
106
+ self.count += 1
107
+ `;
108
+ const concepts = extractPythonConcepts(source, 'test.py');
109
+ const mut = concepts.nodes.find(n => n.kind === 'state_mutation');
110
+ expect(mut).toBeDefined();
111
+ if (mut!.payload.kind === 'state_mutation') {
112
+ expect(mut!.payload.scope).toBe('module');
113
+ }
114
+ });
115
+ });
116
+
117
+ describe('Bilingual: dependency edges', () => {
118
+ it('TS import → dependency edge', () => {
119
+ const sf = tsSourceFile(`
120
+ import express from 'express';
121
+ import { readFile } from 'fs';
122
+ import { helper } from './utils.js';
123
+ `);
124
+ const concepts = extractTsConcepts(sf, 'test.ts');
125
+ expect(concepts.edges.length).toBeGreaterThanOrEqual(3);
126
+
127
+ const external = concepts.edges.find(e => e.payload.kind === 'dependency' && e.payload.subtype === 'external');
128
+ const stdlib = concepts.edges.find(e => e.payload.kind === 'dependency' && e.payload.subtype === 'stdlib');
129
+ const internal = concepts.edges.find(e => e.payload.kind === 'dependency' && e.payload.subtype === 'internal');
130
+
131
+ expect(external).toBeDefined();
132
+ expect(stdlib).toBeDefined();
133
+ expect(internal).toBeDefined();
134
+ });
135
+
136
+ it('Python import → same dependency edge shape', () => {
137
+ const source = `
138
+ import os
139
+ import requests
140
+ from .utils import helper
141
+ `;
142
+ const concepts = extractPythonConcepts(source, 'test.py');
143
+ expect(concepts.edges.length).toBeGreaterThanOrEqual(3);
144
+
145
+ const external = concepts.edges.find(e => e.payload.kind === 'dependency' && e.payload.subtype === 'external');
146
+ const stdlib = concepts.edges.find(e => e.payload.kind === 'dependency' && e.payload.subtype === 'stdlib');
147
+ const internal = concepts.edges.find(e => e.payload.kind === 'dependency' && e.payload.subtype === 'internal');
148
+
149
+ expect(external).toBeDefined();
150
+ expect(stdlib).toBeDefined();
151
+ expect(internal).toBeDefined();
152
+ });
153
+ });
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Bilingual Tests — same concept rule, two languages, same findings.
3
+ *
4
+ * This is the proof that KERN concepts are universal.
5
+ */
6
+
7
+ import { extractPythonConcepts } from '../src/mapper.js';
8
+ import { extractTsConcepts, runConceptRules } from '@kernlang/review';
9
+ import { Project } from 'ts-morph';
10
+
11
+ function tsSourceFile(source: string, filePath = 'test.ts') {
12
+ const project = new Project({ useInMemoryFileSystem: true, compilerOptions: { strict: true } });
13
+ return project.createSourceFile(filePath, source);
14
+ }
15
+
16
+ describe('Bilingual: ignored-error', () => {
17
+ it('TS empty catch → ignored-error finding', () => {
18
+ const sf = tsSourceFile('try { doWork(); } catch (e) {}');
19
+ const concepts = extractTsConcepts(sf, 'test.ts');
20
+ const findings = runConceptRules(concepts, 'test.ts');
21
+ const f = findings.find(f => f.ruleId === 'ignored-error');
22
+ expect(f).toBeDefined();
23
+ expect(f!.severity).toBe('error');
24
+ });
25
+
26
+ it('Python except:pass → same ignored-error finding', () => {
27
+ const source = `
28
+ try:
29
+ do_work()
30
+ except:
31
+ pass
32
+ `;
33
+ const concepts = extractPythonConcepts(source, 'test.py');
34
+ const findings = runConceptRules(concepts, 'test.py');
35
+ const f = findings.find(f => f.ruleId === 'ignored-error');
36
+ expect(f).toBeDefined();
37
+ expect(f!.severity).toBe('error');
38
+ });
39
+
40
+ it('Python except Exception as e: ... → same ignored-error finding', () => {
41
+ const source = `
42
+ try:
43
+ do_work()
44
+ except Exception as e:
45
+ ...
46
+ `;
47
+ const concepts = extractPythonConcepts(source, 'test.py');
48
+ const findings = runConceptRules(concepts, 'test.py');
49
+ const f = findings.find(f => f.ruleId === 'ignored-error');
50
+ expect(f).toBeDefined();
51
+ });
52
+
53
+ it('TS catch with handler → NO finding', () => {
54
+ const sf = tsSourceFile('try { doWork(); } catch (e) { throw new AppError(e); }');
55
+ const concepts = extractTsConcepts(sf, 'test.ts');
56
+ const findings = runConceptRules(concepts, 'test.ts');
57
+ const f = findings.find(f => f.ruleId === 'ignored-error');
58
+ expect(f).toBeUndefined();
59
+ });
60
+
61
+ it('Python except with raise → NO finding', () => {
62
+ const source = `
63
+ try:
64
+ do_work()
65
+ except Exception as e:
66
+ raise AppError(e)
67
+ `;
68
+ const concepts = extractPythonConcepts(source, 'test.py');
69
+ const findings = runConceptRules(concepts, 'test.py');
70
+ const f = findings.find(f => f.ruleId === 'ignored-error');
71
+ expect(f).toBeUndefined();
72
+ });
73
+ });
74
+
75
+ describe('Bilingual: concept parity', () => {
76
+ it('TS throw and Python raise produce same concept shape', () => {
77
+ const tsSf = tsSourceFile('function fail() { throw new Error("boom"); }');
78
+ const tsConcepts = extractTsConcepts(tsSf, 'test.ts');
79
+
80
+ const pyConcepts = extractPythonConcepts('def fail():\n raise ValueError("boom")', 'test.py');
81
+
82
+ const tsRaise = tsConcepts.nodes.find(n => n.kind === 'error_raise');
83
+ const pyRaise = pyConcepts.nodes.find(n => n.kind === 'error_raise');
84
+
85
+ expect(tsRaise).toBeDefined();
86
+ expect(pyRaise).toBeDefined();
87
+ expect(tsRaise!.kind).toBe(pyRaise!.kind);
88
+ expect(tsRaise!.payload.kind).toBe(pyRaise!.payload.kind);
89
+ if (tsRaise!.payload.kind === 'error_raise' && pyRaise!.payload.kind === 'error_raise') {
90
+ expect(tsRaise!.payload.subtype).toBe('throw');
91
+ expect(pyRaise!.payload.subtype).toBe('throw');
92
+ }
93
+ });
94
+
95
+ it('TS fetch and Python requests.get produce same effect concept', () => {
96
+ const tsSf = tsSourceFile('async function getData() { await fetch("/api"); }');
97
+ const tsConcepts = extractTsConcepts(tsSf, 'test.ts');
98
+
99
+ const pyConcepts = extractPythonConcepts('def get_data():\n requests.get("/api")', 'test.py');
100
+
101
+ const tsEffect = tsConcepts.nodes.find(n => n.kind === 'effect');
102
+ const pyEffect = pyConcepts.nodes.find(n => n.kind === 'effect');
103
+
104
+ expect(tsEffect).toBeDefined();
105
+ expect(pyEffect).toBeDefined();
106
+ expect(tsEffect!.kind).toBe(pyEffect!.kind);
107
+ if (tsEffect!.payload.kind === 'effect' && pyEffect!.payload.kind === 'effect') {
108
+ expect(tsEffect!.payload.subtype).toBe('network');
109
+ expect(pyEffect!.payload.subtype).toBe('network');
110
+ }
111
+ });
112
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "NodeNext",
5
+ "moduleResolution": "NodeNext",
6
+ "declaration": true,
7
+ "outDir": "dist",
8
+ "rootDir": "src",
9
+ "strict": true,
10
+ "esModuleInterop": true,
11
+ "skipLibCheck": true,
12
+ "resolveJsonModule": true
13
+ },
14
+ "include": ["src"],
15
+ "references": [
16
+ { "path": "../core" },
17
+ { "path": "../review" }
18
+ ]
19
+ }
@@ -0,0 +1 @@
1
+ {"root":["./src/index.ts","./src/mapper.ts"],"version":"5.9.3"}