readme-assert 7.2.1 → 7.3.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "readme-assert",
3
- "version": "7.2.1",
3
+ "version": "7.3.0",
4
4
  "description": "Run code blocks in your readme as tests",
5
5
  "keywords": [
6
6
  "readme",
package/readme.md CHANGED
@@ -103,6 +103,12 @@ Assert that a promise rejects with `// rejects`:
103
103
  Promise.reject(new Error("no")) // rejects /no/
104
104
  ```
105
105
 
106
+ Or match the error name and message with `//=> rejects`:
107
+
108
+ ```javascript test
109
+ Promise.reject(new TypeError("no")) //=> rejects TypeError: no
110
+ ```
111
+
106
112
  ### TypeScript
107
113
 
108
114
  TypeScript code blocks are supported natively:
@@ -11,6 +11,10 @@ import MagicString from "magic-string";
11
11
  * console.log(x) //=> v → console.log(x); assert.deepEqual(x, v)
12
12
  * expr //=> resolves to v → assert.deepEqual(await expr, v)
13
13
  * expr // rejects /pat/ → assert.rejects(() => expr, /pat/)
14
+ * expr //=> rejects Error: msg → assert.rejects(() => expr, { message: "msg" })
15
+ *
16
+ * When the expression is an AwaitExpression, throws/Error assertions are
17
+ * promoted to async rejects (await converts rejection → throw).
14
18
  *
15
19
  * Uses oxc-parser for AST + comment extraction. Handles both JS and TS.
16
20
  *
@@ -33,6 +37,8 @@ export function commentToAssert(code, { typescript = false } = {}) {
33
37
  const comment = findTrailingComment(comments, node, code);
34
38
  if (!comment) continue;
35
39
 
40
+ const isAwait = node.expression.type === "AwaitExpression";
41
+
36
42
  const match = comment.value.match(/^\s*(=>|→|->)\s*([\s\S]*)$/);
37
43
  const throwsMatch = comment.value.match(/^\s*throws\s+([\s\S]*)$/);
38
44
  const rejectsMatch = comment.value.match(/^\s*rejects\s+([\s\S]*)$/);
@@ -40,6 +46,9 @@ export function commentToAssert(code, { typescript = false } = {}) {
40
46
  if (match) {
41
47
  const rest = match[2].trim();
42
48
  const resolvesMatch = rest.match(/^resolves\s+(?:to\s+)?([\s\S]*)$/);
49
+ const rejectsErrorMatch = rest.match(
50
+ /^rejects\s+((?:[A-Z]\w+)?Error)(?::\s*(.*))?$/,
51
+ );
43
52
  changed = true;
44
53
 
45
54
  const errorMatch = rest.match(/^((?:[A-Z]\w+)?Error)(?::\s*(.*))?$/);
@@ -56,8 +65,28 @@ export function commentToAssert(code, { typescript = false } = {}) {
56
65
  comment.end,
57
66
  `assert.deepEqual(await ${exprSource}, ${expected});`,
58
67
  );
68
+ } else if (rejectsErrorMatch) {
69
+ // expr //=> rejects TypeError: msg → assert.rejects(() => expr, { name, message })
70
+ const errorName = rejectsErrorMatch[1];
71
+ const errorMessage = rejectsErrorMatch[2]?.trim();
72
+ const exprSource = code.slice(
73
+ node.expression.start,
74
+ node.expression.end,
75
+ );
76
+ const props = [`name: "${errorName}"`];
77
+ if (errorMessage) {
78
+ props.push(formatMessageProp(errorMessage));
79
+ }
80
+ s.overwrite(
81
+ node.start,
82
+ comment.end,
83
+ isAwait
84
+ ? `await assert.rejects(async () => { ${exprSource}; }, { ${props.join(", ")} });`
85
+ : `await assert.rejects(() => ${exprSource}, { ${props.join(", ")} });`,
86
+ );
59
87
  } else if (errorMatch) {
60
88
  // expr //=> TypeError: msg → assert.throws(() => { expr }, { name, message })
89
+ // await expr //=> Error: msg → assert.rejects(async () => { expr }, { name, message })
61
90
  const errorName = errorMatch[1];
62
91
  const errorMessage = errorMatch[2]?.trim();
63
92
  const exprSource = code.slice(
@@ -66,17 +95,14 @@ export function commentToAssert(code, { typescript = false } = {}) {
66
95
  );
67
96
  const props = [`name: "${errorName}"`];
68
97
  if (errorMessage) {
69
- const reMatch = errorMessage.match(/^\/(.+)\/([gimsuy]*)$/);
70
- props.push(
71
- reMatch
72
- ? `message: /${reMatch[1]}/${reMatch[2]}`
73
- : `message: "${errorMessage}"`,
74
- );
98
+ props.push(formatMessageProp(errorMessage));
75
99
  }
76
100
  s.overwrite(
77
101
  node.start,
78
102
  comment.end,
79
- `assert.throws(() => { ${exprSource}; }, { ${props.join(", ")} });`,
103
+ isAwait
104
+ ? `await assert.rejects(async () => { ${exprSource}; }, { ${props.join(", ")} });`
105
+ : `assert.throws(() => { ${exprSource}; }, { ${props.join(", ")} });`,
80
106
  );
81
107
  } else if (isConsoleCall(node.expression)) {
82
108
  // console.log(expr) //=> value → keep log, add assertion after.
@@ -112,7 +138,9 @@ export function commentToAssert(code, { typescript = false } = {}) {
112
138
  s.overwrite(
113
139
  node.start,
114
140
  comment.end,
115
- `assert.throws(() => { ${exprSource}; }, ${pattern});`,
141
+ isAwait
142
+ ? `await assert.rejects(async () => { ${exprSource}; }, ${pattern});`
143
+ : `assert.throws(() => { ${exprSource}; }, ${pattern});`,
116
144
  );
117
145
  changed = true;
118
146
  } else if (rejectsMatch) {
@@ -124,7 +152,9 @@ export function commentToAssert(code, { typescript = false } = {}) {
124
152
  s.overwrite(
125
153
  node.start,
126
154
  comment.end,
127
- `await assert.rejects(() => ${exprSource}, ${pattern});`,
155
+ isAwait
156
+ ? `await assert.rejects(async () => { ${exprSource}; }, ${pattern});`
157
+ : `await assert.rejects(() => ${exprSource}, ${pattern});`,
128
158
  );
129
159
  changed = true;
130
160
  }
@@ -153,6 +183,13 @@ function findTrailingComment(comments, node, code) {
153
183
  return null;
154
184
  }
155
185
 
186
+ function formatMessageProp(msg) {
187
+ const reMatch = msg.match(/^\/(.+)\/([gimsuy]*)$/);
188
+ return reMatch
189
+ ? `message: /${reMatch[1]}/${reMatch[2]}`
190
+ : `message: "${msg}"`;
191
+ }
192
+
156
193
  function isConsoleCall(expr) {
157
194
  return (
158
195
  expr.type === "CallExpression" &&
package/src/generate.js CHANGED
@@ -66,7 +66,9 @@ function assembleUnit(blocks) {
66
66
  }
67
67
 
68
68
  const hasESM = imports.length > 0;
69
- const hasCJS = /\brequire\s*\(/.test(bodyLines.join("\n"));
69
+ const body = bodyLines.join("\n");
70
+ const hasAwait = /\bawait\s/.test(body);
71
+ const hasCJS = !hasAwait && /\brequire\s*\(/.test(body);
70
72
 
71
73
  // Place assert import on line 0 (before markdown line 1) so line numbers
72
74
  // in the generated code match the original markdown positions exactly.