Repository: danvk/literate-ts (ts)

Issue:
Combining skip and prepend-to-following causes "TypeError: Cannot read property 'content' of undefined"
I'd like the verifier to ignore checking a snippet _and_ to prepend that snippet to the following snippet.

> // verifier:skip
> // verifier:prepend-to-following
> ```ts
> // a.ts
> const shared = "Cher";
> //    ~~~~~~
> // Cannot redeclare block-scoped variable 'shared'.
> ```
> 
> ```ts
> // b.ts
> const shared = "Cher";
> //    ~~~~~~
> // Cannot redeclare block-scoped variable 'shared'.
> ```

```
TypeError: Cannot read property 'content' of undefined
    at /home/josh/repos/literate-ts/dist/src/code-sample.js:170:78
    at Array.map (<anonymous>)
    at /home/josh/repos/literate-ts/dist/src/code-sample.js:170:14
    at Array.map (<anonymous>)
    at applyPrefixes (/home/josh/repos/literate-ts/dist/src/code-sample.js:167:20)
    at /home/josh/repos/literate-ts/dist/src/main.js:164:57
    at Generator.next (<anonymous>)
    at /home/josh/repos/literate-ts/dist/src/main.js:8:71
    at new Promise (<anonymous>)
    at __awaiter (/home/josh/repos/literate-ts/dist/src/main.js:4:12)
```

Pull request:
Support verify+skip
Fixes #42 

This requires deferring skipping to the checking phase, rather than the extraction phase.

