cuke-dedup 0.4.0 → 0.6.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 +94 -0
- package/README.md +1 -1
- package/docs/ci-and-baselines.md +1 -1
- package/docs/configuration.md +33 -0
- package/docs/reports.md +4 -0
- package/docs/safety-and-limitations.md +10 -0
- package/package.json +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,98 @@ All notable changes to CukeDedup are documented in this file. The project follow
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [0.6.0] - 2026-09-18
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `assertionModules` names module specifiers whose `expect` export is a trusted assertion factory.
|
|
13
|
+
Use it for a local module that re-exports `expect`, or for a runner this build does not recognise
|
|
14
|
+
by name. Both `import` and `require` spellings honour the setting, including
|
|
15
|
+
`import * as fixtures from "./fixtures"` and `import fixtures = require("./fixtures")`. An
|
|
16
|
+
undeclared module stays untrusted, and a facade inferred from registration re-exports is not
|
|
17
|
+
trusted for assertions.
|
|
18
|
+
|
|
19
|
+
### Changed
|
|
20
|
+
|
|
21
|
+
- `vitest`, `chai` and `bun:test` join the recognised assertion factories. Importing `expect` from
|
|
22
|
+
one of them was previously *less* trusted than relying on an injected global, so explicit imports
|
|
23
|
+
were penalised: expected values did not count as behaviour and two steps asserting different
|
|
24
|
+
values could be offered as a `parameterization-candidate`. Chain recognition is shape-based, so
|
|
25
|
+
Chai's `expect(x).to.equal(y)` is read exactly as `expect(x).toBe(y)` is. **Reports can lose those
|
|
26
|
+
spurious findings on upgrade.**
|
|
27
|
+
- A module-scoped constant now reaches the handler fingerprint, so two handlers reading the same
|
|
28
|
+
value through differently named constants are recognised as the same handler. A mutable `let`
|
|
29
|
+
stays unproven, a handler-local binding of the same name shadows the module constant, and a
|
|
30
|
+
constant declared inside another declaration's initializer is out of scope and never substituted.
|
|
31
|
+
A name captured from an enclosing scope is nearer than the module's, so a module constant never
|
|
32
|
+
substitutes over it. **Reports can gain findings on upgrade.**
|
|
33
|
+
|
|
34
|
+
### Fixed
|
|
35
|
+
|
|
36
|
+
- A function declared in a handler and then called contributes its assertions to that handler, so
|
|
37
|
+
conflicting expected values inside it keep the handlers apart instead of surfacing as a
|
|
38
|
+
`parameterization-candidate`. Expansion is single level and terminates on recursion. It is
|
|
39
|
+
declined when the name is bound by anything else in the handler — another declaration, a variable,
|
|
40
|
+
a parameter, a catch binding, a `class`, an `enum`, or a reassignment — because a name-keyed lookup
|
|
41
|
+
cannot tell which body a shadowed call reaches, and expanding the wrong one would attribute
|
|
42
|
+
assertions the handler never runs. A transparent wrapper such as `(check)()` resolves the same
|
|
43
|
+
declaration a bare call does.
|
|
44
|
+
|
|
45
|
+
### Known limitations
|
|
46
|
+
|
|
47
|
+
- A module-scoped constant reaches the handler fingerprint only when it is declared **before** the
|
|
48
|
+
registrations that read it. Two handlers reading the same value through constants declared after
|
|
49
|
+
the `Given`/`When`/`Then` calls are not recognised as the same handler, so the duplicate is
|
|
50
|
+
missed. Moving the declarations above the registrations restores the finding.
|
|
51
|
+
- A handler passed as a call expression, such as `Given("...", makeHandler())`, cannot be resolved
|
|
52
|
+
to a body and takes no part in the handler rules; it is reported as `dynamic or unsupported step
|
|
53
|
+
handler cannot be compared statically`. A handler reached through a member expression, such as
|
|
54
|
+
`Given("...", steps.run)`, is not compared either, and that case is silent. Inline functions —
|
|
55
|
+
arrow, `async`, `function` and generator — and a reference to a local function declaration are
|
|
56
|
+
all compared normally.
|
|
57
|
+
- Step definitions registered through a default import or TypeScript's `import x = require(...)`
|
|
58
|
+
and called as a member, such as `cucumber.Given("...")`, are not discovered, so those files
|
|
59
|
+
contribute no definitions to any rule. Named imports, namespace imports (`import * as cucumber`),
|
|
60
|
+
renamed named imports and `require` destructuring are all recognised.
|
|
61
|
+
|
|
62
|
+
## [0.5.0] - 2026-09-16
|
|
63
|
+
|
|
64
|
+
### Added
|
|
65
|
+
|
|
66
|
+
- Interactive terminal reports show the tool version, selective color, a finding-count footer, and detection time. `NO_COLOR` disables ANSI styling; redirected output remains plain text.
|
|
67
|
+
|
|
68
|
+
### Changed
|
|
69
|
+
|
|
70
|
+
- Terminal findings have a blank line after each rule heading and between findings instead of divider lines.
|
|
71
|
+
- `obj['name']` and `obj.name` read the same property, so two handlers that differ only in that
|
|
72
|
+
spelling are now recognised as the same handler. This applies to every property access in a
|
|
73
|
+
handler, not only assertions: `page['locator']('x')` matches `page.locator('x')`. Only a static
|
|
74
|
+
string literal whose decoded value is a valid identifier is folded — a dynamic key, a template
|
|
75
|
+
literal, or a name with no dot spelling such as `['not.resolves']` keeps its own identity.
|
|
76
|
+
Optional access is preserved, so `a?.['b']` matches `a?.b` and neither matches `a.b`.
|
|
77
|
+
**Reports can gain findings on upgrade**, because pairs that were previously distinguished only
|
|
78
|
+
by access spelling are now duplicates.
|
|
79
|
+
|
|
80
|
+
### Fixed
|
|
81
|
+
|
|
82
|
+
- The regular-expression `v` flag is part of a matcher's identity. It enables Unicode set notation
|
|
83
|
+
and changes character-class semantics, so `/^a gauge$/v` and `/^a gauge$/` match different inputs
|
|
84
|
+
and are no longer reported as `normalized-matcher`. Flags that only affect how a match is executed
|
|
85
|
+
— `g`, `y` and `d` — are still ignored for identity, and two matchers carrying `v` still compare
|
|
86
|
+
as the same matcher.
|
|
87
|
+
- Replacing the assertion factory through a second `require()` of the same module now revokes
|
|
88
|
+
assertion trust. Two `require()` calls for one module return the same cached object, so
|
|
89
|
+
`other.expect = replacement` replaces the factory that a separate `api.expect(...)` call uses.
|
|
90
|
+
Previously only a local alias (`const other = api`) propagated, so handlers relying on a factory
|
|
91
|
+
replaced through the second binding were still reported as duplicates.
|
|
92
|
+
- A `for (const x of xs)` or `for (const x in xs)` head binds `x` for the loop, so a reference to
|
|
93
|
+
`x` in the body is the loop variable rather than an outer constant of the same name. The binding
|
|
94
|
+
was not registered, so the outer constant's value was substituted into the loop body. This was
|
|
95
|
+
wrong in both directions: identical loop handlers with different unrelated outer constants were
|
|
96
|
+
reported as distinct, and different loop bodies sharing an outer constant could be reported as
|
|
97
|
+
duplicates. `class`, `function`, `catch` and classic `for (let i = …)` bindings were already
|
|
98
|
+
handled.
|
|
99
|
+
|
|
8
100
|
## [0.4.0] - 2026-09-15
|
|
9
101
|
|
|
10
102
|
### Changed
|
|
@@ -185,6 +277,8 @@ Initial public release.
|
|
|
185
277
|
- Native Cargo and npm distributions for eight supported targets.
|
|
186
278
|
- A checksum-verified GitHub Action and an agent-oriented CukeDedup skill.
|
|
187
279
|
|
|
280
|
+
[0.6.0]: https://github.com/figueiredoluiz/cuke-dedup/compare/v0.5.0...v0.6.0
|
|
281
|
+
[0.5.0]: https://github.com/figueiredoluiz/cuke-dedup/compare/v0.4.0...v0.5.0
|
|
188
282
|
[0.4.0]: https://github.com/figueiredoluiz/cuke-dedup/compare/v0.3.0...v0.4.0
|
|
189
283
|
[0.3.0]: https://github.com/figueiredoluiz/cuke-dedup/compare/v0.2.1...v0.3.0
|
|
190
284
|
[0.2.1]: https://github.com/figueiredoluiz/cuke-dedup/compare/v0.2.0...v0.2.1
|
package/README.md
CHANGED
package/docs/ci-and-baselines.md
CHANGED
package/docs/configuration.md
CHANGED
|
@@ -134,6 +134,39 @@ Nested, asynchronous, generator, conditional, multi-statement, reordered, rewrit
|
|
|
134
134
|
dynamically constructed wrappers cannot be inferred safely. Declare those names when they still
|
|
135
135
|
take the matcher first and handler second.
|
|
136
136
|
|
|
137
|
+
## Trusted assertion modules
|
|
138
|
+
|
|
139
|
+
`assertionModules` names module specifiers whose `expect` export is a real assertion factory.
|
|
140
|
+
|
|
141
|
+
CukeDedup recognizes `@playwright/test`, `playwright/test`, `@jest/globals`, `expect`, `vitest`,
|
|
142
|
+
`chai`, and `bun:test` without configuration, and it also trusts the ambient `expect` that Jest,
|
|
143
|
+
Vitest, and Playwright inject. Chain recognition is shape-based, so Chai's `expect(x).to.equal(y)`
|
|
144
|
+
is understood exactly as Jest's `expect(x).toBe(y)` is; only the origin of the factory is listed.
|
|
145
|
+
|
|
146
|
+
Declare a module when the factory reaches your steps another way:
|
|
147
|
+
|
|
148
|
+
```json
|
|
149
|
+
{
|
|
150
|
+
"assertionModules": ["./support/fixtures"]
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
A local module that re-exports `expect` is the common case:
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
// support/fixtures.ts
|
|
158
|
+
export { expect } from "@playwright/test";
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Without the declaration, `expect` is untrusted, because the analyzer does not follow a local
|
|
162
|
+
re-export back to its origin. An untrusted factory is not an error: its expected values simply stop
|
|
163
|
+
counting as behaviour, so two steps asserting genuinely different values can be offered as a
|
|
164
|
+
`parameterization-candidate`. Declaring the module restores the distinction. Both `import` and
|
|
165
|
+
`require` spellings honour the setting.
|
|
166
|
+
|
|
167
|
+
Trust only modules that really do expose an assertion factory. Declaring an unrelated module makes
|
|
168
|
+
its call arguments semantically load-bearing and can manufacture similarity evidence.
|
|
169
|
+
|
|
137
170
|
## Custom parameter types
|
|
138
171
|
|
|
139
172
|
`parameterTypes` maps a project-defined Cucumber Expression parameter type to the regular
|
package/docs/reports.md
CHANGED
|
@@ -17,6 +17,10 @@ file-only run prints the generated paths to stdout.
|
|
|
17
17
|
Terminal and JSONL both own stdout and cannot be selected together. JSONL can be combined with
|
|
18
18
|
file reporters without contaminating the stream.
|
|
19
19
|
|
|
20
|
+
The terminal report shows the tool version, leaves a blank line after each rule heading and between findings, and ends with the total number of active findings and detection time. Timing covers discovery, parsing, and analysis; `--no-metrics` omits it.
|
|
21
|
+
|
|
22
|
+
Rule headings are bold cyan, paths are blue, error/warning labels are red/yellow, and footer details are gray only when stdout is a terminal. Set `NO_COLOR` to a nonempty value to disable color. Piped output and the other report formats contain no ANSI color codes.
|
|
23
|
+
|
|
20
24
|
## JSON and HTML
|
|
21
25
|
|
|
22
26
|
JSON schema version `2` contains:
|
|
@@ -25,6 +25,16 @@ resource limits keep failures visible and bound the work performed.
|
|
|
25
25
|
overlap witnesses.
|
|
26
26
|
- Bounded fuzzy indexes preserve a deterministic sample in highly repetitive vocabularies but do
|
|
27
27
|
not promise every possible pair after a work limit is reached.
|
|
28
|
+
- A module-scoped constant declared *after* the registrations that read it is not resolved, even
|
|
29
|
+
though the handler body runs later and the value is initialized by then. Declaration order is the
|
|
30
|
+
conservative rule shared with handler-local constants, where a reference before the declaration is
|
|
31
|
+
a temporal-dead-zone error. The effect is a missed duplicate, never an invented one.
|
|
32
|
+
- A handler produced by calling a generator function, such as
|
|
33
|
+
`Given('a step', (function* () { … })())`, is not compared. Calling a generator returns a
|
|
34
|
+
suspended object and never runs the body, so the registered handler is that object rather than a
|
|
35
|
+
function and its assertions never execute. Treating the suspended body as the handler's behaviour
|
|
36
|
+
would invent meaning the code does not have, so each such registration warns that the handler
|
|
37
|
+
cannot be compared statically and is excluded from handler rules.
|
|
28
38
|
|
|
29
39
|
When analysis is incomplete, existing findings remain valid but the absence of a finding proves
|
|
30
40
|
nothing. Machine reports expose the incomplete or truncated status. Pass `--fail-on-incomplete`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cuke-dedup",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Static analysis for duplicate and reusable Cucumber step definitions",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -47,13 +47,13 @@
|
|
|
47
47
|
"npm/platforms/*"
|
|
48
48
|
],
|
|
49
49
|
"optionalDependencies": {
|
|
50
|
-
"cuke-dedup-darwin-arm64": "0.
|
|
51
|
-
"cuke-dedup-darwin-x64": "0.
|
|
52
|
-
"cuke-dedup-linux-arm64-gnu": "0.
|
|
53
|
-
"cuke-dedup-linux-arm64-musl": "0.
|
|
54
|
-
"cuke-dedup-linux-x64-gnu": "0.
|
|
55
|
-
"cuke-dedup-linux-x64-musl": "0.
|
|
56
|
-
"cuke-dedup-windows-arm64-msvc": "0.
|
|
57
|
-
"cuke-dedup-windows-x64-msvc": "0.
|
|
50
|
+
"cuke-dedup-darwin-arm64": "0.6.0",
|
|
51
|
+
"cuke-dedup-darwin-x64": "0.6.0",
|
|
52
|
+
"cuke-dedup-linux-arm64-gnu": "0.6.0",
|
|
53
|
+
"cuke-dedup-linux-arm64-musl": "0.6.0",
|
|
54
|
+
"cuke-dedup-linux-x64-gnu": "0.6.0",
|
|
55
|
+
"cuke-dedup-linux-x64-musl": "0.6.0",
|
|
56
|
+
"cuke-dedup-windows-arm64-msvc": "0.6.0",
|
|
57
|
+
"cuke-dedup-windows-x64-msvc": "0.6.0"
|
|
58
58
|
}
|
|
59
59
|
}
|