@starci/eslint-canon-be 2.0.0 → 2.0.1

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 CHANGED
@@ -1,5 +1,9 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.0.1 - 2026-10-01
4
+
5
+ - Fixed: `e2e-asserts-persisted-state` accepts `<world>.db.<connection>` and `<world>.services.<name>.api` state reads, the world identified by its `TestWorld` type (from `@starci/test-world` or the `be.tests.world` slot), not by a variable name.
6
+
3
7
  ## 2.0.0 - 2026-09-30
4
8
 
5
9
  - Changed (lane PORT): `ruleParams.be.infraOwners` names `@nestjs/cache-manager` (the `CACHE_MANAGER` token) beside `cache-manager`: it is reached only by `integrations/cache` and `integrations/redis`, which is the retired repository rule `must-use-cache-service`; `infra-import-owner` (R90) covers it with a passing and a violating case.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@starci/eslint-canon-be",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "The StarCi back-end lint canon: every rule its laws publish, gathered as one flat-config plugin.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",
package/testing.mjs CHANGED
@@ -25,7 +25,7 @@
25
25
  import { statSync } from "node:fs"
26
26
  import { basename, dirname, join } from "node:path"
27
27
  import { hfsOf } from "./lib/hfs.mjs"
28
- import { isPackageType } from "./lib/types.mjs"
28
+ import { isPackageType, typeOrigins } from "./lib/types.mjs"
29
29
  import { isServiceSpecFile, isUnitSpecFile, serviceNameOfSpec } from "./lib/unit-spec.mjs"
30
30
 
31
31
  /** The file name of a linted path, in forward-slash form. */
@@ -172,6 +172,27 @@ const STATE_TYPES = ["EntityManager", "DataSource", "QueryRunner"]
172
172
  /** Whether an expression is a receiver of persisted state, by the TYPE it has and the package that declares it. */
173
173
  const isStateReader = (context, node) => STATE_TYPES.some((name) => isPackageType(context, node, name, "typeorm"))
174
174
 
175
+ /** The handle `useTestWorld` answers, by where its type is declared: the `@starci/test-world` package or the repository's world slot. */
176
+ const isWorldHandle = (context, node) => {
177
+ const hfs = hfsOf(context)
178
+ return typeOrigins(context, node).some((origin) => origin.name === "TestWorld" && (origin.module === "@starci/test-world" || hfs.slotOf(origin.file) === "be.tests.world"))
179
+ }
180
+
181
+ /** The name a non-computed member access reads, or null. */
182
+ const propertyNameOf = (node) => (node.type === "MemberExpression" && !node.computed && node.property.type === "Identifier" ? node.property.name : null)
183
+
184
+ /**
185
+ * Whether a member expression is the world's own state read: `<world>.db` (its connections are EntityManagers) or
186
+ * `<world>.services.<name>.api` (a sibling service's own API). The receiver is judged by its type, not its name.
187
+ */
188
+ const isWorldStateRead = (context, node) => {
189
+ if (propertyNameOf(node) === "db") return isWorldHandle(context, node.object)
190
+ if (propertyNameOf(node) !== "api") return false
191
+ const service = node.object
192
+ const services = service.type === "MemberExpression" ? service.object : null
193
+ return Boolean(services) && propertyNameOf(services) === "services" && isWorldHandle(context, services.object)
194
+ }
195
+
175
196
  /** An end-to-end spec that never reads state back proves only that the server replied. */
176
197
  export const e2eAssertsPersistedState = {
177
198
  meta: {
@@ -188,7 +209,7 @@ export const e2eAssertsPersistedState = {
188
209
  let readsState = false
189
210
  return {
190
211
  MemberExpression(node) {
191
- if (!readsState && isStateReader(context, node.object)) readsState = true
212
+ if (!readsState && (isStateReader(context, node.object) || isWorldStateRead(context, node))) readsState = true
192
213
  },
193
214
  CallExpression(node) {
194
215
  if (!readsState && node.arguments.some((argument) => argument.type !== "SpreadElement" && isStateReader(context, argument))) readsState = true