supercov 0.0.43 → 0.0.44

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.
@@ -29,7 +29,18 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
29
29
  const raw = sf.text.slice(range.pos, range.end);
30
30
  if (!/^\/\/\s*observes:/.test(raw) || comments.has(key(sf, range.pos)))
31
31
  continue;
32
- const parsed = /^\/\/\s*observes:\s*(\S+?)#(\S+)(?:\s+(.+?))?\s*$/.exec(raw);
32
+ const parts = raw.split(/;\s*check\s+/);
33
+ const check = parts.length === 2 && parts[1].trim() === "missing call"
34
+ ? "missing-call"
35
+ : parts.length === 2 && parts[1].trim() === "count"
36
+ ? "count"
37
+ : parts.length === 2 && parts[1].trim() === "value"
38
+ ? "value"
39
+ : parts.length === 2 && parts[1].trim() === "completion"
40
+ ? "completion"
41
+ : undefined;
42
+ const checkIssue = parts.length > 1 && !check ? "unsupported-check-recipe" : undefined;
43
+ const parsed = /^\/\/\s*observes:\s*(\S+?)#(\S+)(?:\s+(.+?))?\s*$/.exec(parts[0]);
33
44
  const suffix = parsed?.[3]?.split(/(?:^|\s+)via\s+/);
34
45
  const target = parsed
35
46
  ? {
@@ -44,28 +55,42 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
44
55
  target.file
45
56
  .split("/")
46
57
  .every((part) => part && part !== "." && part !== "..");
47
- const candidates = validPath
48
- ? sites
49
- .filter((s) => s.file === target.file &&
58
+ const matchingSites = validPath
59
+ ? sites.filter((s) => s.file === target.file &&
60
+ // The recipe selects the emission, not a containing callback-return site.
61
+ (!check ||
62
+ (check === "missing-call"
63
+ ? s.category === "log"
64
+ : check === "completion"
65
+ ? s.category === "return" || s.category === "throw"
66
+ : s.kind === "decision" || s.category === "return")) &&
50
67
  (s.owner === target.function || s.fn === target.function) &&
51
68
  (!target.snippet || s.text.includes(target.snippet)))
52
- .map((s) => s.id)
53
69
  : [];
70
+ // An exact whole-site selector is more specific than a containing return
71
+ // whose display text happens to include that expression. Equal sibling
72
+ // sites remain ambiguous. This only selects a node; it proves no behavior.
73
+ const exact = check && target?.snippet
74
+ ? matchingSites.filter((s) => s.text.trim() === target.snippet.trim())
75
+ : [];
76
+ const candidates = (exact.length ? exact : matchingSites).map((s) => s.id);
54
77
  comments.set(key(sf, range.pos), {
55
78
  id: location(sf, range.pos),
56
79
  where: location(sf, range.pos),
57
80
  raw,
58
81
  target,
82
+ ...(check ? { check } : {}),
59
83
  candidateSites: candidates,
60
- issue: !target
61
- ? "invalid-syntax"
62
- : !validPath
63
- ? "invalid-target-path"
64
- : !candidates.length
65
- ? "target-not-in-inventory"
66
- : candidates.length > 1
67
- ? "ambiguous-target"
68
- : undefined,
84
+ issue: checkIssue ??
85
+ (!target
86
+ ? "invalid-syntax"
87
+ : !validPath
88
+ ? "invalid-target-path"
89
+ : !candidates.length
90
+ ? "target-not-in-inventory"
91
+ : candidates.length > 1
92
+ ? "ambiguous-target"
93
+ : undefined),
69
94
  attachments: [],
70
95
  });
71
96
  }
@@ -78,7 +103,17 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
78
103
  visit(sf);
79
104
  }
80
105
  return {
81
- register(node, method, testKey, inert) {
106
+ hasHint(node) {
107
+ let statement = node;
108
+ while (statement.parent && !compiler.isStatement(statement))
109
+ statement = statement.parent;
110
+ if (!compiler.isExpressionStatement(statement))
111
+ return false;
112
+ const sf = node.getSourceFile();
113
+ return (compiler.getLeadingCommentRanges(sf.text, statement.getFullStart()) ??
114
+ []).some((range) => comments.has(key(sf, range.pos)));
115
+ },
116
+ register(node, method, testKey, inert, awaitedObservation) {
82
117
  let statement = node;
83
118
  while (statement.parent && !compiler.isStatement(statement))
84
119
  statement = statement.parent;
@@ -95,6 +130,7 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
95
130
  source: location(sf, node.getStart(sf)),
96
131
  method,
97
132
  inert,
133
+ ...(awaitedObservation ? { awaitedObservation } : {}),
98
134
  };
99
135
  if (!comment.attachments.some((a) => a.testKey === testKey &&
100
136
  a.source === attachment.source &&
@@ -123,7 +159,11 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
123
159
  },
124
160
  ];
125
161
  return matched.map(({ a, b }) => {
126
- const witnessIssue = assertionWitnessIssue(b.phases, a.source, a.method);
162
+ // A source-checked poll is not an explicit assertion phase. Neither
163
+ // a matching phase name nor a passing test can supply its read receipt.
164
+ const witnessIssue = a.awaitedObservation
165
+ ? "observation-capture-unavailable"
166
+ : assertionWitnessIssue(b.phases, a.source, a.method);
127
167
  return {
128
168
  ...comment,
129
169
  id: `${comment.id}@${b.id}`,
@@ -131,6 +171,9 @@ export function collectPragmas(compiler, files, relativeFile, sites) {
131
171
  test: b.id,
132
172
  assertionSource: a.source,
133
173
  assertionMethod: a.method,
174
+ ...(a.awaitedObservation
175
+ ? { awaitedObservation: a.awaitedObservation }
176
+ : {}),
134
177
  witness: witnessIssue
135
178
  ? "unavailable"
136
179
  : "passed",