@ontrails/warden 1.0.0-beta.43 → 1.0.0-beta.45

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,19 @@
1
1
  # @ontrails/warden
2
2
 
3
+ ## 1.0.0-beta.45
4
+
5
+ ### Patch Changes
6
+
7
+ - [`f1bd093`](https://github.com/outfitter-dev/trails/commit/f1bd09395fcf81db0bcb8657030288877c2e26e6): Recognize conditional, aliased, and parenthesized Result provenance while invalidating provenance after reassignment across the implementation-return and redundant-error-wrap rules.
8
+
9
+ ## 1.0.0-beta.44
10
+
11
+ ### Patch Changes
12
+
13
+ - [`b1fbe57`](https://github.com/outfitter-dev/trails/commit/b1fbe574e6f44d1fecb5e3a000270955c0a77b7b): Publish Bun-validated package tarballs through an npm trusted-publishing adapter
14
+ binding, add exact repository metadata for each public workspace package, and
15
+ correct the native Bun release descriptor to its pack-only runtime boundary.
16
+
3
17
  ## 1.0.0-beta.43
4
18
 
5
19
  ### Minor Changes
package/package.json CHANGED
@@ -1,6 +1,11 @@
1
1
  {
2
2
  "name": "@ontrails/warden",
3
- "version": "1.0.0-beta.43",
3
+ "version": "1.0.0-beta.45",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/outfitter-dev/trails.git",
7
+ "directory": "packages/warden"
8
+ },
4
9
  "bin": {
5
10
  "warden": "./bin/warden.ts"
6
11
  },
@@ -27,20 +32,20 @@
27
32
  "clean": "rm -rf dist *.tsbuildinfo"
28
33
  },
29
34
  "dependencies": {
30
- "@ontrails/adapter-kit": "^1.0.0-beta.43",
31
- "@ontrails/cli": "^1.0.0-beta.43",
32
- "@ontrails/config": "^1.0.0-beta.43",
33
- "@ontrails/permits": "^1.0.0-beta.43",
34
- "@ontrails/source": "^1.0.0-beta.43",
35
- "@ontrails/store": "^1.0.0-beta.43",
35
+ "@ontrails/adapter-kit": "^1.0.0-beta.45",
36
+ "@ontrails/cli": "^1.0.0-beta.45",
37
+ "@ontrails/config": "^1.0.0-beta.45",
38
+ "@ontrails/permits": "^1.0.0-beta.45",
39
+ "@ontrails/source": "^1.0.0-beta.45",
40
+ "@ontrails/store": "^1.0.0-beta.45",
36
41
  "oxc-resolver": "11.19.1",
37
42
  "zod": "^4.3.5"
38
43
  },
39
44
  "devDependencies": {
40
- "@ontrails/testing": "^1.0.0-beta.43"
45
+ "@ontrails/testing": "^1.0.0-beta.45"
41
46
  },
42
47
  "peerDependencies": {
43
- "@ontrails/core": "^1.0.0-beta.43",
44
- "@ontrails/topography": "^1.0.0-beta.43"
48
+ "@ontrails/core": "^1.0.0-beta.45",
49
+ "@ontrails/topography": "^1.0.0-beta.45"
45
50
  }
46
51
  }
