@skillsmith/core 0.2.0 → 2.0.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/README.md +233 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/src/analysis/__tests__/incremental.test.d.ts +13 -0
- package/dist/src/analysis/__tests__/incremental.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/incremental.test.js +515 -0
- package/dist/src/analysis/__tests__/incremental.test.js.map +1 -0
- package/dist/src/analysis/__tests__/integration.test.d.ts +14 -0
- package/dist/src/analysis/__tests__/integration.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/integration.test.js +1059 -0
- package/dist/src/analysis/__tests__/integration.test.js.map +1 -0
- package/dist/src/analysis/__tests__/metrics.test.d.ts +9 -0
- package/dist/src/analysis/__tests__/metrics.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/metrics.test.js +369 -0
- package/dist/src/analysis/__tests__/metrics.test.js.map +1 -0
- package/dist/src/analysis/__tests__/performance.test.d.ts +15 -0
- package/dist/src/analysis/__tests__/performance.test.d.ts.map +1 -0
- package/dist/src/analysis/__tests__/performance.test.js +402 -0
- package/dist/src/analysis/__tests__/performance.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/go.test.d.ts +12 -0
- package/dist/src/analysis/adapters/__tests__/go.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/go.test.js +561 -0
- package/dist/src/analysis/adapters/__tests__/go.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts +11 -0
- package/dist/src/analysis/adapters/__tests__/python.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/python.test.js +669 -0
- package/dist/src/analysis/adapters/__tests__/python.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.d.ts +12 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.js +676 -0
- package/dist/src/analysis/adapters/__tests__/rust.test.js.map +1 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts +14 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.d.ts.map +1 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.js +381 -0
- package/dist/src/analysis/adapters/__tests__/typescript.test.js.map +1 -0
- package/dist/src/analysis/adapters/base.d.ts +83 -0
- package/dist/src/analysis/adapters/base.d.ts.map +1 -0
- package/dist/src/analysis/adapters/base.js +40 -0
- package/dist/src/analysis/adapters/base.js.map +1 -0
- package/dist/src/analysis/adapters/factory.d.ts +150 -0
- package/dist/src/analysis/adapters/factory.d.ts.map +1 -0
- package/dist/src/analysis/adapters/factory.js +244 -0
- package/dist/src/analysis/adapters/factory.js.map +1 -0
- package/dist/src/analysis/adapters/go.d.ts +131 -0
- package/dist/src/analysis/adapters/go.d.ts.map +1 -0
- package/dist/src/analysis/adapters/go.js +414 -0
- package/dist/src/analysis/adapters/go.js.map +1 -0
- package/dist/src/analysis/adapters/index.d.ts +20 -0
- package/dist/src/analysis/adapters/index.d.ts.map +1 -0
- package/dist/src/analysis/adapters/index.js +23 -0
- package/dist/src/analysis/adapters/index.js.map +1 -0
- package/dist/src/analysis/adapters/java.d.ts +154 -0
- package/dist/src/analysis/adapters/java.d.ts.map +1 -0
- package/dist/src/analysis/adapters/java.js +407 -0
- package/dist/src/analysis/adapters/java.js.map +1 -0
- package/dist/src/analysis/adapters/python.d.ts +165 -0
- package/dist/src/analysis/adapters/python.d.ts.map +1 -0
- package/dist/src/analysis/adapters/python.js +475 -0
- package/dist/src/analysis/adapters/python.js.map +1 -0
- package/dist/src/analysis/adapters/rust.d.ts +116 -0
- package/dist/src/analysis/adapters/rust.d.ts.map +1 -0
- package/dist/src/analysis/adapters/rust.js +476 -0
- package/dist/src/analysis/adapters/rust.js.map +1 -0
- package/dist/src/analysis/adapters/typescript.d.ts +68 -0
- package/dist/src/analysis/adapters/typescript.d.ts.map +1 -0
- package/dist/src/analysis/adapters/typescript.js +79 -0
- package/dist/src/analysis/adapters/typescript.js.map +1 -0
- package/dist/src/analysis/aggregator.d.ts +193 -0
- package/dist/src/analysis/aggregator.d.ts.map +1 -0
- package/dist/src/analysis/aggregator.js +283 -0
- package/dist/src/analysis/aggregator.js.map +1 -0
- package/dist/src/analysis/cache.d.ts +180 -0
- package/dist/src/analysis/cache.d.ts.map +1 -0
- package/dist/src/analysis/cache.js +279 -0
- package/dist/src/analysis/cache.js.map +1 -0
- package/dist/src/analysis/file-streamer.d.ts +136 -0
- package/dist/src/analysis/file-streamer.d.ts.map +1 -0
- package/dist/src/analysis/file-streamer.js +291 -0
- package/dist/src/analysis/file-streamer.js.map +1 -0
- package/dist/src/analysis/incremental-parser.d.ts +186 -0
- package/dist/src/analysis/incremental-parser.d.ts.map +1 -0
- package/dist/src/analysis/incremental-parser.js +291 -0
- package/dist/src/analysis/incremental-parser.js.map +1 -0
- package/dist/src/analysis/incremental.d.ts +186 -0
- package/dist/src/analysis/incremental.d.ts.map +1 -0
- package/dist/src/analysis/incremental.js +247 -0
- package/dist/src/analysis/incremental.js.map +1 -0
- package/dist/src/analysis/index.d.ts +25 -3
- package/dist/src/analysis/index.d.ts.map +1 -1
- package/dist/src/analysis/index.js +45 -3
- package/dist/src/analysis/index.js.map +1 -1
- package/dist/src/analysis/language-detector.d.ts +92 -0
- package/dist/src/analysis/language-detector.d.ts.map +1 -0
- package/dist/src/analysis/language-detector.js +602 -0
- package/dist/src/analysis/language-detector.js.map +1 -0
- package/dist/src/analysis/memory-monitor.d.ts +199 -0
- package/dist/src/analysis/memory-monitor.d.ts.map +1 -0
- package/dist/src/analysis/memory-monitor.js +271 -0
- package/dist/src/analysis/memory-monitor.js.map +1 -0
- package/dist/src/analysis/metrics.d.ts +300 -0
- package/dist/src/analysis/metrics.d.ts.map +1 -0
- package/dist/src/analysis/metrics.js +537 -0
- package/dist/src/analysis/metrics.js.map +1 -0
- package/dist/src/analysis/router.d.ts +264 -0
- package/dist/src/analysis/router.d.ts.map +1 -0
- package/dist/src/analysis/router.js +398 -0
- package/dist/src/analysis/router.js.map +1 -0
- package/dist/src/analysis/tree-cache.d.ts +208 -0
- package/dist/src/analysis/tree-cache.d.ts.map +1 -0
- package/dist/src/analysis/tree-cache.js +288 -0
- package/dist/src/analysis/tree-cache.js.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts +141 -0
- package/dist/src/analysis/tree-sitter/manager.d.ts.map +1 -0
- package/dist/src/analysis/tree-sitter/manager.js +239 -0
- package/dist/src/analysis/tree-sitter/manager.js.map +1 -0
- package/dist/src/analysis/types.d.ts +69 -6
- package/dist/src/analysis/types.d.ts.map +1 -1
- package/dist/src/analysis/types.js +23 -2
- package/dist/src/analysis/types.js.map +1 -1
- package/dist/src/analysis/worker-pool.d.ts +141 -0
- package/dist/src/analysis/worker-pool.d.ts.map +1 -0
- package/dist/src/analysis/worker-pool.js +418 -0
- package/dist/src/analysis/worker-pool.js.map +1 -0
- package/dist/src/analytics/schema.d.ts +1 -1
- package/dist/src/analytics/schema.d.ts.map +1 -1
- package/dist/src/analytics/schema.js +72 -0
- package/dist/src/analytics/schema.js.map +1 -1
- package/dist/src/api/cache.d.ts +24 -1
- package/dist/src/api/cache.d.ts.map +1 -1
- package/dist/src/api/cache.js +50 -2
- package/dist/src/api/cache.js.map +1 -1
- package/dist/src/api/client.d.ts +132 -2
- package/dist/src/api/client.d.ts.map +1 -1
- package/dist/src/api/client.js +214 -18
- package/dist/src/api/client.js.map +1 -1
- package/dist/src/api/index.d.ts +2 -0
- package/dist/src/api/index.d.ts.map +1 -1
- package/dist/src/api/index.js +7 -0
- package/dist/src/api/index.js.map +1 -1
- package/dist/src/api/types.d.ts +251 -0
- package/dist/src/api/types.d.ts.map +1 -0
- package/dist/src/api/types.js +9 -0
- package/dist/src/api/types.js.map +1 -0
- package/dist/src/benchmarks/memory/MemoryProfiler.d.ts.map +1 -1
- package/dist/src/benchmarks/memory/MemoryProfiler.js.map +1 -1
- package/dist/src/embeddings/index.d.ts.map +1 -1
- package/dist/src/embeddings/index.js.map +1 -1
- package/dist/src/errors.d.ts +1 -0
- package/dist/src/errors.d.ts.map +1 -1
- package/dist/src/errors.js +1 -0
- package/dist/src/errors.js.map +1 -1
- package/dist/src/index.d.ts +3 -3
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/repositories/IndexerRepository.d.ts.map +1 -1
- package/dist/src/repositories/IndexerRepository.js +1 -0
- package/dist/src/repositories/IndexerRepository.js.map +1 -1
- package/dist/src/repositories/SkillRepository.d.ts.map +1 -1
- package/dist/src/repositories/SkillRepository.js +1 -0
- package/dist/src/repositories/SkillRepository.js.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/QuarantineRepository.js.map +1 -1
- package/dist/src/repositories/quarantine/query-builder.d.ts.map +1 -1
- package/dist/src/repositories/quarantine/query-builder.js +1 -1
- package/dist/src/repositories/quarantine/query-builder.js.map +1 -1
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js +3 -3
- package/dist/src/scripts/__tests__/scan-imported-skills.test.js.map +1 -1
- package/dist/src/scripts/github-import/index.js.map +1 -1
- package/dist/src/scripts/import-github-skills.js +1 -1
- package/dist/src/scripts/import-github-skills.js.map +1 -1
- package/dist/src/scripts/skill-scanner/reporter.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/reporter.js.map +1 -1
- package/dist/src/scripts/skill-scanner/scanner.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/scanner.js.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.d.ts.map +1 -1
- package/dist/src/scripts/skill-scanner/trust-scorer.js.map +1 -1
- package/dist/src/scripts/validation/index.js +1 -2
- package/dist/src/scripts/validation/index.js.map +1 -1
- package/dist/src/scripts/validation/pipeline.d.ts.map +1 -1
- package/dist/src/scripts/validation/pipeline.js.map +1 -1
- package/dist/src/scripts/validation/types.d.ts +2 -2
- package/dist/src/security/scanner/SecurityScanner.d.ts.map +1 -1
- package/dist/src/security/scanner/SecurityScanner.js.map +1 -1
- package/dist/src/services/SearchService.d.ts.map +1 -1
- package/dist/src/services/SearchService.js +1 -0
- package/dist/src/services/SearchService.js.map +1 -1
- package/dist/src/session/SessionHealthMonitor.d.ts +1 -1
- package/dist/src/session/SessionHealthMonitor.d.ts.map +1 -1
- package/dist/src/session/SessionHealthMonitor.js +1 -1
- package/dist/src/session/SessionHealthMonitor.js.map +1 -1
- package/dist/src/telemetry/index.d.ts +1 -1
- package/dist/src/telemetry/index.d.ts.map +1 -1
- package/dist/src/telemetry/index.js +2 -2
- package/dist/src/telemetry/index.js.map +1 -1
- package/dist/src/telemetry/posthog.d.ts +27 -5
- package/dist/src/telemetry/posthog.d.ts.map +1 -1
- package/dist/src/telemetry/posthog.js +20 -5
- package/dist/src/telemetry/posthog.js.map +1 -1
- package/dist/src/types/skill.d.ts +3 -0
- package/dist/src/types/skill.d.ts.map +1 -1
- package/dist/src/types.d.ts +2 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/types.js +2 -2
- package/dist/src/types.js.map +1 -1
- package/dist/tests/adapters-factory.test.d.ts +13 -0
- package/dist/tests/adapters-factory.test.d.ts.map +1 -0
- package/dist/tests/adapters-factory.test.js +308 -0
- package/dist/tests/adapters-factory.test.js.map +1 -0
- package/dist/tests/adapters-java.test.d.ts +13 -0
- package/dist/tests/adapters-java.test.d.ts.map +1 -0
- package/dist/tests/adapters-java.test.js +925 -0
- package/dist/tests/adapters-java.test.js.map +1 -0
- package/dist/tests/api/client.validation.test.d.ts +7 -0
- package/dist/tests/api/client.validation.test.d.ts.map +1 -0
- package/dist/tests/api/client.validation.test.js +183 -0
- package/dist/tests/api/client.validation.test.js.map +1 -0
- package/dist/tests/language-detector.test.d.ts +13 -0
- package/dist/tests/language-detector.test.d.ts.map +1 -0
- package/dist/tests/language-detector.test.js +674 -0
- package/dist/tests/language-detector.test.js.map +1 -0
- package/dist/tests/telemetry/posthog.test.d.ts +13 -0
- package/dist/tests/telemetry/posthog.test.d.ts.map +1 -0
- package/dist/tests/telemetry/posthog.test.js +600 -0
- package/dist/tests/telemetry/posthog.test.js.map +1 -0
- package/package.json +5 -6
- package/dist/src/security/RateLimiter.d.ts +0 -337
- package/dist/src/security/RateLimiter.d.ts.map +0 -1
- package/dist/src/security/RateLimiter.js +0 -782
- package/dist/src/security/RateLimiter.js.map +0 -1
- package/dist/src/security/scanner.d.ts +0 -151
- package/dist/src/security/scanner.d.ts.map +0 -1
- package/dist/src/security/scanner.js +0 -599
- package/dist/src/security/scanner.js.map +0 -1
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SMI-1304: Python Language Adapter Tests
|
|
3
|
+
*
|
|
4
|
+
* Comprehensive test suite for the PythonAdapter class.
|
|
5
|
+
* Tests cover import/export extraction, function detection,
|
|
6
|
+
* and framework detection rules.
|
|
7
|
+
*
|
|
8
|
+
* @see docs/architecture/multi-language-analysis.md
|
|
9
|
+
*/
|
|
10
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
11
|
+
import { PythonAdapter } from '../python.js';
|
|
12
|
+
describe('PythonAdapter', () => {
|
|
13
|
+
let adapter;
|
|
14
|
+
beforeEach(() => {
|
|
15
|
+
adapter = new PythonAdapter();
|
|
16
|
+
});
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
adapter.dispose();
|
|
19
|
+
});
|
|
20
|
+
// ============================================================
|
|
21
|
+
// canHandle Tests
|
|
22
|
+
// ============================================================
|
|
23
|
+
describe('canHandle', () => {
|
|
24
|
+
it('handles .py files', () => {
|
|
25
|
+
expect(adapter.canHandle('main.py')).toBe(true);
|
|
26
|
+
expect(adapter.canHandle('/path/to/script.py')).toBe(true);
|
|
27
|
+
});
|
|
28
|
+
it('handles .pyi stub files', () => {
|
|
29
|
+
expect(adapter.canHandle('types.pyi')).toBe(true);
|
|
30
|
+
expect(adapter.canHandle('/path/to/stubs.pyi')).toBe(true);
|
|
31
|
+
});
|
|
32
|
+
it('handles .pyw Windows files', () => {
|
|
33
|
+
expect(adapter.canHandle('gui_app.pyw')).toBe(true);
|
|
34
|
+
});
|
|
35
|
+
it('does not handle other file types', () => {
|
|
36
|
+
expect(adapter.canHandle('main.ts')).toBe(false);
|
|
37
|
+
expect(adapter.canHandle('main.js')).toBe(false);
|
|
38
|
+
expect(adapter.canHandle('main.go')).toBe(false);
|
|
39
|
+
expect(adapter.canHandle('main.rs')).toBe(false);
|
|
40
|
+
expect(adapter.canHandle('main.java')).toBe(false);
|
|
41
|
+
expect(adapter.canHandle('main.txt')).toBe(false);
|
|
42
|
+
});
|
|
43
|
+
it('handles case insensitively (for cross-platform compatibility)', () => {
|
|
44
|
+
// Extensions are normalized to lowercase for cross-platform support
|
|
45
|
+
expect(adapter.canHandle('main.PY')).toBe(true);
|
|
46
|
+
expect(adapter.canHandle('main.Py')).toBe(true);
|
|
47
|
+
expect(adapter.canHandle('main.PYI')).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
// ============================================================
|
|
51
|
+
// parseFile - Import Extraction Tests
|
|
52
|
+
// ============================================================
|
|
53
|
+
describe('parseFile - imports', () => {
|
|
54
|
+
it('extracts simple import statements', () => {
|
|
55
|
+
const content = `
|
|
56
|
+
import os
|
|
57
|
+
import sys
|
|
58
|
+
import json
|
|
59
|
+
`;
|
|
60
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
61
|
+
expect(result.imports).toHaveLength(3);
|
|
62
|
+
expect(result.imports[0]).toMatchObject({
|
|
63
|
+
module: 'os',
|
|
64
|
+
namedImports: [],
|
|
65
|
+
isTypeOnly: false,
|
|
66
|
+
sourceFile: 'test.py',
|
|
67
|
+
});
|
|
68
|
+
expect(result.imports[1]).toMatchObject({
|
|
69
|
+
module: 'sys',
|
|
70
|
+
namedImports: [],
|
|
71
|
+
});
|
|
72
|
+
expect(result.imports[2]).toMatchObject({
|
|
73
|
+
module: 'json',
|
|
74
|
+
namedImports: [],
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
it('extracts import with alias', () => {
|
|
78
|
+
const content = `
|
|
79
|
+
import numpy as np
|
|
80
|
+
import pandas as pd
|
|
81
|
+
`;
|
|
82
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
83
|
+
expect(result.imports).toHaveLength(2);
|
|
84
|
+
expect(result.imports[0]).toMatchObject({
|
|
85
|
+
module: 'numpy',
|
|
86
|
+
defaultImport: 'np',
|
|
87
|
+
});
|
|
88
|
+
expect(result.imports[1]).toMatchObject({
|
|
89
|
+
module: 'pandas',
|
|
90
|
+
defaultImport: 'pd',
|
|
91
|
+
});
|
|
92
|
+
});
|
|
93
|
+
it('extracts from imports with named imports', () => {
|
|
94
|
+
const content = `
|
|
95
|
+
from typing import List, Optional, Dict
|
|
96
|
+
from collections import defaultdict
|
|
97
|
+
`;
|
|
98
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
99
|
+
expect(result.imports).toHaveLength(2);
|
|
100
|
+
expect(result.imports[0]).toMatchObject({
|
|
101
|
+
module: 'typing',
|
|
102
|
+
namedImports: ['List', 'Optional', 'Dict'],
|
|
103
|
+
});
|
|
104
|
+
expect(result.imports[1]).toMatchObject({
|
|
105
|
+
module: 'collections',
|
|
106
|
+
namedImports: ['defaultdict'],
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
it('extracts from imports with aliases', () => {
|
|
110
|
+
const content = `
|
|
111
|
+
from os.path import join as path_join, exists as path_exists
|
|
112
|
+
`;
|
|
113
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
114
|
+
expect(result.imports).toHaveLength(1);
|
|
115
|
+
expect(result.imports[0]).toMatchObject({
|
|
116
|
+
module: 'os.path',
|
|
117
|
+
namedImports: ['join', 'exists'],
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
it('extracts wildcard imports', () => {
|
|
121
|
+
const content = `
|
|
122
|
+
from os import *
|
|
123
|
+
`;
|
|
124
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
125
|
+
expect(result.imports).toHaveLength(1);
|
|
126
|
+
expect(result.imports[0]).toMatchObject({
|
|
127
|
+
module: 'os',
|
|
128
|
+
namedImports: [],
|
|
129
|
+
namespaceImport: '*',
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
it('extracts multi-line imports with parentheses', () => {
|
|
133
|
+
const content = `
|
|
134
|
+
from django.http import (
|
|
135
|
+
HttpResponse,
|
|
136
|
+
HttpResponseRedirect,
|
|
137
|
+
JsonResponse,
|
|
138
|
+
)
|
|
139
|
+
`;
|
|
140
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
141
|
+
expect(result.imports).toHaveLength(1);
|
|
142
|
+
expect(result.imports[0].module).toBe('django.http');
|
|
143
|
+
expect(result.imports[0].namedImports).toContain('HttpResponse');
|
|
144
|
+
expect(result.imports[0].namedImports).toContain('HttpResponseRedirect');
|
|
145
|
+
expect(result.imports[0].namedImports).toContain('JsonResponse');
|
|
146
|
+
});
|
|
147
|
+
it('extracts dotted module names', () => {
|
|
148
|
+
const content = `
|
|
149
|
+
import os.path
|
|
150
|
+
from django.db.models import Model
|
|
151
|
+
`;
|
|
152
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
153
|
+
expect(result.imports).toHaveLength(2);
|
|
154
|
+
expect(result.imports[0].module).toBe('os.path');
|
|
155
|
+
expect(result.imports[1].module).toBe('django.db.models');
|
|
156
|
+
});
|
|
157
|
+
it('skips comments and empty lines', () => {
|
|
158
|
+
const content = `
|
|
159
|
+
# This is a comment
|
|
160
|
+
import os
|
|
161
|
+
|
|
162
|
+
# Another comment
|
|
163
|
+
import sys
|
|
164
|
+
`;
|
|
165
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
166
|
+
expect(result.imports).toHaveLength(2);
|
|
167
|
+
});
|
|
168
|
+
it('includes line numbers', () => {
|
|
169
|
+
const content = `import os
|
|
170
|
+
import sys
|
|
171
|
+
from typing import List
|
|
172
|
+
`;
|
|
173
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
174
|
+
expect(result.imports[0].line).toBe(1);
|
|
175
|
+
expect(result.imports[1].line).toBe(2);
|
|
176
|
+
expect(result.imports[2].line).toBe(3);
|
|
177
|
+
});
|
|
178
|
+
});
|
|
179
|
+
// ============================================================
|
|
180
|
+
// parseFile - Export Extraction Tests
|
|
181
|
+
// ============================================================
|
|
182
|
+
describe('parseFile - exports', () => {
|
|
183
|
+
it('extracts __all__ explicit exports', () => {
|
|
184
|
+
const content = `
|
|
185
|
+
__all__ = ['public_func', 'PublicClass', 'CONSTANT']
|
|
186
|
+
|
|
187
|
+
def public_func():
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def _private_func():
|
|
191
|
+
pass
|
|
192
|
+
|
|
193
|
+
class PublicClass:
|
|
194
|
+
pass
|
|
195
|
+
`;
|
|
196
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
197
|
+
// Should have __all__ exports plus top-level public definitions
|
|
198
|
+
const exportNames = result.exports.map((e) => e.name);
|
|
199
|
+
expect(exportNames).toContain('public_func');
|
|
200
|
+
expect(exportNames).toContain('PublicClass');
|
|
201
|
+
expect(exportNames).toContain('CONSTANT');
|
|
202
|
+
// Private functions should not be in exports
|
|
203
|
+
expect(exportNames).not.toContain('_private_func');
|
|
204
|
+
});
|
|
205
|
+
it('extracts top-level function exports', () => {
|
|
206
|
+
const content = `
|
|
207
|
+
def public_function():
|
|
208
|
+
pass
|
|
209
|
+
|
|
210
|
+
def another_public():
|
|
211
|
+
pass
|
|
212
|
+
|
|
213
|
+
def _private():
|
|
214
|
+
pass
|
|
215
|
+
`;
|
|
216
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
217
|
+
const exportNames = result.exports.map((e) => e.name);
|
|
218
|
+
expect(exportNames).toContain('public_function');
|
|
219
|
+
expect(exportNames).toContain('another_public');
|
|
220
|
+
expect(exportNames).not.toContain('_private');
|
|
221
|
+
});
|
|
222
|
+
it('extracts top-level class exports', () => {
|
|
223
|
+
const content = `
|
|
224
|
+
class PublicClass:
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
class AnotherPublic:
|
|
228
|
+
pass
|
|
229
|
+
|
|
230
|
+
class _PrivateClass:
|
|
231
|
+
pass
|
|
232
|
+
`;
|
|
233
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
234
|
+
const exportNames = result.exports.map((e) => e.name);
|
|
235
|
+
expect(exportNames).toContain('PublicClass');
|
|
236
|
+
expect(exportNames).toContain('AnotherPublic');
|
|
237
|
+
expect(exportNames).not.toContain('_PrivateClass');
|
|
238
|
+
});
|
|
239
|
+
it('identifies export kinds correctly', () => {
|
|
240
|
+
const content = `
|
|
241
|
+
def my_function():
|
|
242
|
+
pass
|
|
243
|
+
|
|
244
|
+
class MyClass:
|
|
245
|
+
pass
|
|
246
|
+
`;
|
|
247
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
248
|
+
const funcExport = result.exports.find((e) => e.name === 'my_function');
|
|
249
|
+
const classExport = result.exports.find((e) => e.name === 'MyClass');
|
|
250
|
+
expect(funcExport?.kind).toBe('function');
|
|
251
|
+
expect(classExport?.kind).toBe('class');
|
|
252
|
+
});
|
|
253
|
+
it('does not export methods (indented functions)', () => {
|
|
254
|
+
const content = `
|
|
255
|
+
class MyClass:
|
|
256
|
+
def method(self):
|
|
257
|
+
pass
|
|
258
|
+
|
|
259
|
+
def _private_method(self):
|
|
260
|
+
pass
|
|
261
|
+
`;
|
|
262
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
263
|
+
const exportNames = result.exports.map((e) => e.name);
|
|
264
|
+
expect(exportNames).not.toContain('method');
|
|
265
|
+
expect(exportNames).not.toContain('_private_method');
|
|
266
|
+
});
|
|
267
|
+
it('handles async function exports', () => {
|
|
268
|
+
const content = `
|
|
269
|
+
async def async_handler():
|
|
270
|
+
pass
|
|
271
|
+
`;
|
|
272
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
273
|
+
const exportNames = result.exports.map((e) => e.name);
|
|
274
|
+
expect(exportNames).toContain('async_handler');
|
|
275
|
+
});
|
|
276
|
+
});
|
|
277
|
+
// ============================================================
|
|
278
|
+
// parseFile - Function Extraction Tests
|
|
279
|
+
// ============================================================
|
|
280
|
+
describe('parseFile - functions', () => {
|
|
281
|
+
it('extracts sync function definitions', () => {
|
|
282
|
+
const content = `
|
|
283
|
+
def sync_function(a, b, c):
|
|
284
|
+
pass
|
|
285
|
+
`;
|
|
286
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
287
|
+
expect(result.functions).toHaveLength(1);
|
|
288
|
+
expect(result.functions[0]).toMatchObject({
|
|
289
|
+
name: 'sync_function',
|
|
290
|
+
parameterCount: 3,
|
|
291
|
+
isAsync: false,
|
|
292
|
+
isExported: true,
|
|
293
|
+
sourceFile: 'test.py',
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
it('extracts async function definitions', () => {
|
|
297
|
+
const content = `
|
|
298
|
+
async def async_function(x):
|
|
299
|
+
pass
|
|
300
|
+
`;
|
|
301
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
302
|
+
expect(result.functions).toHaveLength(1);
|
|
303
|
+
expect(result.functions[0]).toMatchObject({
|
|
304
|
+
name: 'async_function',
|
|
305
|
+
parameterCount: 1,
|
|
306
|
+
isAsync: true,
|
|
307
|
+
isExported: true,
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
it('excludes self and cls from parameter count', () => {
|
|
311
|
+
const content = `
|
|
312
|
+
class MyClass:
|
|
313
|
+
def instance_method(self, a, b):
|
|
314
|
+
pass
|
|
315
|
+
|
|
316
|
+
@classmethod
|
|
317
|
+
def class_method(cls, x):
|
|
318
|
+
pass
|
|
319
|
+
|
|
320
|
+
@staticmethod
|
|
321
|
+
def static_method(y, z):
|
|
322
|
+
pass
|
|
323
|
+
`;
|
|
324
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
325
|
+
const instanceMethod = result.functions.find((f) => f.name === 'instance_method');
|
|
326
|
+
const classMethod = result.functions.find((f) => f.name === 'class_method');
|
|
327
|
+
const staticMethod = result.functions.find((f) => f.name === 'static_method');
|
|
328
|
+
expect(instanceMethod?.parameterCount).toBe(2); // a, b (not self)
|
|
329
|
+
expect(classMethod?.parameterCount).toBe(1); // x (not cls)
|
|
330
|
+
expect(staticMethod?.parameterCount).toBe(2); // y, z
|
|
331
|
+
});
|
|
332
|
+
it('marks top-level functions as exported', () => {
|
|
333
|
+
const content = `
|
|
334
|
+
def top_level():
|
|
335
|
+
pass
|
|
336
|
+
|
|
337
|
+
class MyClass:
|
|
338
|
+
def method(self):
|
|
339
|
+
pass
|
|
340
|
+
`;
|
|
341
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
342
|
+
const topLevel = result.functions.find((f) => f.name === 'top_level');
|
|
343
|
+
const method = result.functions.find((f) => f.name === 'method');
|
|
344
|
+
expect(topLevel?.isExported).toBe(true);
|
|
345
|
+
expect(method?.isExported).toBe(false);
|
|
346
|
+
});
|
|
347
|
+
it('marks private functions as not exported', () => {
|
|
348
|
+
const content = `
|
|
349
|
+
def _private_function():
|
|
350
|
+
pass
|
|
351
|
+
|
|
352
|
+
def __dunder_function__():
|
|
353
|
+
pass
|
|
354
|
+
`;
|
|
355
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
356
|
+
const privateFunc = result.functions.find((f) => f.name === '_private_function');
|
|
357
|
+
const dunderFunc = result.functions.find((f) => f.name === '__dunder_function__');
|
|
358
|
+
expect(privateFunc?.isExported).toBe(false);
|
|
359
|
+
expect(dunderFunc?.isExported).toBe(false);
|
|
360
|
+
});
|
|
361
|
+
it('handles functions with no parameters', () => {
|
|
362
|
+
const content = `
|
|
363
|
+
def no_params():
|
|
364
|
+
pass
|
|
365
|
+
`;
|
|
366
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
367
|
+
expect(result.functions[0].parameterCount).toBe(0);
|
|
368
|
+
});
|
|
369
|
+
it('includes line numbers', () => {
|
|
370
|
+
const content = `def first():
|
|
371
|
+
pass
|
|
372
|
+
|
|
373
|
+
def second():
|
|
374
|
+
pass
|
|
375
|
+
`;
|
|
376
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
377
|
+
expect(result.functions[0].line).toBe(1);
|
|
378
|
+
expect(result.functions[1].line).toBe(4);
|
|
379
|
+
});
|
|
380
|
+
it('handles functions with default parameters', () => {
|
|
381
|
+
const content = `
|
|
382
|
+
def with_defaults(a, b=10, c="hello"):
|
|
383
|
+
pass
|
|
384
|
+
`;
|
|
385
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
386
|
+
// Parameter count includes all params, regardless of defaults
|
|
387
|
+
expect(result.functions[0].parameterCount).toBe(3);
|
|
388
|
+
});
|
|
389
|
+
it('handles functions with *args and **kwargs', () => {
|
|
390
|
+
const content = `
|
|
391
|
+
def with_varargs(a, *args, **kwargs):
|
|
392
|
+
pass
|
|
393
|
+
`;
|
|
394
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
395
|
+
// *args and **kwargs are counted as parameters
|
|
396
|
+
expect(result.functions[0].parameterCount).toBe(3);
|
|
397
|
+
});
|
|
398
|
+
});
|
|
399
|
+
// ============================================================
|
|
400
|
+
// parseFile - Combined Tests
|
|
401
|
+
// ============================================================
|
|
402
|
+
describe('parseFile - complete parsing', () => {
|
|
403
|
+
it('parses a complete Django view file', () => {
|
|
404
|
+
const content = `
|
|
405
|
+
from django.http import HttpResponse, JsonResponse
|
|
406
|
+
from django.views import View
|
|
407
|
+
from .models import User
|
|
408
|
+
|
|
409
|
+
class UserView(View):
|
|
410
|
+
def get(self, request, user_id):
|
|
411
|
+
user = User.objects.get(id=user_id)
|
|
412
|
+
return JsonResponse({'name': user.name})
|
|
413
|
+
|
|
414
|
+
def post(self, request):
|
|
415
|
+
pass
|
|
416
|
+
|
|
417
|
+
def index(request):
|
|
418
|
+
return HttpResponse("Hello")
|
|
419
|
+
`;
|
|
420
|
+
const result = adapter.parseFile(content, 'views.py');
|
|
421
|
+
// Check imports
|
|
422
|
+
expect(result.imports).toHaveLength(3);
|
|
423
|
+
expect(result.imports[0].namedImports).toContain('HttpResponse');
|
|
424
|
+
expect(result.imports[0].namedImports).toContain('JsonResponse');
|
|
425
|
+
// Check exports
|
|
426
|
+
const exportNames = result.exports.map((e) => e.name);
|
|
427
|
+
expect(exportNames).toContain('UserView');
|
|
428
|
+
expect(exportNames).toContain('index');
|
|
429
|
+
// Check functions
|
|
430
|
+
expect(result.functions.length).toBeGreaterThanOrEqual(3);
|
|
431
|
+
});
|
|
432
|
+
it('parses a FastAPI application', () => {
|
|
433
|
+
const content = `
|
|
434
|
+
from fastapi import FastAPI, HTTPException
|
|
435
|
+
from pydantic import BaseModel
|
|
436
|
+
|
|
437
|
+
app = FastAPI()
|
|
438
|
+
|
|
439
|
+
class Item(BaseModel):
|
|
440
|
+
name: str
|
|
441
|
+
price: float
|
|
442
|
+
|
|
443
|
+
@app.get("/")
|
|
444
|
+
async def root():
|
|
445
|
+
return {"message": "Hello World"}
|
|
446
|
+
|
|
447
|
+
@app.post("/items/")
|
|
448
|
+
async def create_item(item: Item):
|
|
449
|
+
return item
|
|
450
|
+
`;
|
|
451
|
+
const result = adapter.parseFile(content, 'main.py');
|
|
452
|
+
// Check imports
|
|
453
|
+
expect(result.imports.some((i) => i.module === 'fastapi')).toBe(true);
|
|
454
|
+
expect(result.imports.some((i) => i.module === 'pydantic')).toBe(true);
|
|
455
|
+
// Check async functions
|
|
456
|
+
const asyncFuncs = result.functions.filter((f) => f.isAsync);
|
|
457
|
+
expect(asyncFuncs.length).toBe(2);
|
|
458
|
+
expect(asyncFuncs.map((f) => f.name)).toContain('root');
|
|
459
|
+
expect(asyncFuncs.map((f) => f.name)).toContain('create_item');
|
|
460
|
+
});
|
|
461
|
+
it('returns empty arrays for empty file', () => {
|
|
462
|
+
const result = adapter.parseFile('', 'empty.py');
|
|
463
|
+
expect(result.imports).toEqual([]);
|
|
464
|
+
expect(result.exports).toEqual([]);
|
|
465
|
+
expect(result.functions).toEqual([]);
|
|
466
|
+
});
|
|
467
|
+
it('handles file with only comments', () => {
|
|
468
|
+
const content = `
|
|
469
|
+
# This is a comment
|
|
470
|
+
# Another comment
|
|
471
|
+
"""
|
|
472
|
+
This is a docstring-style comment
|
|
473
|
+
"""
|
|
474
|
+
`;
|
|
475
|
+
const result = adapter.parseFile(content, 'comments.py');
|
|
476
|
+
expect(result.imports).toEqual([]);
|
|
477
|
+
expect(result.exports).toEqual([]);
|
|
478
|
+
expect(result.functions).toEqual([]);
|
|
479
|
+
});
|
|
480
|
+
});
|
|
481
|
+
// ============================================================
|
|
482
|
+
// getFrameworkRules Tests
|
|
483
|
+
// ============================================================
|
|
484
|
+
describe('getFrameworkRules', () => {
|
|
485
|
+
it('includes Django detection rules', () => {
|
|
486
|
+
const rules = adapter.getFrameworkRules();
|
|
487
|
+
const django = rules.find((r) => r.name === 'Django');
|
|
488
|
+
expect(django).toBeDefined();
|
|
489
|
+
expect(django?.depIndicators).toContain('django');
|
|
490
|
+
expect(django?.importIndicators).toContain('django');
|
|
491
|
+
expect(django?.importIndicators).toContain('django.http');
|
|
492
|
+
});
|
|
493
|
+
it('includes FastAPI detection rules', () => {
|
|
494
|
+
const rules = adapter.getFrameworkRules();
|
|
495
|
+
const fastapi = rules.find((r) => r.name === 'FastAPI');
|
|
496
|
+
expect(fastapi).toBeDefined();
|
|
497
|
+
expect(fastapi?.depIndicators).toContain('fastapi');
|
|
498
|
+
expect(fastapi?.importIndicators).toContain('fastapi');
|
|
499
|
+
expect(fastapi?.importIndicators).toContain('starlette');
|
|
500
|
+
});
|
|
501
|
+
it('includes Flask detection rules', () => {
|
|
502
|
+
const rules = adapter.getFrameworkRules();
|
|
503
|
+
const flask = rules.find((r) => r.name === 'Flask');
|
|
504
|
+
expect(flask).toBeDefined();
|
|
505
|
+
expect(flask?.depIndicators).toContain('flask');
|
|
506
|
+
});
|
|
507
|
+
it('includes pytest detection rules', () => {
|
|
508
|
+
const rules = adapter.getFrameworkRules();
|
|
509
|
+
const pytest = rules.find((r) => r.name === 'pytest');
|
|
510
|
+
expect(pytest).toBeDefined();
|
|
511
|
+
expect(pytest?.depIndicators).toContain('pytest');
|
|
512
|
+
});
|
|
513
|
+
it('includes data science library rules', () => {
|
|
514
|
+
const rules = adapter.getFrameworkRules();
|
|
515
|
+
const pandas = rules.find((r) => r.name === 'pandas');
|
|
516
|
+
const numpy = rules.find((r) => r.name === 'numpy');
|
|
517
|
+
expect(pandas).toBeDefined();
|
|
518
|
+
expect(numpy).toBeDefined();
|
|
519
|
+
expect(pandas?.importIndicators).toContain('pd');
|
|
520
|
+
expect(numpy?.importIndicators).toContain('np');
|
|
521
|
+
});
|
|
522
|
+
it('includes ML framework rules', () => {
|
|
523
|
+
const rules = adapter.getFrameworkRules();
|
|
524
|
+
const tensorflow = rules.find((r) => r.name === 'TensorFlow');
|
|
525
|
+
const pytorch = rules.find((r) => r.name === 'PyTorch');
|
|
526
|
+
expect(tensorflow).toBeDefined();
|
|
527
|
+
expect(pytorch).toBeDefined();
|
|
528
|
+
});
|
|
529
|
+
it('returns non-empty array', () => {
|
|
530
|
+
const rules = adapter.getFrameworkRules();
|
|
531
|
+
expect(rules.length).toBeGreaterThan(0);
|
|
532
|
+
});
|
|
533
|
+
});
|
|
534
|
+
// ============================================================
|
|
535
|
+
// parseIncremental Tests
|
|
536
|
+
// ============================================================
|
|
537
|
+
describe('parseIncremental', () => {
|
|
538
|
+
it('falls back to full parsing without previous tree', () => {
|
|
539
|
+
const content = `
|
|
540
|
+
def my_function():
|
|
541
|
+
pass
|
|
542
|
+
`;
|
|
543
|
+
const result = adapter.parseIncremental(content, 'test.py');
|
|
544
|
+
expect(result.functions).toHaveLength(1);
|
|
545
|
+
expect(result.functions[0].name).toBe('my_function');
|
|
546
|
+
});
|
|
547
|
+
it('handles incremental updates', () => {
|
|
548
|
+
const content1 = `
|
|
549
|
+
def original():
|
|
550
|
+
pass
|
|
551
|
+
`;
|
|
552
|
+
const content2 = `
|
|
553
|
+
def original():
|
|
554
|
+
pass
|
|
555
|
+
|
|
556
|
+
def new_function():
|
|
557
|
+
pass
|
|
558
|
+
`;
|
|
559
|
+
const result1 = adapter.parseIncremental(content1, 'test.py');
|
|
560
|
+
const result2 = adapter.parseIncremental(content2, 'test.py');
|
|
561
|
+
expect(result1.functions).toHaveLength(1);
|
|
562
|
+
expect(result2.functions).toHaveLength(2);
|
|
563
|
+
});
|
|
564
|
+
});
|
|
565
|
+
// ============================================================
|
|
566
|
+
// dispose Tests
|
|
567
|
+
// ============================================================
|
|
568
|
+
describe('dispose', () => {
|
|
569
|
+
it('can be called multiple times without error', () => {
|
|
570
|
+
adapter.dispose();
|
|
571
|
+
adapter.dispose();
|
|
572
|
+
adapter.dispose();
|
|
573
|
+
// Should not throw
|
|
574
|
+
expect(true).toBe(true);
|
|
575
|
+
});
|
|
576
|
+
it('cleans up parser resources', () => {
|
|
577
|
+
// Parse something to initialize any internal state
|
|
578
|
+
adapter.parseFile('import os', 'test.py');
|
|
579
|
+
// Dispose should clean up
|
|
580
|
+
adapter.dispose();
|
|
581
|
+
// Should still be able to parse after dispose
|
|
582
|
+
const result = adapter.parseFile('import sys', 'test.py');
|
|
583
|
+
expect(result.imports).toHaveLength(1);
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
// ============================================================
|
|
587
|
+
// Language and Extensions Properties Tests
|
|
588
|
+
// ============================================================
|
|
589
|
+
describe('properties', () => {
|
|
590
|
+
it('has correct language identifier', () => {
|
|
591
|
+
expect(adapter.language).toBe('python');
|
|
592
|
+
});
|
|
593
|
+
it('has correct extensions', () => {
|
|
594
|
+
expect(adapter.extensions).toContain('.py');
|
|
595
|
+
expect(adapter.extensions).toContain('.pyi');
|
|
596
|
+
expect(adapter.extensions).toContain('.pyw');
|
|
597
|
+
expect(adapter.extensions).toHaveLength(3);
|
|
598
|
+
});
|
|
599
|
+
});
|
|
600
|
+
// ============================================================
|
|
601
|
+
// Edge Cases
|
|
602
|
+
// ============================================================
|
|
603
|
+
describe('edge cases', () => {
|
|
604
|
+
it('handles deeply nested functions', () => {
|
|
605
|
+
const content = `
|
|
606
|
+
def outer():
|
|
607
|
+
def middle():
|
|
608
|
+
def inner():
|
|
609
|
+
pass
|
|
610
|
+
return inner
|
|
611
|
+
return middle
|
|
612
|
+
`;
|
|
613
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
614
|
+
// Should find all functions (including nested)
|
|
615
|
+
const funcNames = result.functions.map((f) => f.name);
|
|
616
|
+
expect(funcNames).toContain('outer');
|
|
617
|
+
expect(funcNames).toContain('middle');
|
|
618
|
+
expect(funcNames).toContain('inner');
|
|
619
|
+
});
|
|
620
|
+
it('handles decorators before functions', () => {
|
|
621
|
+
const content = `
|
|
622
|
+
@decorator
|
|
623
|
+
def decorated():
|
|
624
|
+
pass
|
|
625
|
+
|
|
626
|
+
@decorator1
|
|
627
|
+
@decorator2
|
|
628
|
+
def multi_decorated():
|
|
629
|
+
pass
|
|
630
|
+
`;
|
|
631
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
632
|
+
expect(result.functions).toHaveLength(2);
|
|
633
|
+
});
|
|
634
|
+
it('handles type hints in function signatures', () => {
|
|
635
|
+
const content = `
|
|
636
|
+
def typed_function(a: int, b: str, c: Optional[List[int]]) -> Dict[str, Any]:
|
|
637
|
+
pass
|
|
638
|
+
`;
|
|
639
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
640
|
+
expect(result.functions).toHaveLength(1);
|
|
641
|
+
expect(result.functions[0].parameterCount).toBe(3);
|
|
642
|
+
});
|
|
643
|
+
it('handles multiline function signatures', () => {
|
|
644
|
+
const content = `
|
|
645
|
+
def long_function(
|
|
646
|
+
param1,
|
|
647
|
+
param2,
|
|
648
|
+
param3
|
|
649
|
+
):
|
|
650
|
+
pass
|
|
651
|
+
`;
|
|
652
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
653
|
+
// Note: Current regex implementation may not handle this perfectly
|
|
654
|
+
// but should at least find the function
|
|
655
|
+
expect(result.functions.length).toBeGreaterThanOrEqual(0);
|
|
656
|
+
});
|
|
657
|
+
it('handles relative imports', () => {
|
|
658
|
+
const content = `
|
|
659
|
+
from . import module
|
|
660
|
+
from .. import parent_module
|
|
661
|
+
from .sibling import function
|
|
662
|
+
`;
|
|
663
|
+
const result = adapter.parseFile(content, 'test.py');
|
|
664
|
+
// Current implementation may handle this differently
|
|
665
|
+
expect(result.imports.length).toBeGreaterThanOrEqual(0);
|
|
666
|
+
});
|
|
667
|
+
});
|
|
668
|
+
});
|
|
669
|
+
//# sourceMappingURL=python.test.js.map
|