(source: string, file: string, line: number)
| 140 | * @since 4.0.0 |
| 141 | */ |
| 142 | export const transform = (source: string, file: string, line: number): string => { |
| 143 | if (!source.includes("// =>")) return source |
| 144 | |
| 145 | const parsed = parseSync(file, source, { lang: "ts" }) |
| 146 | const parseError = parsed.errors[0] |
| 147 | if (parseError !== undefined) { |
| 148 | fail(source, parseError.labels[0]?.start ?? 0, file, line, `invalid doctest source: ${parseError.message}`) |
| 149 | } |
| 150 | |
| 151 | const markers = parsed.comments.flatMap((comment) => { |
| 152 | if (comment.type !== "Line") return [] |
| 153 | const match = markerPattern.exec(comment.value) |
| 154 | return match === null ? [] : [{ comment, expected: match[1].trim() }] |
| 155 | }) |
| 156 | if (markers.length === 0) return source |
| 157 | |
| 158 | const { candidates, names } = collect(parsed.program) |
| 159 | let index = 0 |
| 160 | while (names.has(`${helper}${index}`)) index++ |
| 161 | const alias = `${helper}${index}` |
| 162 | const output = new RolldownMagicString(source) |
| 163 | |
| 164 | for (const { comment, expected } of markers) { |
| 165 | parseExpected(expected, source, comment.start, file, line) |
| 166 | const { node, parent } = assertionTarget(source, candidates, comment, file, line) |
| 167 | |
| 168 | if (node.type === "ExpressionStatement") { |
| 169 | if (node.directive !== null) { |
| 170 | fail(source, comment.start, file, line, "doctest assertions cannot target directives") |
| 171 | } |
| 172 | const expression = isNode(node.expression) |
| 173 | ? node.expression |
| 174 | : fail(source, comment.start, file, line, "doctest assertion has no expression") |
| 175 | output.overwrite( |
| 176 | expression.start, |
| 177 | comment.end, |
| 178 | `${alias}(${source.slice(expression.start, expression.end)}, ${expected})` |
| 179 | ) |
| 180 | continue |
| 181 | } |
| 182 | |
| 183 | if (node.type === "VariableDeclaration" && parent?.type !== "ExportNamedDeclaration") { |
| 184 | const declarations = node.declarations |
| 185 | const declaration = Array.isArray(declarations) && declarations.length === 1 ? declarations[0] : undefined |
| 186 | const id = isNode(declaration) && isNode(declaration.id) ? declaration.id : undefined |
| 187 | if ( |
| 188 | node.kind === "const" && node.declare === false && isNode(declaration?.init) && |
| 189 | id?.type === "Identifier" && typeof id.name === "string" |
| 190 | ) { |
| 191 | const indentation = source.slice(source.lastIndexOf("\n", node.start - 1) + 1, node.start) |
| 192 | output.overwrite(node.end, comment.end, `\n${indentation}${alias}(${id.name}, ${expected})`) |
| 193 | continue |
| 194 | } |
| 195 | } |
| 196 | |
| 197 | fail( |
| 198 | source, |
| 199 | comment.start, |
no test coverage detected