projscan 0.9.1 → 0.10.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.
Files changed (66) hide show
  1. package/README.md +14 -2
  2. package/dist/analyzers/deadCodeCheck.d.ts +10 -12
  3. package/dist/analyzers/deadCodeCheck.js +41 -69
  4. package/dist/analyzers/deadCodeCheck.js.map +1 -1
  5. package/dist/analyzers/pythonDependencyRiskCheck.d.ts +2 -0
  6. package/dist/analyzers/pythonDependencyRiskCheck.js +114 -0
  7. package/dist/analyzers/pythonDependencyRiskCheck.js.map +1 -0
  8. package/dist/analyzers/pythonLinterCheck.d.ts +2 -0
  9. package/dist/analyzers/pythonLinterCheck.js +119 -0
  10. package/dist/analyzers/pythonLinterCheck.js.map +1 -0
  11. package/dist/analyzers/pythonTestCheck.d.ts +2 -0
  12. package/dist/analyzers/pythonTestCheck.js +97 -0
  13. package/dist/analyzers/pythonTestCheck.js.map +1 -0
  14. package/dist/analyzers/pythonUnusedDependencyCheck.d.ts +2 -0
  15. package/dist/analyzers/pythonUnusedDependencyCheck.js +76 -0
  16. package/dist/analyzers/pythonUnusedDependencyCheck.js.map +1 -0
  17. package/dist/core/codeGraph.d.ts +6 -7
  18. package/dist/core/codeGraph.js +48 -72
  19. package/dist/core/codeGraph.js.map +1 -1
  20. package/dist/core/fileInspector.d.ts +3 -0
  21. package/dist/core/fileInspector.js +47 -2
  22. package/dist/core/fileInspector.js.map +1 -1
  23. package/dist/core/indexCache.js +5 -1
  24. package/dist/core/indexCache.js.map +1 -1
  25. package/dist/core/issueEngine.js +10 -0
  26. package/dist/core/issueEngine.js.map +1 -1
  27. package/dist/core/languages/LanguageAdapter.d.ts +36 -0
  28. package/dist/core/languages/LanguageAdapter.js +2 -0
  29. package/dist/core/languages/LanguageAdapter.js.map +1 -0
  30. package/dist/core/languages/javascriptAdapter.d.ts +2 -0
  31. package/dist/core/languages/javascriptAdapter.js +68 -0
  32. package/dist/core/languages/javascriptAdapter.js.map +1 -0
  33. package/dist/core/languages/pythonAdapter.d.ts +6 -0
  34. package/dist/core/languages/pythonAdapter.js +142 -0
  35. package/dist/core/languages/pythonAdapter.js.map +1 -0
  36. package/dist/core/languages/pythonExports.d.ts +28 -0
  37. package/dist/core/languages/pythonExports.js +169 -0
  38. package/dist/core/languages/pythonExports.js.map +1 -0
  39. package/dist/core/languages/pythonImports.d.ts +22 -0
  40. package/dist/core/languages/pythonImports.js +104 -0
  41. package/dist/core/languages/pythonImports.js.map +1 -0
  42. package/dist/core/languages/pythonManifests.d.ts +34 -0
  43. package/dist/core/languages/pythonManifests.js +344 -0
  44. package/dist/core/languages/pythonManifests.js.map +1 -0
  45. package/dist/core/languages/registry.d.ts +5 -0
  46. package/dist/core/languages/registry.js +30 -0
  47. package/dist/core/languages/registry.js.map +1 -0
  48. package/dist/core/languages/treeSitterLoader.d.ts +14 -0
  49. package/dist/core/languages/treeSitterLoader.js +75 -0
  50. package/dist/core/languages/treeSitterLoader.js.map +1 -0
  51. package/dist/core/searchIndex.js +8 -0
  52. package/dist/core/searchIndex.js.map +1 -1
  53. package/dist/core/upgradePreview.d.ts +12 -0
  54. package/dist/core/upgradePreview.js +54 -2
  55. package/dist/core/upgradePreview.js.map +1 -1
  56. package/dist/grammars/tree-sitter-python.wasm +0 -0
  57. package/dist/grammars/web-tree-sitter.wasm +0 -0
  58. package/dist/index.d.ts +1 -1
  59. package/dist/index.js +1 -1
  60. package/dist/index.js.map +1 -1
  61. package/dist/mcp/tools.js +48 -0
  62. package/dist/mcp/tools.js.map +1 -1
  63. package/dist/types.d.ts +2 -0
  64. package/dist/utils/fileWalker.js +14 -0
  65. package/dist/utils/fileWalker.js.map +1 -1
  66. package/package.json +10 -5
