code-ranker 2.0.0__tar.gz → 3.0.0a1__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.
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/Cargo.lock +10 -10
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/Cargo.toml +9 -9
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/PKG-INFO +2 -2
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/README.md +1 -1
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/check.rs +94 -3
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/cli.rs +21 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/config/load.rs +33 -20
- code_ranker-3.0.0a1/crates/code-ranker-cli/src/config/metrics.rs +88 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/config/mod.rs +1 -0
- code_ranker-3.0.0a1/crates/code-ranker-cli/src/config/model.rs +444 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/config/rules.rs +23 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/config/violations.rs +48 -41
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/main.rs +8 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/plugin/mod.rs +47 -0
- code_ranker-3.0.0a1/crates/code-ranker-cli/src/recommend/prompt.rs +222 -0
- code_ranker-3.0.0a1/crates/code-ranker-cli/src/recommend/scorecard.rs +346 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/recommend.rs +308 -554
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/report.rs +45 -2
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/tests/e2e.rs +192 -1
- code_ranker-3.0.0a1/crates/code-ranker-plugin-rust/src/collapse.rs +242 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-rust/src/lib.rs +7 -236
- code_ranker-3.0.0a1/crates/code-ranker-plugin-rust/src/module_graph/resolve.rs +758 -0
- code_ranker-3.0.0a1/crates/code-ranker-plugin-rust/src/module_graph/shared.rs +147 -0
- code_ranker-3.0.0a1/crates/code-ranker-plugin-rust/src/module_graph/walk.rs +691 -0
- code_ranker-3.0.0a1/crates/code-ranker-plugin-rust/src/module_graph.rs +206 -0
- code_ranker-2.0.0/crates/code-ranker-cli/src/config/model.rs +0 -256
- code_ranker-2.0.0/crates/code-ranker-plugin-rust/src/module_graph.rs +0 -1710
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/LICENSE +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/analyze.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/config/ignore.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/git.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/logger.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/pipeline.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/src/presets.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-cli/tests/grammar_single_version.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-ecmascript-core/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-ecmascript-core/src/ecmascript_ts.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-ecmascript-core/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-ecmascript-core/src/metrics_tests.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/attrs.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/cycles.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/finalize.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/hk.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/level_graph.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/metrics.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/relativize.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/serialize.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/snapshot.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-graph/src/stats.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/attrs.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/edge.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/graph.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/level.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/log.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/node.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-api/src/plugin.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-javascript/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-javascript/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-python/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-python/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-python/src/metrics_tests.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-python/src/python_ts.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-rust/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-rust/src/crate_graph.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-rust/src/ids.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-rust/src/internal.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-rust/src/rust_ts.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-typescript/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-plugin-typescript/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-test-support/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-test-support/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/Cargo.toml +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/app.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/base.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/diff.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/export-popup.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/export.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/graphviz.umd.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/grouping.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/index.html +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/layout.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/map-interactions.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/map-render.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/map-svg.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/map.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/modal-content.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/modal.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/modal.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/nav.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/node-popup.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/node-table.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/panzoom.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/schema.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/snap-controls.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/snap.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/snarkdown.umd.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/source-links.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/summary.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/tables.css +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/tooltip.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/ui.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/utils.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/assets/view-state.js +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/crates/code-ranker-viewer/src/lib.rs +0 -0
- {code_ranker-2.0.0 → code_ranker-3.0.0a1}/pyproject.toml +0 -0
|
@@ -208,7 +208,7 @@ checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9"
|
|
|
208
208
|
|
|
209
209
|
[[package]]
|
|
210
210
|
name = "code-ranker"
|
|
211
|
-
version = "
|
|
211
|
+
version = "3.0.0-alpha.1"
|
|
212
212
|
dependencies = [
|
|
213
213
|
"anyhow",
|
|
214
214
|
"chrono",
|
|
@@ -231,7 +231,7 @@ dependencies = [
|
|
|
231
231
|
|
|
232
232
|
[[package]]
|
|
233
233
|
name = "code-ranker-ecmascript-core"
|
|
234
|
-
version = "
|
|
234
|
+
version = "3.0.0-alpha.1"
|
|
235
235
|
dependencies = [
|
|
236
236
|
"anyhow",
|
|
237
237
|
"code-ranker-graph",
|
|
@@ -246,7 +246,7 @@ dependencies = [
|
|
|
246
246
|
|
|
247
247
|
[[package]]
|
|
248
248
|
name = "code-ranker-graph"
|
|
249
|
-
version = "
|
|
249
|
+
version = "3.0.0-alpha.1"
|
|
250
250
|
dependencies = [
|
|
251
251
|
"anyhow",
|
|
252
252
|
"chrono",
|
|
@@ -257,7 +257,7 @@ dependencies = [
|
|
|
257
257
|
|
|
258
258
|
[[package]]
|
|
259
259
|
name = "code-ranker-plugin-api"
|
|
260
|
-
version = "
|
|
260
|
+
version = "3.0.0-alpha.1"
|
|
261
261
|
dependencies = [
|
|
262
262
|
"anyhow",
|
|
263
263
|
"chrono",
|
|
@@ -266,7 +266,7 @@ dependencies = [
|
|
|
266
266
|
|
|
267
267
|
[[package]]
|
|
268
268
|
name = "code-ranker-plugin-javascript"
|
|
269
|
-
version = "
|
|
269
|
+
version = "3.0.0-alpha.1"
|
|
270
270
|
dependencies = [
|
|
271
271
|
"anyhow",
|
|
272
272
|
"code-ranker-ecmascript-core",
|
|
@@ -278,7 +278,7 @@ dependencies = [
|
|
|
278
278
|
|
|
279
279
|
[[package]]
|
|
280
280
|
name = "code-ranker-plugin-python"
|
|
281
|
-
version = "
|
|
281
|
+
version = "3.0.0-alpha.1"
|
|
282
282
|
dependencies = [
|
|
283
283
|
"anyhow",
|
|
284
284
|
"code-ranker-graph",
|
|
@@ -292,7 +292,7 @@ dependencies = [
|
|
|
292
292
|
|
|
293
293
|
[[package]]
|
|
294
294
|
name = "code-ranker-plugin-rust"
|
|
295
|
-
version = "
|
|
295
|
+
version = "3.0.0-alpha.1"
|
|
296
296
|
dependencies = [
|
|
297
297
|
"anyhow",
|
|
298
298
|
"cargo_metadata",
|
|
@@ -307,7 +307,7 @@ dependencies = [
|
|
|
307
307
|
|
|
308
308
|
[[package]]
|
|
309
309
|
name = "code-ranker-plugin-typescript"
|
|
310
|
-
version = "
|
|
310
|
+
version = "3.0.0-alpha.1"
|
|
311
311
|
dependencies = [
|
|
312
312
|
"anyhow",
|
|
313
313
|
"code-ranker-ecmascript-core",
|
|
@@ -319,14 +319,14 @@ dependencies = [
|
|
|
319
319
|
|
|
320
320
|
[[package]]
|
|
321
321
|
name = "code-ranker-test-support"
|
|
322
|
-
version = "
|
|
322
|
+
version = "3.0.0-alpha.1"
|
|
323
323
|
dependencies = [
|
|
324
324
|
"code-ranker-plugin-api",
|
|
325
325
|
]
|
|
326
326
|
|
|
327
327
|
[[package]]
|
|
328
328
|
name = "code-ranker-viewer"
|
|
329
|
-
version = "
|
|
329
|
+
version = "3.0.0-alpha.1"
|
|
330
330
|
dependencies = [
|
|
331
331
|
"anyhow",
|
|
332
332
|
"code-ranker-graph",
|
|
@@ -3,7 +3,7 @@ members = ["crates/*"]
|
|
|
3
3
|
resolver = "3"
|
|
4
4
|
|
|
5
5
|
[workspace.package]
|
|
6
|
-
version = "
|
|
6
|
+
version = "3.0.0-alpha.1"
|
|
7
7
|
edition = "2024"
|
|
8
8
|
rust-version = "1.88"
|
|
9
9
|
license = "Apache-2.0"
|
|
@@ -12,14 +12,14 @@ keywords = ["dependency-graph", "coupling", "refactoring", "code-quality", "stat
|
|
|
12
12
|
categories = ["development-tools", "command-line-utilities"]
|
|
13
13
|
|
|
14
14
|
[workspace.dependencies]
|
|
15
|
-
code-ranker-graph = { path = "crates/code-ranker-graph", version = "
|
|
16
|
-
code-ranker-plugin-api = { path = "crates/code-ranker-plugin-api", version = "
|
|
17
|
-
code-ranker-ecmascript-core = { path = "crates/code-ranker-ecmascript-core", version = "
|
|
18
|
-
code-ranker-plugin-rust = { path = "crates/code-ranker-plugin-rust", version = "
|
|
19
|
-
code-ranker-plugin-python = { path = "crates/code-ranker-plugin-python", version = "
|
|
20
|
-
code-ranker-plugin-javascript = { path = "crates/code-ranker-plugin-javascript", version = "
|
|
21
|
-
code-ranker-plugin-typescript = { path = "crates/code-ranker-plugin-typescript", version = "
|
|
22
|
-
code-ranker-viewer = { path = "crates/code-ranker-viewer", version = "
|
|
15
|
+
code-ranker-graph = { path = "crates/code-ranker-graph", version = "3.0.0-alpha.1" }
|
|
16
|
+
code-ranker-plugin-api = { path = "crates/code-ranker-plugin-api", version = "3.0.0-alpha.1" }
|
|
17
|
+
code-ranker-ecmascript-core = { path = "crates/code-ranker-ecmascript-core", version = "3.0.0-alpha.1" }
|
|
18
|
+
code-ranker-plugin-rust = { path = "crates/code-ranker-plugin-rust", version = "3.0.0-alpha.1" }
|
|
19
|
+
code-ranker-plugin-python = { path = "crates/code-ranker-plugin-python", version = "3.0.0-alpha.1" }
|
|
20
|
+
code-ranker-plugin-javascript = { path = "crates/code-ranker-plugin-javascript", version = "3.0.0-alpha.1" }
|
|
21
|
+
code-ranker-plugin-typescript = { path = "crates/code-ranker-plugin-typescript", version = "3.0.0-alpha.1" }
|
|
22
|
+
code-ranker-viewer = { path = "crates/code-ranker-viewer", version = "3.0.0-alpha.1" }
|
|
23
23
|
|
|
24
24
|
anyhow = "1.0"
|
|
25
25
|
globset = "0.4"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: code-ranker
|
|
3
|
-
Version:
|
|
3
|
+
Version: 3.0.0a1
|
|
4
4
|
Classifier: Development Status :: 3 - Alpha
|
|
5
5
|
Classifier: Environment :: Console
|
|
6
6
|
Classifier: Intended Audience :: Developers
|
|
@@ -41,7 +41,7 @@ Structural-analysis tool for **Rust, Python, JavaScript and TypeScript** codebas
|
|
|
41
41
|
|
|
42
42
|
```sh
|
|
43
43
|
cargo install code-ranker --version 1.1.0 # install the CLI
|
|
44
|
-
code-ranker report .
|
|
44
|
+
code-ranker report . # make html report in .code-ranker/ folder
|
|
45
45
|
```
|
|
46
46
|
|
|
47
47
|
`report .` needs no flags: it writes a self-contained HTML report (plus a JSON
|
|
@@ -18,7 +18,7 @@ Structural-analysis tool for **Rust, Python, JavaScript and TypeScript** codebas
|
|
|
18
18
|
|
|
19
19
|
```sh
|
|
20
20
|
cargo install code-ranker --version 1.1.0 # install the CLI
|
|
21
|
-
code-ranker report .
|
|
21
|
+
code-ranker report . # make html report in .code-ranker/ folder
|
|
22
22
|
```
|
|
23
23
|
|
|
24
24
|
`report .` needs no flags: it writes a self-contained HTML report (plus a JSON
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
//! `check` — the linter: evaluate rules (and, with `--baseline`, regressions),
|
|
2
|
-
//! render diagnostics (human / json / github / sarif), and the
|
|
3
|
-
//! current-values dump.
|
|
2
|
+
//! render diagnostics (human / json / github / sarif / codequality), and the
|
|
3
|
+
//! `--suggest-config` current-values dump.
|
|
4
4
|
|
|
5
5
|
use crate::analyze::{analyze_input, load_snapshot_any, project_name};
|
|
6
6
|
use crate::cli::{AnalyzeArgs, OutputFormat};
|
|
@@ -136,6 +136,7 @@ fn emit_diagnostics(
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
OutputFormat::Sarif => println!("{}", sarif_document(violations)),
|
|
139
|
+
OutputFormat::Codequality => println!("{}", codequality_document(violations)),
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
142
|
|
|
@@ -336,7 +337,10 @@ fn group_digits(n: u64) -> String {
|
|
|
336
337
|
/// Minimal SARIF 2.1.0 document. `ruleId` is the dotted rule id (e.g.
|
|
337
338
|
/// `threshold.file.loc`); the rules that actually fired are described under
|
|
338
339
|
/// `tool.driver.rules` (id, group, rationale, helpUri) so the report is self-documenting.
|
|
339
|
-
|
|
340
|
+
/// Each result carries a `partialFingerprints` entry keyed on `(rule, location)` (no
|
|
341
|
+
/// line number) so a consumer matches the same finding across runs even when code
|
|
342
|
+
/// shifts — the same identity `check --baseline` uses internally.
|
|
343
|
+
pub(crate) fn sarif_document(violations: &[config::Violation]) -> String {
|
|
340
344
|
// Distinct fired rule ids, first-seen order, so each results.ruleId resolves.
|
|
341
345
|
let mut seen: Vec<&config::Violation> = Vec::new();
|
|
342
346
|
for v in violations {
|
|
@@ -367,6 +371,15 @@ fn sarif_document(violations: &[config::Violation]) -> String {
|
|
|
367
371
|
"ruleId": v.rule,
|
|
368
372
|
"level": "error",
|
|
369
373
|
"message": { "text": v.summary() },
|
|
374
|
+
// Stable cross-run identity for the consumer (GitHub code scanning,
|
|
375
|
+
// SARIF viewers): the same `(rule, location)` signature `check
|
|
376
|
+
// --baseline` matches on internally. The line number is deliberately
|
|
377
|
+
// excluded, so shifting a finding up/down the file does not reopen it
|
|
378
|
+
// as "new". The value is the readable composite key (no hashing) — a
|
|
379
|
+
// metric finding has at most one `(rule, location)`, so it is unique.
|
|
380
|
+
"partialFingerprints": {
|
|
381
|
+
"codeRankerRuleLocation/v1": format!("{}:{}", v.rule, v.location),
|
|
382
|
+
},
|
|
370
383
|
"properties": { "group": v.group, "graph": v.graph, "weight": v.weight },
|
|
371
384
|
});
|
|
372
385
|
// A physical location lets GitHub code scanning render the result
|
|
@@ -398,6 +411,36 @@ fn sarif_document(violations: &[config::Violation]) -> String {
|
|
|
398
411
|
serde_json::to_string_pretty(&doc).unwrap_or_else(|_| "{}".into())
|
|
399
412
|
}
|
|
400
413
|
|
|
414
|
+
/// GitLab **Code Quality** report (the CodeClimate-derived JSON GitLab ingests as
|
|
415
|
+
/// `artifacts:reports:codequality`). A flat array of issues; GitLab renders them
|
|
416
|
+
/// in the MR widget / diff. Each issue carries the dotted rule id as `check_name`,
|
|
417
|
+
/// the human message, a `major` severity, the repo-relative `location.path` +
|
|
418
|
+
/// `lines.begin`, and a stable `fingerprint` keyed on `(rule, location)` — no line
|
|
419
|
+
/// number, so GitLab tracks the same finding across pipelines even when code
|
|
420
|
+
/// shifts (the same identity SARIF and `check --baseline` use). Unlike GitHub
|
|
421
|
+
/// SARIF this needs no feature flag and works on current GitLab.
|
|
422
|
+
pub(crate) fn codequality_document(violations: &[config::Violation]) -> String {
|
|
423
|
+
let issues: Vec<serde_json::Value> = violations
|
|
424
|
+
.iter()
|
|
425
|
+
.map(|v| {
|
|
426
|
+
serde_json::json!({
|
|
427
|
+
"description": v.summary(),
|
|
428
|
+
"check_name": v.rule,
|
|
429
|
+
// Readable composite identity (no hashing) — a finding has at most
|
|
430
|
+
// one (rule, location), so it is unique; line excluded so a shift
|
|
431
|
+
// does not reopen it.
|
|
432
|
+
"fingerprint": format!("{}:{}", v.rule, v.location),
|
|
433
|
+
"severity": "major",
|
|
434
|
+
"location": {
|
|
435
|
+
"path": violation_rel_path(&v.location).unwrap_or(v.location.as_str()),
|
|
436
|
+
"lines": { "begin": v.line.unwrap_or(1) },
|
|
437
|
+
},
|
|
438
|
+
})
|
|
439
|
+
})
|
|
440
|
+
.collect();
|
|
441
|
+
serde_json::to_string_pretty(&issues).unwrap_or_else(|_| "[]".into())
|
|
442
|
+
}
|
|
443
|
+
|
|
401
444
|
#[cfg(test)]
|
|
402
445
|
mod tests {
|
|
403
446
|
use super::*;
|
|
@@ -454,4 +497,52 @@ mod tests {
|
|
|
454
497
|
let v: serde_json::Value = serde_json::from_str(&doc).unwrap();
|
|
455
498
|
assert!(v["runs"][0]["results"][0].get("locations").is_none());
|
|
456
499
|
}
|
|
500
|
+
|
|
501
|
+
#[test]
|
|
502
|
+
fn codequality_issue_has_fingerprint_path_and_line() {
|
|
503
|
+
let doc = codequality_document(&[viol("{target}/src/x.rs", Some(7))]);
|
|
504
|
+
let v: serde_json::Value = serde_json::from_str(&doc).unwrap();
|
|
505
|
+
let issue = &v[0];
|
|
506
|
+
assert_eq!(issue["check_name"], "threshold.file.loc");
|
|
507
|
+
assert_eq!(issue["severity"], "major");
|
|
508
|
+
assert_eq!(issue["location"]["path"], "src/x.rs");
|
|
509
|
+
assert_eq!(issue["location"]["lines"]["begin"], 7);
|
|
510
|
+
// Stable identity = rule:location, no line (so a shift does not reopen it).
|
|
511
|
+
assert_eq!(issue["fingerprint"], "threshold.file.loc:{target}/src/x.rs");
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
#[test]
|
|
515
|
+
fn codequality_whole_file_metric_defaults_line_to_one() {
|
|
516
|
+
// A whole-file metric has no line → CodeClimate needs one, default 1.
|
|
517
|
+
let doc = codequality_document(&[viol("{target}/src/x.rs", None)]);
|
|
518
|
+
let v: serde_json::Value = serde_json::from_str(&doc).unwrap();
|
|
519
|
+
assert_eq!(v[0]["location"]["lines"]["begin"], 1);
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
#[test]
|
|
523
|
+
fn sarif_partial_fingerprint_is_rule_and_location() {
|
|
524
|
+
let doc = sarif_document(&[viol("{target}/src/x.rs", Some(7))]);
|
|
525
|
+
let v: serde_json::Value = serde_json::from_str(&doc).unwrap();
|
|
526
|
+
let fp = &v["runs"][0]["results"][0]["partialFingerprints"];
|
|
527
|
+
assert_eq!(
|
|
528
|
+
fp["codeRankerRuleLocation/v1"],
|
|
529
|
+
"threshold.file.loc:{target}/src/x.rs"
|
|
530
|
+
);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
#[test]
|
|
534
|
+
fn sarif_partial_fingerprint_is_stable_across_line_shifts() {
|
|
535
|
+
// The same finding at a different line keeps the same fingerprint, so a
|
|
536
|
+
// code shift does not reopen it for the consumer.
|
|
537
|
+
let at_7 = sarif_document(&[viol("{target}/src/x.rs", Some(7))]);
|
|
538
|
+
let at_42 = sarif_document(&[viol("{target}/src/x.rs", Some(42))]);
|
|
539
|
+
let fp = |doc: &str| -> String {
|
|
540
|
+
let v: serde_json::Value = serde_json::from_str(doc).unwrap();
|
|
541
|
+
v["runs"][0]["results"][0]["partialFingerprints"]["codeRankerRuleLocation/v1"]
|
|
542
|
+
.as_str()
|
|
543
|
+
.unwrap()
|
|
544
|
+
.to_owned()
|
|
545
|
+
};
|
|
546
|
+
assert_eq!(fp(&at_7), fp(&at_42));
|
|
547
|
+
}
|
|
457
548
|
}
|
|
@@ -23,6 +23,7 @@ pub(crate) enum OutputFormat {
|
|
|
23
23
|
Json,
|
|
24
24
|
Github,
|
|
25
25
|
Sarif,
|
|
26
|
+
Codequality,
|
|
26
27
|
}
|
|
27
28
|
|
|
28
29
|
/// Common input + analysis options shared by `check` and `report`.
|
|
@@ -137,6 +138,26 @@ pub(crate) enum Command {
|
|
|
137
138
|
#[arg(long = "output.html.path", value_name = "PATH")]
|
|
138
139
|
output_html_path: Option<String>,
|
|
139
140
|
|
|
141
|
+
/// Emit a SARIF 2.1.0 report of rule violations (path from
|
|
142
|
+
/// --output.sarif.path / config / default).
|
|
143
|
+
#[arg(long = "output.sarif")]
|
|
144
|
+
output_sarif: bool,
|
|
145
|
+
|
|
146
|
+
/// SARIF destination: a path or name template, or `stdout`/`-`.
|
|
147
|
+
/// Placeholders: {project-dir}, {ts}, {git-hash}, {git-hash-N}. Selects SARIF.
|
|
148
|
+
#[arg(long = "output.sarif.path", value_name = "PATH")]
|
|
149
|
+
output_sarif_path: Option<String>,
|
|
150
|
+
|
|
151
|
+
/// Emit a GitLab Code Quality (CodeClimate) report of rule violations
|
|
152
|
+
/// (path from --output.codequality.path / config / default).
|
|
153
|
+
#[arg(long = "output.codequality")]
|
|
154
|
+
output_codequality: bool,
|
|
155
|
+
|
|
156
|
+
/// Code Quality destination: a path or name template, or `stdout`/`-`.
|
|
157
|
+
/// Placeholders: {project-dir}, {ts}, {git-hash}, {git-hash-N}. Selects it.
|
|
158
|
+
#[arg(long = "output.codequality.path", value_name = "PATH")]
|
|
159
|
+
output_codequality_path: Option<String>,
|
|
160
|
+
|
|
140
161
|
/// Emit the AI prompt for one principle (default to a `…-{preset}.md` file).
|
|
141
162
|
#[arg(long = "output.prompt")]
|
|
142
163
|
output_prompt: bool,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
//! Config loading: discover `code-ranker.toml` (or `Cargo.toml` metadata),
|
|
2
2
|
//! apply inline `KEY=VALUE` and `--cycle-rule` / `--threshold` CLI overrides.
|
|
3
3
|
|
|
4
|
-
use super::model::{Config, CycleRule, MetricThresholds, parse_number};
|
|
4
|
+
use super::model::{Config, CycleRule, MetricThresholds, parse_number, quote_suffixed_thresholds};
|
|
5
5
|
use anyhow::{Context, Result};
|
|
6
6
|
use code_ranker_plugin_api::log;
|
|
7
7
|
use std::path::Path;
|
|
@@ -46,7 +46,8 @@ fn load_file(workspace: &Path, explicit: Option<&Path>) -> Result<(Config, Optio
|
|
|
46
46
|
if let Some(path) = explicit {
|
|
47
47
|
let text =
|
|
48
48
|
std::fs::read_to_string(path).with_context(|| format!("reading {}", path.display()))?;
|
|
49
|
-
let cfg = toml::from_str(&text)
|
|
49
|
+
let cfg = toml::from_str("e_suffixed_thresholds(&text))
|
|
50
|
+
.with_context(|| format!("parsing {}", path.display()))?;
|
|
50
51
|
return Ok((cfg, Some(path.display().to_string())));
|
|
51
52
|
}
|
|
52
53
|
|
|
@@ -57,7 +58,8 @@ fn load_file(workspace: &Path, explicit: Option<&Path>) -> Result<(Config, Optio
|
|
|
57
58
|
if p.exists() {
|
|
58
59
|
let text =
|
|
59
60
|
std::fs::read_to_string(&p).with_context(|| format!("reading {}", p.display()))?;
|
|
60
|
-
let cfg = toml::from_str(&text)
|
|
61
|
+
let cfg = toml::from_str("e_suffixed_thresholds(&text))
|
|
62
|
+
.with_context(|| format!("parsing {}", p.display()))?;
|
|
61
63
|
let canonical = p.canonicalize().unwrap_or(p);
|
|
62
64
|
return Ok((cfg, Some(canonical.display().to_string())));
|
|
63
65
|
}
|
|
@@ -79,8 +81,8 @@ fn load_from_cargo_toml(dir: &Path) -> Result<Option<(Config, String)>> {
|
|
|
79
81
|
}
|
|
80
82
|
let text =
|
|
81
83
|
std::fs::read_to_string(&cargo).with_context(|| format!("reading {}", cargo.display()))?;
|
|
82
|
-
let val: toml::Value =
|
|
83
|
-
|
|
84
|
+
let val: toml::Value = toml::from_str("e_suffixed_thresholds(&text))
|
|
85
|
+
.with_context(|| format!("parsing {}", cargo.display()))?;
|
|
84
86
|
|
|
85
87
|
let section = val
|
|
86
88
|
.get("workspace")
|
|
@@ -202,17 +204,13 @@ fn set_threshold(cfg: &mut Config, scope: &str, metric: &str, val: f64) -> Resul
|
|
|
202
204
|
}
|
|
203
205
|
|
|
204
206
|
fn set_metric(bucket: &mut MetricThresholds, metric: &str, val: f64) -> Result<()> {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
"fan_out" => bucket.fan_out = Some(val),
|
|
211
|
-
"loc" => bucket.loc = Some(val),
|
|
212
|
-
other => anyhow::bail!(
|
|
213
|
-
"unknown metric {other:?}; expected hk|cyclomatic|cognitive|fan_in|fan_out|loc"
|
|
214
|
-
),
|
|
207
|
+
if !super::metrics::is_threshold_metric(metric) {
|
|
208
|
+
anyhow::bail!(
|
|
209
|
+
"unknown threshold metric {metric:?}; expected a per-file metric such as \
|
|
210
|
+
sloc, loc, cyclomatic, cognitive, hk, fan_in, fan_out, mi, volume, bugs"
|
|
211
|
+
);
|
|
215
212
|
}
|
|
213
|
+
bucket.set(metric.to_string(), val);
|
|
216
214
|
Ok(())
|
|
217
215
|
}
|
|
218
216
|
|
|
@@ -258,8 +256,8 @@ mod tests {
|
|
|
258
256
|
.unwrap();
|
|
259
257
|
assert_eq!(cfg.rules.cycles.chain, CycleRule::Max(0));
|
|
260
258
|
assert_eq!(cfg.rules.cycles.mutual, CycleRule::Off);
|
|
261
|
-
assert_eq!(cfg.rules.thresholds.file.cognitive, Some(25.0));
|
|
262
|
-
assert_eq!(cfg.rules.thresholds.file.hk, Some(1000.0));
|
|
259
|
+
assert_eq!(cfg.rules.thresholds.file.get("cognitive"), Some(25.0));
|
|
260
|
+
assert_eq!(cfg.rules.thresholds.file.get("hk"), Some(1000.0));
|
|
263
261
|
}
|
|
264
262
|
|
|
265
263
|
#[test]
|
|
@@ -278,6 +276,7 @@ mod tests {
|
|
|
278
276
|
"output.html.enabled=true",
|
|
279
277
|
"rules.cycles.chain=7",
|
|
280
278
|
"rules.thresholds.file.loc=800",
|
|
279
|
+
"rules.thresholds.file.sloc=1200",
|
|
281
280
|
],
|
|
282
281
|
)
|
|
283
282
|
.unwrap();
|
|
@@ -289,7 +288,8 @@ mod tests {
|
|
|
289
288
|
assert_eq!(cfg.output.json.enabled, Some(false));
|
|
290
289
|
assert_eq!(cfg.output.html.enabled, Some(true));
|
|
291
290
|
assert_eq!(cfg.rules.cycles.chain, CycleRule::Max(7));
|
|
292
|
-
assert_eq!(cfg.rules.thresholds.file.loc, Some(800.0));
|
|
291
|
+
assert_eq!(cfg.rules.thresholds.file.get("loc"), Some(800.0));
|
|
292
|
+
assert_eq!(cfg.rules.thresholds.file.get("sloc"), Some(1200.0));
|
|
293
293
|
}
|
|
294
294
|
|
|
295
295
|
#[test]
|
|
@@ -320,8 +320,21 @@ mod tests {
|
|
|
320
320
|
#[test]
|
|
321
321
|
fn set_metric_each_then_unknown() {
|
|
322
322
|
let mut b = MetricThresholds::default();
|
|
323
|
-
|
|
323
|
+
// The full open vocabulary is accepted — not just the legacy six.
|
|
324
|
+
for m in [
|
|
325
|
+
"hk",
|
|
326
|
+
"cyclomatic",
|
|
327
|
+
"cognitive",
|
|
328
|
+
"fan_in",
|
|
329
|
+
"fan_out",
|
|
330
|
+
"loc",
|
|
331
|
+
"sloc",
|
|
332
|
+
"mi",
|
|
333
|
+
"bugs",
|
|
334
|
+
"volume",
|
|
335
|
+
] {
|
|
324
336
|
set_metric(&mut b, m, 1.0).unwrap();
|
|
337
|
+
assert_eq!(b.get(m), Some(1.0));
|
|
325
338
|
}
|
|
326
339
|
assert!(set_metric(&mut b, "bogus", 1.0).is_err());
|
|
327
340
|
}
|
|
@@ -331,7 +344,7 @@ mod tests {
|
|
|
331
344
|
let mut cfg = Config::default();
|
|
332
345
|
assert!(set_threshold(&mut cfg, "function", "loc", 1.0).is_err());
|
|
333
346
|
set_threshold(&mut cfg, "file", "hk", 5.0).unwrap();
|
|
334
|
-
assert_eq!(cfg.rules.thresholds.file.hk, Some(5.0));
|
|
347
|
+
assert_eq!(cfg.rules.thresholds.file.get("hk"), Some(5.0));
|
|
335
348
|
assert!(set_cycle(&mut cfg, "weird", CycleRule::Off).is_err());
|
|
336
349
|
}
|
|
337
350
|
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
//! The per-file threshold metric vocabulary: every metric that can carry a
|
|
2
|
+
//! `[rules.thresholds.file]` limit, with its concern group and human label.
|
|
3
|
+
//!
|
|
4
|
+
//! This is a **leaf** module — it depends on nothing else in `config`, so both
|
|
5
|
+
//! the data model (`model`, which validates config keys against it) and the rule
|
|
6
|
+
//! catalog (`rules`, which resolves a metric's group through it) can use it
|
|
7
|
+
//! without forming a `model ↔ rules` dependency cycle.
|
|
8
|
+
|
|
9
|
+
/// A per-file metric that can carry a threshold: its key, the concern `group`
|
|
10
|
+
/// (one of `CPX` / `CPL` / `SIZ`, matching the [`super::rules::RULES`] groups),
|
|
11
|
+
/// and the human `label` used in the breach message. A threshold is a
|
|
12
|
+
/// `value > limit` gate, so this is the whole numeric per-file vocabulary — every
|
|
13
|
+
/// metric the engine emits is accepted, not a hand-picked subset.
|
|
14
|
+
/// `threshold_metrics_cover_engine_specs` (test) guards that this list stays in
|
|
15
|
+
/// sync with the engine's metric specs.
|
|
16
|
+
pub struct ThresholdMetric {
|
|
17
|
+
pub key: &'static str,
|
|
18
|
+
pub group: &'static str,
|
|
19
|
+
pub label: &'static str,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
pub const THRESHOLD_METRICS: &[ThresholdMetric] = &[
|
|
23
|
+
// CPX — control-flow complexity, maintainability, and Halstead effort.
|
|
24
|
+
tm("cyclomatic", "CPX", "cyclomatic complexity"),
|
|
25
|
+
tm("cognitive", "CPX", "cognitive complexity"),
|
|
26
|
+
tm("exits", "CPX", "exit points"),
|
|
27
|
+
tm("args", "CPX", "argument count"),
|
|
28
|
+
tm("closures", "CPX", "closure count"),
|
|
29
|
+
tm("mi", "CPX", "maintainability index"),
|
|
30
|
+
tm("mi_sei", "CPX", "maintainability index (SEI)"),
|
|
31
|
+
tm("length", "CPX", "Halstead length"),
|
|
32
|
+
tm("vocabulary", "CPX", "Halstead vocabulary"),
|
|
33
|
+
tm("volume", "CPX", "Halstead volume"),
|
|
34
|
+
tm("effort", "CPX", "Halstead effort"),
|
|
35
|
+
tm("time", "CPX", "Halstead time"),
|
|
36
|
+
tm("bugs", "CPX", "Halstead bugs"),
|
|
37
|
+
tm("unsafe", "CPX", "unsafe blocks"),
|
|
38
|
+
// SIZ — size.
|
|
39
|
+
tm("sloc", "SIZ", "source loc"),
|
|
40
|
+
tm("loc", "SIZ", "source loc"),
|
|
41
|
+
tm("lloc", "SIZ", "logical loc"),
|
|
42
|
+
tm("cloc", "SIZ", "comment loc"),
|
|
43
|
+
tm("blank", "SIZ", "blank lines"),
|
|
44
|
+
tm("tloc", "SIZ", "test loc"),
|
|
45
|
+
tm("items", "SIZ", "item count"),
|
|
46
|
+
// CPL — coupling.
|
|
47
|
+
tm("fan_in", "CPL", "fan-in"),
|
|
48
|
+
tm("fan_out", "CPL", "fan-out"),
|
|
49
|
+
tm("fan_out_external", "CPL", "external fan-out"),
|
|
50
|
+
tm("hk", "CPL", "Henry-Kafura hk"),
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
const fn tm(key: &'static str, group: &'static str, label: &'static str) -> ThresholdMetric {
|
|
54
|
+
ThresholdMetric { key, group, label }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/// The threshold metadata for a metric key, if it is a known per-file metric.
|
|
58
|
+
pub fn threshold_metric(key: &str) -> Option<&'static ThresholdMetric> {
|
|
59
|
+
THRESHOLD_METRICS.iter().find(|m| m.key == key)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/// Is `key` a metric that can carry a per-file threshold?
|
|
63
|
+
pub fn is_threshold_metric(key: &str) -> bool {
|
|
64
|
+
threshold_metric(key).is_some()
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
#[cfg(test)]
|
|
68
|
+
mod tests {
|
|
69
|
+
use super::*;
|
|
70
|
+
|
|
71
|
+
#[test]
|
|
72
|
+
fn threshold_metrics_cover_engine_specs() {
|
|
73
|
+
// Every numeric per-file metric the engine emits must be thresholdable, so
|
|
74
|
+
// the config accepts the full vocabulary. `cycle` is a string attribute
|
|
75
|
+
// (a cycle kind), not a numeric threshold, so it is excluded.
|
|
76
|
+
let (metrics, _) = code_ranker_graph::metric_specs();
|
|
77
|
+
let (coupling, _) = code_ranker_graph::coupling_specs();
|
|
78
|
+
for key in metrics.keys().chain(coupling.keys()) {
|
|
79
|
+
if key == "cycle" {
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
assert!(
|
|
83
|
+
is_threshold_metric(key),
|
|
84
|
+
"engine metric {key:?} is not in THRESHOLD_METRICS — add it (with a group)"
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|