Patch:
diff --git a/src/code-sample.ts b/src/code-sample.ts
index bd43717..01308f9 100644
--- a/src/code-sample.ts
+++ b/src/code-sample.ts
@@ -111,23 +111,22 @@ function process(
         lastMetadata = generateIdMetadata(slug + '-' + (1 + lineNum), sourceFile, lineNum);
       }
       if (lastMetadata) {
-        if (!skipNext && !skipRemaining) {
-          samples.push({
-            ...lastMetadata,
-            sectionHeader: lastSectionHeader,
-            language: lastLanguage as CodeSample['language'],
-            content,
-            prefixes,
-            replacementId: nextIsReplaced ?? undefined,
-            nodeModules,
-            isTSX: nextIsTSX,
-            checkJS: nextShouldCheckJs,
-            sourceFile,
-            lineNumber: lineNum,
-            tsOptions: {...tsOptions},
-            prefixesLength: 0,
-          });
-        }
+        samples.push({
+          ...lastMetadata,
+          sectionHeader: lastSectionHeader,
+          language: lastLanguage as CodeSample['language'],
+          content,
+          prefixes,
+          replacementId: nextIsReplaced ?? undefined,
+          nodeModules,
+          isTSX: nextIsTSX,
+          checkJS: nextShouldCheckJs,
+          sourceFile,
+          lineNumber: lineNum,
+          tsOptions: {...tsOptions},
+          prefixesLength: 0,
+          skip: skipNext || skipRemaining,
+        });
         if (prependNext) {
           prefixes = prefixes.concat([
             {
@@ -223,6 +222,7 @@ export function applyPrefixes(samples: PrefixedCodeSample[]): CodeSample[] {
       checkJS: sample.checkJS,
       sourceFile: sample.sourceFile,
       lineNumber: sample.lineNumber,
+      skip: sample.skip,
       prefixesLength: _.sum(combinedPrefixes.map(p => p.split('\n').length)),
       content,
     };
diff --git a/src/processor.ts b/src/processor.ts
index da4e5c4..1db37bc 100644
--- a/src/processor.ts
+++ b/src/processor.ts
@@ -97,9 +97,11 @@ export class Processor {
     finishFile();
   }
 
-  async checkSample(sample: CodeSample, idToSample: {[id: string]: CodeSample}) {
-    const {id, language} = sample;
-    const {content} = sample;
+  async checkSample(sample: CodeSample, idToSample: {[id: string]: CodeSample}): Promise<void> {
+    const {id, language, content, skip} = sample;
+    if (skip) {
+      return;
+    }
     startSample(sample);
 
     if (language === 'ts' || (language === 'js' && sample.checkJS)) {
diff --git a/src/types.ts b/src/types.ts
index af9199b..6980922 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -21,6 +21,7 @@ export interface CodeSample {
   nodeModules: readonly string[];
   /** Combined length of prefixes, for offsetting error messages */
   prefixesLength: number;
+  skip: boolean;
   output?: SampleOutput;
 }
 


Test patch:
diff --git a/src/test/__snapshots__/asciidoc.test.ts.snap b/src/test/__snapshots__/asciidoc.test.ts.snap
index cea916f..820cbe2 100644
--- a/src/test/__snapshots__/asciidoc.test.ts.snap
+++ b/src/test/__snapshots__/asciidoc.test.ts.snap
@@ -177,6 +177,37 @@ END #././src/test/inputs/equivalent.asciidoc:39 (--- ms)
 }
 `;
 
+exports[`checker asciidoc checker snapshots "./src/test/inputs/prepend-and-skip.asciidoc": ./src/test/inputs/prepend-and-skip.asciidoc 1`] = `
+{
+  "logs": [
+    "---- BEGIN FILE ./src/test/inputs/prepend-and-skip.asciidoc
+",
+    "Found 2 code samples in ./src/test/inputs/prepend-and-skip.asciidoc",
+    "BEGIN #././src/test/inputs/prepend-and-skip.asciidoc:13 (--filter prepend-and-skip-13)
+",
+    "matched errors:",
+    "  expected:  Cannot redeclare block-scoped variable 'shared'. b.ts",
+    "    actual: Cannot redeclare block-scoped variable 'shared'.",
+    "  error messages could not be matched!",
+    "matched errors:",
+    "  expected:  Cannot redeclare block-scoped variable 'shared'.",
+    "    actual: Cannot redeclare block-scoped variable 'shared'.",
+    "  error messages could not be matched!",
+    "Matched 2/2 errors.",
+    "
+END #././src/test/inputs/prepend-and-skip.asciidoc:13 (--- ms)
+",
+    "---- END FILE ./src/test/inputs/prepend-and-skip.asciidoc
+",
+  ],
+  "statuses": [
+    "1/1: ././src/test/inputs/prepend-and-skip.asciidoc",
+    "1/1: ././src/test/inputs/prepend-and-skip.asciidoc: 1/2 ././src/test/inputs/prepend-and-skip.asciidoc:6",
+    "1/1: ././src/test/inputs/prepend-and-skip.asciidoc: 2/2 ././src/test/inputs/prepend-and-skip.asciidoc:13",
+  ],
+}
+`;
+
 exports[`checker asciidoc checker snapshots "./src/test/inputs/president.asciidoc": ./src/test/inputs/president.asciidoc 1`] = `
 {
   "logs": [
@@ -249,6 +280,7 @@ exports[`extractSamples snapshot: backticks 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "backticks.asciidoc",
     "tsOptions": {},
   },
@@ -265,6 +297,7 @@ exports[`extractSamples snapshot: backticks 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "backticks.asciidoc",
     "tsOptions": {},
   },
@@ -281,6 +314,7 @@ exports[`extractSamples snapshot: backticks 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "backticks.asciidoc",
     "tsOptions": {},
   },
@@ -304,6 +338,7 @@ exports[`extractSamples snapshot: commented-sample 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "commented-sample.asciidoc",
     "tsOptions": {},
   },
@@ -332,6 +367,7 @@ val  // Type is number | null",
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "commented-sample.asciidoc",
     "tsOptions": {},
   },
@@ -353,6 +389,7 @@ exports[`extractSamples snapshot: commented-sample-with-error 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "commented-sample-with-error.asciidoc",
     "tsOptions": {},
   },
@@ -374,6 +411,7 @@ exports[`extractSamples snapshot: commented-sample-with-error 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "commented-sample-with-error.asciidoc",
     "tsOptions": {},
   },
@@ -397,6 +435,7 @@ exports[`extractSamples snapshot: doc1 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "doc1.asciidoc",
     "tsOptions": {},
   },
@@ -427,6 +466,7 @@ fetchANumber
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "empty-twoslash.asciidoc",
     "tsOptions": {},
   },
@@ -454,6 +494,7 @@ type T = keyof Point;
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "equivalent.asciidoc",
     "tsOptions": {},
   },
@@ -475,6 +516,7 @@ type T = keyof Point;
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "equivalent.asciidoc",
     "tsOptions": {},
   },
@@ -496,6 +538,7 @@ type T = keyof Point;
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "equivalent.asciidoc",
     "tsOptions": {},
   },
@@ -518,6 +561,7 @@ type T = keyof Point;
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "equivalent.asciidoc",
     "tsOptions": {},
   },
@@ -541,6 +585,7 @@ exports[`extractSamples snapshot: inline-replacement 1`] = `
     "prefixesLength": 0,
     "replacementId": "calculate-age-replacement",
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "inline-replacement.asciidoc",
     "tsOptions": {},
   },
@@ -561,6 +606,7 @@ exports[`extractSamples snapshot: inline-replacement 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "inline-replacement.asciidoc",
     "tsOptions": {},
   },
@@ -579,6 +625,7 @@ exports[`extractSamples snapshot: inline-replacement 1`] = `
     "prefixesLength": 0,
     "replacementId": "calculate-age-replacement",
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "inline-replacement.asciidoc",
     "tsOptions": {},
   },
@@ -604,6 +651,7 @@ exports[`extractSamples snapshot: multilinetype 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "multilinetype.asciidoc",
     "tsOptions": {},
   },
@@ -624,6 +672,7 @@ exports[`extractSamples snapshot: multilinetype 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "multilinetype.asciidoc",
     "tsOptions": {},
   },
@@ -645,6 +694,7 @@ exports[`extractSamples snapshot: noid 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "noid.asciidoc",
     "tsOptions": {},
   },
@@ -666,6 +716,7 @@ exports[`extractSamples snapshot: prepend 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend.asciidoc",
     "tsOptions": {},
   },
@@ -686,6 +737,7 @@ exports[`extractSamples snapshot: prepend 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend.asciidoc",
     "tsOptions": {},
   },
@@ -702,12 +754,62 @@ exports[`extractSamples snapshot: prepend 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend.asciidoc",
     "tsOptions": {},
   },
 ]
 `;
 
+exports[`extractSamples snapshot: prepend-and-skip 1`] = `
+[
+  {
+    "checkJS": false,
+    "content": "// a.ts
+const shared = "Cher";
+//    ~~~~~~
+// Cannot redeclare block-scoped variable 'shared'.",
+    "descriptor": "./prepend-and-skip.asciidoc:6",
+    "id": "prepend-and-skip-6",
+    "isTSX": false,
+    "language": "ts",
+    "lineNumber": 5,
+    "nodeModules": [],
+    "prefixes": [],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": true,
+    "sourceFile": "prepend-and-skip.asciidoc",
+    "tsOptions": {},
+  },
+  {
+    "checkJS": false,
+    "content": "// b.ts
+const shared = "Cher";
+//    ~~~~~~
+// Cannot redeclare block-scoped variable 'shared'.",
+    "descriptor": "./prepend-and-skip.asciidoc:13",
+    "id": "prepend-and-skip-13",
+    "isTSX": false,
+    "language": "ts",
+    "lineNumber": 12,
+    "nodeModules": [],
+    "prefixes": [
+      {
+        "id": "prepend-and-skip-6",
+      },
+    ],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": false,
+    "sourceFile": "prepend-and-skip.asciidoc",
+    "tsOptions": {},
+  },
+]
+`;
+
 exports[`extractSamples snapshot: prepend-multiple 1`] = `
 [
   {
@@ -723,6 +825,7 @@ exports[`extractSamples snapshot: prepend-multiple 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend-multiple.asciidoc",
     "tsOptions": {},
   },
@@ -743,6 +846,7 @@ exports[`extractSamples snapshot: prepend-multiple 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend-multiple.asciidoc",
     "tsOptions": {},
   },
@@ -766,6 +870,7 @@ exports[`extractSamples snapshot: prepend-multiple 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend-multiple.asciidoc",
     "tsOptions": {},
   },
@@ -794,6 +899,7 @@ const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "president.asciidoc",
     "tsOptions": {},
   },
@@ -813,10 +919,31 @@ const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "president.asciidoc",
     "tsOptions": {},
   },
 ]
 `;
 
-exports[`extractSamples snapshot: skip 1`] = `[]`;
+exports[`extractSamples snapshot: skip 1`] = `
+[
+  {
+    "checkJS": false,
+    "content": "keyof A&B = (keyof A) | (keyof B)",
+    "descriptor": "./skip.asciidoc:4",
+    "id": "skip-4",
+    "isTSX": false,
+    "language": "ts",
+    "lineNumber": 3,
+    "nodeModules": [],
+    "prefixes": [],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": true,
+    "sourceFile": "skip.asciidoc",
+    "tsOptions": {},
+  },
+]
+`;
diff --git a/src/test/__snapshots__/markdown.test.ts.snap b/src/test/__snapshots__/markdown.test.ts.snap
index fd76c03..6968ad5 100644
--- a/src/test/__snapshots__/markdown.test.ts.snap
+++ b/src/test/__snapshots__/markdown.test.ts.snap
@@ -15,6 +15,7 @@ exports[`markdown should match snapshots: commented-sample 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "commented-sample.md",
     "tsOptions": {},
   },
@@ -43,6 +44,7 @@ val  // Type is number | null",
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "commented-sample.md",
     "tsOptions": {},
   },
@@ -66,6 +68,7 @@ exports[`markdown should match snapshots: doc1 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "doc1.md",
     "tsOptions": {},
   },
@@ -83,6 +86,7 @@ exports[`markdown should match snapshots: doc1 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "doc1.md",
     "tsOptions": {},
   },
@@ -108,6 +112,7 @@ exports[`markdown should match snapshots: multilinetype 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "multilinetype.md",
     "tsOptions": {},
   },
@@ -128,6 +133,7 @@ exports[`markdown should match snapshots: multilinetype 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "multilinetype.md",
     "tsOptions": {},
   },
@@ -149,6 +155,7 @@ exports[`markdown should match snapshots: noid 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "noid.md",
     "tsOptions": {},
   },
@@ -170,6 +177,7 @@ exports[`markdown should match snapshots: prepend 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend.md",
     "tsOptions": {},
   },
@@ -190,6 +198,7 @@ exports[`markdown should match snapshots: prepend 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend.md",
     "tsOptions": {},
   },
@@ -206,6 +215,7 @@ exports[`markdown should match snapshots: prepend 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend.md",
     "tsOptions": {},
   },
@@ -227,6 +237,7 @@ exports[`markdown should match snapshots: prepend-multiple 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend-multiple.md",
     "tsOptions": {},
   },
@@ -247,6 +258,7 @@ exports[`markdown should match snapshots: prepend-multiple 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend-multiple.md",
     "tsOptions": {},
   },
@@ -270,10 +282,31 @@ exports[`markdown should match snapshots: prepend-multiple 1`] = `
     "prefixesLength": 0,
     "replacementId": undefined,
     "sectionHeader": null,
+    "skip": false,
     "sourceFile": "prepend-multiple.md",
     "tsOptions": {},
   },
 ]
 `;
 
-exports[`markdown should match snapshots: skip 1`] = `[]`;
+exports[`markdown should match snapshots: skip 1`] = `
+[
+  {
+    "checkJS": false,
+    "content": "keyof A&B = (keyof A) | (keyof B)",
+    "descriptor": "./skip.md:3",
+    "id": "skip-3",
+    "isTSX": false,
+    "language": "ts",
+    "lineNumber": 2,
+    "nodeModules": [],
+    "prefixes": [],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": true,
+    "sourceFile": "skip.md",
+    "tsOptions": {},
+  },
+]
+`;
diff --git a/src/test/asciidoc.test.ts b/src/test/asciidoc.test.ts
index 17f07ec..2a26644 100644
--- a/src/test/asciidoc.test.ts
+++ b/src/test/asciidoc.test.ts
@@ -121,7 +121,11 @@ describe('extractSamples', () => {
   });
 
   test('skip directive', () => {
-    expect(extractSamples(ASCIIDOC_SKIP, 'skip', 'source.asciidoc')).toEqual([]);
+    expect(extractSamples(ASCIIDOC_SKIP, 'skip', 'source.asciidoc')).toMatchObject([
+      {
+        skip: true,
+      },
+    ]);
   });
 
   test('tsconfig directive', () => {
@@ -412,6 +416,17 @@ describe('extractSamples', () => {
         'source.asciidoc',
       ),
     ).toEqual([
+      {
+        ...baseExtract,
+        content: 'const x = 12;',
+        descriptor: './source.asciidoc:5',
+        id: 'header-reset-done-with-file-5',
+        language: 'ts',
+        lineNumber: 4,
+        replacementId: undefined,
+        sectionHeader: 'Chapter 1',
+        skip: true,
+      },
       {
         ...baseExtract,
         descriptor: './source.asciidoc:11',
@@ -421,6 +436,7 @@ describe('extractSamples', () => {
         language: 'ts',
         content: `const x = 12;`,
         prefixes: [],
+        skip: false,
       },
     ]);
   });
@@ -450,6 +466,7 @@ describe('checker', () => {
     './src/test/inputs/president.asciidoc',
     './src/test/inputs/equivalent.asciidoc',
     './src/test/inputs/empty-twoslash.asciidoc',
+    './src/test/inputs/prepend-and-skip.asciidoc',
   ])(
     'asciidoc checker snapshots %p',
     async inputFile => {
diff --git a/src/test/common.ts b/src/test/common.ts
index 99f2e70..83d36e2 100644
--- a/src/test/common.ts
+++ b/src/test/common.ts
@@ -8,6 +8,7 @@ export const baseSample = {
   sectionHeader: null,
   sourceFile: 'source.asciidoc',
   prefixesLength: 0,
+  skip: false,
 };
 
 export const baseExtract = {
diff --git a/src/test/inputs/prepend-and-skip.asciidoc b/src/test/inputs/prepend-and-skip.asciidoc
new file mode 100644
index 0000000..d17cb06
--- /dev/null
+++ b/src/test/inputs/prepend-and-skip.asciidoc
@@ -0,0 +1,17 @@
+Regression test for https://github.com/danvk/literate-ts/issues/42
+
+// verifier:skip
+// verifier:prepend-to-following
+```ts
+// a.ts
+const shared = "Cher";
+//    ~~~~~~
+// Cannot redeclare block-scoped variable 'shared'.
+```
+
+```ts
+// b.ts
+const shared = "Cher";
+//    ~~~~~~
+// Cannot redeclare block-scoped variable 'shared'.
+```