@@ -25,8 +25,11 @@ import {
25
25
  getNodeImported,
26
26
  getNodeInit,
27
27
  getNodeLocal,
28
+ getNodeLeft,
28
29
  getNodeName,
30
+ getNodeOperator,
29
31
  getNodeReturnType,
32
+ getNodeRight,
30
33
  getNodeSource,
31
34
  getNodeTypeAnnotation,
32
35
  getNodeValue,
@@ -97,12 +100,15 @@ export type ScopedHelperMap = ReadonlyMap<
97
100
 
98
101
  export type MutableScopedHelperMap = Map<ReadonlySet<string>, Set<string>>;
99
102
 
100
- type ScopedResultVariableMap = ReadonlyMap<
103
+ export type ScopedResultVariableMap = ReadonlyMap<
101
104
  ReadonlySet<string>,
102
105
  ReadonlySet<string>
103
106
  >;
104
107
 
105
- type MutableScopedResultVariableMap = Map<ReadonlySet<string>, Set<string>>;
108
+ export type MutableScopedResultVariableMap = Map<
109
+ ReadonlySet<string>,
110
+ Set<string>
111
+ >;
106
112
 
107
113
  export const findNearestBindingScope = (
108
114
  name: string,
@@ -224,8 +230,8 @@ const unwrapReturnExpression = (node: AstNode): AstNode => {
224
230
  return current;
225
231
  };
226
232
 
227
- /** Check if a return argument is an allowed Result value. */
228
- const isAllowedReturnArgument = (
233
+ /** Check whether an expression has visible Result-producing provenance. */
234
+ export const isResultProducingExpression = (
229
235
  argument: AstNode,
230
236
  helperNames: ReadonlySet<string>,
231
237
  resultVars: ScopedResultVariableMap,
@@ -240,7 +246,7 @@ const isAllowedReturnArgument = (
240
246
  return (
241
247
  consequent !== undefined &&
242
248
  alternate !== undefined &&
243
- isAllowedReturnArgument(
249
+ isResultProducingExpression(
244
250
  consequent,
245
251
  helperNames,
246
252
  resultVars,
@@ -248,7 +254,7 @@ const isAllowedReturnArgument = (
248
254
  scopes,
249
255
  scopedHelpers
250
256
  ) &&
251
- isAllowedReturnArgument(
257
+ isResultProducingExpression(
252
258
  alternate,
253
259
  helperNames,
254
260
  resultVars,
@@ -369,6 +375,14 @@ const addScopedResultVariable = (
369
375
  resultVars.set(scope, new Set([name]));
370
376
  };
371
377
 
378
+ const clearScopedResultVariable = (
379
+ resultVars: MutableScopedResultVariableMap,
380
+ scope: ReadonlySet<string>,
381
+ name: string
382
+ ): void => {
383
+ resultVars.get(scope)?.delete(name);
384
+ };
385
+
372
386
  /** Record `const helper = (): Result<...> => ...` declarations for the current lexical scope. */
373
387
  export const trackScopedResultHelperDeclaration = (
374
388
  node: AstNode,
@@ -440,8 +454,14 @@ const trackResultVariable = (
440
454
  }
441
455
  if (
442
456
  hasResultVariableAnnotation(id, sourceCode, resultTypeNames) ||
443
- isResultExpression(init) ||
444
- isHelperCall(init, helperNames, namespaceHelpers, scopes, scopedHelpers)
457
+ isResultProducingExpression(
458
+ init,
459
+ helperNames,
460
+ resultVars,
461
+ namespaceHelpers,
462
+ scopes,
463
+ scopedHelpers
464
+ )
445
465
  ) {
446
466
  const bindingScope = findNearestBindingScope(name, scopes);
447
467
  if (bindingScope) {
@@ -451,6 +471,63 @@ const trackResultVariable = (
451
471
  }
452
472
  };
453
473
 
474
+ export const collectDirectResultAssignments = (
475
+ body: AstNode
476
+ ): ReadonlySet<AstNode> => {
477
+ const assignments = new Set<AstNode>();
478
+ for (const statement of getNodeBodyStatements(body)) {
479
+ if (statement.type !== 'ExpressionStatement') {
480
+ continue;
481
+ }
482
+ const expression = getNodeExpression(statement);
483
+ if (expression?.type === 'AssignmentExpression') {
484
+ assignments.add(expression);
485
+ }
486
+ }
487
+ return assignments;
488
+ };
489
+
490
+ const trackResultAssignment = (
491
+ node: AstNode,
492
+ resultVars: MutableScopedResultVariableMap,
493
+ helperNames: ReadonlySet<string>,
494
+ namespaceHelpers: NamespaceHelperMap,
495
+ directAssignments: ReadonlySet<AstNode>,
496
+ scopes: readonly ReadonlySet<string>[],
497
+ scopedHelpers: ScopedHelperMap
498
+ ): void => {
499
+ const left = getNodeLeft(node);
500
+ const name = identifierName(left);
501
+ if (!name) {
502
+ return;
503
+ }
504
+ const bindingScope = findNearestBindingScope(name, scopes);
505
+ if (!bindingScope) {
506
+ return;
507
+ }
508
+ const right = getNodeRight(node);
509
+ const resultRhs =
510
+ getNodeOperator(node) === '=' &&
511
+ right &&
512
+ isResultProducingExpression(
513
+ right,
514
+ helperNames,
515
+ resultVars,
516
+ namespaceHelpers,
517
+ scopes,
518
+ scopedHelpers
519
+ );
520
+ if (
521
+ resultRhs &&
522
+ (isScopedResultVariableBinding(name, scopes, resultVars) ||
523
+ directAssignments.has(node))
524
+ ) {
525
+ addScopedResultVariable(resultVars, bindingScope, name);
526
+ return;
527
+ }
528
+ clearScopedResultVariable(resultVars, bindingScope, name);
529
+ };
530
+
454
531
  // ---------------------------------------------------------------------------
455
532
  // Return statement checking
456
533
  // ---------------------------------------------------------------------------
@@ -470,6 +547,7 @@ const checkReturnStatements = (
470
547
  const resultVars: MutableScopedResultVariableMap = new Map();
471
548
  const scopedHelpers: MutableScopedHelperMap = new Map();
472
549
  const initialScopes = implScope.size > 0 ? [implScope] : [];
550
+ const directAssignments = collectDirectResultAssignments(blockBody);
473
551
 
474
552
  walkWithScopes(
475
553
  blockBody,
@@ -494,6 +572,18 @@ const checkReturnStatements = (
494
572
  );
495
573
  }
496
574
 
575
+ if (node.type === 'AssignmentExpression') {
576
+ trackResultAssignment(
577
+ node,
578
+ resultVars,
579
+ helperNames,
580
+ namespaceHelpers,
581
+ directAssignments,
582
+ currentScopes,
583
+ scopedHelpers
584
+ );
585
+ }
586
+
497
587
  if (node.type !== 'ReturnStatement') {
498
588
  return;
499
589
  }
@@ -505,7 +595,7 @@ const checkReturnStatements = (
505
595
  }
506
596
 
507
597
  if (
508
- isAllowedReturnArgument(
598
+ isResultProducingExpression(
509
599
  argument,
510
600
  helperNames,
511
601
  resultVars,
@@ -23,11 +23,11 @@ import {
23
23
  import type { AstNode, AstParentContext } from '@ontrails/source';
24
24
  import {
25
25
  collectAllResultHelperNames,
26
+ collectDirectResultAssignments,
26
27
  collectNamespaceHelperImports,
27
28
  collectResultTypeNames,
28
29
  findNearestBindingScope,
29
- isHelperCall,
30
- isResultExpression,
30
+ isResultProducingExpression,
31
31
  trackScopedResultHelperDeclaration,
32
32
  } from './implementation-returns-result.js';
33
33
  import { isTestFile } from './scan.js';
@@ -82,16 +82,6 @@ const getErrorSourceVariable = (node: AstNode | null): string | null => {
82
82
  return identifierName(member?.object);
83
83
  };
84
84
 
85
- const isResultProducingExpression = (
86
- node: AstNode,
87
- helperNames: ReadonlySet<string>,
88
- namespaceHelpers: NamespaceHelperMap,
89
- scopes: readonly ReadonlySet<string>[],
90
- scopedHelpers: ScopedHelperMap
91
- ): boolean =>
92
- isResultExpression(node) ||
93
- isHelperCall(node, helperNames, namespaceHelpers, scopes, scopedHelpers);
94
-
95
85
  type ResultProvenance = Map<ReadonlySet<string>, Set<string>>;
96
86
 
97
87
  const markResultVariable = (
@@ -158,6 +148,7 @@ const trackVariableDeclarator = (
158
148
  isResultProducingExpression(
159
149
  init,
160
150
  helperNames,
151
+ provenance,
161
152
  namespaceHelpers,
162
153
  scopes,
163
154
  scopedHelpers
@@ -175,11 +166,10 @@ const trackAssignmentExpression = (
175
166
  helperNames: ReadonlySet<string>,
176
167
  namespaceHelpers: NamespaceHelperMap,
177
168
  scopedHelpers: ScopedHelperMap,
169
+ directAssignments: ReadonlySet<AstNode>,
178
170
  scopes: readonly ReadonlySet<string>[]
179
171
  ): void => {
180
172
  const left = getNodeLeft(node);
181
- const operator = getNodeOperator(node);
182
- const right = getNodeRight(node);
183
173
  const name = identifierName(left);
184
174
  if (!name) {
185
175
  return;
@@ -188,16 +178,22 @@ const trackAssignmentExpression = (
188
178
  if (!scope) {
189
179
  return;
190
180
  }
191
- if (
192
- operator === '=' &&
181
+ const right = getNodeRight(node);
182
+ const resultRhs =
183
+ getNodeOperator(node) === '=' &&
193
184
  right &&
194
185
  isResultProducingExpression(
195
186
  right,
196
187
  helperNames,
188
+ provenance,
197
189
  namespaceHelpers,
198
190
  scopes,
199
191
  scopedHelpers
200
- )
192
+ );
193
+ if (
194
+ resultRhs &&
195
+ (hasResultProvenance(provenance, scope, name) ||
196
+ directAssignments.has(node))
201
197
  ) {
202
198
  markResultVariable(provenance, scope, name);
203
199
  return;
@@ -258,6 +254,7 @@ const checkFunctionBody = (
258
254
  const scopedHelpers: MutableScopedHelperMap = new Map();
259
255
  const implScope = collectScopeFrameBindings(owner);
260
256
  const initialScopes = implScope.size > 0 ? [implScope] : [];
257
+ const directAssignments = collectDirectResultAssignments(body);
261
258
 
262
259
  walkWithScopes(
263
260
  body,
@@ -287,6 +284,7 @@ const checkFunctionBody = (
287
284
  helperNames,
288
285
  namespaceHelpers,
289
286
  scopedHelpers,
287
+ directAssignments,
290
288
  scopes
291
289
  );
292
290
  return;