energy-state-analyzer 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -2
- package/README.md +30 -6
- package/dist/cli.js +2 -1
- package/dist/cli.js.map +1 -1
- package/dist/web-tree-sitter.wasm +0 -0
- package/package.json +55 -10
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
last_commit_released:
|
|
2
|
+
last_commit_released: b3dda28dffdd3dc8c6cee2d5b07d5a99d50761cb
|
|
3
3
|
name: energy-state-analyzer
|
|
4
4
|
updaters:
|
|
5
5
|
- command: npm version {version} --no-git-tag-version --allow-same-version
|
|
@@ -11,6 +11,32 @@ All notable changes to the "energy-state-analyzer" extension are generated by
|
|
|
11
11
|
[EasyBuild.ShipIt](https://github.com/easybuild-org/EasyBuild.ShipIt) from
|
|
12
12
|
[Conventional Commits](https://www.conventionalcommits.org/).
|
|
13
13
|
|
|
14
|
+
## 0.2.0 - 2026-08-25
|
|
15
|
+
|
|
16
|
+
### 🚀 Features
|
|
17
|
+
|
|
18
|
+
* Add opaque-boolean-literal detector (#16) ([d12447b](https://github.com/cardamomcode/energy-state-analyzer/commit/d12447bb07607c5fdc0b62781fb6df69ffbef1f4))
|
|
19
|
+
|
|
20
|
+
### 🐞 Bug Fixes
|
|
21
|
+
|
|
22
|
+
* Add braces to satisfy eslint curly rule (#10) ([8fd41cd](https://github.com/cardamomcode/energy-state-analyzer/commit/8fd41cde59103455a76ab1cbb504492578bbc3b3))
|
|
23
|
+
* Tailor primitive-obsession advice to each language (#7) ([cf7807b](https://github.com/cardamomcode/energy-state-analyzer/commit/cf7807b55de2efbb0befd8a6a8a282f3a33215f3))
|
|
24
|
+
* Cover primitive-obsession membership check with a test, correct heatmap color docs (#12) ([ba0050c](https://github.com/cardamomcode/energy-state-analyzer/commit/ba0050cb2b1894d0d90f7e80b273defa72f77277))
|
|
25
|
+
* Split magic-value detection into separate number/string detectors (#13) ([4aca127](https://github.com/cardamomcode/energy-state-analyzer/commit/4aca127af06ee4ea2d71a4ccb8d47d26ef22014e))
|
|
26
|
+
* Dogfood the extension's own CLI on its source, fix bugs it found (#14) ([bffadd3](https://github.com/cardamomcode/energy-state-analyzer/commit/bffadd3b2d50e700414ad313afdcc68be385125b))
|
|
27
|
+
* Suppress primitive-obsession swap-risk for Python keyword-only params (#15) ([eb1c592](https://github.com/cardamomcode/energy-state-analyzer/commit/eb1c59211823a6dd2dff152be8bd536531b4648a))
|
|
28
|
+
* Exempt Python module-level constants from magic-number detection (#17) ([0024607](https://github.com/cardamomcode/energy-state-analyzer/commit/0024607849eb0d15e5d5c5925a5e2b804b49616f))
|
|
29
|
+
|
|
30
|
+
<strong><small>[View changes on Github](https://github.com/cardamomcode/energy-state-analyzer/compare/0cbd44479c8048e4313fae49fcb157a52900d5f6..b3dda28dffdd3dc8c6cee2d5b07d5a99d50761cb)</small></strong>
|
|
31
|
+
|
|
32
|
+
## 0.1.2 - 2026-08-24
|
|
33
|
+
|
|
34
|
+
### 🐞 Bug Fixes
|
|
35
|
+
|
|
36
|
+
* Restore shebang in bundled dist/cli.js ([8b54f00](https://github.com/cardamomcode/energy-state-analyzer/commit/8b54f00269b555b7743cbffb648e411142496efe))
|
|
37
|
+
|
|
38
|
+
<strong><small>[View changes on Github](https://github.com/cardamomcode/energy-state-analyzer/compare/94e05303b32730a02656ebd820f356b50d49b59b..0cbd44479c8048e4313fae49fcb157a52900d5f6)</small></strong>
|
|
39
|
+
|
|
14
40
|
## [0.1.1]
|
|
15
41
|
|
|
16
42
|
- Added the missing `license` field to `package.json` (MIT) — npm was displaying the package as "Proprietary".
|
|
@@ -36,4 +62,4 @@ this point were written by hand.
|
|
|
36
62
|
## [0.0.6]
|
|
37
63
|
|
|
38
64
|
- Added a file coherence check that flags files with too many large functions (configurable via `energyStateAnalyzer.coherence.largeFunctionLines` and `.maxLargeFunctions`), independent of total function count, so languages like F# with many small functions per module aren't penalized.
|
|
39
|
-
- Initial release
|
|
65
|
+
- Initial release
|
package/README.md
CHANGED
|
@@ -8,13 +8,15 @@ Visualizes "energy states" in Python, F#, and TypeScript code as you edit: parts
|
|
|
8
8
|
- **Cyclomatic complexity** — flags functions with too many independent execution paths (`if`/`for`/`while`/`except`/boolean operators/ternaries all count equally, regardless of nesting).
|
|
9
9
|
- **Cognitive complexity** — flags functions that are hard to *read*, weighting each decision point by how deeply it's nested and not penalizing early-return guard clauses.
|
|
10
10
|
- **Excessive nesting** — flags `if`/`for`/`while`/`with` blocks nested more than 3 levels deep.
|
|
11
|
-
- **File coherence** — flags files with too many functions or imports (a sign of "utils/helpers sprawl"), and separately flags files with too many large functions (regardless of total function count, so languages like F# with many small functions per module aren't penalized).
|
|
12
|
-
- **Magic
|
|
11
|
+
- **File coherence** — flags files with too many functions or imports (a sign of "utils/helpers sprawl"), and separately flags files with too many large functions (regardless of total function count, so languages like F# with many small functions per module aren't penalized). A file where most functions share a leading name word (e.g. all `extractFoo`/`extractBar`) is treated as one coherent domain and exempted from the function-count check.
|
|
12
|
+
- **Magic numbers** — flags numeric literals used outside of a named binding, an index/key position, or a default parameter value. Numbers get no free pass for "looking like prose" the way strings do, so this stays broad; an `energyStateAnalyzer.magicNumber.allowlist` setting (default `[0, 1, -1, 2]`) exempts the values that recur constantly without carrying hidden meaning.
|
|
13
|
+
- **Magic strings** — flags a string literal only where an unnamed one actually risks a silent typo: compared with `==`/`===`, checked for membership (Python's `x in (...)`), or used as a dict/object key. A message being logged, thrown, or returned isn't a decision point, so it's left alone entirely — as is any f-string/template-literal/`.format()`/`%`-formatted string, since a placeholder is itself evidence the string isn't a stand-in for an enum value. To cut single-use false positives further, a qualifying literal is only flagged once it recurs at a decision point at least `energyStateAnalyzer.magicString.minDuplicates` times (default `2`) across the file.
|
|
13
14
|
- **Parameter explosion** — flags functions with more than 5 parameters.
|
|
14
15
|
- **Inversion opportunities** — flags large dominant `if` blocks and nested validation chains that could be rewritten as guard clauses with early returns.
|
|
15
|
-
- **Primitive obsession** — flags consecutive same-typed primitive parameters (e.g. `lat: float, lon: float`) that callers can silently swap, and variables compared against 3+ distinct string literals (a de facto enum encoded as strings). Runs on Python, F#, and TypeScript; Python additionally flags a variable checked against a literal tuple/list/set in one `in` expression, since F# and TypeScript have no direct equivalent construct.
|
|
16
|
+
- **Primitive obsession** — flags consecutive same-typed primitive parameters (e.g. `lat: float, lon: float`) that callers can silently swap, and variables compared against 3+ distinct string literals (a de facto enum encoded as strings). Runs on Python, F#, and TypeScript; Python additionally flags a variable checked against a literal tuple/list/set in one `in` expression, since F# and TypeScript have no direct equivalent construct. In Python, a same-typed pair is not flagged when both parameters are keyword-only (after a bare `*` or `*args` in the signature) since a caller can no longer pass them positionally — named-parameter naming is still a weaker mitigation than a distinct type (`NewType`, a dataclass, etc.), since nothing stops a future `**kwargs`-splat call from transposing the values. This suppression doesn't apply to TypeScript (no argument-labeling syntax for positional params) or F# (named arguments are optional at the call site, so they don't prevent a positional call).
|
|
16
17
|
- **Match opportunities** — flags an `if`/`elif`/`elif` chain (or TypeScript's nested `else if`) of 3 or more branches that all compare the same single variable to a literal, suggesting a `match`/`switch` statement instead. Runs on Python, F#, and TypeScript.
|
|
17
18
|
- **Logical operator as control flow** — flags a bare `condition && doSomething()` (or `condition || fallback()`) statement, an `if` hidden behind a boolean operator instead of written as one. Runs on Python and TypeScript; not on F#, which has no such statement-level idiom in its grammar.
|
|
19
|
+
- **Opaque boolean literal** — flags a bare `true`/`false` passed positionally into a call (e.g. `configure(true)`), since a reader can't tell what it means without checking the callee's signature. Suppressed when the boolean is labeled at the call site: a Python keyword argument (`configure(retries=True)`), a TypeScript object-literal field (`configure({ retries: true })`), or F#'s named-argument syntax (`configure(retries = true)`) — unlike the primitive-obsession suppression above, F#'s named args count here even though they're optional, since this rule is about reader comprehension at this call site, not about preventing a future misuse. The preferred fix is usually splitting into two clearly named functions (`enableRetries()`/`disableRetries()`) or an enum; naming the argument is an acceptable but weaker mitigation. Deliberately conservative: only literal `true`/`false` are flagged, not bare `0`/`1`, to avoid noise on ordinary numeric arguments.
|
|
18
20
|
|
|
19
21
|
Violations are shown three ways:
|
|
20
22
|
|
|
@@ -22,7 +24,7 @@ Violations are shown three ways:
|
|
|
22
24
|
- A hover tooltip explaining the specific violation.
|
|
23
25
|
- An entry in the Problems panel, sourced as "Energy State Analyzer".
|
|
24
26
|
|
|
25
|
-
For functions flagged as too complex (cyclomatic or cognitive), a progressive
|
|
27
|
+
For functions flagged as too complex (cyclomatic or cognitive), a progressive heatmap in the configured high-energy color (orange by default) is also painted across the function body: each contributing line (an `if`, `for`, `and`, etc.) is shaded from light to dark based on how much it drives up that function's complexity relative to its own worst line — so you can see exactly which branches to break apart first, instead of just knowing the function as a whole is complex.
|
|
26
28
|
|
|
27
29
|
## Energy and Entropy
|
|
28
30
|
|
|
@@ -60,6 +62,21 @@ Modeled on [SonarSource's metric](https://www.sonarsource.com/resources/cognitiv
|
|
|
60
62
|
|
|
61
63
|
This project's implementation is a simplified first pass on the SonarSource spec: `for`/`while` `else` clauses are scored like `if`/`else`, boolean-chain merging only looks at the immediate parent operator, and recursive calls aren't specially detected.
|
|
62
64
|
|
|
65
|
+
### Interpreting the Scores
|
|
66
|
+
|
|
67
|
+
A raw number like "34" doesn't mean much on its own. For cyclomatic complexity, McCabe's original 1976 paper proposed risk bands that are still the closest thing to an industry consensus (echoed by SonarQube, ESLint's `complexity` rule, and NIST guidance):
|
|
68
|
+
|
|
69
|
+
| Score | Risk | Roughly |
|
|
70
|
+
| --- | --- | --- |
|
|
71
|
+
| 1–10 | Low | Simple, easy to test exhaustively |
|
|
72
|
+
| 11–20 | Moderate | Getting harder to cover with tests |
|
|
73
|
+
| 21–50 | High | Complex, testing all paths is impractical |
|
|
74
|
+
| 50+ | Very high | Effectively untestable |
|
|
75
|
+
|
|
76
|
+
Cognitive complexity has no equivalent formal consensus, since it's a newer, vendor-originated metric, but SonarSource's own convention (and this extension's defaults) treat **15** as the point where a function is hard enough to hold in your head that it's worth splitting up, with scores past 25 or so being seriously hard to follow regardless of how testable the underlying paths are.
|
|
77
|
+
|
|
78
|
+
The two scores can diverge on the same function: a flat function with many independent branches can have high cyclomatic complexity but modest cognitive complexity (easy to read, hard to test exhaustively), while deeply nested code can be the reverse. See [Energy and Entropy](#energy-and-entropy) above for why this extension tracks them separately rather than collapsing them into one score.
|
|
79
|
+
|
|
63
80
|
## Command-Line Usage
|
|
64
81
|
|
|
65
82
|
The same detectors also run headlessly, without VS Code — useful for CI or for an AI coding agent that wants to check the complexity of code it just generated and keep refactoring until it's clean. Published to npm, so no clone or install step is required:
|
|
@@ -97,8 +114,13 @@ Detector thresholds are configurable under **Settings → Energy State Analyzer*
|
|
|
97
114
|
- `energyStateAnalyzer.cognitiveComplexity.mediumThreshold` / `.highThreshold`
|
|
98
115
|
- `energyStateAnalyzer.coherence.largeFunctionLines` — line count above which a function counts as "large" (default `20`).
|
|
99
116
|
- `energyStateAnalyzer.coherence.maxLargeFunctions` — number of large functions a file can contain before it's flagged (default `5`).
|
|
117
|
+
- `energyStateAnalyzer.coherence.singleDomainNameShare` — share (0-1) of a file's functions that must share a leading name word (e.g. `extractFoo`/`extractBar`) to be treated as one coherent domain, skipping the function-count sprawl check (default `0.7`).
|
|
100
118
|
- `energyStateAnalyzer.matchOpportunity.minBranches` — number of branches an if/elif chain must have, all keyed on the same variable, before it's flagged as a match/switch opportunity (default `3`).
|
|
101
|
-
- `energyStateAnalyzer.
|
|
119
|
+
- `energyStateAnalyzer.magicNumber.enabled` — whether to flag magic numbers (default `true`).
|
|
120
|
+
- `energyStateAnalyzer.magicNumber.allowlist` — numeric literals that are never flagged, regardless of context (default `[0, 1, -1, 2]`).
|
|
121
|
+
- `energyStateAnalyzer.magicString.enabled` — whether to flag magic strings (default `true`).
|
|
122
|
+
- `energyStateAnalyzer.magicString.minDuplicates` — number of times the same string literal must recur at a decision point before it's flagged (default `2`).
|
|
123
|
+
- `energyStateAnalyzer.magicString.allowlist` — string literals that are never flagged, regardless of context (default `["", "utf-8", "__main__"]`).
|
|
102
124
|
- `energyStateAnalyzer.colors.highEnergy` / `.mediumEnergy` / `.lowEnergy` — hex colors for the high/medium/low severity background tint and gutter icon (defaults `#fb8500` orange, `#ffb703` gold, `#99dd99` green).
|
|
103
125
|
- `energyStateAnalyzer.colors.backgroundOpacity` — opacity of the severity background tint (default `0.1`).
|
|
104
126
|
|
|
@@ -110,7 +132,9 @@ Changes take effect immediately on the active editor.
|
|
|
110
132
|
|
|
111
133
|
## Known Issues
|
|
112
134
|
|
|
113
|
-
- Nesting depth and parameter count thresholds are not yet configurable — only cyclomatic complexity, cognitive complexity, the large-function coherence check, the match-opportunity branch count, and the magic-
|
|
135
|
+
- Nesting depth and parameter count thresholds are not yet configurable — only cyclomatic complexity, cognitive complexity, the large-function coherence check, the match-opportunity branch count, and the magic-number/magic-string detectors are.
|
|
136
|
+
- The magic-string detector's decision-point scan (equality/membership/dict-key) and its formatted-string exemption are fully implemented for Python and partially for TypeScript (no `.includes()` membership support yet) and F# (no dict/subscript node, no interpolated-string exemption) — see the `LanguageAdapter` fields in `src/core/language.ts` for exactly what's modeled per language.
|
|
137
|
+
- The magic-string detector doesn't (yet) special-case enum-like keyword/default arguments (e.g. `mode="fast"`) as a lower-confidence decision point — only equality, membership, and dict/index-key positions count.
|
|
114
138
|
- The inversion-opportunities detector only fires for Python and TypeScript; F#'s grammar has no block-boundary node to anchor that heuristic on (see Architecture).
|
|
115
139
|
- TypeScript arrow functions aren't analyzed by complexity/parameter-count/coherence (same limitation Python already has for `lambda`) — only named `function` declarations and class methods are.
|
|
116
140
|
- The primitive-obsession detector's `in (a, b, c)`-style membership check only runs on Python; F#'s grammar has no direct equivalent, and TypeScript's idiom (`[...].includes(x)`) is a call expression rather than a comparison node.
|