@opensip-cli/checks-typescript 0.1.9 → 0.1.11

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.
Files changed (244) hide show
  1. package/README.md +4 -2
  2. package/dist/__tests__/all-checks-execute.test.d.ts.map +1 -1
  3. package/dist/__tests__/all-checks-execute.test.js +0 -1
  4. package/dist/__tests__/all-checks-execute.test.js.map +1 -1
  5. package/dist/__tests__/behavior-fixtures-2.test.d.ts.map +1 -1
  6. package/dist/__tests__/behavior-fixtures-2.test.js +0 -1
  7. package/dist/__tests__/behavior-fixtures-2.test.js.map +1 -1
  8. package/dist/__tests__/behavior-fixtures-3.test.d.ts.map +1 -1
  9. package/dist/__tests__/behavior-fixtures-3.test.js +0 -1
  10. package/dist/__tests__/behavior-fixtures-3.test.js.map +1 -1
  11. package/dist/__tests__/behavior-fixtures-4.test.d.ts.map +1 -1
  12. package/dist/__tests__/behavior-fixtures-4.test.js +0 -1
  13. package/dist/__tests__/behavior-fixtures-4.test.js.map +1 -1
  14. package/dist/__tests__/behavior-fixtures-5.test.d.ts.map +1 -1
  15. package/dist/__tests__/behavior-fixtures-5.test.js +0 -1
  16. package/dist/__tests__/behavior-fixtures-5.test.js.map +1 -1
  17. package/dist/__tests__/behavior-fixtures-6.test.js +10 -0
  18. package/dist/__tests__/behavior-fixtures-6.test.js.map +1 -1
  19. package/dist/__tests__/behavior-fixtures.test.d.ts.map +1 -1
  20. package/dist/__tests__/behavior-fixtures.test.js +2 -4
  21. package/dist/__tests__/behavior-fixtures.test.js.map +1 -1
  22. package/dist/__tests__/branch-fixtures-2.test.d.ts.map +1 -1
  23. package/dist/__tests__/branch-fixtures-2.test.js +0 -1
  24. package/dist/__tests__/branch-fixtures-2.test.js.map +1 -1
  25. package/dist/__tests__/branch-fixtures-3.test.d.ts.map +1 -1
  26. package/dist/__tests__/branch-fixtures-3.test.js +0 -1
  27. package/dist/__tests__/branch-fixtures-3.test.js.map +1 -1
  28. package/dist/__tests__/branch-fixtures.test.d.ts.map +1 -1
  29. package/dist/__tests__/branch-fixtures.test.js +0 -1
  30. package/dist/__tests__/branch-fixtures.test.js.map +1 -1
  31. package/dist/checks/architecture/__tests__/live-view-through-cli-live.test.d.ts +2 -0
  32. package/dist/checks/architecture/__tests__/live-view-through-cli-live.test.d.ts.map +1 -0
  33. package/dist/checks/architecture/__tests__/live-view-through-cli-live.test.js +13 -0
  34. package/dist/checks/architecture/__tests__/live-view-through-cli-live.test.js.map +1 -0
  35. package/dist/checks/architecture/contracts-schema-consistency.d.ts.map +1 -1
  36. package/dist/checks/architecture/contracts-schema-consistency.js +0 -3
  37. package/dist/checks/architecture/contracts-schema-consistency.js.map +1 -1
  38. package/dist/checks/architecture/drizzle-orm-migration-guardrails.d.ts.map +1 -1
  39. package/dist/checks/architecture/drizzle-orm-migration-guardrails.js +1 -0
  40. package/dist/checks/architecture/drizzle-orm-migration-guardrails.js.map +1 -1
  41. package/dist/checks/architecture/index.d.ts +1 -0
  42. package/dist/checks/architecture/index.d.ts.map +1 -1
  43. package/dist/checks/architecture/index.js +1 -0
  44. package/dist/checks/architecture/index.js.map +1 -1
  45. package/dist/checks/architecture/live-view-through-cli-live.d.ts +8 -0
  46. package/dist/checks/architecture/live-view-through-cli-live.d.ts.map +1 -0
  47. package/dist/checks/architecture/live-view-through-cli-live.js +43 -0
  48. package/dist/checks/architecture/live-view-through-cli-live.js.map +1 -0
  49. package/dist/checks/architecture/missing-type-exports.d.ts.map +1 -1
  50. package/dist/checks/architecture/missing-type-exports.js +1 -1
  51. package/dist/checks/architecture/missing-type-exports.js.map +1 -1
  52. package/dist/checks/architecture/module-coupling-fan-out.d.ts.map +1 -1
  53. package/dist/checks/architecture/module-coupling-fan-out.js +6 -2
  54. package/dist/checks/architecture/module-coupling-fan-out.js.map +1 -1
  55. package/dist/checks/architecture/no-bootstrap-tool-import.d.ts.map +1 -1
  56. package/dist/checks/architecture/no-bootstrap-tool-import.js +1 -0
  57. package/dist/checks/architecture/no-bootstrap-tool-import.js.map +1 -1
  58. package/dist/checks/architecture/no-run-done-result.d.ts.map +1 -1
  59. package/dist/checks/architecture/no-run-done-result.js +1 -0
  60. package/dist/checks/architecture/no-run-done-result.js.map +1 -1
  61. package/dist/checks/architecture/package-json-exports-field.d.ts.map +1 -1
  62. package/dist/checks/architecture/package-json-exports-field.js +1 -1
  63. package/dist/checks/architecture/package-json-exports-field.js.map +1 -1
  64. package/dist/checks/architecture/phantom-dependency-detection.d.ts.map +1 -1
  65. package/dist/checks/architecture/phantom-dependency-detection.js +0 -3
  66. package/dist/checks/architecture/phantom-dependency-detection.js.map +1 -1
  67. package/dist/checks/architecture/tsconfig-extends-validation.d.ts.map +1 -1
  68. package/dist/checks/architecture/tsconfig-extends-validation.js +0 -2
  69. package/dist/checks/architecture/tsconfig-extends-validation.js.map +1 -1
  70. package/dist/checks/quality/code-structure/__tests__/duplicate-utility-lang-substrate.test.d.ts +5 -0
  71. package/dist/checks/quality/code-structure/__tests__/duplicate-utility-lang-substrate.test.d.ts.map +1 -0
  72. package/dist/checks/quality/code-structure/__tests__/duplicate-utility-lang-substrate.test.js +17 -0
  73. package/dist/checks/quality/code-structure/__tests__/duplicate-utility-lang-substrate.test.js.map +1 -0
  74. package/dist/checks/quality/code-structure/duplicate-utility-functions-config.d.ts +18 -0
  75. package/dist/checks/quality/code-structure/duplicate-utility-functions-config.d.ts.map +1 -0
  76. package/dist/checks/quality/code-structure/duplicate-utility-functions-config.js +36 -0
  77. package/dist/checks/quality/code-structure/duplicate-utility-functions-config.js.map +1 -0
  78. package/dist/checks/quality/code-structure/duplicate-utility-functions-helpers.d.ts +15 -0
  79. package/dist/checks/quality/code-structure/duplicate-utility-functions-helpers.d.ts.map +1 -0
  80. package/dist/checks/quality/code-structure/duplicate-utility-functions-helpers.js +288 -0
  81. package/dist/checks/quality/code-structure/duplicate-utility-functions-helpers.js.map +1 -0
  82. package/dist/checks/quality/code-structure/duplicate-utility-functions.d.ts +1 -26
  83. package/dist/checks/quality/code-structure/duplicate-utility-functions.d.ts.map +1 -1
  84. package/dist/checks/quality/code-structure/duplicate-utility-functions.js +3 -407
  85. package/dist/checks/quality/code-structure/duplicate-utility-functions.js.map +1 -1
  86. package/dist/checks/quality/data-integrity/__tests__/null-safety-fp.test.js +39 -2
  87. package/dist/checks/quality/data-integrity/__tests__/null-safety-fp.test.js.map +1 -1
  88. package/dist/checks/quality/data-integrity/array-validation-detectors.d.ts +17 -0
  89. package/dist/checks/quality/data-integrity/array-validation-detectors.d.ts.map +1 -0
  90. package/dist/checks/quality/data-integrity/array-validation-detectors.js +184 -0
  91. package/dist/checks/quality/data-integrity/array-validation-detectors.js.map +1 -0
  92. package/dist/checks/quality/data-integrity/array-validation.d.ts +0 -2
  93. package/dist/checks/quality/data-integrity/array-validation.d.ts.map +1 -1
  94. package/dist/checks/quality/data-integrity/array-validation.js +2 -360
  95. package/dist/checks/quality/data-integrity/array-validation.js.map +1 -1
  96. package/dist/checks/quality/data-integrity/database-schema-validation.d.ts.map +1 -1
  97. package/dist/checks/quality/data-integrity/database-schema-validation.js +0 -1
  98. package/dist/checks/quality/data-integrity/database-schema-validation.js.map +1 -1
  99. package/dist/checks/quality/data-integrity/null-safety-analyze.d.ts +33 -0
  100. package/dist/checks/quality/data-integrity/null-safety-analyze.d.ts.map +1 -0
  101. package/dist/checks/quality/data-integrity/null-safety-analyze.js +164 -0
  102. package/dist/checks/quality/data-integrity/null-safety-analyze.js.map +1 -0
  103. package/dist/checks/quality/data-integrity/null-safety-config.d.ts +50 -0
  104. package/dist/checks/quality/data-integrity/null-safety-config.d.ts.map +1 -0
  105. package/dist/checks/quality/data-integrity/null-safety-config.js +69 -0
  106. package/dist/checks/quality/data-integrity/null-safety-config.js.map +1 -0
  107. package/dist/checks/quality/data-integrity/null-safety-heuristics.d.ts +76 -0
  108. package/dist/checks/quality/data-integrity/null-safety-heuristics.d.ts.map +1 -0
  109. package/dist/checks/quality/data-integrity/null-safety-heuristics.js +276 -0
  110. package/dist/checks/quality/data-integrity/null-safety-heuristics.js.map +1 -0
  111. package/dist/checks/quality/data-integrity/null-safety-prefixes.d.ts +13 -0
  112. package/dist/checks/quality/data-integrity/null-safety-prefixes.d.ts.map +1 -0
  113. package/dist/checks/quality/data-integrity/null-safety-prefixes.js +333 -0
  114. package/dist/checks/quality/data-integrity/null-safety-prefixes.js.map +1 -0
  115. package/dist/checks/quality/data-integrity/null-safety.d.ts +2 -82
  116. package/dist/checks/quality/data-integrity/null-safety.d.ts.map +1 -1
  117. package/dist/checks/quality/data-integrity/null-safety.js +3 -796
  118. package/dist/checks/quality/data-integrity/null-safety.js.map +1 -1
  119. package/dist/checks/quality/frontend/test-only-frontend-modules.d.ts.map +1 -1
  120. package/dist/checks/quality/frontend/test-only-frontend-modules.js +0 -2
  121. package/dist/checks/quality/frontend/test-only-frontend-modules.js.map +1 -1
  122. package/dist/checks/quality/linting/typescript-frontend.d.ts.map +1 -1
  123. package/dist/checks/quality/linting/typescript-frontend.js +1 -0
  124. package/dist/checks/quality/linting/typescript-frontend.js.map +1 -1
  125. package/dist/checks/quality/observability/logger-event-name-format.d.ts.map +1 -1
  126. package/dist/checks/quality/observability/logger-event-name-format.js +0 -1
  127. package/dist/checks/quality/observability/logger-event-name-format.js.map +1 -1
  128. package/dist/checks/quality/observability/no-hardcoded-correlation-id.d.ts.map +1 -1
  129. package/dist/checks/quality/observability/no-hardcoded-correlation-id.js +2 -3
  130. package/dist/checks/quality/observability/no-hardcoded-correlation-id.js.map +1 -1
  131. package/dist/checks/quality/patterns/__tests__/async-waterfall-sequential.test.d.ts +8 -0
  132. package/dist/checks/quality/patterns/__tests__/async-waterfall-sequential.test.d.ts.map +1 -0
  133. package/dist/checks/quality/patterns/__tests__/async-waterfall-sequential.test.js +87 -0
  134. package/dist/checks/quality/patterns/__tests__/async-waterfall-sequential.test.js.map +1 -0
  135. package/dist/checks/quality/patterns/__tests__/error-handling-probes.test.d.ts +2 -0
  136. package/dist/checks/quality/patterns/__tests__/error-handling-probes.test.d.ts.map +1 -0
  137. package/dist/checks/quality/patterns/__tests__/error-handling-probes.test.js +51 -0
  138. package/dist/checks/quality/patterns/__tests__/error-handling-probes.test.js.map +1 -0
  139. package/dist/checks/quality/patterns/__tests__/result-pattern-registration-guards.test.d.ts +2 -0
  140. package/dist/checks/quality/patterns/__tests__/result-pattern-registration-guards.test.d.ts.map +1 -0
  141. package/dist/checks/quality/patterns/__tests__/result-pattern-registration-guards.test.js +89 -0
  142. package/dist/checks/quality/patterns/__tests__/result-pattern-registration-guards.test.js.map +1 -0
  143. package/dist/checks/quality/patterns/__tests__/throws-documentation-analyze.test.d.ts +5 -0
  144. package/dist/checks/quality/patterns/__tests__/throws-documentation-analyze.test.d.ts.map +1 -0
  145. package/dist/checks/quality/patterns/__tests__/throws-documentation-analyze.test.js +78 -0
  146. package/dist/checks/quality/patterns/__tests__/throws-documentation-analyze.test.js.map +1 -0
  147. package/dist/checks/quality/patterns/__tests__/toctou-fp.test.js +44 -0
  148. package/dist/checks/quality/patterns/__tests__/toctou-fp.test.js.map +1 -1
  149. package/dist/checks/quality/patterns/async-waterfall-analysis.d.ts +17 -0
  150. package/dist/checks/quality/patterns/async-waterfall-analysis.d.ts.map +1 -0
  151. package/dist/checks/quality/patterns/async-waterfall-analysis.js +215 -0
  152. package/dist/checks/quality/patterns/async-waterfall-analysis.js.map +1 -0
  153. package/dist/checks/quality/patterns/async-waterfall-branch-keys.d.ts +6 -0
  154. package/dist/checks/quality/patterns/async-waterfall-branch-keys.d.ts.map +1 -0
  155. package/dist/checks/quality/patterns/async-waterfall-branch-keys.js +54 -0
  156. package/dist/checks/quality/patterns/async-waterfall-branch-keys.js.map +1 -0
  157. package/dist/checks/quality/patterns/async-waterfall-detection.d.ts.map +1 -1
  158. package/dist/checks/quality/patterns/async-waterfall-detection.js +3 -352
  159. package/dist/checks/quality/patterns/async-waterfall-detection.js.map +1 -1
  160. package/dist/checks/quality/patterns/containing-function-name.d.ts +3 -0
  161. package/dist/checks/quality/patterns/containing-function-name.d.ts.map +1 -0
  162. package/dist/checks/quality/patterns/containing-function-name.js +21 -0
  163. package/dist/checks/quality/patterns/containing-function-name.js.map +1 -0
  164. package/dist/checks/quality/patterns/error-handling-quality.d.ts +3 -0
  165. package/dist/checks/quality/patterns/error-handling-quality.d.ts.map +1 -1
  166. package/dist/checks/quality/patterns/error-handling-quality.js +150 -30
  167. package/dist/checks/quality/patterns/error-handling-quality.js.map +1 -1
  168. package/dist/checks/quality/patterns/result-pattern-consistency.d.ts +3 -0
  169. package/dist/checks/quality/patterns/result-pattern-consistency.d.ts.map +1 -1
  170. package/dist/checks/quality/patterns/result-pattern-consistency.js +136 -69
  171. package/dist/checks/quality/patterns/result-pattern-consistency.js.map +1 -1
  172. package/dist/checks/quality/patterns/throws-documentation-analyze.d.ts +14 -0
  173. package/dist/checks/quality/patterns/throws-documentation-analyze.d.ts.map +1 -0
  174. package/dist/checks/quality/patterns/throws-documentation-analyze.js +352 -0
  175. package/dist/checks/quality/patterns/throws-documentation-analyze.js.map +1 -0
  176. package/dist/checks/quality/patterns/throws-documentation-constants.d.ts +15 -0
  177. package/dist/checks/quality/patterns/throws-documentation-constants.d.ts.map +1 -0
  178. package/dist/checks/quality/patterns/throws-documentation-constants.js +94 -0
  179. package/dist/checks/quality/patterns/throws-documentation-constants.js.map +1 -0
  180. package/dist/checks/quality/patterns/throws-documentation.d.ts +1 -11
  181. package/dist/checks/quality/patterns/throws-documentation.d.ts.map +1 -1
  182. package/dist/checks/quality/patterns/throws-documentation.js +4 -472
  183. package/dist/checks/quality/patterns/throws-documentation.js.map +1 -1
  184. package/dist/checks/quality/patterns/toctou-race-condition-classify.d.ts +23 -0
  185. package/dist/checks/quality/patterns/toctou-race-condition-classify.d.ts.map +1 -0
  186. package/dist/checks/quality/patterns/toctou-race-condition-classify.js +125 -0
  187. package/dist/checks/quality/patterns/toctou-race-condition-classify.js.map +1 -0
  188. package/dist/checks/quality/patterns/toctou-race-condition-collection.d.ts +24 -0
  189. package/dist/checks/quality/patterns/toctou-race-condition-collection.d.ts.map +1 -0
  190. package/dist/checks/quality/patterns/toctou-race-condition-collection.js +248 -0
  191. package/dist/checks/quality/patterns/toctou-race-condition-collection.js.map +1 -0
  192. package/dist/checks/quality/patterns/toctou-race-condition-constants.d.ts +32 -0
  193. package/dist/checks/quality/patterns/toctou-race-condition-constants.d.ts.map +1 -0
  194. package/dist/checks/quality/patterns/toctou-race-condition-constants.js +115 -0
  195. package/dist/checks/quality/patterns/toctou-race-condition-constants.js.map +1 -0
  196. package/dist/checks/quality/patterns/toctou-race-condition.d.ts +1 -29
  197. package/dist/checks/quality/patterns/toctou-race-condition.d.ts.map +1 -1
  198. package/dist/checks/quality/patterns/toctou-race-condition.js +11 -536
  199. package/dist/checks/quality/patterns/toctou-race-condition.js.map +1 -1
  200. package/dist/checks/quality/unused-config-options.d.ts.map +1 -1
  201. package/dist/checks/quality/unused-config-options.js +0 -4
  202. package/dist/checks/quality/unused-config-options.js.map +1 -1
  203. package/dist/checks/resilience/__tests__/detached-promises-sync-detection.test.d.ts +2 -0
  204. package/dist/checks/resilience/__tests__/detached-promises-sync-detection.test.d.ts.map +1 -0
  205. package/dist/checks/resilience/__tests__/detached-promises-sync-detection.test.js +98 -0
  206. package/dist/checks/resilience/__tests__/detached-promises-sync-detection.test.js.map +1 -0
  207. package/dist/checks/resilience/callback-invocation-safe.d.ts.map +1 -1
  208. package/dist/checks/resilience/callback-invocation-safe.js +0 -1
  209. package/dist/checks/resilience/callback-invocation-safe.js.map +1 -1
  210. package/dist/checks/resilience/context-leakage.d.ts.map +1 -1
  211. package/dist/checks/resilience/context-leakage.js +1 -0
  212. package/dist/checks/resilience/context-leakage.js.map +1 -1
  213. package/dist/checks/resilience/detached-promises-detection.d.ts +7 -0
  214. package/dist/checks/resilience/detached-promises-detection.d.ts.map +1 -0
  215. package/dist/checks/resilience/detached-promises-detection.js +228 -0
  216. package/dist/checks/resilience/detached-promises-detection.js.map +1 -0
  217. package/dist/checks/resilience/detached-promises-sync-constants.d.ts +36 -0
  218. package/dist/checks/resilience/detached-promises-sync-constants.d.ts.map +1 -0
  219. package/dist/checks/resilience/detached-promises-sync-constants.js +299 -0
  220. package/dist/checks/resilience/detached-promises-sync-constants.js.map +1 -0
  221. package/dist/checks/resilience/detached-promises-sync-detection.d.ts +14 -0
  222. package/dist/checks/resilience/detached-promises-sync-detection.d.ts.map +1 -0
  223. package/dist/checks/resilience/detached-promises-sync-detection.js +69 -0
  224. package/dist/checks/resilience/detached-promises-sync-detection.js.map +1 -0
  225. package/dist/checks/resilience/detached-promises.d.ts +1 -14
  226. package/dist/checks/resilience/detached-promises.d.ts.map +1 -1
  227. package/dist/checks/resilience/detached-promises.js +2 -598
  228. package/dist/checks/resilience/detached-promises.js.map +1 -1
  229. package/dist/checks/resilience/no-raw-fetch.d.ts.map +1 -1
  230. package/dist/checks/resilience/no-raw-fetch.js +1 -0
  231. package/dist/checks/resilience/no-raw-fetch.js.map +1 -1
  232. package/dist/checks/resilience/no-unbounded-concurrency.d.ts.map +1 -1
  233. package/dist/checks/resilience/no-unbounded-concurrency.js +1 -0
  234. package/dist/checks/resilience/no-unbounded-concurrency.js.map +1 -1
  235. package/dist/checks/security/sql-injection.d.ts.map +1 -1
  236. package/dist/checks/security/sql-injection.js +0 -1
  237. package/dist/checks/security/sql-injection.js.map +1 -1
  238. package/dist/display/architecture.d.ts.map +1 -1
  239. package/dist/display/architecture.js +1 -0
  240. package/dist/display/architecture.js.map +1 -1
  241. package/dist/display/types.d.ts.map +1 -1
  242. package/dist/display/types.js +0 -1
  243. package/dist/display/types.js.map +1 -1
  244. package/package.json +5 -5
