qgis-plugin-analyzer 1.4.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.4.0/src/qgis_plugin_analyzer.egg-info → qgis_plugin_analyzer-1.6.0}/PKG-INFO +32 -10
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/README.md +31 -9
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/pyproject.toml +7 -2
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/__init__.py +2 -1
- 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.6.0/src/analyzer/commands.py +163 -0
- 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.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/markdown_reporter.py +88 -14
- qgis_plugin_analyzer-1.6.0/src/analyzer/reporters/summary_reporter.py +399 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/qgis_rules.py +3 -1
- qgis_plugin_analyzer-1.6.0/src/analyzer/scanner.py +302 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/secrets.py +84 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/security_checker.py +85 -0
- qgis_plugin_analyzer-1.6.0/src/analyzer/security_rules.py +127 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/transformers.py +29 -8
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/__init__.py +2 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/path_utils.py +53 -1
- {qgis_plugin_analyzer-1.4.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.4.0 → qgis_plugin_analyzer-1.6.0/src/qgis_plugin_analyzer.egg-info}/PKG-INFO +32 -10
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/SOURCES.txt +22 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_scanner.py +1 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_validators.py +41 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_vulnerability.py +1 -0
- qgis_plugin_analyzer-1.4.0/src/analyzer/cli.py +0 -311
- qgis_plugin_analyzer-1.4.0/src/analyzer/engine.py +0 -586
- qgis_plugin_analyzer-1.4.0/src/analyzer/fixer.py +0 -314
- qgis_plugin_analyzer-1.4.0/src/analyzer/reporters/summary_reporter.py +0 -222
- qgis_plugin_analyzer-1.4.0/src/analyzer/scanner.py +0 -794
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/LICENSE +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/setup.cfg +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/__init__.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/models/__init__.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/models/analysis_models.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/__init__.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/reporters/html_reporter.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/__init__.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/rules/modernization_rules.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/semantic.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/ast_utils.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/config_utils.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/logging_utils.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/analyzer/utils/performance_utils.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/dependency_links.txt +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/entry_points.txt +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/src/qgis_plugin_analyzer.egg-info/top_level.txt +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_analyzer.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_fixer.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_high_complexity.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_safety.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_security.py +0 -0
- {qgis_plugin_analyzer-1.4.0 → qgis_plugin_analyzer-1.6.0}/tests/test_semantic.py +0 -0
{qgis_plugin_analyzer-1.4.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
|
|
@@ -34,13 +34,16 @@ Dynamic: license-file
|
|
|
34
34
|

|
|
35
35
|

|
|
36
36
|

|
|
37
|
-

|
|
38
|
+

|
|
39
|
+

|
|
39
40
|
|
|
40
41
|
The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for QGIS (PyQGIS) plugin developers. Its goal is to elevate plugin quality by ensuring they follow community best practices and are optimized for AI-assisted development.
|
|
41
42
|
|
|
42
43
|
## ✨ Main Features
|
|
43
44
|
|
|
45
|
+
- **Security Core (Bandit-inspired)**: Professional vulnerability scanning detecting `eval`, `exec`, shell injections, and SQL injection risks.
|
|
46
|
+
- **Deep Entropy Secret Scanner**: Detects hardcoded API keys, passwords, and sensitive tokens using regex and information entropy.
|
|
44
47
|
- **High-Performance Engine**: Parallel analysis powered by `ProcessPoolExecutor` for ultra-fast execution on multi-core systems.
|
|
45
48
|
- **Project Auto-Detection**: Intelligently distinguishes between official QGIS Plugins and Generic Python Projects, tailoring validation logic accordingly.
|
|
46
49
|
- **Advanced Ignore Engine**: Robust `.analyzerignore` support with non-anchored patterns and smart default excludes (`.venv`, `build`, etc.).
|
|
@@ -59,21 +62,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
|
|
|
59
62
|
|
|
60
63
|
| Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
|
|
61
64
|
| :--- | :---: | :---: | :---: | :---: |
|
|
65
|
+
| **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
|
|
62
66
|
| **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
|
|
63
67
|
| **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
|
|
64
68
|
| **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
|
|
65
69
|
| **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
|
|
66
|
-
| **
|
|
67
|
-
| **
|
|
68
|
-
| **Architecture Audit** | ✅ (UI/Core) | ❌ | ❌ | ❌ |
|
|
70
|
+
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | ✅ (Server-side) |
|
|
71
|
+
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | ✅ (Server-side) |
|
|
69
72
|
| **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
|
|
70
73
|
| **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
|
|
71
74
|
|
|
72
75
|
### Key Differentiators
|
|
73
76
|
|
|
74
|
-
1. **
|
|
75
|
-
2. **
|
|
76
|
-
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.
|
|
77
80
|
4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
|
|
78
81
|
5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
|
|
79
82
|
|
|
@@ -129,7 +132,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
|
|
|
129
132
|
|
|
130
133
|
```yaml
|
|
131
134
|
- repo: https://github.com/geociencio/qgis-plugin-analyzer
|
|
132
|
-
rev:
|
|
135
|
+
rev: main # Use 'main' for latest features or a specific tag like v1.5.0
|
|
133
136
|
hooks:
|
|
134
137
|
- id: qgis-plugin-analyzer
|
|
135
138
|
```
|
|
@@ -188,6 +191,7 @@ Audits an existing QGIS plugin repository.
|
|
|
188
191
|
| :--- | :--- | :--- |
|
|
189
192
|
| `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
|
|
190
193
|
| `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
|
|
194
|
+
| `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
|
|
191
195
|
| `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
|
|
192
196
|
|
|
193
197
|
### `qgis-analyzer fix`
|
|
@@ -210,6 +214,18 @@ Shows a professional, color-coded summary of findings directly in your terminal.
|
|
|
210
214
|
| `-b`, `--by` | Granularity of the summary: `total`, `modules`, `functions`, `classes`. | `total` |
|
|
211
215
|
| `-i`, `--input` | Path to the `project_context.json` file to summarize. | `analysis_results/project_context.json` |
|
|
212
216
|
|
|
217
|
+
### `qgis-analyzer security`
|
|
218
|
+
Performs a focused security scan on a file or directory.
|
|
219
|
+
|
|
220
|
+
| Argument | Description | Default |
|
|
221
|
+
| :--- | :--- | :--- |
|
|
222
|
+
| `path` | **(Required)** Path to the file or directory to scan. | N/A |
|
|
223
|
+
| `--deep` | Run more intensive (but slower) security checks. | `False` |
|
|
224
|
+
| `-p`, `--profile`| Configuration profile. | `default` |
|
|
225
|
+
|
|
226
|
+
### `qgis-analyzer version`
|
|
227
|
+
Shows the current version of the analyzer.
|
|
228
|
+
|
|
213
229
|
**Example:**
|
|
214
230
|
```bash
|
|
215
231
|
# Executive summary
|
|
@@ -245,6 +261,7 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
245
261
|
- **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
|
|
246
262
|
- **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
|
|
247
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.
|
|
248
265
|
|
|
249
266
|
### Industry & Community Standards
|
|
250
267
|
- **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
|
|
@@ -254,6 +271,11 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
254
271
|
- **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
|
|
255
272
|
- **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
|
|
256
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
|
+
|
|
257
279
|
### Internal Resources
|
|
258
280
|
- **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
|
|
259
281
|
- **[Standardized Scoring Metrics](docs/SCORING_STANDARDS.md)**: Mathematical logic and thresholds for project evaluation.
|
|
@@ -6,13 +6,16 @@
|
|
|
6
6
|

|
|
7
7
|

|
|
8
8
|

|
|
9
|
-

|
|
10
|
+

|
|
11
|
+

|
|
11
12
|
|
|
12
13
|
The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for QGIS (PyQGIS) plugin developers. Its goal is to elevate plugin quality by ensuring they follow community best practices and are optimized for AI-assisted development.
|
|
13
14
|
|
|
14
15
|
## ✨ Main Features
|
|
15
16
|
|
|
17
|
+
- **Security Core (Bandit-inspired)**: Professional vulnerability scanning detecting `eval`, `exec`, shell injections, and SQL injection risks.
|
|
18
|
+
- **Deep Entropy Secret Scanner**: Detects hardcoded API keys, passwords, and sensitive tokens using regex and information entropy.
|
|
16
19
|
- **High-Performance Engine**: Parallel analysis powered by `ProcessPoolExecutor` for ultra-fast execution on multi-core systems.
|
|
17
20
|
- **Project Auto-Detection**: Intelligently distinguishes between official QGIS Plugins and Generic Python Projects, tailoring validation logic accordingly.
|
|
18
21
|
- **Advanced Ignore Engine**: Robust `.analyzerignore` support with non-anchored patterns and smart default excludes (`.venv`, `build`, etc.).
|
|
@@ -31,21 +34,21 @@ The **QGIS Plugin Analyzer** is a static analysis tool designed specifically for
|
|
|
31
34
|
|
|
32
35
|
| Feature | **QGIS Plugin Analyzer** | flake8-qgis | Ruff (Standard) | Official Repo Bot |
|
|
33
36
|
| :--- | :---: | :---: | :---: | :---: |
|
|
37
|
+
| **Run Locally / Offline**| ✅ (Your Machine) | ✅ | ✅ | ❌ (Upload Only) |
|
|
34
38
|
| **Static Linting** | ✅ (Ruff + Custom) | ✅ (flake8) | ✅ (General) | ✅ (Limited) |
|
|
35
39
|
| **QGIS-Specific Rules**| ✅ (Precise AST) | ✅ (Regex/AST) | ❌ | ✅ |
|
|
36
40
|
| **Interactive Auto-Fix**| ✅ | ❌ | ❌ | ❌ |
|
|
37
41
|
| **Semantic Analysis** | ✅ | ❌ | ❌ | ❌ |
|
|
38
|
-
| **
|
|
39
|
-
| **
|
|
40
|
-
| **Architecture Audit** | ✅ (UI/Core) | ❌ | ❌ | ❌ |
|
|
42
|
+
| **Security Audit** | ✅ (Bandit-style) | ❌ | ❌ | ✅ (Server-side) |
|
|
43
|
+
| **Secret Scanning** | ✅ (Entropy) | ❌ | ❌ | ✅ (Server-side) |
|
|
41
44
|
| **HTML/MD Reports** | ✅ | ❌ | ❌ | ❌ |
|
|
42
45
|
| **AI Context Gen** | ✅ (Project Brain) | ❌ | ❌ | ❌ |
|
|
43
46
|
|
|
44
47
|
### Key Differentiators
|
|
45
48
|
|
|
46
|
-
1. **
|
|
47
|
-
2. **
|
|
48
|
-
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.
|
|
49
52
|
4. **Zero Runtime Stack**: Minimal footprint, ultra-fast execution, and easy CI integration.
|
|
50
53
|
5. **AI-Centric Design**: Built to help developers and AI agents understand complex QGIS plugins instantly.
|
|
51
54
|
|
|
@@ -101,7 +104,7 @@ You can run `qgis-plugin-analyzer` automatically before every commit to ensure q
|
|
|
101
104
|
|
|
102
105
|
```yaml
|
|
103
106
|
- repo: https://github.com/geociencio/qgis-plugin-analyzer
|
|
104
|
-
rev:
|
|
107
|
+
rev: main # Use 'main' for latest features or a specific tag like v1.5.0
|
|
105
108
|
hooks:
|
|
106
109
|
- id: qgis-plugin-analyzer
|
|
107
110
|
```
|
|
@@ -160,6 +163,7 @@ Audits an existing QGIS plugin repository.
|
|
|
160
163
|
| :--- | :--- | :--- |
|
|
161
164
|
| `project_path` | **(Required)** Path to the plugin directory to analyze. | N/A |
|
|
162
165
|
| `-o`, `--output` | Directory where HTML/Markdown reports will be saved. | `./analysis_results` |
|
|
166
|
+
| `-r`, `--report` | Explicitly generate detailed HTML/Markdown reports. | `False` |
|
|
163
167
|
| `-p`, `--profile`| Configuration profile from `pyproject.toml` (`default`, `release`). | `default` |
|
|
164
168
|
|
|
165
169
|
### `qgis-analyzer fix`
|
|
@@ -182,6 +186,18 @@ Shows a professional, color-coded summary of findings directly in your terminal.
|
|
|
182
186
|
| `-b`, `--by` | Granularity of the summary: `total`, `modules`, `functions`, `classes`. | `total` |
|
|
183
187
|
| `-i`, `--input` | Path to the `project_context.json` file to summarize. | `analysis_results/project_context.json` |
|
|
184
188
|
|
|
189
|
+
### `qgis-analyzer security`
|
|
190
|
+
Performs a focused security scan on a file or directory.
|
|
191
|
+
|
|
192
|
+
| Argument | Description | Default |
|
|
193
|
+
| :--- | :--- | :--- |
|
|
194
|
+
| `path` | **(Required)** Path to the file or directory to scan. | N/A |
|
|
195
|
+
| `--deep` | Run more intensive (but slower) security checks. | `False` |
|
|
196
|
+
| `-p`, `--profile`| Configuration profile. | `default` |
|
|
197
|
+
|
|
198
|
+
### `qgis-analyzer version`
|
|
199
|
+
Shows the current version of the analyzer.
|
|
200
|
+
|
|
185
201
|
**Example:**
|
|
186
202
|
```bash
|
|
187
203
|
# Executive summary
|
|
@@ -217,6 +233,7 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
217
233
|
- **[QGIS Plugin Repository Requirements](https://plugins.qgis.org/publish/)**: Mandatory criteria for plugin approval in the official repository.
|
|
218
234
|
- **[QGIS Coding Standards](https://docs.qgis.org/latest/en/docs/developer_guide/codingstandards.html)**: Core style and organization guidelines for the QGIS project.
|
|
219
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.
|
|
220
237
|
|
|
221
238
|
### Industry & Community Standards
|
|
222
239
|
- **[flake8-qgis Rules](https://github.com/qgis/flake8-qgis)**: Community-driven linting rules for PyQGIS (QGS101-106).
|
|
@@ -226,6 +243,11 @@ The development of this analyzer is based on official QGIS community guidelines,
|
|
|
226
243
|
- **[Conventional Commits](https://www.conventionalcommits.org/)**: Standard for clear, machine-readable commit history.
|
|
227
244
|
- **[Keep a Changelog](https://keepachangelog.com/)**: Best practices for maintainable version history.
|
|
228
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
|
+
|
|
229
251
|
### Internal Resources
|
|
230
252
|
- **[Detailed Rules Catalog](RULES.md)**: Full documentation of all audit rules implemented in this analyzer.
|
|
231
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"
|
|
@@ -43,6 +43,10 @@ dev = [
|
|
|
43
43
|
"ruff>=0.9.0",
|
|
44
44
|
"mypy>=1.0.0",
|
|
45
45
|
"qgis-stubs>=0.1",
|
|
46
|
+
"pytest>=8.3.5",
|
|
47
|
+
"PyYAML>=6.0",
|
|
48
|
+
"types-pyyaml>=6.0.12.20241230",
|
|
49
|
+
"build>=1.2.2.post1",
|
|
46
50
|
]
|
|
47
51
|
|
|
48
52
|
[tool.mypy]
|
|
@@ -56,7 +60,7 @@ disallow_untyped_defs = false # Gradual adoption
|
|
|
56
60
|
[tool.ruff]
|
|
57
61
|
line-length = 100
|
|
58
62
|
target-version = "py38"
|
|
59
|
-
exclude = ["src/analyzer/templates"]
|
|
63
|
+
exclude = ["src/analyzer/templates", ".ai-context"]
|
|
60
64
|
|
|
61
65
|
[tool.ruff.lint]
|
|
62
66
|
select = ["E", "F", "W", "I", "N", "UP", "B"]
|
|
@@ -64,6 +68,7 @@ ignore = ["E701", "E501"] # Permite if cond: statement y líneas largas
|
|
|
64
68
|
|
|
65
69
|
[tool.ruff.lint.per-file-ignores]
|
|
66
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
|
|
67
72
|
|
|
68
73
|
[tool.qgis-analyzer.profiles.default]
|
|
69
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
|