diff --git a/lib/doctest.js b/lib/doctest.js index 48b9e1d..fe63b56 100644 --- a/lib/doctest.js +++ b/lib/doctest.js @@ -144,12 +144,12 @@ const rewriteJs = sourceType => ({ if (type === 'Line') { maybePushLine (value, start, loc.start.line); } else { - let offset = start; - let number = loc.start.line; - for (const text of value.split ('\n')) { - maybePushLine (text.replace (/^\s*[*]/, ''), offset, number); - offset += '\n'.length; - number += 1; + for (let from = 0, number = loc.start.line; ; number += 1) { + const to = value.indexOf ('\n', from); + const text = to < 0 ? value.slice (from) : value.slice (from, to); + maybePushLine (text.replace (/^\s*[*]/, ''), start + from, number); + if (to < 0) break; + from = to + '\n'.length; } } } @@ -198,41 +198,61 @@ const rewriteJs = sourceType => ({ }; const rewriteCoffee = ({ - prefix: _prefix, + prefix, openingDelimiter, closingDelimiter, }) => input => { - // 1a: Extract prefixed comment lines - const lines = []; - // 1b: Preserve other lines + // 1: Lex source text to extract comments + const tokens = CoffeeScript.tokens (input); + const comments = CoffeeScript.helpers.extractAllCommentTokens (tokens); + + // 2: Preserve source text between comments const chunks = []; { - const prefix = '#' + _prefix; - let number = 0; - for (const [text, indent, rest] of input.matchAll (/^([ \t]*)(.*)\n?/gm)) { - number += 1; - if (rest.startsWith (prefix)) { - const unprefixed = (rest.slice (prefix.length)).trimStart (); - const line = Line (number) (unprefixed); - lines.push ([indent, line]); - } else { - chunks.push ([number, text]); + let offset = 0; + for (const {locationData: {range: [start, end]}} of comments) { + chunks.push ([offset, input.slice (offset, start)]); + offset = end; + } + chunks.push ([offset, input.slice (offset)]); + } + + // 3: Extract prefixed comment lines + const lines = []; + for (const {content, locationData} of comments) { + const indent = ' '.repeat (locationData.first_column); + const offset = locationData.range[0]; + let number = locationData.first_line + 1; + if (locationData.last_line > locationData.first_line) { + for (let from = 0; ; number += 1) { + const to = content.indexOf ('\n', from); + const text = to < 0 ? content.slice (from) : content.slice (from, to); + const line = Line (number) (text.trimStart ()); + lines.push ([offset + from, indent, line]); + if (to < 0) break; + from = to + '\n'.length; + } + } else { + const text = content.trimStart (); + if (text.startsWith (prefix)) { + const unprefixed = (text.slice (prefix.length)).trimStart (); + lines.push ([offset, indent, Line (number) (unprefixed)]); } } } - // 2: Coalesce related input and output lines + // 4: Coalesce related input and output lines const tests = []; { let test; let state = openingDelimiter == null ? 'open' : 'closed'; - for (const [indent, line] of lines) { + for (const [offset, indent, line] of lines) { if (state === 'closed') { if (line.text === openingDelimiter) state = 'open'; } else if (line.text === closingDelimiter) { state = 'closed'; } else if (line.text.startsWith ('>')) { - tests.push ([line.number, test = {indent, input: {lines: [line]}}]); + tests.push ([offset, test = {indent, input: {lines: [line]}}]); state = 'input'; } else if (line.text.startsWith ('.')) { test[state].lines.push (line); @@ -249,15 +269,15 @@ const rewriteCoffee = ({ } } - // 3: Convert doctests to source text - for (const [number, test] of tests) { - chunks.push ([number, wrapCoffee (test)]); + // 5: Convert doctests to source text + for (const [offset, test] of tests) { + chunks.push ([offset, wrapCoffee (test)]); } - // 4: Sort verbatim and generated source text by original line numbers + // 6: Sort verbatim and generated source text by original offsets chunks.sort (([a], [b]) => a - b); - // 5: Concatenate source text + // 7: Concatenate source text let sourceText = ''; for (const [, text] of chunks) sourceText += text; return CoffeeScript.compile (sourceText); diff --git a/test/shared/index.coffee b/test/shared/index.coffee index 118d4eb..582d625 100644 --- a/test/shared/index.coffee +++ b/test/shared/index.coffee @@ -104,20 +104,20 @@ do -> # ..... .5 # 1234.5 - 23: 'TODO: multiline comment' - # - # > 3 ** 3 - 2 ** 2 - # 23 - # - - 24: 'TODO: multiline comment with wrapped input' - # - # > (["foo", "bar", "baz"] - # . .slice(0, -1) - # . .join(" ") - # . .toUpperCase()) - # "FOO BAR" - # + 23: 'multiline comment' + ### + > 3 ** 3 - 2 ** 2 + 23 + ### + + 24: 'multiline comment with wrapped input' + ### + > (["foo", "bar", "baz"] + . .slice(0, -1) + . .join(" ") + . .toUpperCase()) + "FOO BAR" + ###