@@ -1,806 +1,13 @@
1
- // @fitness-ignore-file file-length-limit -- cohesive single-check module; splitting risks breaking the detector contract
2
1
  /**
3
2
  * @fileoverview Null/Undefined Safety Check
4
3
  *
5
4
  * Detects unsafe property and method access without null checks.
6
5
  */
7
6
  import { defineCheck, getCheckConfig, isTestFile, } from '@opensip-cli/fitness';
8
- import { getSharedSourceFile, isTypeNullable, } from '@opensip-cli/lang-typescript';
9
- import * as ts from 'typescript';
10
7
  import { getSharedTypeCheckedProgram } from '../../../shared/type-program.js';
11
- /**
12
- * Patterns that indicate the access is already protected
13
- */
14
- const SAFE_PATTERNS = [
15
- /\?\./, // Optional chaining
16
- /!!/, // Double negation
17
- /\?\?/, // Nullish coalescing
18
- /if\s*\(/, // Conditional check
19
- /&&/, // Logical AND guard
20
- ];
21
- /**
22
- * Call prefixes whose results are non-null by construction, so a property
23
- * access on them needs no guard. Scope is deliberately limited to facts that
24
- * hold for ANY codebase:
25
- *
26
- * 1. Language / runtime guarantees — `Object.*`, `Array.*`, `JSON.*`,
27
- * `new URL()`, Node `crypto`/`child_process`, `Intl`, the TS compiler API.
28
- * 2. Widely-used libraries whose builder/query APIs are documented non-null
29
- * — Zod, TypeORM, Drizzle, better-sqlite3, neverthrow `Result`, Express.
30
- * 3. Generic builder-pattern conventions — `builder.`, `*ResultBuilder.`.
31
- *
32
- * PROJECT-SPECIFIC safe symbols must NOT be hardcoded here: baking one
33
- * codebase's invariants into a generic check silently suppresses real null
34
- * bugs in every other codebase (e.g. an adopter whose own `getThing()` can
35
- * return null). Adopters extend this set with their own factories via the
36
- * `additionalSafeBuilders` recipe-config key — see `buildEffectiveSafeBuilders`.
37
- */
38
- export const SAFE_BUILDER_PREFIXES = [
39
- // 1. Language / runtime guarantees
40
- 'Object.entries',
41
- 'Object.values',
42
- 'Object.keys',
43
- 'Object.assign',
44
- 'Object.freeze',
45
- 'Array.from',
46
- 'Array.isArray',
47
- 'String(',
48
- 'Number(',
49
- 'Boolean(',
50
- 'Buffer.from',
51
- 'JSON.stringify',
52
- 'JSON.parse',
53
- 'process.memoryUsage',
54
- 'pathToFileURL(',
55
- 'fileURLToPath(',
56
- 'new URL(',
57
- 'spawn(',
58
- 'fork(',
59
- 'createHash(',
60
- 'createHmac(',
61
- 'createCipheriv(',
62
- 'createDecipheriv(',
63
- 'new Intl.',
64
- 'Intl.NumberFormat',
65
- 'Intl.DateTimeFormat',
66
- // TypeScript compiler API (always return valid objects)
67
- 'sourceFile.getLineAndCharacterOfPosition',
68
- 'node.getText',
69
- 'node.getStart',
70
- 'node.getEnd',
71
- 'node.getWidth',
72
- 'node.getFullWidth',
73
- // Browser APIs with guaranteed non-null returns
74
- 'window.matchMedia',
75
- 'document.createElement',
76
- 'document.createTextNode',
77
- // 2. Common library builder / query APIs
78
- 'z.', // Zod schema builder (z.string(), z.object(), …)
79
- 'createQueryBuilder', // TypeORM QueryBuilder
80
- 'getRepository', // TypeORM Repository
81
- 'EntityManager.', // TypeORM EntityManager
82
- 'queryBuilder.', // TypeORM QueryBuilder variable
83
- 'repository.', // TypeORM Repository variable
84
- 'Result.', // Result pattern builder
85
- 'ResultAsync.', // neverthrow ResultAsync
86
- 'prepare(', // better-sqlite3 db.prepare() → Statement
87
- 'drizzle(', // Drizzle instance creation
88
- 'db.select', // Drizzle query builder
89
- 'db.insert',
90
- 'db.update',
91
- 'db.delete',
92
- 'res.status', // Express/Fastify response chaining
93
- 'response.status',
94
- // 3. Generic builder-pattern conventions
95
- 'builder.',
96
- 'ResultBuilder.',
97
- 'ScenarioResultBuilder.',
98
- ];
99
- /**
100
- * Known safe method names in fluent APIs that always return `this` or non-null values.
101
- */
102
- const SAFE_FLUENT_METHODS = new Set([
103
- // Promise methods
104
- 'then',
105
- 'catch',
106
- 'finally',
107
- // Array methods (iteration)
108
- 'map',
109
- 'filter',
110
- 'reduce',
111
- 'flatMap',
112
- 'forEach',
113
- 'some',
114
- 'every',
115
- 'find',
116
- 'findIndex',
117
- 'findLast',
118
- 'findLastIndex',
119
- 'includes',
120
- 'indexOf',
121
- 'lastIndexOf',
122
- 'at',
123
- 'flat',
124
- 'entries',
125
- 'keys',
126
- 'values',
127
- // Array methods (mutation/creation)
128
- 'slice',
129
- 'concat',
130
- 'sort',
131
- 'reverse',
132
- 'join',
133
- 'push',
134
- 'pop',
135
- 'shift',
136
- 'unshift',
137
- 'fill',
138
- // String methods
139
- 'trim',
140
- 'trimStart',
141
- 'trimEnd',
142
- 'toLowerCase',
143
- 'toUpperCase',
144
- 'toLocaleLowerCase',
145
- 'toLocaleUpperCase',
146
- 'split',
147
- 'replace',
148
- 'replaceAll',
149
- 'substring',
150
- 'substr',
151
- 'slice',
152
- 'padStart',
153
- 'padEnd',
154
- 'charAt',
155
- 'charCodeAt',
156
- 'startsWith',
157
- 'endsWith',
158
- 'match',
159
- 'search',
160
- 'normalize',
161
- 'repeat',
162
- // Iterator methods
163
- 'next',
164
- // Buffer methods
165
- 'toString',
166
- // HTTP response chaining (Express/Fastify)
167
- 'json',
168
- 'send',
169
- 'status',
170
- 'header',
171
- 'type',
172
- 'code',
173
- // TypeORM QueryBuilder fluent methods
174
- 'where',
175
- 'andWhere',
176
- 'orWhere',
177
- 'having',
178
- 'orderBy',
179
- 'addOrderBy',
180
- 'groupBy',
181
- 'addGroupBy',
182
- 'select',
183
- 'addSelect',
184
- 'leftJoin',
185
- 'leftJoinAndSelect',
186
- 'innerJoin',
187
- 'innerJoinAndSelect',
188
- 'limit',
189
- 'offset',
190
- 'skip',
191
- 'take',
192
- 'getOne',
193
- 'getMany',
194
- 'getRawOne',
195
- 'getRawMany',
196
- 'execute',
197
- // Result/Option pattern methods
198
- 'map',
199
- 'mapErr',
200
- 'andThen',
201
- 'orElse',
202
- 'unwrapOr',
203
- 'match',
204
- // Builder pattern methods
205
- 'set',
206
- 'with',
207
- 'withId',
208
- 'withCode',
209
- 'withMessage',
210
- 'withDetails',
211
- 'withContext',
212
- 'withCause',
213
- 'build',
214
- 'add',
215
- 'remove',
216
- 'update',
217
- 'delete',
218
- 'insert',
219
- // Event bus / subscription methods
220
- 'subscribe',
221
- 'unsubscribe',
222
- 'emit',
223
- 'on',
224
- 'off',
225
- 'once',
226
- // Pino logger methods (return this)
227
- 'child',
228
- 'bindings',
229
- 'level',
230
- 'info',
231
- 'warn',
232
- 'error',
233
- 'debug',
234
- 'trace',
235
- 'fatal',
236
- // Drizzle ORM column builder methods (always return updated column definition)
237
- 'notNull',
238
- 'default',
239
- 'references',
240
- 'primaryKey',
241
- 'unique',
242
- '$default',
243
- '$onUpdate',
244
- // Drizzle ORM query methods
245
- 'from',
246
- 'where',
247
- 'returning',
248
- 'onConflictDoNothing',
249
- 'onConflictDoUpdate',
250
- 'innerJoin',
251
- 'leftJoin',
252
- 'rightJoin',
253
- 'fullJoin',
254
- // better-sqlite3 Statement methods (always return valid results)
255
- 'run',
256
- 'all',
257
- 'get',
258
- 'pluck',
259
- 'iterate',
260
- 'bind',
261
- 'columns',
262
- 'expand',
263
- // TypeScript compiler API methods (always return valid objects)
264
- 'getLineAndCharacterOfPosition',
265
- 'getText',
266
- 'getStart',
267
- 'getEnd',
268
- 'getWidth',
269
- 'getFullWidth',
270
- 'getSourceFile',
271
- 'getChildAt',
272
- 'getChildren',
273
- 'getFirstToken',
274
- 'getLastToken',
275
- 'forEachChild',
276
- // Map/Set methods
277
- 'get',
278
- 'set',
279
- 'has',
280
- 'delete',
281
- 'clear',
282
- 'size',
283
- // Singleton/factory return methods
284
- 'getInstance',
285
- 'create',
286
- 'of',
287
- // Immutable-combinator methods — return a new non-null value built from
288
- // the receiver (e.g. OTel `Resource.merge`, Immutable.js `.merge`,
289
- // builder `.concat`/`.assign`). The chain result is never null.
290
- 'merge',
291
- 'mergeWith',
292
- // Vitest/Jest assertion methods (expect() always returns Assertion object)
293
- 'toBe',
294
- 'toEqual',
295
- 'toStrictEqual',
296
- 'toBeDefined',
297
- 'toBeUndefined',
298
- 'toBeNull',
299
- 'toBeTruthy',
300
- 'toBeFalsy',
301
- 'toBeGreaterThan',
302
- 'toBeGreaterThanOrEqual',
303
- 'toBeLessThan',
304
- 'toBeLessThanOrEqual',
305
- 'toBeCloseTo',
306
- 'toBeInstanceOf',
307
- 'toBeNaN',
308
- 'toContain',
309
- 'toContainEqual',
310
- 'toHaveLength',
311
- 'toHaveProperty',
312
- 'toHaveBeenCalled',
313
- 'toHaveBeenCalledTimes',
314
- 'toHaveBeenCalledWith',
315
- 'toHaveBeenLastCalledWith',
316
- 'toHaveBeenNthCalledWith',
317
- 'toHaveReturned',
318
- 'toHaveReturnedTimes',
319
- 'toHaveReturnedWith',
320
- 'toHaveLastReturnedWith',
321
- 'toHaveNthReturnedWith',
322
- 'toThrow',
323
- 'toThrowError',
324
- 'toMatch',
325
- 'toMatchObject',
326
- 'toMatchSnapshot',
327
- 'toMatchInlineSnapshot',
328
- 'resolves',
329
- 'rejects',
330
- 'not',
331
- // Vitest/Jest mock methods (vi.fn() always returns Mock object)
332
- 'mockResolvedValue',
333
- 'mockResolvedValueOnce',
334
- 'mockRejectedValue',
335
- 'mockRejectedValueOnce',
336
- 'mockReturnValue',
337
- 'mockReturnValueOnce',
338
- 'mockImplementation',
339
- 'mockImplementationOnce',
340
- 'mockClear',
341
- 'mockReset',
342
- 'mockRestore',
343
- 'mockReturnThis',
344
- 'mockName',
345
- // Node.js crypto Hash/Hmac fluent methods (always return this or string)
346
- 'update',
347
- 'digest',
348
- 'final',
349
- // Node.js ChildProcess methods (always exist on ChildProcess)
350
- 'unref',
351
- 'ref',
352
- 'kill',
353
- // Intl formatter methods (always return formatted string)
354
- 'format',
355
- 'formatToParts',
356
- 'resolvedOptions',
357
- // neverthrow Result methods (safe after isOk/isErr guard)
358
- 'unwrapOr',
359
- 'unwrapErr',
360
- '_unsafeUnwrap',
361
- '_unsafeUnwrapErr',
362
- // typed-inject Injector chain — every .provide* call returns a new Injector<T>, never null
363
- 'provideValue',
364
- 'provideClass',
365
- 'provideFactory',
366
- 'provide',
367
- // Drizzle column builder — column.$type<T>() always returns the same column reference
368
- '$type',
369
- // Commander.js Command builder — every chained method returns the Command instance
370
- 'command',
371
- 'description',
372
- 'option',
373
- 'requiredOption',
374
- 'action',
375
- 'argument',
376
- 'version',
377
- 'name',
378
- 'alias',
379
- 'aliases',
380
- 'addCommand',
381
- 'addOption',
382
- 'addArgument',
383
- 'hook',
384
- 'usage',
385
- 'summary',
386
- 'helpOption',
387
- 'addHelpText',
388
- 'showHelpAfterError',
389
- 'showSuggestionAfterError',
390
- 'exitOverride',
391
- 'configureOutput',
392
- 'configureHelp',
393
- 'allowExcessArguments',
394
- 'allowUnknownOption',
395
- 'enablePositionalOptions',
396
- 'passThroughOptions',
397
- 'storeOptionsAsProperties',
398
- 'copyInheritedSettings',
399
- 'combineFlagAndOptionalValue',
400
- ]);
401
- /**
402
- * Common method name prefixes that indicate safe (non-null) return values.
403
- * Methods starting with these prefixes are conventionally designed to always
404
- * return a value or throw, never return null/undefined.
405
- */
406
- export const SAFE_METHOD_PREFIXES = [
407
- 'get',
408
- 'set',
409
- 'is',
410
- 'has',
411
- 'to',
412
- 'with',
413
- 'from',
414
- 'of',
415
- 'create',
416
- 'build',
417
- 'add',
418
- 'remove',
419
- 'update',
420
- 'delete',
421
- 'find',
422
- 'load',
423
- 'save',
424
- 'parse',
425
- 'format',
426
- 'validate',
427
- 'check',
428
- 'resolve',
429
- 'register',
430
- 'unregister',
431
- // Reading conventions (returns a value or throws — never null)
432
- 'read',
433
- 'open',
434
- 'compute',
435
- 'make',
436
- 'render',
437
- 'ensure',
438
- // Functional conventions — pure transforms / current-scope accessors that
439
- // always return a value (never null). Matches helpers like `classifyCatalog`,
440
- // `filterContent`, `currentScenarioRegistry`, `pickAdapter`.
441
- 'classify',
442
- 'filter',
443
- 'current',
444
- 'pick',
445
- 'select',
446
- ];
447
- /**
448
- * Check if a call expression is a known safe builder pattern.
449
- *
450
- * Two paths:
451
- * 1. Explicit allowlist (`SAFE_BUILDER_PREFIXES`) — exact-prefix match on the
452
- * full call text (e.g. `z.string(`, `pathToFileURL(`).
453
- * 2. Convention heuristic — when the callee is a bare identifier whose name
454
- * starts with a recognised safe verb (`get*`, `read*`, `resolve*`,
455
- * `current*`, `create*`, `build*`, etc.). This is the same convention that
456
- * already covers fluent-chain methods via `isSafeFluentMethod`; applying it
457
- * to standalone calls closes the gap for helpers like `resolveProjectPaths`,
458
- * `readScope`, `currentScenarioRegistry`, etc. whose names convey the same
459
- * "returns a value or throws" contract.
460
- */
461
- function isSafeBuilderPattern(expression, sourceFile, safeBuilders) {
462
- const text = expression.getText(sourceFile);
463
- if (safeBuilders.some((prefix) => text.startsWith(prefix)))
464
- return true;
465
- if (ts.isIdentifier(expression.expression)) {
466
- return isSafeFluentMethod(expression.expression.text);
467
- }
468
- return false;
469
- }
470
- /**
471
- * Check if a method name is a known safe fluent API method.
472
- * Matches either an exact entry in SAFE_FLUENT_METHODS or a method whose name
473
- * starts with a common safe prefix (get, set, is, has, to, etc.).
474
- */
475
- function isSafeFluentMethod(methodName) {
476
- if (SAFE_FLUENT_METHODS.has(methodName))
477
- return true;
478
- return SAFE_METHOD_PREFIXES.some((prefix) => methodName.startsWith(prefix));
479
- }
480
- /**
481
- * Walk ancestors to find an enclosing truthiness guard whose condition
482
- * references the access's base expression — an `if (...)`, a `cond ? … : …`,
483
- * or the left side of a `&&` chain (e.g. `if (candidates.length === 1 &&
484
- * candidates[0]) { … candidates[0].bodyHash … }`).
485
- *
486
- * The line-local {@link SAFE_PATTERNS} scan only inspects the physical line
487
- * of the access, so a guard placed on a *previous* line is missed. This
488
- * closes that cross-line gap. Substring matching is intentionally lenient:
489
- * the check errs toward treating a guarded access as safe (fewer false
490
- * positives), consistent with the existing line-local guard handling.
491
- */
492
- function isGuardedByEnclosingCondition(node, sourceFile) {
493
- const baseText = node.expression.getText(sourceFile);
494
- let current = node;
495
- let parent = node.parent;
496
- while (parent) {
497
- if (ts.isIfStatement(parent) && parent.expression.getText(sourceFile).includes(baseText)) {
498
- return true;
499
- }
500
- if (ts.isConditionalExpression(parent) &&
501
- parent.condition.getText(sourceFile).includes(baseText)) {
502
- return true;
503
- }
504
- if (ts.isBinaryExpression(parent) &&
505
- parent.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken &&
506
- parent.right === current &&
507
- parent.left.getText(sourceFile).includes(baseText)) {
508
- return true;
509
- }
510
- current = parent;
511
- parent = parent.parent;
512
- }
513
- return false;
514
- }
515
- /**
516
- * Check if a property access originates from `this`.
517
- * Accessing properties on `this` is always safe — the object exists within its own methods.
518
- */
519
- function isThisAccess(node) {
520
- let current = node.expression;
521
- while (ts.isCallExpression(current) || ts.isPropertyAccessExpression(current)) {
522
- current = current.expression;
523
- }
524
- return current.kind === ts.SyntaxKind.ThisKeyword;
525
- }
526
- /**
527
- * Count the depth of a method chain (number of chained property accesses / calls).
528
- * e.g. `a.b().c().d` has depth 3.
529
- */
530
- function getChainDepth(node) {
531
- let depth = 0;
532
- let current = node.expression;
533
- while (ts.isCallExpression(current) || ts.isPropertyAccessExpression(current)) {
534
- if (ts.isCallExpression(current)) {
535
- depth++;
536
- current = current.expression;
537
- }
538
- else {
539
- current = current.expression;
540
- }
541
- }
542
- return depth;
543
- }
544
- /**
545
- * Check if a property access chain is on a Zod method call
546
- * Handles chained calls like z.string().min(1).optional()
547
- */
548
- function isZodBuilderChain(node, sourceFile) {
549
- // Walk the full expression chain to find if it originates from z.xxx()
550
- // Handles arbitrary depth: z.string().regex().optional().superRefine().pipe()
551
- let current = node.expression;
552
- while (current) {
553
- if (ts.isCallExpression(current)) {
554
- const result = checkZodCallExpression(current, sourceFile);
555
- if (result.resolved)
556
- return result.isZod;
557
- current = result.next;
558
- continue;
559
- }
560
- if (ts.isPropertyAccessExpression(current)) {
561
- if (current.expression.getText(sourceFile) === 'z')
562
- return true;
563
- current = current.expression;
564
- continue;
565
- }
566
- if (ts.isIdentifier(current)) {
567
- return current.text === 'z';
568
- }
569
- break;
570
- }
571
- return false;
572
- }
573
- /** Check if a call expression callee originates from z.xxx() */
574
- function checkZodCallExpression(node, sourceFile) {
575
- const callee = node.expression;
576
- if (ts.isPropertyAccessExpression(callee)) {
577
- if (callee.getText(sourceFile).startsWith('z.'))
578
- return { resolved: true, isZod: true };
579
- return { resolved: false, next: callee.expression };
580
- }
581
- if (ts.isIdentifier(callee)) {
582
- return { resolved: true, isZod: callee.text === 'z' };
583
- }
584
- return { resolved: false, next: callee };
585
- }
586
- /**
587
- * Check if a property access is part of a fluent API chain
588
- * Handles patterns like promise.then().catch() or queryBuilder.where().orderBy()
589
- */
590
- function isFluentChain(node) {
591
- const expression = node.expression;
592
- // Check if we're accessing a property on a call expression
593
- if (!ts.isCallExpression(expression))
594
- return false;
595
- // Walk the chain — if ANY method in the chain is a known fluent method, the chain is safe
596
- let current = expression;
597
- while (ts.isCallExpression(current)) {
598
- if (ts.isPropertyAccessExpression(current.expression)) {
599
- const methodName = current.expression.name.text;
600
- if (isSafeFluentMethod(methodName)) {
601
- return true;
602
- }
603
- // Walk deeper into the chain
604
- current = current.expression.expression;
605
- continue;
606
- }
607
- break;
608
- }
609
- return false;
610
- }
611
- /**
612
- * Path patterns where null-safety findings are dominated by safe-by-construction
613
- * builders that the AST analyzer cannot fully resolve:
614
- *
615
- * - `**\/di/fragment.ts`, `**\/di/fragments/*.ts` — typed-inject Injector chains
616
- * (`.provideValue/.provideClass/...` always return Injector<T>); the chain
617
- * is split across many lines so the AST chain-depth heuristic does not always
618
- * apply. The whole-file safe-list captures the convention.
619
- * - `**\/schema/*.ts`, `**\/*-schema.ts` — Drizzle/Zod schema declarations are
620
- * pure column/shape builders. No runtime null-access surface to protect.
621
- *
622
- * These are deliberately generic path conventions. Project-specific safe paths
623
- * (e.g. a bespoke schema/DI folder layout) belong in the
624
- * `additionalSafeNullPaths` recipe-config key, not in these built-in defaults.
625
- */
626
- const SAFE_NULL_PATHS = [
627
- /\/di\/fragment\.ts$/,
628
- /\/di\/fragments\//,
629
- /\/schema\//,
630
- /-schema\.ts$/,
631
- ];
632
- /** Merge built-in defaults with the recipe-config slice. */
633
- function buildEffectiveSafePaths() {
634
- const cfg = getCheckConfig('null-safety');
635
- const extras = (cfg.additionalSafeNullPaths ?? []).map((src) => new RegExp(src, 'i'));
636
- return [...SAFE_NULL_PATHS, ...extras];
637
- }
638
- /**
639
- * Merge the built-in (generic) safe-builder prefixes with any project-specific
640
- * ones supplied via `checks.config['null-safety'].additionalSafeBuilders`.
641
- */
642
- function buildEffectiveSafeBuilders() {
643
- const cfg = getCheckConfig('null-safety');
644
- return [...SAFE_BUILDER_PREFIXES, ...(cfg.additionalSafeBuilders ?? [])];
645
- }
646
- function isSafeNullPath(filePath, paths) {
647
- return paths.some((p) => p.test(filePath));
648
- }
649
- /**
650
- * @param {*} content
651
- * @param {*} filePath
652
- * @returns {*}
653
- * Analyze a file for null safety issues. Exported for the FP-regression
654
- * suite (see `__tests__/null-safety-fp.test.ts`).
655
- */
656
- export function analyzeNullSafety(content, filePath) {
657
- const violations = [];
658
- // Skip safe-by-construction path families (DI fragments + schema declarations).
659
- // Built-in defaults are merged with the recipe-config slice once per file.
660
- const safePaths = buildEffectiveSafePaths();
661
- if (isSafeNullPath(filePath, safePaths))
662
- return violations;
663
- // Effective safe-builder prefixes = generic built-ins + project config.
664
- const safeBuilders = buildEffectiveSafeBuilders();
665
- try {
666
- const sourceFile = getSharedSourceFile(filePath, content);
667
- if (!sourceFile)
668
- return [];
669
- const visit = (node) => {
670
- ts.forEachChild(node, visit);
671
- // Only check property access expressions that aren't optional chains
672
- if (!ts.isPropertyAccessExpression(node) || ts.isOptionalChain(node))
673
- return;
674
- const expression = node.expression;
675
- // Only flag call expressions or element access (potentially nullable)
676
- if (!ts.isCallExpression(expression) && !ts.isElementAccessExpression(expression))
677
- return;
678
- // Skip property access on `this` — the object always exists in its own methods
679
- if (isThisAccess(node))
680
- return;
681
- // Skip method chains longer than 2 — fluent APIs are designed to return non-null
682
- if (getChainDepth(node) > 2)
683
- return;
684
- // Skip Zod builder pattern chains (z.string().min(1).optional())
685
- if (isZodBuilderChain(node, sourceFile))
686
- return;
687
- // Skip known safe builder patterns
688
- if (ts.isCallExpression(expression) &&
689
- isSafeBuilderPattern(expression, sourceFile, safeBuilders))
690
- return;
691
- // Skip fluent API chains (promise.then().catch(), queryBuilder.where().orderBy())
692
- if (isFluentChain(node))
693
- return;
694
- const propName = node.name.text;
695
- // Skip if accessing a known safe fluent method
696
- if (isSafeFluentMethod(propName))
697
- return;
698
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
699
- const lineText = content.split('\n')[line] ?? '';
700
- // Skip if line has safety patterns
701
- if (SAFE_PATTERNS.some((p) => p.test(lineText)))
702
- return;
703
- // Skip if guarded by an enclosing if / ternary / && condition on a
704
- // previous line (the line-local scan above only sees this line).
705
- if (isGuardedByEnclosingCondition(node, sourceFile))
706
- return;
707
- // Skip common safe cases
708
- if (['length', 'toString', 'valueOf'].includes(propName))
709
- return;
710
- const lineNum = line + 1;
711
- const matchText = node.getText(sourceFile);
712
- violations.push({
713
- line: lineNum,
714
- column: character + 1,
715
- message: `Potentially unsafe property access '.${propName}' without null check`,
716
- severity: 'warning',
717
- type: 'unsafe-access',
718
- suggestion: `Use optional chaining: change '.${propName}' to '?.${propName}', or add an explicit null/undefined check before accessing the property`,
719
- match: matchText,
720
- });
721
- };
722
- visit(sourceFile);
723
- }
724
- catch {
725
- // @swallow-ok Skip files that fail to parse
726
- }
727
- return violations;
728
- }
729
- /**
730
- * Type-aware variant (D2): walk the Program's SourceFile and flag a property
731
- * access on a call/element-access result ONLY when the receiver's actual type
732
- * includes `null`/`undefined`. The TypeChecker subsumes every heuristic the
733
- * convention path uses — control-flow narrowing (guards), builder/Zod return
734
- * types, and chain depth all fall out of real types — so this detector is
735
- * deliberately minimal. Fail-open: `any`/`unknown`/unresolved types are not
736
- * nullable per `isTypeNullable`, so "the compiler doesn't know" never flags.
737
- *
738
- * Reads the same path skip + escape-hatch config as the convention path
739
- * (`additionalSafeNullPaths`, `additionalSafeBuilders`). Exported for the
740
- * type-aware test suite.
741
- */
742
- export function analyzeNullSafetyTyped(sourceFile, checker, filePath) {
743
- const violations = [];
744
- const safePaths = buildEffectiveSafePaths();
745
- if (isSafeNullPath(filePath, safePaths))
746
- return violations;
747
- // Manual escape hatch for symbols the checker can't resolve (untyped JS
748
- // boundaries, ambient factories): a matching receiver call-text is trusted.
749
- const safeBuilders = buildEffectiveSafeBuilders();
750
- const visit = (node) => {
751
- ts.forEachChild(node, visit);
752
- if (!ts.isPropertyAccessExpression(node) || ts.isOptionalChain(node))
753
- return;
754
- const expression = node.expression;
755
- if (!ts.isCallExpression(expression) && !ts.isElementAccessExpression(expression))
756
- return;
757
- // No `isThisAccess` skip here (unlike the convention path): the checker types
758
- // `this`-rooted chains correctly — `this.prop` isn't a candidate (receiver is
759
- // not a call), and `this.getThing()` where getThing() returns nullable SHOULD
760
- // flag — so the heuristic would only cause false negatives.
761
- const propName = node.name.text;
762
- if (['length', 'toString', 'valueOf'].includes(propName))
763
- return;
764
- const receiverText = expression.getText(sourceFile);
765
- if (safeBuilders.some((prefix) => receiverText.startsWith(prefix)))
766
- return;
767
- // The one decision: does the receiver's ACTUAL type include null/undefined?
768
- if (!isTypeNullable(checker.getTypeAtLocation(expression)))
769
- return;
770
- const { line, character } = sourceFile.getLineAndCharacterOfPosition(node.getStart());
771
- violations.push({
772
- line: line + 1,
773
- column: character + 1,
774
- message: `Potentially unsafe property access '.${propName}' without null check`,
775
- severity: 'warning',
776
- type: 'unsafe-access',
777
- suggestion: `Use optional chaining: change '.${propName}' to '?.${propName}', or add an explicit null/undefined check before accessing the property`,
778
- match: node.getText(sourceFile),
779
- });
780
- };
781
- visit(sourceFile);
782
- return violations;
783
- }
784
- /** Type-aware per-file analysis (D2): flag via the shared Program's checker. */
785
- function analyzeFileTyped(program, filePath) {
786
- const sourceFile = program.getSourceFile(filePath);
787
- if (!sourceFile)
788
- return []; // not in the Program (e.g. excluded) — skip
789
- return analyzeNullSafetyTyped(sourceFile, program.checker, filePath);
790
- }
791
- /** Convention per-file analysis (default): scan the filtered content (no types). */
792
- async function analyzeFileConvention(files, filePath) {
793
- try {
794
- // FileAccessor.read applies this check's `strip-strings` contentFilter, so
795
- // `content` matches what the prior per-file `analyze` mode received.
796
- const content = await files.read(filePath);
797
- return analyzeNullSafety(content, filePath);
798
- }
799
- catch {
800
- // @fitness-ignore-next-line error-handling-quality -- an unreadable target file is an expected skip (the engine's own analyze mode does the same — see define-check.ts executeAnalyzeMode); a pure check has no actionable error to surface here.
801
- return []; // unreadable file — skip, matching per-file analyze resilience
802
- }
803
- }
8
+ import { analyzeFileConvention, analyzeFileTyped } from './null-safety-analyze.js';
9
+ export { analyzeNullSafety, analyzeNullSafetyTyped } from './null-safety-analyze.js';
10
+ export { SAFE_BUILDER_PREFIXES, SAFE_METHOD_PREFIXES } from './null-safety-config.js';
804
11
  /**
805
12
  * Check: quality/null-safety
806
13
  *