qgis-plugin-analyzer 1.5.0__tar.gz → 1.6.0__tar.gz
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.
- {qgis_plugin_analyzer-1.5.0/src/qgis_plugin_analyzer.egg-info → qgis_plugin_analyzer-1.6.0}/PKG-INFO +16 -7
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/README.md +15 -6
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/pyproject.toml +6 -2
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/__init__.py +14 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/app.py +147 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/base.py +93 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/__init__.py +19 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/analyze.py +47 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/fix.py +58 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/init.py +41 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/list_rules.py +41 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/security.py +46 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/summary.py +52 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli/commands/version.py +41 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/cli.py +34 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/commands.py +7 -7
- qgis_plugin_analyzer-1.6.0/src/analyzer/engine.py +832 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/fixer.py +390 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/markdown_reporter.py +48 -15
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/summary_reporter.py +193 -80
- qgis_plugin_analyzer-1.6.0/src/analyzer/scanner.py +302 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/transformers.py +29 -8
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/__init__.py +2 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/path_utils.py +53 -1
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/validators.py +90 -55
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/__init__.py +19 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/base.py +75 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/composite_visitor.py +73 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/imports_visitor.py +85 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/metrics_visitor.py +158 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/security_visitor.py +52 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/visitors/standards_visitor.py +284 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0/src/qgis_plugin_analyzer.egg-info}/PKG-INFO +16 -7
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/SOURCES.txt +18 -1
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_scanner.py +1 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_validators.py +41 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_vulnerability.py +1 -0
- qgis_plugin_analyzer-1.5.0/src/analyzer/cli.py +0 -214
- qgis_plugin_analyzer-1.5.0/src/analyzer/engine.py +0 -649
- qgis_plugin_analyzer-1.5.0/src/analyzer/fixer.py +0 -314
- qgis_plugin_analyzer-1.5.0/src/analyzer/scanner.py +0 -222
- qgis_plugin_analyzer-1.5.0/src/analyzer/visitors.py +0 -455
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/LICENSE +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/setup.cfg +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/__init__.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/__init__.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/models/__init__.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/models/analysis_models.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/__init__.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/html_reporter.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/__init__.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/modernization_rules.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/qgis_rules.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/secrets.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/security_checker.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/security_rules.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/semantic.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/ast_utils.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/config_utils.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/logging_utils.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/performance_utils.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/dependency_links.txt +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/entry_points.txt +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/top_level.txt +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_analyzer.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_fixer.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_high_complexity.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_safety.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_security.py +0 -0
- {qgis_plugin_analyzer-1.5.0 → qgis_plugin_analyzer-1.6.0}/tests/test_semantic.py +0 -0
{qgis_plugin_analyzer-1.5.0/src/qgis_plugin_analyzer.egg-info → qgis_plugin_analyzer-1.6.0}/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: qgis-plugin-analyzer
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.0
|
|
4
4
|
Summary: A professional static analysis tool for QGIS (PyQGIS) plugins
|
|
5
5
|
Author-email: geociencio <juanbernales@gmail.com>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -62,20 +62,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
|
|
|
62
62
|
|
|
63
63
|
| Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
|
|
64
64
|
| :--- | :---: | :---: | :---: | :---: |
|
|
65
|
+
| **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
|
|
65
66
|
| **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
|
|
66
67
|
| **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
|
|
67
68
|
| **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
|
|
68
69
|
| **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
|
|
69
|
-
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ |
|
|
70
|
-
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ |
|
|
70
|
+
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | ✅ (Server-side) |
|
|
71
|
+
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | ✅ (Server-side) |
|
|
71
72
|
| **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
|
|
72
73
|
| **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
|
|
73
74
|
|
|
74
75
|
### Key Differentiators
|
|
75
76
|
|
|
76
|
-
1. **
|
|
77
|
-
2. **
|
|
78
|
-
3. **
|
|
77
|
+
1. **Shift Left (Run Locally)**: The biggest advantage is being able to run the **same high-standard checks** as the Official Repository *before* you upload your plugin. No more "reject-fix-upload" loops.
|
|
78
|
+
2. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
|
|
79
|
+
3. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
|
|
79
80
|
4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
|
|
80
81
|
5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
|
|
81
82
|
|
|
@@ -131,7 +132,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
|
|
|
131
132
|
|
|
132
133
|
```yaml
|
|
133
134
|
- repo: https://github.com/geociencio/qgis-plugin-analyzer
|
|
134
|
-
rev:
|
|
135
|
+
rev: main # Use 'main' for latest features or a specific tag like v1.5.0
|
|
135
136
|
hooks:
|
|
136
137
|
- id: qgis-plugin-analyzer
|
|
137
138
|
```
|
|
@@ -190,6 +191,7 @@ Audits an existing QGIS plugin repository.
|
|
|
190
191
|
| :--- | :--- | :--- |
|
|
191
192
|
| `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
|
|
192
193
|
| `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
|
|
194
|
+
| `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
|
|
193
195
|
| `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
|
|
194
196
|
|
|
195
197
|
### `qgis-analyzer fix`
|
|
@@ -218,6 +220,7 @@ Performs a focused security scan on a file or directory.
|
|
|
218
220
|
| Argument | Description | Default |
|
|
219
221
|
| :--- | :--- | :--- |
|
|
220
222
|
| `path` | **(Required)** Path to the file or directory to scan. | N/A |
|
|
223
|
+
| `--deep` | Run more intensive (but slower) security checks. | `False` |
|
|
221
224
|
| `-p`, `--profile`| Configuration profile. | `default` |
|
|
222
225
|
|
|
223
226
|
### `qgis-analyzer version`
|
|
@@ -258,6 +261,7 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
258
261
|
- **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
|
|
259
262
|
- **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
|
|
260
263
|
- **[QGIS HIG (Human Interface Guidelines)](https://docs.qgis.org/latest/en/docs/developer_guide/hig.html)**: Standards for consistent and accessible user interface design.
|
|
264
|
+
- **[QGIS Security Scanning Documentation](https://plugins.qgis.org/docs/security-scanning)**: Official guide on automated security analysis (Bandit, detect-secrets) for plugins.
|
|
261
265
|
|
|
262
266
|
### Industry & Community Standards
|
|
263
267
|
- **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
|
|
@@ -267,6 +271,11 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
267
271
|
- **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
|
|
268
272
|
- **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
|
|
269
273
|
|
|
274
|
+
### Security Standards
|
|
275
|
+
- **[Bandit (PyCQA)](https://bandit.readthedocs.io/)**: The security rules implemented (B1xx - B6xx) are directly derived from the Bandit project's rule set for identifying common security issues in Python code.
|
|
276
|
+
- **[CWE (Common Weakness Enumeration)](https://cwe.mitre.org/)**: Security findings are mapped to standard CWE IDs (e.g., CWE-78 Command Injection, CWE-89 SQL Injection) for industry-standard classification.
|
|
277
|
+
- **[OWASP Top 10](https://owasp.org/www-project-top-ten/)**: The "Hardcoded Secret" and "Injection" checks align with critical OWASP vulnerabilities.
|
|
278
|
+
|
|
270
279
|
### Internal Resources
|
|
271
280
|
- **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
|
|
272
281
|
- **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
|
|
@@ -34,20 +34,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
|
|
|
34
34
|
|
|
35
35
|
| Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
|
|
36
36
|
| :--- | :---: | :---: | :---: | :---: |
|
|
37
|
+
| **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
|
|
37
38
|
| **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
|
|
38
39
|
| **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
|
|
39
40
|
| **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
|
|
40
41
|
| **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
|
|
41
|
-
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ |
|
|
42
|
-
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ |
|
|
42
|
+
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | ✅ (Server-side) |
|
|
43
|
+
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | ✅ (Server-side) |
|
|
43
44
|
| **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
|
|
44
45
|
| **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
|
|
45
46
|
|
|
46
47
|
### Key Differentiators
|
|
47
48
|
|
|
48
|
-
1. **
|
|
49
|
-
2. **
|
|
50
|
-
3. **
|
|
49
|
+
1. **Shift Left (Run Locally)**: The biggest advantage is being able to run the **same high-standard checks** as the Official Repository *before* you upload your plugin. No more "reject-fix-upload" loops.
|
|
50
|
+
2. **High-Performance Hybrid Engine**: Combines multi-core AST processing with deep understanding of cross-file relationships and Qt-specific patterns.
|
|
51
|
+
3. **Safety-First Auto-Fixing**: AST-based transformations with Git status verification and interactive diff previews.
|
|
51
52
|
4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
|
|
52
53
|
5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
|
|
53
54
|
|
|
@@ -103,7 +104,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
|
|
|
103
104
|
|
|
104
105
|
```yaml
|
|
105
106
|
- repo: https://github.com/geociencio/qgis-plugin-analyzer
|
|
106
|
-
rev:
|
|
107
|
+
rev: main # Use 'main' for latest features or a specific tag like v1.5.0
|
|
107
108
|
hooks:
|
|
108
109
|
- id: qgis-plugin-analyzer
|
|
109
110
|
```
|
|
@@ -162,6 +163,7 @@ Audits an existing QGIS plugin repository.
|
|
|
162
163
|
| :--- | :--- | :--- |
|
|
163
164
|
| `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
|
|
164
165
|
| `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
|
|
166
|
+
| `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
|
|
165
167
|
| `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
|
|
166
168
|
|
|
167
169
|
### `qgis-analyzer fix`
|
|
@@ -190,6 +192,7 @@ Performs a focused security scan on a file or directory.
|
|
|
190
192
|
| Argument | Description | Default |
|
|
191
193
|
| :--- | :--- | :--- |
|
|
192
194
|
| `path` | **(Required)** Path to the file or directory to scan. | N/A |
|
|
195
|
+
| `--deep` | Run more intensive (but slower) security checks. | `False` |
|
|
193
196
|
| `-p`, `--profile`| Configuration profile. | `default` |
|
|
194
197
|
|
|
195
198
|
### `qgis-analyzer version`
|
|
@@ -230,6 +233,7 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
230
233
|
- **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
|
|
231
234
|
- **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
|
|
232
235
|
- **[QGIS HIG (Human Interface Guidelines)](https://docs.qgis.org/latest/en/docs/developer_guide/hig.html)**: Standards for consistent and accessible user interface design.
|
|
236
|
+
- **[QGIS Security Scanning Documentation](https://plugins.qgis.org/docs/security-scanning)**: Official guide on automated security analysis (Bandit, detect-secrets) for plugins.
|
|
233
237
|
|
|
234
238
|
### Industry & Community Standards
|
|
235
239
|
- **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
|
|
@@ -239,6 +243,11 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
239
243
|
- **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
|
|
240
244
|
- **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
|
|
241
245
|
|
|
246
|
+
### Security Standards
|
|
247
|
+
- **[Bandit (PyCQA)](https://bandit.readthedocs.io/)**: The security rules implemented (B1xx - B6xx) are directly derived from the Bandit project's rule set for identifying common security issues in Python code.
|
|
248
|
+
- **[CWE (Common Weakness Enumeration)](https://cwe.mitre.org/)**: Security findings are mapped to standard CWE IDs (e.g., CWE-78 Command Injection, CWE-89 SQL Injection) for industry-standard classification.
|
|
249
|
+
- **[OWASP Top 10](https://owasp.org/www-project-top-ten/)**: The "Hardcoded Secret" and "Injection" checks align with critical OWASP vulnerabilities.
|
|
250
|
+
|
|
242
251
|
### Internal Resources
|
|
243
252
|
- **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
|
|
244
253
|
- **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "qgis-plugin-analyzer"
|
|
3
|
-
version = "1.
|
|
3
|
+
version = "1.6.0"
|
|
4
4
|
description = "A professional static analysis tool for QGIS (PyQGIS) plugins"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.8"
|
|
@@ -44,6 +44,9 @@ dev = [
|
|
|
44
44
|
"mypy>=1.0.0",
|
|
45
45
|
"qgis-stubs>=0.1",
|
|
46
46
|
"pytest>=8.3.5",
|
|
47
|
+
"PyYAML>=6.0",
|
|
48
|
+
"types-pyyaml>=6.0.12.20241230",
|
|
49
|
+
"build>=1.2.2.post1",
|
|
47
50
|
]
|
|
48
51
|
|
|
49
52
|
[tool.mypy]
|
|
@@ -57,7 +60,7 @@ disallow_untyped_defs = false # Gradual adoption
|
|
|
57
60
|
[tool.ruff]
|
|
58
61
|
line-length = 100
|
|
59
62
|
target-version = "py38"
|
|
60
|
-
exclude = ["src/analyzer/templates"]
|
|
63
|
+
exclude = ["src/analyzer/templates", ".ai-context"]
|
|
61
64
|
|
|
62
65
|
[tool.ruff.lint]
|
|
63
66
|
select = ["E", "F", "W", "I", "N", "UP", "B"]
|
|
@@ -65,6 +68,7 @@ ignore = ["E701", "E501"] # Permite if cond: statement y líneas largas
|
|
|
65
68
|
|
|
66
69
|
[tool.ruff.lint.per-file-ignores]
|
|
67
70
|
"src/analyzer/reporters.py" = ["F401", "F403", "F405"] # Dominate usa mucho import *
|
|
71
|
+
"src/analyzer/visitors/*.py" = ["N802"] # visit_* methods are part of ast.NodeVisitor API
|
|
68
72
|
|
|
69
73
|
[tool.qgis-analyzer.profiles.default]
|
|
70
74
|
strict = false
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
"""CLI Application orchestrator."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import pathlib
|
|
5
|
+
import sys
|
|
6
|
+
from typing import Dict, List, Optional
|
|
7
|
+
|
|
8
|
+
from ..utils import logger, setup_logger
|
|
9
|
+
from .base import BaseCommand
|
|
10
|
+
from .commands import (
|
|
11
|
+
AnalyzeCommand,
|
|
12
|
+
FixCommand,
|
|
13
|
+
InitCommand,
|
|
14
|
+
ListRulesCommand,
|
|
15
|
+
SecurityCommand,
|
|
16
|
+
SummaryCommand,
|
|
17
|
+
VersionCommand,
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class CLIApp:
|
|
22
|
+
"""Main CLI application orchestrator.
|
|
23
|
+
|
|
24
|
+
Manages command registration, argument parsing, and execution.
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self):
|
|
28
|
+
"""Initialize the CLI application with all available commands."""
|
|
29
|
+
self.commands: Dict[str, BaseCommand] = self._discover_commands()
|
|
30
|
+
|
|
31
|
+
def _discover_commands(self) -> Dict[str, BaseCommand]:
|
|
32
|
+
"""Auto-discover and instantiate all command classes.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
Dictionary mapping command names to command instances.
|
|
36
|
+
"""
|
|
37
|
+
command_classes: List[type[BaseCommand]] = [
|
|
38
|
+
AnalyzeCommand,
|
|
39
|
+
SecurityCommand,
|
|
40
|
+
FixCommand,
|
|
41
|
+
ListRulesCommand,
|
|
42
|
+
InitCommand,
|
|
43
|
+
SummaryCommand,
|
|
44
|
+
VersionCommand,
|
|
45
|
+
]
|
|
46
|
+
return {cmd().name: cmd() for cmd in command_classes}
|
|
47
|
+
|
|
48
|
+
def _build_parser(self) -> argparse.ArgumentParser:
|
|
49
|
+
"""Build the argument parser with all commands.
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
Configured ArgumentParser instance.
|
|
53
|
+
"""
|
|
54
|
+
from .. import __version__
|
|
55
|
+
|
|
56
|
+
parser = argparse.ArgumentParser(
|
|
57
|
+
description="QGIS Plugin Analyzer - A guardian for your PyQGIS code"
|
|
58
|
+
)
|
|
59
|
+
parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}")
|
|
60
|
+
subparsers = parser.add_subparsers(dest="command", help="Command to execute")
|
|
61
|
+
|
|
62
|
+
# Register all commands
|
|
63
|
+
for cmd in self.commands.values():
|
|
64
|
+
cmd_parser = subparsers.add_parser(cmd.name, help=cmd.help)
|
|
65
|
+
cmd.configure_parser(cmd_parser)
|
|
66
|
+
|
|
67
|
+
return parser
|
|
68
|
+
|
|
69
|
+
def _parse_args(
|
|
70
|
+
self, parser: argparse.ArgumentParser, argv: Optional[List[str]] = None
|
|
71
|
+
) -> argparse.Namespace:
|
|
72
|
+
"""Parse command-line arguments with legacy support.
|
|
73
|
+
|
|
74
|
+
Args:
|
|
75
|
+
parser: The argument parser.
|
|
76
|
+
argv: Optional argument list (defaults to sys.argv).
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
Parsed arguments namespace.
|
|
80
|
+
"""
|
|
81
|
+
if argv is None:
|
|
82
|
+
argv = sys.argv[1:]
|
|
83
|
+
|
|
84
|
+
# Legacy support: default to 'analyze' if first arg is a path
|
|
85
|
+
if argv and argv[0] not in self.commands and not argv[0].startswith("-"):
|
|
86
|
+
argv.insert(0, "analyze")
|
|
87
|
+
|
|
88
|
+
return parser.parse_args(argv)
|
|
89
|
+
|
|
90
|
+
def _setup_logging(self, args: argparse.Namespace) -> None:
|
|
91
|
+
"""Setup logging based on command arguments.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
args: Parsed command-line arguments.
|
|
95
|
+
"""
|
|
96
|
+
output_dir = pathlib.Path(getattr(args, "output", "./analysis_results")).resolve()
|
|
97
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
98
|
+
setup_logger(output_dir)
|
|
99
|
+
|
|
100
|
+
def _execute_command(self, args: argparse.Namespace) -> int:
|
|
101
|
+
"""Execute the selected command.
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
args: Parsed command-line arguments.
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Exit code from command execution.
|
|
108
|
+
"""
|
|
109
|
+
if not args.command or args.command not in self.commands:
|
|
110
|
+
return 1
|
|
111
|
+
|
|
112
|
+
command = self.commands[args.command]
|
|
113
|
+
return command.execute(args)
|
|
114
|
+
|
|
115
|
+
def run(self, argv: Optional[List[str]] = None) -> int:
|
|
116
|
+
"""Run the CLI application.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
argv: Optional argument list (defaults to sys.argv).
|
|
120
|
+
|
|
121
|
+
Returns:
|
|
122
|
+
Exit code (0 for success, non-zero for failure).
|
|
123
|
+
"""
|
|
124
|
+
parser = self._build_parser()
|
|
125
|
+
|
|
126
|
+
try:
|
|
127
|
+
args = self._parse_args(parser, argv)
|
|
128
|
+
|
|
129
|
+
if not args.command:
|
|
130
|
+
parser.print_help()
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
self._setup_logging(args)
|
|
134
|
+
return self._execute_command(args)
|
|
135
|
+
|
|
136
|
+
except KeyboardInterrupt:
|
|
137
|
+
logger.info("\n⏹️ Analysis interrupted.")
|
|
138
|
+
return 1
|
|
139
|
+
except FileNotFoundError as e:
|
|
140
|
+
logger.error(f"Error: File not found: {e}")
|
|
141
|
+
return 1
|
|
142
|
+
except ValueError as e:
|
|
143
|
+
logger.error(f"Error: {e}")
|
|
144
|
+
return 1
|
|
145
|
+
except Exception as e:
|
|
146
|
+
logger.critical(f"Critical Error: {e}", exc_info=True)
|
|
147
|
+
return 1
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"""Base command class for CLI commands."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
import pathlib
|
|
5
|
+
from abc import ABC, abstractmethod
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class BaseCommand(ABC):
|
|
10
|
+
"""Abstract base class for CLI commands.
|
|
11
|
+
|
|
12
|
+
Each command encapsulates its own argument configuration and execution logic.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
@property
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def name(self) -> str:
|
|
18
|
+
"""Command name as it appears in the CLI.
|
|
19
|
+
|
|
20
|
+
Returns:
|
|
21
|
+
The command name string.
|
|
22
|
+
"""
|
|
23
|
+
|
|
24
|
+
@property
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def help(self) -> str:
|
|
27
|
+
"""Help text for the command.
|
|
28
|
+
|
|
29
|
+
Returns:
|
|
30
|
+
A brief description of what the command does.
|
|
31
|
+
"""
|
|
32
|
+
|
|
33
|
+
@abstractmethod
|
|
34
|
+
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
35
|
+
"""Configure command-specific arguments.
|
|
36
|
+
|
|
37
|
+
Args:
|
|
38
|
+
parser: The argument parser for this command.
|
|
39
|
+
"""
|
|
40
|
+
|
|
41
|
+
@abstractmethod
|
|
42
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
43
|
+
"""Execute the command.
|
|
44
|
+
|
|
45
|
+
Args:
|
|
46
|
+
args: Parsed command-line arguments.
|
|
47
|
+
|
|
48
|
+
Returns:
|
|
49
|
+
Exit code (0 for success, non-zero for failure).
|
|
50
|
+
"""
|
|
51
|
+
|
|
52
|
+
def add_common_args(
|
|
53
|
+
self,
|
|
54
|
+
parser: argparse.ArgumentParser,
|
|
55
|
+
include_output: bool = True,
|
|
56
|
+
include_profile: bool = True,
|
|
57
|
+
) -> None:
|
|
58
|
+
"""Add common arguments shared across multiple commands.
|
|
59
|
+
|
|
60
|
+
Args:
|
|
61
|
+
parser: The argument parser to add arguments to.
|
|
62
|
+
include_output: Whether to include the --output argument.
|
|
63
|
+
include_profile: Whether to include the --profile argument.
|
|
64
|
+
"""
|
|
65
|
+
if include_output:
|
|
66
|
+
parser.add_argument(
|
|
67
|
+
"-o",
|
|
68
|
+
"--output",
|
|
69
|
+
help="Output directory for reports",
|
|
70
|
+
default="./analysis_results",
|
|
71
|
+
)
|
|
72
|
+
if include_profile:
|
|
73
|
+
parser.add_argument(
|
|
74
|
+
"-p",
|
|
75
|
+
"--profile",
|
|
76
|
+
help="Configuration profile from pyproject.toml",
|
|
77
|
+
default="default",
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def setup_output_dir(self, args: argparse.Namespace) -> Optional[pathlib.Path]:
|
|
81
|
+
"""Setup and return the output directory if present in args.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
args: Parsed command-line arguments.
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
The resolved output directory path, or None if not applicable.
|
|
88
|
+
"""
|
|
89
|
+
if hasattr(args, "output"):
|
|
90
|
+
output_dir = pathlib.Path(args.output).resolve()
|
|
91
|
+
output_dir.mkdir(parents=True, exist_ok=True)
|
|
92
|
+
return output_dir
|
|
93
|
+
return None
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""CLI commands package."""
|
|
2
|
+
|
|
3
|
+
from .analyze import AnalyzeCommand
|
|
4
|
+
from .fix import FixCommand
|
|
5
|
+
from .init import InitCommand
|
|
6
|
+
from .list_rules import ListRulesCommand
|
|
7
|
+
from .security import SecurityCommand
|
|
8
|
+
from .summary import SummaryCommand
|
|
9
|
+
from .version import VersionCommand
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"AnalyzeCommand",
|
|
13
|
+
"SecurityCommand",
|
|
14
|
+
"FixCommand",
|
|
15
|
+
"ListRulesCommand",
|
|
16
|
+
"InitCommand",
|
|
17
|
+
"SummaryCommand",
|
|
18
|
+
"VersionCommand",
|
|
19
|
+
]
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""Analyze command implementation."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ...commands import handle_analyze
|
|
6
|
+
from ..base import BaseCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AnalyzeCommand(BaseCommand):
|
|
10
|
+
"""Command to analyze an existing QGIS plugin."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
"""Command name."""
|
|
15
|
+
return "analyze"
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def help(self) -> str:
|
|
19
|
+
"""Command help text."""
|
|
20
|
+
return "Analyze an existing QGIS plugin"
|
|
21
|
+
|
|
22
|
+
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
23
|
+
"""Configure analyze command arguments.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
parser: The argument parser for this command.
|
|
27
|
+
"""
|
|
28
|
+
parser.add_argument("project_path", help="Path to the QGIS project to analyze")
|
|
29
|
+
self.add_common_args(parser)
|
|
30
|
+
parser.add_argument(
|
|
31
|
+
"-r",
|
|
32
|
+
"--report",
|
|
33
|
+
action="store_true",
|
|
34
|
+
help="Generate detailed HTML/Markdown reports",
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
38
|
+
"""Execute the analyze command.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
args: Parsed command-line arguments.
|
|
42
|
+
|
|
43
|
+
Returns:
|
|
44
|
+
Exit code (0 for success).
|
|
45
|
+
"""
|
|
46
|
+
handle_analyze(args)
|
|
47
|
+
return 0
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"""Fix command implementation."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ...commands import handle_fix
|
|
6
|
+
from ..base import BaseCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class FixCommand(BaseCommand):
|
|
10
|
+
"""Command to auto-fix common QGIS plugin issues."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
"""Command name."""
|
|
15
|
+
return "fix"
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def help(self) -> str:
|
|
19
|
+
"""Command help text."""
|
|
20
|
+
return "Auto-fix common QGIS plugin issues"
|
|
21
|
+
|
|
22
|
+
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
23
|
+
"""Configure fix command arguments.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
parser: The argument parser for this command.
|
|
27
|
+
"""
|
|
28
|
+
parser.add_argument("path", type=str, help="Path to the QGIS plugin directory")
|
|
29
|
+
parser.add_argument(
|
|
30
|
+
"--dry-run",
|
|
31
|
+
action="store_true",
|
|
32
|
+
default=True,
|
|
33
|
+
help="Show proposed changes without applying (default: True)",
|
|
34
|
+
)
|
|
35
|
+
parser.add_argument("--apply", action="store_true", help="Apply fixes (disables dry-run)")
|
|
36
|
+
parser.add_argument(
|
|
37
|
+
"--auto-approve",
|
|
38
|
+
action="store_true",
|
|
39
|
+
help="Apply all fixes without confirmation",
|
|
40
|
+
)
|
|
41
|
+
self.add_common_args(parser, include_output=False)
|
|
42
|
+
parser.add_argument(
|
|
43
|
+
"--rules",
|
|
44
|
+
type=str,
|
|
45
|
+
help="Comma-separated list of rule IDs to fix",
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
49
|
+
"""Execute the fix command.
|
|
50
|
+
|
|
51
|
+
Args:
|
|
52
|
+
args: Parsed command-line arguments.
|
|
53
|
+
|
|
54
|
+
Returns:
|
|
55
|
+
Exit code (0 for success).
|
|
56
|
+
"""
|
|
57
|
+
handle_fix(args)
|
|
58
|
+
return 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Init command implementation."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ...commands import handle_init
|
|
6
|
+
from ..base import BaseCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class InitCommand(BaseCommand):
|
|
10
|
+
"""Command to initialize a new .analyzerignore with defaults."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
"""Command name."""
|
|
15
|
+
return "init"
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def help(self) -> str:
|
|
19
|
+
"""Command help text."""
|
|
20
|
+
return "Initialize a new .analyzerignore with defaults"
|
|
21
|
+
|
|
22
|
+
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
23
|
+
"""Configure init command arguments.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
parser: The argument parser for this command.
|
|
27
|
+
"""
|
|
28
|
+
# No additional arguments needed
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
32
|
+
"""Execute the init command.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
args: Parsed command-line arguments.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Exit code (0 for success).
|
|
39
|
+
"""
|
|
40
|
+
handle_init()
|
|
41
|
+
return 0
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""List rules command implementation."""
|
|
2
|
+
|
|
3
|
+
import argparse
|
|
4
|
+
|
|
5
|
+
from ...commands import handle_list_rules
|
|
6
|
+
from ..base import BaseCommand
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class ListRulesCommand(BaseCommand):
|
|
10
|
+
"""Command to list all available QGIS audit rules."""
|
|
11
|
+
|
|
12
|
+
@property
|
|
13
|
+
def name(self) -> str:
|
|
14
|
+
"""Command name."""
|
|
15
|
+
return "list-rules"
|
|
16
|
+
|
|
17
|
+
@property
|
|
18
|
+
def help(self) -> str:
|
|
19
|
+
"""Command help text."""
|
|
20
|
+
return "List all available QGIS audit rules"
|
|
21
|
+
|
|
22
|
+
def configure_parser(self, parser: argparse.ArgumentParser) -> None:
|
|
23
|
+
"""Configure list-rules command arguments.
|
|
24
|
+
|
|
25
|
+
Args:
|
|
26
|
+
parser: The argument parser for this command.
|
|
27
|
+
"""
|
|
28
|
+
# No additional arguments needed
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
def execute(self, args: argparse.Namespace) -> int:
|
|
32
|
+
"""Execute the list-rules command.
|
|
33
|
+
|
|
34
|
+
Args:
|
|
35
|
+
args: Parsed command-line arguments.
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
Exit code (0 for success).
|
|
39
|
+
"""
|
|
40
|
+
handle_list_rules()
|
|
41
|
+
return 0
|