package/README.md CHANGED
@@ -190,10 +190,22 @@ This outputs a [shields.io](https://shields.io) badge URL and markdown snippet y
190
190
 
191
191
  ## What It Detects
192
192
 
193
- **Languages**: TypeScript, JavaScript, Python, Go, Rust, Java, Ruby, C/C++, PHP, Swift, Kotlin, and 20+ more
193
+ **Languages**: TypeScript, JavaScript, Python (full AST analysis for all three), plus file-level detection for Go, Rust, Java, Ruby, C/C++, PHP, Swift, Kotlin, and 20+ more.
194
194
 
195
195
  **Frameworks**: React, Next.js, Vue, Nuxt, Svelte, Angular, Express, Fastify, NestJS, Vite, Tailwind CSS, Prisma, and more
196
196
 
197
+ ### Python (0.10)
198
+
199
+ Python repos now get the same treatment JS/TS has had since 0.6:
200
+
201
+ - **AST-accurate import graph.** `from pkg.mod import x`, relative imports, `__init__.py` packages, `__all__`. Parsed via tree-sitter-python (wasm, offline).
202
+ - **Python-aware analyzers.** Missing pytest / ruff / black config. Deprecated packages (nose, simplejson, pycrypto). Unused `pyproject.toml` / `requirements.txt` deps. Missing lockfile.
203
+ - **Code search.** BM25 and semantic modes work on `.py` files out of the box.
204
+ - **Hotspots + dead code.** Same scoring as JS/TS, with `__init__.py` and pytest test-file conventions understood.
205
+ - **MCP tools work unchanged.** `projscan_graph`, `projscan_search`, `projscan_doctor`, `projscan_hotspots`, etc. all accept Python projects. Agents can ask "which files import `pkg.core`?" and get an answer in milliseconds.
206
+
207
+ `projscan_upgrade` remains Node-only for now - a Python equivalent (reading pip / poetry metadata) is on the roadmap.
208
+
197
209
  **Issues**:
198
210
  - Missing linting (ESLint) and formatting (Prettier) configuration
199
211
  - Missing test framework
@@ -210,7 +222,7 @@ This outputs a [shields.io](https://shields.io) badge URL and markdown snippet y
210
222
  - **5,000 files** analyzed in under 1.5 seconds
211
223
  - **20,000 files** analyzed in under 3 seconds
212
224
  - **Zero network requests** - everything runs locally
213
- - **4 runtime dependencies** - minimal footprint
225
+ - **6 runtime dependencies** - still minimal footprint (the two tree-sitter packages add ~640 KB of vendored wasm)
214
226
 
215
227
  ## CI/CD Integration
216
228
 
@@ -1,18 +1,16 @@
1
1
  import type { FileEntry, Issue } from '../types.js';
2
2
  /**
3
- * Flag exports that are never imported anywhere in the project. This catches:
4
- * - dead named exports left over from refactors
5
- * - utilities that are implemented but never hooked up
3
+ * Flag source files whose exports nothing imports. Language-agnostic: uses the
4
+ * code graph directly, so JS/TS/Python all get the same correctness guarantees.
6
5
  *
7
- * Does NOT flag:
8
- * - files listed as the package's public entry points (main, exports, types, bin)
9
- * - default exports (too many false positives - framework conventions)
10
- * - test files (they're not supposed to export)
11
- * - index files (barrels re-export for public use)
6
+ * Skipped:
7
+ * - public package entries (main/exports/bin/types in package.json)
8
+ * - test files (JS conventions + pytest `test_*.py` / `*_test.py` / `tests/`)
9
+ * - barrel files (`index.*` for JS, `__init__.py` for Python)
10
+ * - default-only exports (too many framework false positives)
12
11
  *
13
- * False-positive guard: if a file is the target of at least one import, we treat
14
- * all its exports as "possibly used" - the regex-based graph can't tell which
15
- * named export is imported via `import { ... } from './barrel'`. This keeps
16
- * noise low at the cost of missing some dead exports that live in used files.
12
+ * False-positive guard: if any import resolves to this file, we treat ALL its
13
+ * exports as possibly used - the graph can't always tell which named export
14
+ * got picked up from a barrel.
17
15
  */
18
16
  export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -1,47 +1,36 @@
1
1
  import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { buildImportGraph } from '../core/importGraph.js';
4
- import { extractExports } from '../core/fileInspector.js';
5
- const SOURCE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts']);
6
- // Never flag these - they're public API by definition
3
+ import { buildCodeGraph } from '../core/codeGraph.js';
4
+ import { getAdapterFor } from '../core/languages/registry.js';
5
+ const SOURCE_EXTENSIONS = new Set([
6
+ '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts',
7
+ '.py', '.pyw',
8
+ ]);
9
+ // Never flag these - they're public API by definition (JS convention).
7
10
  const PUBLIC_PATH_PREFIXES = ['src/index', 'index.'];
11
+ // Names (sans extension) that are barrel-equivalents and should never be
12
+ // flagged as dead. `index` for JS/TS, `__init__` for Python packages.
13
+ const BARREL_BASENAMES = new Set(['index', '__init__']);
8
14
  /**
9
- * Flag exports that are never imported anywhere in the project. This catches:
10
- * - dead named exports left over from refactors
11
- * - utilities that are implemented but never hooked up
15
+ * Flag source files whose exports nothing imports. Language-agnostic: uses the
16
+ * code graph directly, so JS/TS/Python all get the same correctness guarantees.
12
17
  *
13
- * Does NOT flag:
14
- * - files listed as the package's public entry points (main, exports, types, bin)
15
- * - default exports (too many false positives - framework conventions)
16
- * - test files (they're not supposed to export)
17
- * - index files (barrels re-export for public use)
18
+ * Skipped:
19
+ * - public package entries (main/exports/bin/types in package.json)
20
+ * - test files (JS conventions + pytest `test_*.py` / `*_test.py` / `tests/`)
21
+ * - barrel files (`index.*` for JS, `__init__.py` for Python)
22
+ * - default-only exports (too many framework false positives)
18
23
  *
19
- * False-positive guard: if a file is the target of at least one import, we treat
20
- * all its exports as "possibly used" - the regex-based graph can't tell which
21
- * named export is imported via `import { ... } from './barrel'`. This keeps
22
- * noise low at the cost of missing some dead exports that live in used files.
24
+ * False-positive guard: if any import resolves to this file, we treat ALL its
25
+ * exports as possibly used - the graph can't always tell which named export
26
+ * got picked up from a barrel.
23
27
  */
24
28
  export async function check(rootPath, files) {
25
29
  const sourceFiles = files.filter((f) => SOURCE_EXTENSIONS.has(f.extension));
26
30
  if (sourceFiles.length === 0)
27
31
  return [];
28
32
  const publicEntries = await loadPublicEntries(rootPath);
29
- const graph = await buildImportGraph(rootPath, sourceFiles);
30
- // Build a set of files that are the target of at least one relative import.
31
- // A relative import specifier is resolved against its importing file's dir,
32
- // so we convert each relative specifier into a candidate target path.
33
- const importedTargets = new Set();
34
- for (const [importingFile, specifiers] of graph.byFile) {
35
- const importingDir = path.posix.dirname(importingFile);
36
- for (const spec of specifiers) {
37
- if (!spec.startsWith('.'))
38
- continue;
39
- const resolved = path.posix.normalize(path.posix.join(importingDir, spec));
40
- for (const candidate of resolutionCandidates(resolved)) {
41
- importedTargets.add(candidate);
42
- }
43
- }
44
- }
33
+ const graph = await buildCodeGraph(rootPath, sourceFiles);
45
34
  const issues = [];
46
35
  for (const file of sourceFiles) {
47
36
  if (isTestFile(file.relativePath))
@@ -50,27 +39,25 @@ export async function check(rootPath, files) {
50
39
  continue;
51
40
  if (isPublicEntry(file.relativePath, publicEntries))
52
41
  continue;
53
- if (importedTargets.has(file.relativePath))
42
+ // Any importer → file is used.
43
+ if ((graph.localImporters.get(file.relativePath)?.size ?? 0) > 0)
54
44
  continue;
55
- if (importedTargets.has(stripExtension(file.relativePath)))
45
+ const graphFile = graph.files.get(file.relativePath);
46
+ if (!graphFile || !graphFile.parseOk)
56
47
  continue;
57
- let content;
58
- try {
59
- content = await fs.readFile(file.absolutePath, 'utf-8');
60
- }
61
- catch {
62
- continue;
63
- }
64
- const exports = extractExports(content).filter((e) => e.type !== 'default' && e.name !== 'default');
65
- if (exports.length === 0)
48
+ const namedExports = graphFile.exports.filter((e) => e.name !== 'default' && e.kind !== 'default');
49
+ if (namedExports.length === 0)
66
50
  continue;
51
+ const adapter = getAdapterFor(file.relativePath);
52
+ const languageLabel = adapter?.id ?? 'file';
53
+ const kindLabel = languageLabel === 'python' ? 'name' : 'export';
67
54
  issues.push({
68
55
  id: `unused-exports-${file.relativePath}`,
69
- title: `Unused exports in ${file.relativePath}`,
70
- description: `${exports.length} named export${exports.length === 1 ? '' : 's'} (${exports
56
+ title: `Unused ${kindLabel}s in ${file.relativePath}`,
57
+ description: `${namedExports.length} named ${kindLabel}${namedExports.length === 1 ? '' : 's'} (${namedExports
71
58
  .slice(0, 5)
72
59
  .map((e) => e.name)
73
- .join(', ')}${exports.length > 5 ? `, … +${exports.length - 5}` : ''}) but nothing in the project imports this file. Dead code or awaiting wiring?`,
60
+ .join(', ')}${namedExports.length > 5 ? `, … +${namedExports.length - 5}` : ''}) but nothing in the project imports this file. Dead code or awaiting wiring?`,
74
61
  severity: 'info',
75
62
  category: 'architecture',
76
63
  fixAvailable: false,
@@ -80,14 +67,19 @@ export async function check(rootPath, files) {
80
67
  return issues;
81
68
  }
82
69
  function isTestFile(relativePath) {
70
+ const base = path.basename(relativePath);
83
71
  return (relativePath.includes('.test.') ||
84
72
  relativePath.includes('.spec.') ||
85
73
  relativePath.includes('__tests__') ||
86
- relativePath.startsWith('tests/'));
74
+ relativePath.startsWith('tests/') ||
75
+ relativePath.includes('/tests/') ||
76
+ // pytest conventions
77
+ /^test_.+\.py$/.test(base) ||
78
+ /^.+_test\.py$/.test(base));
87
79
  }
88
80
  function isBarrelFile(relativePath) {
89
81
  const base = path.basename(relativePath, path.extname(relativePath));
90
- return base === 'index';
82
+ return BARREL_BASENAMES.has(base);
91
83
  }
92
84
  function isPublicEntry(relativePath, publicEntries) {
93
85
  if (publicEntries.has(relativePath))
@@ -104,26 +96,6 @@ function stripExtension(p) {
104
96
  const ext = path.extname(p);
105
97
  return ext ? p.slice(0, -ext.length) : p;
106
98
  }
107
- /**
108
- * Return the set of possible resolution targets for a relative import
109
- * specifier (already joined+normalized). For './foo' we yield:
110
- * foo, foo.ts, foo.tsx, foo.js, foo.jsx, foo.mjs, foo.cjs,
111
- * foo/index.ts, foo/index.tsx, foo/index.js, ...
112
- */
113
- function resolutionCandidates(base) {
114
- const exts = ['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs', '.mts', '.cts'];
115
- const out = [base];
116
- for (const ext of exts) {
117
- out.push(base + ext);
118
- out.push(base + '/index' + ext);
119
- }
120
- // Handle imports written with an explicit ".js" that actually resolve to a .ts file (ESM+NodeNext)
121
- if (base.endsWith('.js')) {
122
- const noJs = base.slice(0, -3);
123
- out.push(noJs + '.ts', noJs + '.tsx');
124
- }
125
- return out;
126
- }
127
99
  async function loadPublicEntries(rootPath) {
128
100
  const entries = new Set();
129
101
  const pkgPath = path.join(rootPath, 'package.json');
@@ -143,7 +115,7 @@ async function loadPublicEntries(rootPath) {
143
115
  collectExports(pkg.exports, entries);
144
116
  }
145
117
  catch {
146
- // package.json missing/unreadable - don't guard, every file is a candidate
118
+ // package.json missing - nothing to guard
147
119
  }
148
120
  return entries;
149
121
  }
@@ -1 +1 @@
1
- {"version":3,"file":"deadCodeCheck.js","sourceRoot":"","sources":["../../src/analyzers/deadCodeCheck.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"deadCodeCheck.js","sourceRoot":"","sources":["../../src/analyzers/deadCodeCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,114 @@
1
+ import { detectPythonProject } from '../core/languages/pythonManifests.js';
2
+ // Small, conservative seed lists. Easy to extend later.
3
+ const DEPRECATED_WARNING = new Set([
4
+ 'nose',
5
+ 'simplejson',
6
+ 'pycrypto',
7
+ 'mysql-python',
8
+ ]);
9
+ const DEPRECATED_INFO = new Set([
10
+ 'python-dateutil',
11
+ ]);
12
+ const HEAVY_INFO = new Set([
13
+ 'pandas',
14
+ 'numpy',
15
+ 'torch',
16
+ 'tensorflow',
17
+ ]);
18
+ const DEPRECATION_REASONS = {
19
+ nose: 'nose is retired. Use pytest instead.',
20
+ simplejson: 'simplejson is no longer needed; the stdlib json module is equivalent for most use cases.',
21
+ pycrypto: 'pycrypto is unmaintained. Use pycryptodome as a drop-in replacement.',
22
+ 'mysql-python': 'mysql-python is Python 2 only. Use mysqlclient or PyMySQL.',
23
+ 'python-dateutil': 'python-dateutil is heavy. Many use cases are now covered by stdlib datetime.',
24
+ };
25
+ const HEAVY_REASONS = {
26
+ pandas: 'pandas is a heavy dependency (~30MB). Reach for it only if you actually need dataframes.',
27
+ numpy: 'numpy is a heavy dependency (~15MB). Only pull it if you need numerical arrays.',
28
+ torch: 'torch is a very heavy dependency. Consider torch-cpu if GPU is not needed.',
29
+ tensorflow: 'tensorflow is a very heavy dependency. Consider tensorflow-cpu for inference-only use.',
30
+ };
31
+ export async function check(rootPath, files) {
32
+ const info = await detectPythonProject(rootPath, files);
33
+ if (!info)
34
+ return [];
35
+ const issues = [];
36
+ const seen = new Set();
37
+ for (const dep of info.declared) {
38
+ const id = `dep-risk-${dep.name}`;
39
+ if (seen.has(id))
40
+ continue;
41
+ if (DEPRECATED_WARNING.has(dep.name)) {
42
+ seen.add(id);
43
+ issues.push({
44
+ id,
45
+ title: `Deprecated Python package: ${dep.name}`,
46
+ description: DEPRECATION_REASONS[dep.name] ?? `${dep.name} is deprecated.`,
47
+ severity: 'error',
48
+ category: 'dependencies',
49
+ fixAvailable: false,
50
+ locations: [{ file: dep.source, line: dep.line }],
51
+ });
52
+ continue;
53
+ }
54
+ if (DEPRECATED_INFO.has(dep.name)) {
55
+ seen.add(id);
56
+ issues.push({
57
+ id,
58
+ title: `Consider alternatives to ${dep.name}`,
59
+ description: DEPRECATION_REASONS[dep.name] ?? `${dep.name} may be avoidable.`,
60
+ severity: 'info',
61
+ category: 'dependencies',
62
+ fixAvailable: false,
63
+ locations: [{ file: dep.source, line: dep.line }],
64
+ });
65
+ continue;
66
+ }
67
+ if (HEAVY_INFO.has(dep.name)) {
68
+ seen.add(id);
69
+ issues.push({
70
+ id,
71
+ title: `Heavy Python dependency: ${dep.name}`,
72
+ description: HEAVY_REASONS[dep.name] ?? `${dep.name} is a heavy dependency.`,
73
+ severity: 'info',
74
+ category: 'dependencies',
75
+ fixAvailable: false,
76
+ locations: [{ file: dep.source, line: dep.line }],
77
+ });
78
+ continue;
79
+ }
80
+ // Wildcard / unpinned: only flag entries that came from a requirements file
81
+ // (pyproject.toml dependency strings without a version spec are common and
82
+ // not a smell). An empty versionSpec from requirements.txt IS a smell.
83
+ if (dep.source.endsWith('.txt') &&
84
+ (dep.versionSpec === '' || dep.versionSpec === '*')) {
85
+ seen.add(id);
86
+ issues.push({
87
+ id,
88
+ title: `Unpinned Python dependency: ${dep.name}`,
89
+ description: `\`${dep.name}\` in ${dep.source} has no version constraint. Pin to a specific version (==X.Y.Z) for reproducible builds.`,
90
+ severity: 'error',
91
+ category: 'dependencies',
92
+ fixAvailable: false,
93
+ locations: [{ file: dep.source, line: dep.line }],
94
+ });
95
+ }
96
+ }
97
+ // No-lockfile: only emit if there ARE declared deps AND no lockfile-like
98
+ // thing exists. pyproject-only projects without a lockfile are fine for
99
+ // library-style packages, so this is a warning not an error.
100
+ if (!info.hasLockfile && info.declared.length > 0) {
101
+ const severity = 'warning';
102
+ issues.push({
103
+ id: 'dep-risk-no-python-lockfile',
104
+ title: 'No Python lockfile detected',
105
+ description: 'No lockfile (poetry.lock / Pipfile.lock / pdm.lock / uv.lock / conda-lock.yml) or pinned requirements.txt found. Builds may resolve different versions over time.',
106
+ severity,
107
+ category: 'dependencies',
108
+ fixAvailable: false,
109
+ locations: [{ file: info.manifestFiles[0] ?? 'pyproject.toml' }],
110
+ });
111
+ }
112
+ return issues;
113
+ }
114
+ //# sourceMappingURL=pythonDependencyRiskCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonDependencyRiskCheck.js","sourceRoot":"","sources":["../../src/analyzers/pythonDependencyRiskCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,119 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const LINTER_CONFIG_FILES = [
4
+ 'ruff.toml',
5
+ '.ruff.toml',
6
+ '.flake8',
7
+ '.pylintrc',
8
+ 'pylintrc',
9
+ ];
10
+ const FORMATTER_CONFIG_FILES = [
11
+ '.autopep8',
12
+ '.yapfrc',
13
+ 'yapf.ini',
14
+ ];
15
+ async function tryRead(absolutePath) {
16
+ try {
17
+ return await fs.readFile(absolutePath, 'utf-8');
18
+ }
19
+ catch {
20
+ return null;
21
+ }
22
+ }
23
+ export async function check(rootPath, files) {
24
+ const hasPython = files.some((f) => f.extension === '.py' || f.extension === '.pyw');
25
+ if (!hasPython)
26
+ return [];
27
+ const rootBasenames = new Set(files
28
+ .filter((f) => !f.directory || f.directory === '.')
29
+ .map((f) => path.basename(f.relativePath)));
30
+ const pyproject = await tryRead(path.join(rootPath, 'pyproject.toml'));
31
+ const setupCfg = await tryRead(path.join(rootPath, 'setup.cfg'));
32
+ const reqRels = files
33
+ .filter((f) => (!f.directory || f.directory === '.') &&
34
+ /^requirements(-.*)?\.txt$/i.test(path.basename(f.relativePath)))
35
+ .map((f) => f.relativePath);
36
+ let requirementsBlob = '';
37
+ for (const rel of reqRels) {
38
+ const content = await tryRead(path.join(rootPath, rel));
39
+ if (content)
40
+ requirementsBlob += content + '\n';
41
+ }
42
+ const manifestHaystack = [pyproject ?? '', setupCfg ?? '', requirementsBlob].join('\n');
43
+ // ── Linter detection ──
44
+ let hasLinter = false;
45
+ for (const f of LINTER_CONFIG_FILES) {
46
+ if (rootBasenames.has(f)) {
47
+ hasLinter = true;
48
+ break;
49
+ }
50
+ }
51
+ if (!hasLinter && pyproject) {
52
+ if (/\[tool\.ruff\]|\[tool\.flake8\]|\[tool\.pylint\]|\[tool\.pylint\./.test(pyproject)) {
53
+ hasLinter = true;
54
+ }
55
+ }
56
+ if (!hasLinter && setupCfg) {
57
+ if (/\[flake8\]|\[pylint\]/.test(setupCfg))
58
+ hasLinter = true;
59
+ }
60
+ if (!hasLinter) {
61
+ for (const name of ['ruff', 'flake8', 'pylint', 'pyflakes']) {
62
+ const re = new RegExp(`(^|[\\s"'\`\\[\\],={}><~^!;])${name}(\\b|[^a-zA-Z0-9_.-])`, 'im');
63
+ if (re.test(manifestHaystack)) {
64
+ hasLinter = true;
65
+ break;
66
+ }
67
+ }
68
+ }
69
+ // ── Formatter detection ──
70
+ // ruff also formats (ruff format), so if linter is ruff that satisfies formatter.
71
+ let hasFormatter = false;
72
+ for (const f of FORMATTER_CONFIG_FILES) {
73
+ if (rootBasenames.has(f)) {
74
+ hasFormatter = true;
75
+ break;
76
+ }
77
+ }
78
+ if (!hasFormatter && pyproject) {
79
+ if (/\[tool\.black\]|\[tool\.autopep8\]|\[tool\.yapf\]|\[tool\.ruff\.format\]|\[tool\.ruff\]/.test(pyproject)) {
80
+ hasFormatter = true;
81
+ }
82
+ }
83
+ if (!hasFormatter && setupCfg) {
84
+ if (/\[yapf\]/.test(setupCfg))
85
+ hasFormatter = true;
86
+ }
87
+ if (!hasFormatter) {
88
+ for (const name of ['black', 'ruff', 'autopep8', 'yapf']) {
89
+ const re = new RegExp(`(^|[\\s"'\`\\[\\],={}><~^!;])${name}(\\b|[^a-zA-Z0-9_.-])`, 'im');
90
+ if (re.test(manifestHaystack)) {
91
+ hasFormatter = true;
92
+ break;
93
+ }
94
+ }
95
+ }
96
+ const issues = [];
97
+ if (!hasLinter) {
98
+ issues.push({
99
+ id: 'missing-python-linter',
100
+ title: 'No Python linter configured',
101
+ description: 'No ruff / flake8 / pylint configuration or dependency found. A linter catches bugs and enforces style.',
102
+ severity: 'warning',
103
+ category: 'linting',
104
+ fixAvailable: false,
105
+ });
106
+ }
107
+ if (!hasFormatter) {
108
+ issues.push({
109
+ id: 'missing-python-formatter',
110
+ title: 'No Python formatter configured',
111
+ description: 'No black / ruff-format / autopep8 / yapf configuration or dependency found. A formatter ensures consistent code style.',
112
+ severity: 'warning',
113
+ category: 'formatting',
114
+ fixAvailable: false,
115
+ });
116
+ }
117
+ return issues;
118
+ }
119
+ //# sourceMappingURL=pythonLinterCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonLinterCheck.js","sourceRoot":"","sources":["../../src/analyzers/pythonLinterCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;
@@ -0,0 +1,97 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ const TEST_FRAMEWORKS = ['pytest', 'unittest', 'nose', 'nose2', 'ward'];
4
+ function isPythonTestFile(rel) {
5
+ const base = path.basename(rel);
6
+ if (/^test_.+\.py$/.test(base))
7
+ return true;
8
+ if (/^.+_test\.py$/.test(base))
9
+ return true;
10
+ if (rel.startsWith('tests/') || rel.includes('/tests/'))
11
+ return base.endsWith('.py');
12
+ return false;
13
+ }
14
+ async function tryRead(absolutePath) {
15
+ try {
16
+ return await fs.readFile(absolutePath, 'utf-8');
17
+ }
18
+ catch {
19
+ return null;
20
+ }
21
+ }
22
+ export async function check(rootPath, files) {
23
+ const hasPython = files.some((f) => f.extension === '.py' || f.extension === '.pyw');
24
+ if (!hasPython)
25
+ return [];
26
+ const pyproject = await tryRead(path.join(rootPath, 'pyproject.toml'));
27
+ const setupCfg = await tryRead(path.join(rootPath, 'setup.cfg'));
28
+ const pytestIni = await tryRead(path.join(rootPath, 'pytest.ini'));
29
+ const toxIni = await tryRead(path.join(rootPath, 'tox.ini'));
30
+ // Collect all requirements*.txt contents into one searchable blob.
31
+ const reqRels = files
32
+ .filter((f) => (!f.directory || f.directory === '.') &&
33
+ /^requirements(-.*)?\.txt$/i.test(path.basename(f.relativePath)))
34
+ .map((f) => f.relativePath);
35
+ let requirementsBlob = '';
36
+ for (const rel of reqRels) {
37
+ const content = await tryRead(path.join(rootPath, rel));
38
+ if (content)
39
+ requirementsBlob += content + '\n';
40
+ }
41
+ let hasFramework = false;
42
+ const manifestHaystack = [pyproject ?? '', setupCfg ?? '', requirementsBlob].join('\n');
43
+ for (const fw of TEST_FRAMEWORKS) {
44
+ // Use a word-boundary-ish match so "pytest" doesn't spuriously match "pytest-cov"
45
+ // for purposes of "is pytest the framework?" - but we also accept pytest-*, so
46
+ // a simple case-insensitive containment is fine in practice.
47
+ const re = new RegExp(`(^|[\\s"'\`\\[\\],={}><~^!;])${fw}(\\b|[^a-zA-Z0-9_.-])`, 'im');
48
+ if (re.test(manifestHaystack)) {
49
+ hasFramework = true;
50
+ break;
51
+ }
52
+ }
53
+ if (!hasFramework && pytestIni !== null)
54
+ hasFramework = true;
55
+ if (!hasFramework && toxIni !== null && /\[pytest\]|\[tool:pytest\]|pytest/i.test(toxIni)) {
56
+ hasFramework = true;
57
+ }
58
+ if (!hasFramework && pyproject !== null && /\[tool\.pytest\.ini_options\]/.test(pyproject)) {
59
+ hasFramework = true;
60
+ }
61
+ // Last-resort detection: `import unittest` (or `from unittest ...`) inside a
62
+ // pytest-conventional test file. Some projects rely on stdlib unittest and
63
+ // never declare a framework in manifests.
64
+ const testFiles = files.filter((f) => isPythonTestFile(f.relativePath));
65
+ if (!hasFramework) {
66
+ for (const f of testFiles) {
67
+ const content = await tryRead(f.absolutePath);
68
+ if (content && /^\s*(import\s+unittest|from\s+unittest\s+import)/m.test(content)) {
69
+ hasFramework = true;
70
+ break;
71
+ }
72
+ }
73
+ }
74
+ const issues = [];
75
+ if (!hasFramework) {
76
+ issues.push({
77
+ id: 'missing-python-test-framework',
78
+ title: 'No Python test framework detected',
79
+ description: 'No pytest/unittest configuration or dependency found. Testing is essential for code quality and reliability.',
80
+ severity: 'warning',
81
+ category: 'testing',
82
+ fixAvailable: false,
83
+ });
84
+ }
85
+ else if (testFiles.length === 0) {
86
+ issues.push({
87
+ id: 'no-python-test-files',
88
+ title: 'No Python test files found',
89
+ description: 'A Python test framework is configured but no test files were found (expected test_*.py, *_test.py, or under tests/).',
90
+ severity: 'info',
91
+ category: 'testing',
92
+ fixAvailable: false,
93
+ });
94
+ }
95
+ return issues;
96
+ }
97
+ //# sourceMappingURL=pythonTestCheck.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"pythonTestCheck.js","sourceRoot":"","sources":["../../src/analyzers/pythonTestCheck.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ import type { FileEntry, Issue } from '../types.js';
2
+ export declare function check(rootPath: string, files: FileEntry[]): Promise<Issue[]>;