@tinkcarlos/skillora 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.
- package/.claude/skills/.temp-skill-index.md +245 -0
- package/.claude/skills/SKILL.md +264 -0
- package/.claude/skills/api-scaffolding/SKILL.md +431 -0
- package/.claude/skills/api-scaffolding/agents/backend-architect.md +282 -0
- package/.claude/skills/api-scaffolding/agents/django-pro.md +144 -0
- package/.claude/skills/api-scaffolding/agents/fastapi-pro.md +156 -0
- package/.claude/skills/api-scaffolding/agents/graphql-architect.md +146 -0
- package/.claude/skills/api-scaffolding/skills/fastapi-templates/SKILL.md +171 -0
- package/.claude/skills/api-testing-observability/SKILL.md +583 -0
- package/.claude/skills/api-testing-observability/agents/api-documenter.md +146 -0
- package/.claude/skills/api-testing-observability/commands/api-mock.md +1320 -0
- package/.claude/skills/brainstorming/SKILL.md +283 -0
- package/.claude/skills/bug-fixing/SKILL.md +382 -0
- package/.claude/skills/bug-fixing/references/backend-guide.md +132 -0
- package/.claude/skills/bug-fixing/references/bug-guide.md +354 -0
- package/.claude/skills/bug-fixing/references/bug-record-template.md +134 -0
- package/.claude/skills/bug-fixing/references/bug-records.md +88 -0
- package/.claude/skills/bug-fixing/references/code-review-gate.md +81 -0
- package/.claude/skills/bug-fixing/references/common-bugs.md +140 -0
- package/.claude/skills/bug-fixing/references/complete-workflow.md +361 -0
- package/.claude/skills/bug-fixing/references/config-driven-fixes.md +136 -0
- package/.claude/skills/bug-fixing/references/context-isolation-protocol.md +268 -0
- package/.claude/skills/bug-fixing/references/cross-surface-regression.md +120 -0
- package/.claude/skills/bug-fixing/references/database-investigation.md +129 -0
- package/.claude/skills/bug-fixing/references/dependency-and-integrity-protocol.md +369 -0
- package/.claude/skills/bug-fixing/references/fix-completeness-checklist.md +239 -0
- package/.claude/skills/bug-fixing/references/frontend-guide.md +219 -0
- package/.claude/skills/bug-fixing/references/fullstack-joint-guide.md +123 -0
- package/.claude/skills/bug-fixing/references/functional-breakage.md +117 -0
- package/.claude/skills/bug-fixing/references/ide-lint-errors-guide.md +176 -0
- package/.claude/skills/bug-fixing/references/impact-analysis.md +511 -0
- package/.claude/skills/bug-fixing/references/investigation-checklist.md +263 -0
- package/.claude/skills/bug-fixing/references/knowledge-extraction-guide.md +531 -0
- package/.claude/skills/bug-fixing/references/knowledge-workflow.md +212 -0
- package/.claude/skills/bug-fixing/references/post-edit-quality-gate.md +30 -0
- package/.claude/skills/bug-fixing/references/python-env-and-testing.md +126 -0
- package/.claude/skills/bug-fixing/references/rca-guide.md +428 -0
- package/.claude/skills/bug-fixing/references/similar-bug-patterns.md +113 -0
- package/.claude/skills/bug-fixing/references/skill-delegation-guide.md +350 -0
- package/.claude/skills/bug-fixing/references/skill-orchestration.md +155 -0
- package/.claude/skills/bug-fixing/references/testing-strategy.md +350 -0
- package/.claude/skills/bug-fixing/references/tooling-build-scripts.md +162 -0
- package/.claude/skills/bug-fixing/references/user-input-validation.md +77 -0
- package/.claude/skills/bug-fixing/references/ux-patterns.md +158 -0
- package/.claude/skills/bug-fixing/references/windows-terminal-hygiene.md +106 -0
- package/.claude/skills/bug-fixing/references/zero-regression-matrix.md +239 -0
- package/.claude/skills/bug-fixing/references/zero-risk-protocol.md +102 -0
- package/.claude/skills/bug-fixing/scripts/format_code.py +611 -0
- package/.claude/skills/bug-fixing/scripts/generate_report_template.py +74 -0
- package/.claude/skills/bug-fixing/scripts/lint_check.py +816 -0
- package/.claude/skills/bug-fixing/scripts/requirements.txt +36 -0
- package/.claude/skills/cicd-pipeline/SKILL.md +300 -0
- package/.claude/skills/code-review/SKILL.md +535 -0
- package/.claude/skills/code-review/references/anti-pattern-scan.md +102 -0
- package/.claude/skills/code-review/references/automated-analysis.md +456 -0
- package/.claude/skills/code-review/references/backend-common-issues.md +589 -0
- package/.claude/skills/code-review/references/backend-expert-guide.md +415 -0
- package/.claude/skills/code-review/references/backend-review.md +868 -0
- package/.claude/skills/code-review/references/batch-processing-strategy.md +198 -0
- package/.claude/skills/code-review/references/call-chain-analysis-protocol.md +166 -0
- package/.claude/skills/code-review/references/common-patterns.md +321 -0
- package/.claude/skills/code-review/references/configuration-review.md +425 -0
- package/.claude/skills/code-review/references/control-flow-completeness.md +114 -0
- package/.claude/skills/code-review/references/database-review.md +298 -0
- package/.claude/skills/code-review/references/dependency-and-integrity-protocol.md +313 -0
- package/.claude/skills/code-review/references/external-standards.md +51 -0
- package/.claude/skills/code-review/references/feature-review.md +329 -0
- package/.claude/skills/code-review/references/file-review-template.md +326 -0
- package/.claude/skills/code-review/references/frontend-advanced.md +654 -0
- package/.claude/skills/code-review/references/frontend-common-issues.md +482 -0
- package/.claude/skills/code-review/references/frontend-expert-guide.md +342 -0
- package/.claude/skills/code-review/references/frontend-review.md +783 -0
- package/.claude/skills/code-review/references/fullstack-consistency.md +418 -0
- package/.claude/skills/code-review/references/fullstack-review.md +477 -0
- package/.claude/skills/code-review/references/functional-completeness.md +386 -0
- package/.claude/skills/code-review/references/hidden-bugs-detection.md +473 -0
- package/.claude/skills/code-review/references/ide-lint-errors-guide.md +173 -0
- package/.claude/skills/code-review/references/infrastructure-review.md +453 -0
- package/.claude/skills/code-review/references/iteration-review.md +264 -0
- package/.claude/skills/code-review/references/job-review.md +335 -0
- package/.claude/skills/code-review/references/layered-checklist-protocol.md +157 -0
- package/.claude/skills/code-review/references/logic-completeness.md +535 -0
- package/.claude/skills/code-review/references/mandatory-checklist.md +288 -0
- package/.claude/skills/code-review/references/multi-language-guide.md +800 -0
- package/.claude/skills/code-review/references/new-project-review.md +226 -0
- package/.claude/skills/code-review/references/non-code-files-review.md +451 -0
- package/.claude/skills/code-review/references/overlooked-issues.md +657 -0
- package/.claude/skills/code-review/references/platform-specific-review.md +195 -0
- package/.claude/skills/code-review/references/precision-analysis-protocol.md +260 -0
- package/.claude/skills/code-review/references/python-patterns.md +494 -0
- package/.claude/skills/code-review/references/rca-techniques.md +362 -0
- package/.claude/skills/code-review/references/report-template.md +430 -0
- package/.claude/skills/code-review/references/resource-limits-and-degradation.md +137 -0
- package/.claude/skills/code-review/references/review-dimensions.md +311 -0
- package/.claude/skills/code-review/references/review-guide.md +202 -0
- package/.claude/skills/code-review/references/review-knowledge-workflow.md +257 -0
- package/.claude/skills/code-review/references/review-progress-tracker-protocol.md +172 -0
- package/.claude/skills/code-review/references/review-record-template.md +195 -0
- package/.claude/skills/code-review/references/skill-orchestration.md +143 -0
- package/.claude/skills/code-review/references/ui-ux-review.md +470 -0
- package/.claude/skills/containerization/SKILL.md +313 -0
- package/.claude/skills/database-migrations/agents/database-admin.md +142 -0
- package/.claude/skills/database-migrations/agents/database-optimizer.md +144 -0
- package/.claude/skills/database-migrations/commands/migration-observability.md +408 -0
- package/.claude/skills/database-migrations/commands/sql-migrations.md +492 -0
- package/.claude/skills/finishing-a-development-branch/SKILL.md +319 -0
- package/.claude/skills/frontend-design/LICENSE.txt +177 -0
- package/.claude/skills/frontend-design/SKILL.md +587 -0
- package/.claude/skills/frontend-design/references/color-consistency.md +487 -0
- package/.claude/skills/frontend-design/references/color-palettes-full.md +657 -0
- package/.claude/skills/frontend-design/references/design-system-generator.md +285 -0
- package/.claude/skills/frontend-design/references/font-pairings-full.md +705 -0
- package/.claude/skills/frontend-design/references/industry-anti-patterns.md +281 -0
- package/.claude/skills/frontend-design/references/layout-anti-patterns.md +582 -0
- package/.claude/skills/frontend-design/references/motion-patterns.md +659 -0
- package/.claude/skills/frontend-design/references/pre-delivery-checklist.md +153 -0
- package/.claude/skills/frontend-design/references/responsive-design.md +555 -0
- package/.claude/skills/frontend-design/references/style-modification-rules.md +335 -0
- package/.claude/skills/frontend-design/references/ui-styles-full.md +383 -0
- package/.claude/skills/frontend-design/references/ui-styles-rating.md +191 -0
- package/.claude/skills/frontend-design/references/ux-guidelines.md +640 -0
- package/.claude/skills/fullstack-developer/SKILL.md +512 -0
- package/.claude/skills/fullstack-developer/references/api-contract-guide.md +312 -0
- package/.claude/skills/fullstack-developer/references/api-response-patterns.md +223 -0
- package/.claude/skills/fullstack-developer/references/async-patterns.md +220 -0
- package/.claude/skills/fullstack-developer/references/bug-prevention.md +914 -0
- package/.claude/skills/fullstack-developer/references/code-quality-checklist.md +271 -0
- package/.claude/skills/fullstack-developer/references/complete-development-workflow.md +278 -0
- package/.claude/skills/fullstack-developer/references/context-isolation-protocol.md +256 -0
- package/.claude/skills/fullstack-developer/references/database-migration.md +331 -0
- package/.claude/skills/fullstack-developer/references/dependency-and-integrity-protocol.md +390 -0
- package/.claude/skills/fullstack-developer/references/development-phases.md +333 -0
- package/.claude/skills/fullstack-developer/references/expert-guide.md +214 -0
- package/.claude/skills/fullstack-developer/references/file-import-patterns.md +114 -0
- package/.claude/skills/fullstack-developer/references/graceful-degradation-patterns.md +78 -0
- package/.claude/skills/fullstack-developer/references/ide-lint-errors-guide.md +183 -0
- package/.claude/skills/fullstack-developer/references/integration-testing.md +301 -0
- package/.claude/skills/fullstack-developer/references/mock-api-patterns.md +307 -0
- package/.claude/skills/fullstack-developer/references/phase-gate-template.md +249 -0
- package/.claude/skills/fullstack-developer/references/post-edit-quality-gate.md +30 -0
- package/.claude/skills/fullstack-developer/references/python-engineering.md +79 -0
- package/.claude/skills/fullstack-developer/references/skill-orchestration.md +214 -0
- package/.claude/skills/fullstack-developer/references/skill-router-table.md +304 -0
- package/.claude/skills/fullstack-developer/references/state-sync.md +217 -0
- package/.claude/skills/fullstack-developer/references/ui-testing-checklist.md +292 -0
- package/.claude/skills/fullstack-developer/scripts/format_code.py +611 -0
- package/.claude/skills/fullstack-developer/scripts/lint_check.py +816 -0
- package/.claude/skills/fullstack-developer/scripts/requirements.txt +36 -0
- package/.claude/skills/performance-optimization/SKILL.md +250 -0
- package/.claude/skills/product-requirements/SKILL.md +357 -0
- package/.claude/skills/product-requirements/references/acceptance-criteria.md +335 -0
- package/.claude/skills/product-requirements/references/answer-first-questioning-protocol.md +299 -0
- package/.claude/skills/product-requirements/references/competitive-analysis-guide.md +183 -0
- package/.claude/skills/product-requirements/references/document-accuracy-protocol.md +253 -0
- package/.claude/skills/product-requirements/references/document-management-protocol.md +278 -0
- package/.claude/skills/product-requirements/references/external-standards.md +62 -0
- package/.claude/skills/product-requirements/references/feature-spec-template.md +359 -0
- package/.claude/skills/product-requirements/references/knowledge-acquisition-protocol.md +251 -0
- package/.claude/skills/product-requirements/references/plan-execution-protocol.md +334 -0
- package/.claude/skills/product-requirements/references/plan-generation-protocol.md +264 -0
- package/.claude/skills/product-requirements/references/prioritization-frameworks.md +80 -0
- package/.claude/skills/product-requirements/references/requirement-decomposition-protocol.md +291 -0
- package/.claude/skills/product-requirements/references/user-story-examples.md +297 -0
- package/.claude/skills/product-requirements/references/workflow-templates.md +266 -0
- package/.claude/skills/react-best-practices/SKILL.md +198 -0
- package/.claude/skills/react-best-practices/references/advanced-patterns.md +94 -0
- package/.claude/skills/react-best-practices/references/bundle-optimization.md +182 -0
- package/.claude/skills/react-best-practices/references/client-data-fetching.md +112 -0
- package/.claude/skills/react-best-practices/references/complete-guide.md +2249 -0
- package/.claude/skills/react-best-practices/references/eliminating-waterfalls.md +169 -0
- package/.claude/skills/react-best-practices/references/javascript-performance.md +256 -0
- package/.claude/skills/react-best-practices/references/rendering-performance.md +230 -0
- package/.claude/skills/react-best-practices/references/rerender-optimization.md +214 -0
- package/.claude/skills/react-best-practices/references/server-performance.md +182 -0
- package/.claude/skills/security-audit/SKILL.md +226 -0
- package/.claude/skills/shared-references/advanced-debugging-techniques.md +186 -0
- package/.claude/skills/shared-references/code-quality-checklist.md +218 -0
- package/.claude/skills/shared-references/code-review-efficiency-guide.md +125 -0
- package/.claude/skills/shared-references/mcp-dependency-compatibility-protocol.md +276 -0
- package/.claude/skills/shared-references/skill-call-graph.md +230 -0
- package/.claude/skills/shared-references/skill-orchestration-protocol.md +281 -0
- package/.claude/skills/shared-references/subagent-dispatch-templates.md +199 -0
- package/.claude/skills/skill-expert-skills/LICENSE.txt +204 -0
- package/.claude/skills/skill-expert-skills/QUICK_NAVIGATION.md +374 -0
- package/.claude/skills/skill-expert-skills/SKILL.md +247 -0
- package/.claude/skills/skill-expert-skills/docs/_index.md +91 -0
- package/.claude/skills/skill-expert-skills/references/deep-research-methodology.md +389 -0
- package/.claude/skills/skill-expert-skills/references/docs-generation-workflow.md +398 -0
- package/.claude/skills/skill-expert-skills/references/domain-expertise-protocol.md +343 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/_index.md +54 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/backend-expertise.md +517 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/bug-fixing-expertise.md +363 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/code-review-expertise.md +392 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge/frontend-expertise.md +410 -0
- package/.claude/skills/skill-expert-skills/references/domain-knowledge-template.md +503 -0
- package/.claude/skills/skill-expert-skills/references/examples.md +782 -0
- package/.claude/skills/skill-expert-skills/references/integration-examples.md +655 -0
- package/.claude/skills/skill-expert-skills/references/knowledge-validation-checklist.md +246 -0
- package/.claude/skills/skill-expert-skills/references/latest-knowledge-acquisition.md +461 -0
- package/.claude/skills/skill-expert-skills/references/mcp-tools-guide.md +439 -0
- package/.claude/skills/skill-expert-skills/references/official-best-practices.md +616 -0
- package/.claude/skills/skill-expert-skills/references/patterns.md +218 -0
- package/.claude/skills/skill-expert-skills/references/plugin-skills-guide.md +432 -0
- package/.claude/skills/skill-expert-skills/references/requirement-elicitation-protocol.md +290 -0
- package/.claude/skills/skill-expert-skills/references/skill-creator-SKILL.md +353 -0
- package/.claude/skills/skill-expert-skills/references/skill-templates.md +583 -0
- package/.claude/skills/skill-expert-skills/references/skills-knowledge-base.md +561 -0
- package/.claude/skills/skill-expert-skills/references/tools-guide.md +379 -0
- package/.claude/skills/skill-expert-skills/references/troubleshooting.md +378 -0
- package/.claude/skills/skill-expert-skills/references/universality-guide.md +205 -0
- package/.claude/skills/skill-expert-skills/references/writing-style-guide.md +466 -0
- package/.claude/skills/skill-expert-skills/scripts/__pycache__/quick_validate.cpython-313.pyc +0 -0
- package/.claude/skills/skill-expert-skills/scripts/__pycache__/universal_validate.cpython-313.pyc +0 -0
- package/.claude/skills/skill-expert-skills/scripts/analyze_trigger.py +425 -0
- package/.claude/skills/skill-expert-skills/scripts/diff_with_official.py +188 -0
- package/.claude/skills/skill-expert-skills/scripts/init_skill.py +349 -0
- package/.claude/skills/skill-expert-skills/scripts/package_skill.py +156 -0
- package/.claude/skills/skill-expert-skills/scripts/quick_validate.py +493 -0
- package/.claude/skills/skill-expert-skills/scripts/requirements.txt +2 -0
- package/.claude/skills/skill-expert-skills/scripts/universal_validate.py +182 -0
- package/.claude/skills/skill-expert-skills/scripts/upgrade_skill.py +431 -0
- package/.claude/skills/subagent-driven-development/SKILL.md +268 -0
- package/.claude/skills/test-driven-development/SKILL.md +246 -0
- package/.claude/skills/test-driven-development/references/testing-anti-patterns.md +192 -0
- package/.claude/skills/using-git-worktrees/SKILL.md +266 -0
- package/.claude/skills/using-skillstack/SKILL.md +127 -0
- package/.claude/skills/vercel-deploy/SKILL.md +166 -0
- package/.claude/skills/vercel-deploy/scripts/deploy.sh +249 -0
- package/.claude/skills/verification-before-completion/SKILL.md +305 -0
- package/.claude/skills/writing-plans/SKILL.md +259 -0
- package/README.md +69 -0
- package/bin/cli.js +468 -0
- package/lib/init.js +333 -0
- package/package.json +29 -0
|
@@ -0,0 +1,816 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Lint Check Script - Detect and categorize code warnings/errors after modification.
|
|
4
|
+
|
|
5
|
+
Features:
|
|
6
|
+
- Supports Python (ruff/pylint/mypy), TypeScript/JavaScript (eslint/tsc)
|
|
7
|
+
- Smart priority classification with rule-based and pattern-based detection
|
|
8
|
+
- Parallel processing for multiple files
|
|
9
|
+
- JSON output for CI/CD integration
|
|
10
|
+
- Auto-fix support for compatible linters
|
|
11
|
+
- Dependency checking with install suggestions
|
|
12
|
+
|
|
13
|
+
Usage:
|
|
14
|
+
python lint_check.py <file_path> # Check single file
|
|
15
|
+
python lint_check.py <file1> <file2> # Check multiple files (parallel)
|
|
16
|
+
python lint_check.py --dir <directory> # Check directory
|
|
17
|
+
python lint_check.py <file_path> --fix # Auto-fix if possible
|
|
18
|
+
python lint_check.py --check-deps # Check linter dependencies
|
|
19
|
+
python lint_check.py <file_path> --json # JSON output for CI/CD
|
|
20
|
+
|
|
21
|
+
Exit codes:
|
|
22
|
+
0 - No errors found (may have warnings)
|
|
23
|
+
1 - P0/P1 errors found (must fix)
|
|
24
|
+
2 - P2 warnings found (should fix)
|
|
25
|
+
3 - Linter not available
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
from __future__ import annotations
|
|
29
|
+
|
|
30
|
+
import argparse
|
|
31
|
+
import subprocess
|
|
32
|
+
import sys
|
|
33
|
+
import os
|
|
34
|
+
import re
|
|
35
|
+
import json
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import List, Tuple, Optional, Dict, Any, Set
|
|
38
|
+
from dataclasses import dataclass, field, asdict
|
|
39
|
+
from enum import Enum
|
|
40
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
41
|
+
import threading
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
# Configure stdout/stderr encoding for Windows
|
|
45
|
+
def _configure_stdio() -> None:
|
|
46
|
+
"""Ensure UTF-8 encoding for stdout/stderr on Windows"""
|
|
47
|
+
if sys.platform == 'win32':
|
|
48
|
+
os.environ.setdefault('PYTHONIOENCODING', 'utf-8')
|
|
49
|
+
for stream in (sys.stdout, sys.stderr):
|
|
50
|
+
try:
|
|
51
|
+
stream.reconfigure(encoding='utf-8', errors='replace')
|
|
52
|
+
except Exception:
|
|
53
|
+
pass
|
|
54
|
+
|
|
55
|
+
_configure_stdio()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class Priority(Enum):
|
|
59
|
+
"""Error priority levels"""
|
|
60
|
+
P0 = 0 # Must fix immediately (syntax, critical type errors)
|
|
61
|
+
P1 = 1 # Must fix before commit (imports, declarations)
|
|
62
|
+
P2 = 2 # Should fix (linting warnings)
|
|
63
|
+
P3 = 3 # Optional (style nits)
|
|
64
|
+
|
|
65
|
+
def __str__(self) -> str:
|
|
66
|
+
return self.name
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@dataclass
|
|
70
|
+
class DependencyStatus:
|
|
71
|
+
"""Status of a linter dependency"""
|
|
72
|
+
name: str
|
|
73
|
+
available: bool
|
|
74
|
+
version: Optional[str] = None
|
|
75
|
+
install_cmd: Optional[str] = None
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
@dataclass
|
|
79
|
+
class LintIssue:
|
|
80
|
+
"""Single lint issue"""
|
|
81
|
+
file_path: str
|
|
82
|
+
line: int
|
|
83
|
+
column: int
|
|
84
|
+
message: str
|
|
85
|
+
rule_id: str
|
|
86
|
+
priority: Priority
|
|
87
|
+
category: str
|
|
88
|
+
suggestion: Optional[str] = None
|
|
89
|
+
fixable: bool = False
|
|
90
|
+
|
|
91
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
92
|
+
d = asdict(self)
|
|
93
|
+
d['priority'] = str(self.priority)
|
|
94
|
+
return d
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class LintResult:
|
|
99
|
+
"""Lint result for a file"""
|
|
100
|
+
file_path: str
|
|
101
|
+
success: bool
|
|
102
|
+
issues: List[LintIssue] = field(default_factory=list)
|
|
103
|
+
linter_used: Optional[str] = None
|
|
104
|
+
error_message: Optional[str] = None
|
|
105
|
+
|
|
106
|
+
@property
|
|
107
|
+
def p0_count(self) -> int:
|
|
108
|
+
return sum(1 for i in self.issues if i.priority == Priority.P0)
|
|
109
|
+
|
|
110
|
+
@property
|
|
111
|
+
def p1_count(self) -> int:
|
|
112
|
+
return sum(1 for i in self.issues if i.priority == Priority.P1)
|
|
113
|
+
|
|
114
|
+
@property
|
|
115
|
+
def p2_count(self) -> int:
|
|
116
|
+
return sum(1 for i in self.issues if i.priority == Priority.P2)
|
|
117
|
+
|
|
118
|
+
@property
|
|
119
|
+
def p3_count(self) -> int:
|
|
120
|
+
return sum(1 for i in self.issues if i.priority == Priority.P3)
|
|
121
|
+
|
|
122
|
+
@property
|
|
123
|
+
def has_blocking_issues(self) -> bool:
|
|
124
|
+
return self.p0_count > 0 or self.p1_count > 0
|
|
125
|
+
|
|
126
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
127
|
+
return {
|
|
128
|
+
'file_path': self.file_path,
|
|
129
|
+
'success': self.success,
|
|
130
|
+
'issues': [i.to_dict() for i in self.issues],
|
|
131
|
+
'linter_used': self.linter_used,
|
|
132
|
+
'error_message': self.error_message,
|
|
133
|
+
'counts': {
|
|
134
|
+
'p0': self.p0_count,
|
|
135
|
+
'p1': self.p1_count,
|
|
136
|
+
'p2': self.p2_count,
|
|
137
|
+
'p3': self.p3_count,
|
|
138
|
+
'total': len(self.issues)
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
class PriorityClassifier:
|
|
144
|
+
"""Smart priority classification for lint issues"""
|
|
145
|
+
|
|
146
|
+
# Rule ID prefixes that indicate specific priorities
|
|
147
|
+
RULE_PRIORITY_MAP = {
|
|
148
|
+
# Python - Ruff
|
|
149
|
+
Priority.P0: {
|
|
150
|
+
'E999', # Syntax error
|
|
151
|
+
'E902', # IO error
|
|
152
|
+
'F401', # Imported but unused (can cause issues)
|
|
153
|
+
'F821', # Undefined name
|
|
154
|
+
'F822', # Undefined name in __all__
|
|
155
|
+
'F823', # Local variable referenced before assignment
|
|
156
|
+
},
|
|
157
|
+
Priority.P1: {
|
|
158
|
+
'E', # Error prefix
|
|
159
|
+
'F', # Pyflakes
|
|
160
|
+
'PLE', # Pylint errors
|
|
161
|
+
'I001', # Import sorting
|
|
162
|
+
},
|
|
163
|
+
Priority.P2: {
|
|
164
|
+
'W', # Warning prefix
|
|
165
|
+
'PLW', # Pylint warnings
|
|
166
|
+
'C', # Convention
|
|
167
|
+
'N', # Naming
|
|
168
|
+
'D', # Docstring
|
|
169
|
+
},
|
|
170
|
+
# TypeScript/ESLint
|
|
171
|
+
'error': Priority.P1,
|
|
172
|
+
'warn': Priority.P2,
|
|
173
|
+
'warning': Priority.P2,
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
# Pattern-based classification (regex patterns)
|
|
177
|
+
PATTERN_PRIORITY = {
|
|
178
|
+
Priority.P0: [
|
|
179
|
+
r'syntax\s*error', r'invalid\s*syntax', r'unexpected\s*token',
|
|
180
|
+
r'cannot\s*parse', r'parse\s*error', r'SyntaxError',
|
|
181
|
+
r'unterminated', r'unexpected\s*end', r'unexpected\s*EOF',
|
|
182
|
+
r'missing.*semicolon', r'missing.*bracket', r'missing.*brace',
|
|
183
|
+
r'illegal', r'invalid\s*character',
|
|
184
|
+
],
|
|
185
|
+
Priority.P1: [
|
|
186
|
+
r'undefined', r'not\s*defined', r'cannot\s*find', r'no\s*such',
|
|
187
|
+
r'import.*error', r'module.*not\s*found', r'cannot\s*resolve',
|
|
188
|
+
r'type.*mismatch', r'incompatible\s*type', r'not\s*assignable',
|
|
189
|
+
r'missing.*import', r'unresolved.*reference', r'does\s*not\s*exist',
|
|
190
|
+
r'has\s*no\s*attribute', r'has\s*no\s*member', r'is\s*not\s*a\s*function',
|
|
191
|
+
r'expected.*argument', r'too\s*(many|few)\s*arguments',
|
|
192
|
+
r'cannot\s*call', r'not\s*callable', r'return\s*type.*mismatch',
|
|
193
|
+
],
|
|
194
|
+
Priority.P2: [
|
|
195
|
+
r'unused', r'never\s*used', r'is\s*defined\s*but\s*never',
|
|
196
|
+
r'deprecated', r'complexity', r'too\s*many', r'too\s*long',
|
|
197
|
+
r'line\s*too\s*long', r'missing\s*docstring', r'missing\s*type',
|
|
198
|
+
r'could\s*be', r'consider', r'prefer', r'should\s*be',
|
|
199
|
+
r'shadowing', r'redefinition', r'reassigned',
|
|
200
|
+
],
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
# Fix suggestions based on patterns
|
|
204
|
+
SUGGESTIONS = {
|
|
205
|
+
r'undefined|not\s*defined': "Add import statement or define the variable/function",
|
|
206
|
+
r'unused|never\s*used': "Remove unused code or add usage",
|
|
207
|
+
r'import': "Check import path and module name",
|
|
208
|
+
r'type.*mismatch|not\s*assignable': "Check type annotations and ensure compatibility",
|
|
209
|
+
r'missing': "Add required syntax element or import",
|
|
210
|
+
r'deprecated': "Update to use recommended API",
|
|
211
|
+
r'cannot\s*find\s*module': "Install package or check import path",
|
|
212
|
+
r'no\s*attribute|no\s*member': "Check spelling or add required method/property",
|
|
213
|
+
r'argument': "Check function signature and argument count",
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
def __init__(self):
|
|
217
|
+
# Compile patterns for efficiency
|
|
218
|
+
self._compiled_patterns = {
|
|
219
|
+
priority: [re.compile(p, re.IGNORECASE) for p in patterns]
|
|
220
|
+
for priority, patterns in self.PATTERN_PRIORITY.items()
|
|
221
|
+
}
|
|
222
|
+
self._suggestion_patterns = [
|
|
223
|
+
(re.compile(p, re.IGNORECASE), suggestion)
|
|
224
|
+
for p, suggestion in self.SUGGESTIONS.items()
|
|
225
|
+
]
|
|
226
|
+
|
|
227
|
+
def classify(self, message: str, rule_id: str, severity: Optional[str] = None) -> Priority:
|
|
228
|
+
"""Classify issue priority based on rule ID, message, and severity"""
|
|
229
|
+
rule_upper = rule_id.upper()
|
|
230
|
+
|
|
231
|
+
# 1. Check exact rule ID match
|
|
232
|
+
for priority, rules in self.RULE_PRIORITY_MAP.items():
|
|
233
|
+
if isinstance(rules, set) and rule_upper in rules:
|
|
234
|
+
return priority
|
|
235
|
+
|
|
236
|
+
# 2. Check rule ID prefix
|
|
237
|
+
for priority, rules in self.RULE_PRIORITY_MAP.items():
|
|
238
|
+
if isinstance(rules, set):
|
|
239
|
+
for rule_prefix in rules:
|
|
240
|
+
if len(rule_prefix) <= 3 and rule_upper.startswith(rule_prefix):
|
|
241
|
+
return priority
|
|
242
|
+
|
|
243
|
+
# 3. Check severity string
|
|
244
|
+
if severity:
|
|
245
|
+
severity_lower = severity.lower()
|
|
246
|
+
if 'error' in severity_lower:
|
|
247
|
+
return Priority.P1
|
|
248
|
+
elif 'warn' in severity_lower:
|
|
249
|
+
return Priority.P2
|
|
250
|
+
|
|
251
|
+
# 4. Check message patterns
|
|
252
|
+
for priority, patterns in self._compiled_patterns.items():
|
|
253
|
+
for pattern in patterns:
|
|
254
|
+
if pattern.search(message):
|
|
255
|
+
return priority
|
|
256
|
+
|
|
257
|
+
# 5. Default based on rule prefix conventions
|
|
258
|
+
if rule_upper.startswith('E') or rule_id.startswith('TS'):
|
|
259
|
+
return Priority.P1
|
|
260
|
+
elif rule_upper.startswith('W'):
|
|
261
|
+
return Priority.P2
|
|
262
|
+
|
|
263
|
+
return Priority.P3
|
|
264
|
+
|
|
265
|
+
def get_suggestion(self, message: str) -> Optional[str]:
|
|
266
|
+
"""Get fix suggestion based on message"""
|
|
267
|
+
for pattern, suggestion in self._suggestion_patterns:
|
|
268
|
+
if pattern.search(message):
|
|
269
|
+
return suggestion
|
|
270
|
+
return None
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class LintChecker:
|
|
274
|
+
"""Multi-language lint checker with parallel processing"""
|
|
275
|
+
|
|
276
|
+
EXTENSION_MAP = {
|
|
277
|
+
'.py': 'python', '.pyw': 'python', '.pyi': 'python',
|
|
278
|
+
'.ts': 'typescript', '.tsx': 'typescript',
|
|
279
|
+
'.js': 'javascript', '.jsx': 'javascript',
|
|
280
|
+
'.mjs': 'javascript', '.cjs': 'javascript',
|
|
281
|
+
'.vue': 'vue', '.go': 'go', '.rs': 'rust',
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
INSTALL_COMMANDS = {
|
|
285
|
+
'ruff': 'pip install ruff',
|
|
286
|
+
'pylint': 'pip install pylint',
|
|
287
|
+
'mypy': 'pip install mypy',
|
|
288
|
+
'eslint': 'npm install -g eslint',
|
|
289
|
+
'tsc': 'npm install -g typescript',
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
def __init__(self, verbose: bool = False, max_workers: int = 4):
|
|
293
|
+
self.verbose = verbose
|
|
294
|
+
self.max_workers = max_workers
|
|
295
|
+
self.classifier = PriorityClassifier()
|
|
296
|
+
self._linter_cache: Dict[str, bool] = {}
|
|
297
|
+
self._cache_lock = threading.Lock()
|
|
298
|
+
|
|
299
|
+
def _log(self, message: str) -> None:
|
|
300
|
+
if self.verbose:
|
|
301
|
+
print(f"[LINT] {message}", file=sys.stderr)
|
|
302
|
+
|
|
303
|
+
def _check_command(self, cmd: str) -> bool:
|
|
304
|
+
"""Check if a command is available (thread-safe)"""
|
|
305
|
+
with self._cache_lock:
|
|
306
|
+
if cmd in self._linter_cache:
|
|
307
|
+
return self._linter_cache[cmd]
|
|
308
|
+
|
|
309
|
+
try:
|
|
310
|
+
check_cmd = 'where' if sys.platform == 'win32' else 'which'
|
|
311
|
+
result = subprocess.run([check_cmd, cmd], capture_output=True, text=True, timeout=5)
|
|
312
|
+
available = result.returncode == 0
|
|
313
|
+
except Exception:
|
|
314
|
+
available = False
|
|
315
|
+
|
|
316
|
+
with self._cache_lock:
|
|
317
|
+
self._linter_cache[cmd] = available
|
|
318
|
+
return available
|
|
319
|
+
|
|
320
|
+
def _get_version(self, cmd: str) -> Optional[str]:
|
|
321
|
+
"""Get version of a command"""
|
|
322
|
+
try:
|
|
323
|
+
result = subprocess.run([cmd, '--version'], capture_output=True, text=True, timeout=5)
|
|
324
|
+
if result.returncode == 0:
|
|
325
|
+
return result.stdout.strip().split('\n')[0]
|
|
326
|
+
except Exception:
|
|
327
|
+
pass
|
|
328
|
+
return None
|
|
329
|
+
|
|
330
|
+
def check_dependencies(self) -> List[DependencyStatus]:
|
|
331
|
+
"""Check all linter dependencies"""
|
|
332
|
+
deps = []
|
|
333
|
+
for name in ['ruff', 'pylint', 'mypy', 'eslint', 'tsc']:
|
|
334
|
+
# For eslint/tsc, also check npx
|
|
335
|
+
if name in ('eslint', 'tsc'):
|
|
336
|
+
available = self._check_command(name) or self._check_command('npx')
|
|
337
|
+
else:
|
|
338
|
+
available = self._check_command(name)
|
|
339
|
+
|
|
340
|
+
deps.append(DependencyStatus(
|
|
341
|
+
name=name,
|
|
342
|
+
available=available,
|
|
343
|
+
version=self._get_version(name) if available else None,
|
|
344
|
+
install_cmd=self.INSTALL_COMMANDS.get(name)
|
|
345
|
+
))
|
|
346
|
+
return deps
|
|
347
|
+
|
|
348
|
+
def _parse_ruff_output(self, output: str, file_path: str) -> List[LintIssue]:
|
|
349
|
+
"""Parse ruff linter output"""
|
|
350
|
+
issues = []
|
|
351
|
+
# Ruff format: file:line:col: CODE message
|
|
352
|
+
pattern = r'^(.+?):(\d+):(\d+):\s*(\w+)\s+(.+)$'
|
|
353
|
+
|
|
354
|
+
for line in output.strip().split('\n'):
|
|
355
|
+
if not line.strip():
|
|
356
|
+
continue
|
|
357
|
+
match = re.match(pattern, line)
|
|
358
|
+
if match:
|
|
359
|
+
_, line_num, col, rule_id, message = match.groups()
|
|
360
|
+
priority = self.classifier.classify(message, rule_id)
|
|
361
|
+
issues.append(LintIssue(
|
|
362
|
+
file_path=file_path,
|
|
363
|
+
line=int(line_num),
|
|
364
|
+
column=int(col),
|
|
365
|
+
message=message.strip(),
|
|
366
|
+
rule_id=rule_id,
|
|
367
|
+
priority=priority,
|
|
368
|
+
category='ruff',
|
|
369
|
+
suggestion=self.classifier.get_suggestion(message),
|
|
370
|
+
fixable=rule_id not in ('E999', 'E902')
|
|
371
|
+
))
|
|
372
|
+
return issues
|
|
373
|
+
|
|
374
|
+
def _parse_mypy_output(self, output: str, file_path: str) -> List[LintIssue]:
|
|
375
|
+
"""Parse mypy type checker output"""
|
|
376
|
+
issues = []
|
|
377
|
+
# mypy format: file:line: error: message [error-code]
|
|
378
|
+
pattern = r'^(.+?):(\d+):\s*(error|warning|note):\s*(.+?)(?:\s*\[([^\]]+)\])?$'
|
|
379
|
+
|
|
380
|
+
for line in output.strip().split('\n'):
|
|
381
|
+
if not line.strip():
|
|
382
|
+
continue
|
|
383
|
+
match = re.match(pattern, line)
|
|
384
|
+
if match:
|
|
385
|
+
_, line_num, severity, message, error_code = match.groups()
|
|
386
|
+
rule_id = error_code or f'mypy-{severity}'
|
|
387
|
+
priority = self.classifier.classify(message, rule_id, severity)
|
|
388
|
+
|
|
389
|
+
# Mypy errors are typically P1
|
|
390
|
+
if severity == 'error' and priority == Priority.P3:
|
|
391
|
+
priority = Priority.P1
|
|
392
|
+
|
|
393
|
+
issues.append(LintIssue(
|
|
394
|
+
file_path=file_path,
|
|
395
|
+
line=int(line_num),
|
|
396
|
+
column=0,
|
|
397
|
+
message=message.strip(),
|
|
398
|
+
rule_id=rule_id,
|
|
399
|
+
priority=priority,
|
|
400
|
+
category='mypy',
|
|
401
|
+
suggestion=self.classifier.get_suggestion(message),
|
|
402
|
+
fixable=False
|
|
403
|
+
))
|
|
404
|
+
return issues
|
|
405
|
+
|
|
406
|
+
def _parse_eslint_output(self, output: str, file_path: str) -> List[LintIssue]:
|
|
407
|
+
"""Parse ESLint JSON output"""
|
|
408
|
+
issues = []
|
|
409
|
+
try:
|
|
410
|
+
data = json.loads(output)
|
|
411
|
+
for file_result in data:
|
|
412
|
+
for msg in file_result.get('messages', []):
|
|
413
|
+
rule_id = msg.get('ruleId') or 'eslint'
|
|
414
|
+
message = msg.get('message', '')
|
|
415
|
+
severity = 'error' if msg.get('severity', 1) == 2 else 'warning'
|
|
416
|
+
priority = self.classifier.classify(message, rule_id, severity)
|
|
417
|
+
|
|
418
|
+
issues.append(LintIssue(
|
|
419
|
+
file_path=file_path,
|
|
420
|
+
line=msg.get('line', 0),
|
|
421
|
+
column=msg.get('column', 0),
|
|
422
|
+
message=message.strip(),
|
|
423
|
+
rule_id=rule_id,
|
|
424
|
+
priority=priority,
|
|
425
|
+
category='eslint',
|
|
426
|
+
suggestion=self.classifier.get_suggestion(message),
|
|
427
|
+
fixable=msg.get('fix') is not None
|
|
428
|
+
))
|
|
429
|
+
except json.JSONDecodeError:
|
|
430
|
+
pass
|
|
431
|
+
return issues
|
|
432
|
+
|
|
433
|
+
def _parse_tsc_output(self, output: str, file_path: str) -> List[LintIssue]:
|
|
434
|
+
"""Parse TypeScript compiler output"""
|
|
435
|
+
issues = []
|
|
436
|
+
# TSC format: file(line,col): error TSXXXX: message
|
|
437
|
+
pattern = r'^(.+?)\((\d+),(\d+)\):\s*(error|warning)\s+(TS\d+):\s*(.+)$'
|
|
438
|
+
|
|
439
|
+
for line in output.strip().split('\n'):
|
|
440
|
+
match = re.match(pattern, line)
|
|
441
|
+
if match:
|
|
442
|
+
_, line_num, col, severity, code, message = match.groups()
|
|
443
|
+
priority = self.classifier.classify(message, code, severity)
|
|
444
|
+
issues.append(LintIssue(
|
|
445
|
+
file_path=file_path,
|
|
446
|
+
line=int(line_num),
|
|
447
|
+
column=int(col),
|
|
448
|
+
message=message.strip(),
|
|
449
|
+
rule_id=code,
|
|
450
|
+
priority=priority,
|
|
451
|
+
category='tsc',
|
|
452
|
+
suggestion=self.classifier.get_suggestion(message),
|
|
453
|
+
fixable=False
|
|
454
|
+
))
|
|
455
|
+
return issues
|
|
456
|
+
|
|
457
|
+
def _check_python(self, file_path: str, auto_fix: bool = False) -> LintResult:
|
|
458
|
+
"""Check Python file using ruff and optionally mypy"""
|
|
459
|
+
path = Path(file_path).resolve()
|
|
460
|
+
issues = []
|
|
461
|
+
linters_used = []
|
|
462
|
+
|
|
463
|
+
# Run ruff
|
|
464
|
+
if self._check_command('ruff'):
|
|
465
|
+
cmd = ['ruff', 'check']
|
|
466
|
+
if auto_fix:
|
|
467
|
+
cmd.append('--fix')
|
|
468
|
+
cmd.append(str(path))
|
|
469
|
+
|
|
470
|
+
try:
|
|
471
|
+
result = subprocess.run(
|
|
472
|
+
cmd, capture_output=True, text=True,
|
|
473
|
+
encoding='utf-8', errors='replace', timeout=30
|
|
474
|
+
)
|
|
475
|
+
ruff_issues = self._parse_ruff_output(result.stdout + result.stderr, str(path))
|
|
476
|
+
issues.extend(ruff_issues)
|
|
477
|
+
linters_used.append('ruff')
|
|
478
|
+
except Exception as e:
|
|
479
|
+
self._log(f"ruff error: {e}")
|
|
480
|
+
|
|
481
|
+
# Run mypy for type checking
|
|
482
|
+
if self._check_command('mypy'):
|
|
483
|
+
cmd = ['mypy', '--no-error-summary', str(path)]
|
|
484
|
+
try:
|
|
485
|
+
result = subprocess.run(
|
|
486
|
+
cmd, capture_output=True, text=True,
|
|
487
|
+
encoding='utf-8', errors='replace', timeout=60
|
|
488
|
+
)
|
|
489
|
+
mypy_issues = self._parse_mypy_output(result.stdout + result.stderr, str(path))
|
|
490
|
+
# Deduplicate with ruff issues
|
|
491
|
+
existing_locations = {(i.line, i.message[:50]) for i in issues}
|
|
492
|
+
for issue in mypy_issues:
|
|
493
|
+
if (issue.line, issue.message[:50]) not in existing_locations:
|
|
494
|
+
issues.append(issue)
|
|
495
|
+
linters_used.append('mypy')
|
|
496
|
+
except Exception as e:
|
|
497
|
+
self._log(f"mypy error: {e}")
|
|
498
|
+
|
|
499
|
+
if not linters_used:
|
|
500
|
+
return LintResult(
|
|
501
|
+
file_path=str(path),
|
|
502
|
+
success=False,
|
|
503
|
+
error_message="No Python linter available. Install: pip install ruff mypy"
|
|
504
|
+
)
|
|
505
|
+
|
|
506
|
+
return LintResult(
|
|
507
|
+
file_path=str(path),
|
|
508
|
+
success=True,
|
|
509
|
+
issues=issues,
|
|
510
|
+
linter_used='+'.join(linters_used)
|
|
511
|
+
)
|
|
512
|
+
|
|
513
|
+
def _check_js_ts(self, file_path: str, auto_fix: bool = False) -> LintResult:
|
|
514
|
+
"""Check JavaScript/TypeScript file"""
|
|
515
|
+
path = Path(file_path).resolve()
|
|
516
|
+
issues = []
|
|
517
|
+
linters_used = []
|
|
518
|
+
|
|
519
|
+
# Run ESLint
|
|
520
|
+
eslint_cmd = None
|
|
521
|
+
if self._check_command('eslint'):
|
|
522
|
+
eslint_cmd = ['eslint']
|
|
523
|
+
elif self._check_command('npx'):
|
|
524
|
+
eslint_cmd = ['npx', '--yes', 'eslint']
|
|
525
|
+
|
|
526
|
+
if eslint_cmd:
|
|
527
|
+
cmd = eslint_cmd + ['--format=json']
|
|
528
|
+
if auto_fix:
|
|
529
|
+
cmd.append('--fix')
|
|
530
|
+
cmd.append(str(path))
|
|
531
|
+
|
|
532
|
+
try:
|
|
533
|
+
result = subprocess.run(
|
|
534
|
+
cmd, capture_output=True, text=True,
|
|
535
|
+
encoding='utf-8', errors='replace', timeout=30
|
|
536
|
+
)
|
|
537
|
+
eslint_issues = self._parse_eslint_output(result.stdout, str(path))
|
|
538
|
+
issues.extend(eslint_issues)
|
|
539
|
+
linters_used.append('eslint')
|
|
540
|
+
except Exception as e:
|
|
541
|
+
self._log(f"eslint error: {e}")
|
|
542
|
+
|
|
543
|
+
# Run TypeScript compiler for type checking
|
|
544
|
+
if str(path).endswith(('.ts', '.tsx')):
|
|
545
|
+
tsc_cmd = None
|
|
546
|
+
if self._check_command('tsc'):
|
|
547
|
+
tsc_cmd = ['tsc']
|
|
548
|
+
elif self._check_command('npx'):
|
|
549
|
+
tsc_cmd = ['npx', '--yes', 'tsc']
|
|
550
|
+
|
|
551
|
+
if tsc_cmd:
|
|
552
|
+
cmd = tsc_cmd + ['--noEmit', '--pretty', 'false', str(path)]
|
|
553
|
+
try:
|
|
554
|
+
result = subprocess.run(
|
|
555
|
+
cmd, capture_output=True, text=True,
|
|
556
|
+
encoding='utf-8', errors='replace', timeout=60
|
|
557
|
+
)
|
|
558
|
+
tsc_issues = self._parse_tsc_output(result.stdout + result.stderr, str(path))
|
|
559
|
+
issues.extend(tsc_issues)
|
|
560
|
+
if 'tsc' not in linters_used:
|
|
561
|
+
linters_used.append('tsc')
|
|
562
|
+
except Exception as e:
|
|
563
|
+
self._log(f"tsc error: {e}")
|
|
564
|
+
|
|
565
|
+
if not linters_used:
|
|
566
|
+
return LintResult(
|
|
567
|
+
file_path=str(path),
|
|
568
|
+
success=False,
|
|
569
|
+
error_message="No JS/TS linter available. Install: npm install -g eslint typescript"
|
|
570
|
+
)
|
|
571
|
+
|
|
572
|
+
return LintResult(
|
|
573
|
+
file_path=str(path),
|
|
574
|
+
success=True,
|
|
575
|
+
issues=issues,
|
|
576
|
+
linter_used='+'.join(linters_used)
|
|
577
|
+
)
|
|
578
|
+
|
|
579
|
+
def check_file(self, file_path: str, auto_fix: bool = False) -> LintResult:
|
|
580
|
+
"""Check a single file for lint issues"""
|
|
581
|
+
path = Path(file_path)
|
|
582
|
+
|
|
583
|
+
if not path.exists():
|
|
584
|
+
return LintResult(
|
|
585
|
+
file_path=str(path),
|
|
586
|
+
success=False,
|
|
587
|
+
error_message=f"File not found: {file_path}"
|
|
588
|
+
)
|
|
589
|
+
|
|
590
|
+
ext = path.suffix.lower()
|
|
591
|
+
language = self.EXTENSION_MAP.get(ext)
|
|
592
|
+
|
|
593
|
+
if language == 'python':
|
|
594
|
+
return self._check_python(str(path), auto_fix)
|
|
595
|
+
elif language in ('typescript', 'javascript', 'vue'):
|
|
596
|
+
return self._check_js_ts(str(path), auto_fix)
|
|
597
|
+
else:
|
|
598
|
+
return LintResult(
|
|
599
|
+
file_path=str(path),
|
|
600
|
+
success=True,
|
|
601
|
+
issues=[],
|
|
602
|
+
error_message=f"No linter configured for extension: {ext}"
|
|
603
|
+
)
|
|
604
|
+
|
|
605
|
+
def check_files(self, file_paths: List[str], auto_fix: bool = False) -> List[LintResult]:
|
|
606
|
+
"""Check multiple files (parallel processing)"""
|
|
607
|
+
if len(file_paths) == 1:
|
|
608
|
+
return [self.check_file(file_paths[0], auto_fix)]
|
|
609
|
+
|
|
610
|
+
results = []
|
|
611
|
+
with ThreadPoolExecutor(max_workers=self.max_workers) as executor:
|
|
612
|
+
future_to_path = {
|
|
613
|
+
executor.submit(self.check_file, path, auto_fix): path
|
|
614
|
+
for path in file_paths
|
|
615
|
+
}
|
|
616
|
+
for future in as_completed(future_to_path):
|
|
617
|
+
results.append(future.result())
|
|
618
|
+
|
|
619
|
+
# Sort by original order
|
|
620
|
+
path_order = {path: i for i, path in enumerate(file_paths)}
|
|
621
|
+
results.sort(key=lambda r: path_order.get(r.file_path, 999))
|
|
622
|
+
return results
|
|
623
|
+
|
|
624
|
+
def check_directory(self, dir_path: str, auto_fix: bool = False,
|
|
625
|
+
extensions: Optional[List[str]] = None) -> List[LintResult]:
|
|
626
|
+
"""Check all supported files in a directory"""
|
|
627
|
+
path = Path(dir_path)
|
|
628
|
+
if not path.exists():
|
|
629
|
+
return [LintResult(
|
|
630
|
+
file_path=str(path),
|
|
631
|
+
success=False,
|
|
632
|
+
error_message=f"Directory not found: {dir_path}"
|
|
633
|
+
)]
|
|
634
|
+
|
|
635
|
+
if extensions is None:
|
|
636
|
+
extensions = list(self.EXTENSION_MAP.keys())
|
|
637
|
+
|
|
638
|
+
files = []
|
|
639
|
+
for ext in extensions:
|
|
640
|
+
files.extend(path.rglob(f'*{ext}'))
|
|
641
|
+
|
|
642
|
+
exclude_patterns = [
|
|
643
|
+
'node_modules', '__pycache__', '.git', 'dist', 'build',
|
|
644
|
+
'venv', '.venv', 'target', '.next', '.nuxt', 'coverage'
|
|
645
|
+
]
|
|
646
|
+
files = [f for f in files if not any(p in str(f) for p in exclude_patterns)]
|
|
647
|
+
|
|
648
|
+
return self.check_files([str(f) for f in files], auto_fix)
|
|
649
|
+
|
|
650
|
+
|
|
651
|
+
def print_dependencies(deps: List[DependencyStatus]) -> None:
|
|
652
|
+
"""Print dependency status"""
|
|
653
|
+
print("\n" + "=" * 60)
|
|
654
|
+
print("š§ LINTER DEPENDENCIES")
|
|
655
|
+
print("=" * 60)
|
|
656
|
+
|
|
657
|
+
for dep in deps:
|
|
658
|
+
status = "ā
" if dep.available else "ā"
|
|
659
|
+
version = f" ({dep.version})" if dep.version else ""
|
|
660
|
+
print(f" {status} {dep.name}{version}")
|
|
661
|
+
if not dep.available and dep.install_cmd:
|
|
662
|
+
print(f" Install: {dep.install_cmd}")
|
|
663
|
+
|
|
664
|
+
print("=" * 60)
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def print_issues(results: List[LintResult], summary_only: bool = False,
|
|
668
|
+
json_output: bool = False) -> None:
|
|
669
|
+
"""Print lint issues with formatting"""
|
|
670
|
+
if json_output:
|
|
671
|
+
output = {
|
|
672
|
+
'results': [r.to_dict() for r in results],
|
|
673
|
+
'summary': {
|
|
674
|
+
'total_files': len(results),
|
|
675
|
+
'p0': sum(r.p0_count for r in results),
|
|
676
|
+
'p1': sum(r.p1_count for r in results),
|
|
677
|
+
'p2': sum(r.p2_count for r in results),
|
|
678
|
+
'p3': sum(r.p3_count for r in results),
|
|
679
|
+
'blocking': any(r.has_blocking_issues for r in results)
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
print(json.dumps(output, indent=2, ensure_ascii=False))
|
|
683
|
+
return
|
|
684
|
+
|
|
685
|
+
print("\n" + "=" * 70)
|
|
686
|
+
print("š LINT CHECK RESULTS")
|
|
687
|
+
print("=" * 70)
|
|
688
|
+
|
|
689
|
+
total_p0 = sum(r.p0_count for r in results)
|
|
690
|
+
total_p1 = sum(r.p1_count for r in results)
|
|
691
|
+
total_p2 = sum(r.p2_count for r in results)
|
|
692
|
+
total_p3 = sum(r.p3_count for r in results)
|
|
693
|
+
|
|
694
|
+
if not summary_only:
|
|
695
|
+
for result in results:
|
|
696
|
+
if result.error_message and not result.success:
|
|
697
|
+
print(f"\nā {result.file_path}")
|
|
698
|
+
print(f" Error: {result.error_message}")
|
|
699
|
+
continue
|
|
700
|
+
|
|
701
|
+
if not result.issues:
|
|
702
|
+
print(f"\nā
{result.file_path} - No issues [{result.linter_used}]")
|
|
703
|
+
continue
|
|
704
|
+
|
|
705
|
+
print(f"\nš {result.file_path} [{result.linter_used}]")
|
|
706
|
+
|
|
707
|
+
priority_emoji = {
|
|
708
|
+
Priority.P0: "š“ P0 (CRITICAL)",
|
|
709
|
+
Priority.P1: "š P1 (ERROR)",
|
|
710
|
+
Priority.P2: "š” P2 (WARNING)",
|
|
711
|
+
Priority.P3: "āŖ P3 (INFO)"
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
for priority in Priority:
|
|
715
|
+
priority_issues = [i for i in result.issues if i.priority == priority]
|
|
716
|
+
if not priority_issues:
|
|
717
|
+
continue
|
|
718
|
+
|
|
719
|
+
print(f"\n {priority_emoji[priority]}:")
|
|
720
|
+
for issue in priority_issues[:10]: # Limit to 10 per priority
|
|
721
|
+
fix_indicator = "š§" if issue.fixable else ""
|
|
722
|
+
print(f" L{issue.line}:{issue.column} [{issue.rule_id}] {issue.message} {fix_indicator}")
|
|
723
|
+
if issue.suggestion:
|
|
724
|
+
print(f" š” {issue.suggestion}")
|
|
725
|
+
|
|
726
|
+
if len(priority_issues) > 10:
|
|
727
|
+
print(f" ... and {len(priority_issues) - 10} more")
|
|
728
|
+
|
|
729
|
+
# Summary
|
|
730
|
+
print("\n" + "-" * 70)
|
|
731
|
+
print("š SUMMARY")
|
|
732
|
+
print("-" * 70)
|
|
733
|
+
print(f" š“ P0 (Critical): {total_p0}")
|
|
734
|
+
print(f" š P1 (Error): {total_p1}")
|
|
735
|
+
print(f" š” P2 (Warning): {total_p2}")
|
|
736
|
+
print(f" āŖ P3 (Info): {total_p3}")
|
|
737
|
+
print(f" āāāāāāāāāāāāāāāāāāāāā")
|
|
738
|
+
print(f" Total Issues: {total_p0 + total_p1 + total_p2 + total_p3}")
|
|
739
|
+
|
|
740
|
+
if total_p0 + total_p1 > 0:
|
|
741
|
+
print("\n ā STATUS: BLOCKING - Must fix P0/P1 issues before commit")
|
|
742
|
+
elif total_p2 > 0:
|
|
743
|
+
print("\n ā ļø STATUS: WARNING - Should fix P2 issues")
|
|
744
|
+
else:
|
|
745
|
+
print("\n ā
STATUS: PASS - No blocking issues")
|
|
746
|
+
|
|
747
|
+
print("=" * 70)
|
|
748
|
+
|
|
749
|
+
|
|
750
|
+
def main() -> int:
|
|
751
|
+
parser = argparse.ArgumentParser(
|
|
752
|
+
description="Check code files for lint errors and warnings",
|
|
753
|
+
formatter_class=argparse.RawDescriptionHelpFormatter,
|
|
754
|
+
epilog="""
|
|
755
|
+
Examples:
|
|
756
|
+
python lint_check.py src/app.py # Check single file
|
|
757
|
+
python lint_check.py src/app.py src/utils.ts # Check multiple files (parallel)
|
|
758
|
+
python lint_check.py --dir src/ # Check directory
|
|
759
|
+
python lint_check.py src/app.py --fix # Auto-fix if possible
|
|
760
|
+
python lint_check.py --check-deps # Check linter dependencies
|
|
761
|
+
python lint_check.py src/app.py --json # JSON output for CI/CD
|
|
762
|
+
"""
|
|
763
|
+
)
|
|
764
|
+
parser.add_argument('files', nargs='*', help='Files to check')
|
|
765
|
+
parser.add_argument('--dir', '-d', help='Check all supported files in directory')
|
|
766
|
+
parser.add_argument('--fix', '-f', action='store_true', help='Attempt to auto-fix issues')
|
|
767
|
+
parser.add_argument('--check-deps', action='store_true', help='Check linter dependencies')
|
|
768
|
+
parser.add_argument('--summary-only', '-s', action='store_true', help='Only show summary')
|
|
769
|
+
parser.add_argument('--json', '-j', action='store_true', help='Output results as JSON')
|
|
770
|
+
parser.add_argument('--verbose', '-v', action='store_true', help='Show detailed output')
|
|
771
|
+
parser.add_argument('--ext', action='append', help='File extensions to check')
|
|
772
|
+
parser.add_argument('--workers', '-w', type=int, default=4, help='Number of parallel workers')
|
|
773
|
+
|
|
774
|
+
args = parser.parse_args()
|
|
775
|
+
|
|
776
|
+
checker = LintChecker(verbose=args.verbose, max_workers=args.workers)
|
|
777
|
+
|
|
778
|
+
# Check dependencies mode
|
|
779
|
+
if args.check_deps:
|
|
780
|
+
deps = checker.check_dependencies()
|
|
781
|
+
if args.json:
|
|
782
|
+
print(json.dumps([asdict(d) for d in deps], indent=2))
|
|
783
|
+
else:
|
|
784
|
+
print_dependencies(deps)
|
|
785
|
+
return 0 if all(d.available for d in deps) else 3
|
|
786
|
+
|
|
787
|
+
if not args.files and not args.dir:
|
|
788
|
+
parser.print_help()
|
|
789
|
+
return 1
|
|
790
|
+
|
|
791
|
+
if args.dir:
|
|
792
|
+
results = checker.check_directory(args.dir, auto_fix=args.fix, extensions=args.ext)
|
|
793
|
+
else:
|
|
794
|
+
results = checker.check_files(args.files, auto_fix=args.fix)
|
|
795
|
+
|
|
796
|
+
print_issues(results, summary_only=args.summary_only, json_output=args.json)
|
|
797
|
+
|
|
798
|
+
# Determine exit code
|
|
799
|
+
total_p0 = sum(r.p0_count for r in results)
|
|
800
|
+
total_p1 = sum(r.p1_count for r in results)
|
|
801
|
+
total_p2 = sum(r.p2_count for r in results)
|
|
802
|
+
|
|
803
|
+
if not all(r.success for r in results):
|
|
804
|
+
if any("not available" in (r.error_message or "") for r in results):
|
|
805
|
+
return 3
|
|
806
|
+
|
|
807
|
+
if total_p0 + total_p1 > 0:
|
|
808
|
+
return 1
|
|
809
|
+
elif total_p2 > 0:
|
|
810
|
+
return 2
|
|
811
|
+
|
|
812
|
+
return 0
|
|
813
|
+
|
|
814
|
+
|
|
815
|
+
if __name__ == "__main__":
|
|
816
|
+
sys.exit(main())
|