Repository: danvk/literate-ts (ts)

Issue:
Only list failing tests in summary
You're probably interested in the failures, not the successes.

```
././chapters/ch-write-run/avoid-non-ecma.asciidoc 20/20 passed
././chapters/ch-write-run/model-env.asciidoc 4/5 passed
 ✗ ./chapters/ch-write-run/model-env.asciidoc:85
././chapters/ch-write-run/performance.asciidoc 11/11 passed
././chapters/ch-write-run/runtime-types.asciidoc 8/8 passed
././chapters/ch-write-run/source-maps-debug.asciidoc 4/4 passed
././chapters/ch-write-run/types-or-tests.asciidoc 8/8 passed
././chapters/ch-write-run/understand-the-dom.asciidoc 9/9 passed
✗ 15 / 957 samples failed.
```


Tooling: tests are too chatty
Run

    yarn test

You'll see hundreds of `console.log`s, some even in red, but it's all green. It would be nice if the tests were less chatty. It would be easier to find failures, too.
feat: Check maximum line lengths
O'Reilly is unhappy with anything longer than 81 characters. As an alternative to #239, literate-ts could at least complain about long lines.

Pull request description:
feat: check for line length
Fixes #259 
Fixes #258 
Fixes #247 

This reads `printWidth` from the `prettier` section of `package.json` and reports errors inline.

This wound up being kind of tricky to get right -- you want to run the line length tests on the raw sample, not the processed sample, and you don't want to run cosmetic checks on samples in commented-out blocks. For program listings, you _do_ want to strip HTML tags.

O'Reilly line length limit is 81 per [style guide](https://oreillymedia.github.io/production-resources/styleguide/#line-length-ZKs1FLck). I'm an Animal book, and my code sample are all "Body (top-level code)." I don't know what "Examples" is referring to on that page.

Patch:
diff --git a/.prettierrc b/.prettierrc
deleted file mode 100644
index 1045fb6..0000000
--- a/.prettierrc
+++ /dev/null
@@ -1,8 +0,0 @@
-{
-  "arrowParens": "avoid",
-  "printWidth": 100,
-  "singleQuote": true,
-  "trailingComma": "all",
-  "bracketSpacing": false,
-  "jsxBracketSameLine": true
-}
diff --git a/README.md b/README.md
index 129ad28..cd7b288 100644
--- a/README.md
+++ b/README.md
@@ -357,6 +357,18 @@ This is only supported for Asciidoc. Here's what it looks like:
 ++++
 ```
 
+## Line length enforcement
+
+If you fill out a `"prettier"` section in your `package.json`, literate-ts will enforce a maximum line length on your code samples:
+
+```json
+{
+  "prettier": {
+    "printWidth": 81
+  }
+}
+```
+
 ## Command-Line Options
 
 - `--help`: Show help.
diff --git a/package.json b/package.json
index 428b9d0..ae4ea35 100644
--- a/package.json
+++ b/package.json
@@ -55,5 +55,13 @@
     "ts-jest": "^29.1.1",
     "ts-node": "^10.9.1",
     "typescript": "^5.4.2"
+  },
+  "prettier": {
+    "arrowParens": "avoid",
+    "printWidth": 100,
+    "singleQuote": true,
+    "trailingComma": "all",
+    "bracketSpacing": false,
+    "jsxBracketSameLine": true
   }
 }
diff --git a/src/asciidoc.ts b/src/asciidoc.ts
index 7fd5122..478633a 100644
--- a/src/asciidoc.ts
+++ b/src/asciidoc.ts
@@ -11,6 +11,7 @@ const NODE_PROGRAM_LISTING = /<pre data-type="programlisting".*>&gt; /;
 
 export function extractAsciidocSamples(sourceFile: string, text: string, p: Processor) {
   let isIgnoringFencedCodeBlocks = false;
+  let inCommentBlock = false;
   const lines = text.split('\n');
 
   const extractCodeSample = (i: number, until: string) => {
@@ -48,6 +49,14 @@ export function extractAsciidocSamples(sourceFile: string, text: string, p: Proc
       } else {
         p.setDirective(directive);
       }
+    } else if (line === '////') {
+      if (inCommentBlock) {
+        p.endCommentBlock();
+        inCommentBlock = false;
+      } else {
+        p.startCommentBlock();
+        inCommentBlock = true;
+      }
     } else {
       const matches = matchAndExtract(EXTRACT_BACKTICKS, line);
       if (matches) {
diff --git a/src/code-sample.ts b/src/code-sample.ts
index 0afb63f..5d70a40 100644
--- a/src/code-sample.ts
+++ b/src/code-sample.ts
@@ -15,6 +15,8 @@ export interface Processor {
   setNextId(idMetadata: IdMetadata): void;
   setNextLanguage(lang: string | null): void;
   addSample(code: string): void;
+  startCommentBlock(): void;
+  endCommentBlock(): void;
   resetWithNormalLine(): void;
 }
 
@@ -41,6 +43,7 @@ function process(
   let nextIsTSX = false;
   let nextShouldCheckJs = false;
   let targetFilename: string | null = null;
+  let inCommentBlock = false;
 
   const p: Processor = {
     setLineNum(line) {
@@ -121,6 +124,12 @@ function process(
     setNextLanguage(lang) {
       lastLanguage = lang;
     },
+    startCommentBlock() {
+      inCommentBlock = true;
+    },
+    endCommentBlock() {
+      inCommentBlock = false;
+    },
     addSample(content) {
       if (
         !lastMetadata &&
@@ -150,6 +159,7 @@ function process(
           prefixesLength: 0,
           skip: skipNext || skipRemaining,
           auxiliaryFiles: [],
+          inCommentBlock,
         });
         if (prependNext) {
           prefixes = prefixes.concat([
@@ -248,6 +258,7 @@ export function applyPrefixes(samples: PrefixedCodeSample[]): CodeSample[] {
       checkJS: sample.checkJS,
       sourceFile: sample.sourceFile,
       lineNumber: sample.lineNumber,
+      inCommentBlock: sample.inCommentBlock,
       targetFilename: sample.targetFilename,
       auxiliaryFiles: auxiliary.map(({id}) => {
         const sample = idToSample[id];
@@ -259,6 +270,8 @@ export function applyPrefixes(samples: PrefixedCodeSample[]): CodeSample[] {
       }),
       skip: sample.skip,
       prefixesLength: _.sum(combinedPrefixes.map(p => p.split('\n').length)),
+      originalContent:
+        sample.originalContent ?? sample.content === content ? undefined : sample.content,
       content,
     };
   });
@@ -270,6 +283,10 @@ export function applyReplacements(
 ): PrefixedCodeSample[] {
   const samples = _.cloneDeep(rawSamples) as PrefixedCodeSample[];
   const idToSample = _.keyBy(samples, 'id');
+  for (const sample of samples) {
+    // will be deleted from unaltered samples later
+    sample.originalContent = sample.content;
+  }
 
   // First check the external replacements
   for (const sample of samples) {
@@ -299,6 +316,11 @@ export function applyReplacements(
     }
   }
 
+  for (const sample of samples) {
+    if (sample.content === sample.originalContent) {
+      delete sample.originalContent;
+    }
+  }
   return samples;
 }
 
@@ -348,6 +370,7 @@ export function addResolvedChecks(sample: CodeSample): CodeSample {
 
   return {
     ...sample,
+    originalContent: sample.originalContent ?? content,
     content: newContent,
   };
 }
diff --git a/src/logger.ts b/src/logger.ts
index 646240c..1211827 100644
--- a/src/logger.ts
+++ b/src/logger.ts
@@ -29,7 +29,7 @@ export function log(message: string) {
   // TODO(danvk): figure out how to use a jest mock
   if (global.__TEST__) {
     logsForTest.push(message);
-    console.log(message);
+    // console.log(message);
     return;
   }
 
diff --git a/src/main.ts b/src/main.ts
index 4752e5d..351435e 100644
--- a/src/main.ts
+++ b/src/main.ts
@@ -75,13 +75,18 @@ export function main() {
     for (const [file, fileResults] of Object.entries(getTestResults())) {
       const displayPath = isAbsolute(file) ? file : `./${file}`;
       const numPassed = _.sum(_.map(fileResults, n => (n === 0 ? 1 : 0)));
-      console.log(`./${displayPath}`, `${numPassed}/${_.size(fileResults)} passed`);
-      for (const [id, failures] of Object.entries(fileResults)) {
-        numTotal += 1;
-        if (failures > 0) {
-          console.log(chalk.red(` ✗ ${id}`));
-          numFailures += 1;
+      const numSamples = _.size(fileResults);
+      if (numPassed < numSamples) {
+        console.log(`${displayPath}`, `${numPassed}/${numSamples} passed`);
+        for (const [id, failures] of Object.entries(fileResults)) {
+          numTotal += 1;
+          if (failures > 0) {
+            console.log(chalk.red(` ✗ ${id}`));
+            numFailures += 1;
+          }
         }
+      } else {
+        numTotal += numSamples;
       }
     }
 
diff --git a/src/processor.ts b/src/processor.ts
index 1ed8e41..2f4ef7b 100644
--- a/src/processor.ts
+++ b/src/processor.ts
@@ -1,6 +1,6 @@
 import fs from 'fs';
 import {ParseError, parse as parseJSONC, printParseErrorCode} from 'jsonc-parser';
-import {dirname, isAbsolute} from 'path';
+import {dirname, isAbsolute, resolve} from 'path';
 
 import {
   applyPrefixes,
@@ -16,6 +16,8 @@ import {ConfigBundle, checkProgramListing, checkTs} from './ts-checker.js';
 import {runNode} from './node-runner.js';
 import {Args} from './args.js';
 import _ from 'lodash';
+import {NormalizedReadResult, readPackageUpSync} from 'read-pkg-up';
+import {htmlToText} from 'html-to-text';
 
 function checkOutput(expectedOutput: string, input: CodeSample) {
   const actualOutput = input.output;
@@ -76,6 +78,26 @@ function checkEmitOutput(expectedOutput: string, input: CodeSample) {
   }
 }
 
+function checkLineLengths(sample: CodeSample, printWidth: number) {
+  if (sample.inCommentBlock) {
+    return; // no need to run cosmetic checks on commented-out code samples.
+  }
+  let content = sample.originalContent ?? sample.content;
+  if (sample.language === 'node') {
+    // program listing, need to strip HTML to get displayed line length.
+    content = htmlToText(content);
+  }
+  const lines = content.split('\n');
+  lines.forEach((line, i) => {
+    line = lines[i].trimEnd();
+    if (line.length > printWidth) {
+      fail(`Line too long: ${line.length} > ${printWidth}`, {
+        location: {line: i + sample.prefixesLength, start: printWidth, end: line.length},
+      });
+    }
+  });
+}
+
 export class Processor {
   argv: Args;
   typeScriptBundle: ConfigBundle;
@@ -107,6 +129,9 @@ export class Processor {
     const replacedSamples = applyReplacements(rawSamples, this.sources);
     const samples = applyPrefixes(replacedSamples).map(addResolvedChecks);
 
+    const sourceFileAbsPath = resolve(process.cwd(), path);
+    const pkg = readPackageUpSync({cwd: sourceFileAbsPath});
+
     const outputs = _.keyBy(samples, 'id');
     for (const [i, sample] of Object.entries(samples)) {
       const n = 1 + Number(i);
@@ -114,13 +139,17 @@ export class Processor {
         continue;
       }
       this.setStatus(`${fileStatus}: ${n}/${samples.length} ${sample.descriptor}`);
-      await this.checkSample(sample, outputs);
+      await this.checkSample(sample, outputs, pkg);
     }
 
     finishFile();
   }
 
-  async checkSample(sample: CodeSample, idToSample: {[id: string]: CodeSample}): Promise<void> {
+  async checkSample(
+    sample: CodeSample,
+    idToSample: {[id: string]: CodeSample},
+    pkg: NormalizedReadResult | undefined,
+  ): Promise<void> {
     const {id, language, content, skip} = sample;
     if (skip) {
       return;
@@ -185,6 +214,12 @@ export class Processor {
         checkOutput(content, input);
       }
     }
+
+    const printWidth = pkg?.packageJson.prettier?.printWidth;
+    if (printWidth) {
+      checkLineLengths(sample, printWidth);
+    }
+
     finishSample();
   }
 }
diff --git a/src/types.ts b/src/types.ts
index 7b8680b..dfecba6 100644
--- a/src/types.ts
+++ b/src/types.ts
@@ -18,6 +18,8 @@ export interface CodeSample {
   tsOptions: CompilerOptions;
   sourceFile: string;
   lineNumber: number;
+  /** Is this code sample in a commented-out block? (Disables cosmetic checks.) */
+  inCommentBlock: boolean;
   nodeModules: readonly string[];
   /** If set, write this sample to a file with the given name instead of prepending. */
   targetFilename: string | null;
@@ -26,6 +28,8 @@ export interface CodeSample {
   prefixesLength: number;
   skip: boolean;
   output?: SampleOutput;
+  /** The raw content of the code sample (if different than content) */
+  originalContent?: string;
 }
 
 export interface Prefix {


Test patch:
diff --git a/src/test-tracker.ts b/src/test-tracker.ts
index ac76a9a..e92ba26 100644
--- a/src/test-tracker.ts
+++ b/src/test-tracker.ts
@@ -70,7 +70,7 @@ export function fail(message: string, context: FailureContext = {}) {
         1 + location.end
       }: ${message}`
     : `💥 ${sample?.descriptor}: ${message}`;
-  if (!isLoggingToStderr()) {
+  if (!isLoggingToStderr() && !global.__TEST__) {
     console.error('\n' + fullMessage);
   }
   log(fullMessage);
diff --git a/src/test/__snapshots__/asciidoc.test.ts.snap b/src/test/__snapshots__/asciidoc.test.ts.snap
index 1265a24..f25eb5a 100644
--- a/src/test/__snapshots__/asciidoc.test.ts.snap
+++ b/src/test/__snapshots__/asciidoc.test.ts.snap
@@ -502,6 +502,49 @@ END #././src/test/inputs/issue-235.asciidoc:5 (--- ms)
 }
 `;
 
+exports[`checker asciidoc checker snapshots "./src/test/inputs/long-lines.asciidoc": ./src/test/inputs/long-lines.asciidoc 1`] = `
+{
+  "logs": [
+    "---- BEGIN FILE ./src/test/inputs/long-lines.asciidoc
+",
+    "Found 4 code samples in ./src/test/inputs/long-lines.asciidoc",
+    "BEGIN #././src/test/inputs/long-lines.asciidoc:5 (--filter long-lines-5)
+",
+    "
+END #././src/test/inputs/long-lines.asciidoc:5 (--- ms)
+",
+    "BEGIN #././src/test/inputs/long-lines.asciidoc:18 (--filter long-lines-18)
+",
+    "Code passed type checker.",
+    "
+END #././src/test/inputs/long-lines.asciidoc:18 (--- ms)
+",
+    "BEGIN #././src/test/inputs/long-lines.asciidoc:26 (--filter long-lines-26)
+",
+    "Code passed type checker.",
+    "💥 ./src/test/inputs/long-lines.asciidoc:26:101-104: Line too long: 103 > 100",
+    "
+END #././src/test/inputs/long-lines.asciidoc:26 (--- ms)
+",
+    "BEGIN #././src/test/inputs/long-lines.asciidoc:32 (--filter long-lines-32)
+",
+    "Node session matched program listing.",
+    "
+END #././src/test/inputs/long-lines.asciidoc:32 (--- ms)
+",
+    "---- END FILE ./src/test/inputs/long-lines.asciidoc
+",
+  ],
+  "statuses": [
+    "1/1: ././src/test/inputs/long-lines.asciidoc",
+    "1/1: ././src/test/inputs/long-lines.asciidoc: 1/4 ././src/test/inputs/long-lines.asciidoc:5",
+    "1/1: ././src/test/inputs/long-lines.asciidoc: 2/4 ././src/test/inputs/long-lines.asciidoc:18",
+    "1/1: ././src/test/inputs/long-lines.asciidoc: 3/4 ././src/test/inputs/long-lines.asciidoc:26",
+    "1/1: ././src/test/inputs/long-lines.asciidoc: 4/4 ././src/test/inputs/long-lines.asciidoc:32",
+  ],
+}
+`;
+
 exports[`checker asciidoc checker snapshots "./src/test/inputs/node-output.asciidoc": ./src/test/inputs/node-output.asciidoc 1`] = `
 {
   "logs": [
@@ -842,6 +885,7 @@ declare global {
 }",
     "descriptor": "./augment-dom.asciidoc:6",
     "id": "augment-dom-6",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 5,
@@ -863,6 +907,7 @@ declare global {
 img?.src  // ok",
     "descriptor": "./augment-dom.asciidoc:23",
     "id": "augment-dom-23",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 22,
@@ -893,6 +938,7 @@ exports[`extractSamples snapshot: backticks 1`] = `
 }",
     "descriptor": "./backticks.asciidoc:3",
     "id": "greet-ts",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 4,
@@ -912,6 +958,7 @@ exports[`extractSamples snapshot: backticks 1`] = `
     "content": "export const Example = () => <div />;",
     "descriptor": "./backticks.asciidoc:12",
     "id": "greet-tsx",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "tsx",
     "lineNumber": 13,
@@ -931,6 +978,7 @@ exports[`extractSamples snapshot: backticks 1`] = `
     "content": "console.log("end of file");",
     "descriptor": "./backticks.asciidoc:27",
     "id": "backticks-27",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 26,
@@ -958,6 +1006,7 @@ exports[`extractSamples snapshot: check-emit 1`] = `
 const num = 1_234_567;",
     "descriptor": "./check-emit.asciidoc:7",
     "id": "sep-es2021",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 9,
@@ -983,6 +1032,7 @@ const num = 1_234_567;",
 const num = 1_234_567;",
     "descriptor": "./check-emit.asciidoc:14",
     "id": "sep-es2021-emit-js",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "js",
     "lineNumber": 16,
@@ -1008,6 +1058,7 @@ const num = 1_234_567;",
 const num = 1_234_567;",
     "descriptor": "./check-emit.asciidoc:24",
     "id": "sep-es5",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 26,
@@ -1033,6 +1084,7 @@ const num = 1_234_567;",
 var num = 1234567;",
     "descriptor": "./check-emit.asciidoc:31",
     "id": "sep-es5-emit-js",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "js",
     "lineNumber": 33,
@@ -1057,6 +1109,7 @@ var num = 1234567;",
     "content": "const [x, y] = [1, 2];",
     "descriptor": "./check-emit.asciidoc:40",
     "id": "spread",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 42,
@@ -1081,6 +1134,7 @@ var num = 1234567;",
     "content": "var [x, y] = [1, 2];",
     "descriptor": "./check-emit.asciidoc:48",
     "id": "spread-emit-js",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "js",
     "lineNumber": 50,
@@ -1109,6 +1163,7 @@ var num = 1234567;",
 }",
     "descriptor": "./check-emit.asciidoc:56",
     "id": "indent",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 58,
@@ -1138,6 +1193,7 @@ var num = 1234567;",
 }",
     "descriptor": "./check-emit.asciidoc:66",
     "id": "indent-emit-js",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "js",
     "lineNumber": 68,
@@ -1176,6 +1232,7 @@ exports[`extractSamples snapshot: check-jsonc 1`] = `
 }",
     "descriptor": "./check-jsonc.asciidoc:5",
     "id": "check-jsonc-5",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "json",
     "lineNumber": 4,
@@ -1200,6 +1257,7 @@ exports[`extractSamples snapshot: check-jsonc 1`] = `
 }",
     "descriptor": "./check-jsonc.asciidoc:21",
     "id": "check-jsonc-21",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "json",
     "lineNumber": 20,
@@ -1224,6 +1282,7 @@ exports[`extractSamples snapshot: commented-sample 1`] = `
     "content": "function somethingDangerous() {}",
     "descriptor": "./commented-sample.asciidoc:7",
     "id": "commented-sample-7",
+    "inCommentBlock": true,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 6,
@@ -1251,6 +1310,7 @@ try {
 val  // Type is number | null",
     "descriptor": "./commented-sample.asciidoc:13",
     "id": "commented-sample-13",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 12,
@@ -1279,6 +1339,7 @@ exports[`extractSamples snapshot: commented-sample-with-error 1`] = `
     "content": "const value: number = "123";",
     "descriptor": "./commented-sample-with-error.asciidoc:7",
     "id": "commented-sample-with-error-7",
+    "inCommentBlock": true,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 6,
@@ -1299,6 +1360,7 @@ exports[`extractSamples snapshot: commented-sample-with-error 1`] = `
 //          ^? const value: number",
     "descriptor": "./commented-sample-with-error.asciidoc:17",
     "id": "commented-sample-with-error-17",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 16,
@@ -1329,6 +1391,7 @@ exports[`extractSamples snapshot: doc1 1`] = `
 }",
     "descriptor": "./doc1.asciidoc:3",
     "id": "greet-ts",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 5,
@@ -1362,6 +1425,7 @@ fetchANumber
 // ^?",
     "descriptor": "./empty-twoslash.asciidoc:7",
     "id": "empty-twoslash-7",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 6,
@@ -1392,6 +1456,7 @@ type T = keyof Point;
 //   ^? type T = keyof Point",
     "descriptor": "./equivalent.asciidoc:8",
     "id": "equivalent-8",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 7,
@@ -1412,6 +1477,7 @@ type T = keyof Point;
 //   ^? type T2 = keyof Point (equivalent to "x" | "y")",
     "descriptor": "./equivalent.asciidoc:23",
     "id": "equivalent-23",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 22,
@@ -1436,6 +1502,7 @@ type T = keyof Point;
 //   ^? type T2 = keyof Point (equivalent to "x" | "y" | "z")",
     "descriptor": "./equivalent.asciidoc:31",
     "id": "equivalent-31",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 30,
@@ -1461,6 +1528,7 @@ type T = keyof Point;
 //      (equivalent to "x" | "y" | "z")",
     "descriptor": "./equivalent.asciidoc:39",
     "id": "equivalent-39",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 38,
@@ -1489,6 +1557,7 @@ type T = keyof Point;
 }",
     "descriptor": "./equivalent.asciidoc:48",
     "id": "equivalent-48",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 47,
@@ -1517,6 +1586,7 @@ type T = keyof Point;
 }",
     "descriptor": "./equivalent.asciidoc:60",
     "id": "equivalent-60",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 59,
@@ -1546,6 +1616,7 @@ exports[`extractSamples snapshot: error-start-of-line 1`] = `
 // Error: Cannot find name 'x'.",
     "descriptor": "./error-start-of-line.asciidoc:5",
     "id": "error-start-of-line-5",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 4,
@@ -1572,6 +1643,7 @@ const app = express();
 //    ^? const app: Express",
     "descriptor": "./express.asciidoc:8",
     "id": "express-8",
+    "inCommentBlock": true,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 7,
@@ -1603,6 +1675,7 @@ app.get('/health', (request, response) => {
 });",
     "descriptor": "./express.asciidoc:16",
     "id": "express-16",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 15,
@@ -1635,6 +1708,7 @@ exports[`extractSamples snapshot: inline-replacement 1`] = `
 }",
     "descriptor": "./inline-replacement.asciidoc:6",
     "id": "inline-replacement-6",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 5,
@@ -1658,6 +1732,7 @@ exports[`extractSamples snapshot: inline-replacement 1`] = `
 }",
     "descriptor": "./inline-replacement.asciidoc:12",
     "id": "calculate-age-replacement",
+    "inCommentBlock": true,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 14,
@@ -1679,6 +1754,7 @@ exports[`extractSamples snapshot: inline-replacement 1`] = `
 }",
     "descriptor": "./inline-replacement.asciidoc:28",
     "id": "inline-replacement-28",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 27,
@@ -1706,6 +1782,7 @@ const numArgsBetter = (...args: any[]) => args.length;
 //    ^? const numArgsBetter: (...args: any) => numberjsdklfjklsd",
     "descriptor": "./issue-235.asciidoc:5",
     "id": "issue-235-5",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 4,
@@ -1722,6 +1799,104 @@ const numArgsBetter = (...args: any[]) => args.length;
 ]
 `;
 
+exports[`extractSamples snapshot: long-lines 1`] = `
+[
+  {
+    "auxiliaryFiles": [],
+    "checkJS": false,
+    "content": "{
+  "prettier": {
+    "printWidth": 81
+  }
+}",
+    "descriptor": "./long-lines.asciidoc:5",
+    "id": "long-lines-5",
+    "inCommentBlock": false,
+    "isTSX": false,
+    "language": "json",
+    "lineNumber": 4,
+    "nodeModules": [],
+    "prefixes": [],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": false,
+    "sourceFile": "long-lines.asciidoc",
+    "targetFilename": null,
+    "tsOptions": {},
+  },
+  {
+    "auxiliaryFiles": [],
+    "checkJS": false,
+    "content": "// This is an extremely long line. This is an extremely long line. This is an extremely long line. This is an extremely long line.",
+    "descriptor": "./long-lines.asciidoc:18",
+    "id": "long-lines-18",
+    "inCommentBlock": true,
+    "isTSX": false,
+    "language": "ts",
+    "lineNumber": 17,
+    "nodeModules": [],
+    "prefixes": [],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": false,
+    "sourceFile": "long-lines.asciidoc",
+    "targetFilename": null,
+    "tsOptions": {},
+  },
+  {
+    "auxiliaryFiles": [],
+    "checkJS": false,
+    "content": "// This is also a long line. 100 characters ought to be enough for anyone. Don't you think that's true?",
+    "descriptor": "./long-lines.asciidoc:26",
+    "id": "long-lines-26",
+    "inCommentBlock": false,
+    "isTSX": false,
+    "language": "ts",
+    "lineNumber": 25,
+    "nodeModules": [],
+    "prefixes": [
+      {
+        "id": "long-lines-18",
+      },
+    ],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": false,
+    "sourceFile": "long-lines.asciidoc",
+    "targetFilename": null,
+    "tsOptions": {},
+  },
+  {
+    "auxiliaryFiles": [],
+    "checkJS": false,
+    "content": "<pre data-type="programlisting">&gt; <strong>["some", "text", "that", "is", "long", "but", "not", "too", "long"].length</strong>
+9</pre>",
+    "descriptor": "./long-lines.asciidoc:32",
+    "id": "long-lines-32",
+    "inCommentBlock": false,
+    "isTSX": false,
+    "language": "node",
+    "lineNumber": 31,
+    "nodeModules": [],
+    "prefixes": [
+      {
+        "id": "long-lines-18",
+      },
+    ],
+    "prefixesLength": 0,
+    "replacementId": undefined,
+    "sectionHeader": null,
+    "skip": false,
+    "sourceFile": "long-lines.asciidoc",
+    "targetFilename": null,
+    "tsOptions": {},
+  },
+]
+`;
+
 exports[`extractSamples snapshot: multilinetype 1`] = `
 [
   {
@@ -1734,6 +1909,7 @@ exports[`extractSamples snapshot: multilinetype 1`] = `
 // }",
     "descriptor": "./multilinetype.asciidoc:4",
     "id": "multilinetype-4",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 3,
@@ -1757,6 +1933,7 @@ exports[`extractSamples snapshot: multilinetype 1`] = `
 }",
     "descriptor": "./multilinetype.asciidoc:14",
     "id": "multilinetype-14",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 13,
@@ -1780,6 +1957,7 @@ exports[`extractSamples snapshot: multilinetype 1`] = `
 //       }",
     "descriptor": "./multilinetype.asciidoc:25",
     "id": "multilinetype-25",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 24,
@@ -1802,6 +1980,7 @@ exports[`extractSamples snapshot: multilinetype 1`] = `
 const p = o;",
     "descriptor": "./multilinetype.asciidoc:37",
     "id": "multilinetype-37",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 36,
@@ -1829,6 +2008,7 @@ const x = xs[3];
 //    ^? const x: number",
     "descriptor": "./node-output.asciidoc:8",
     "id": "node-output-8",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 7,
@@ -1848,6 +2028,7 @@ const x = xs[3];
     "content": "console.log(x.toFixed(1));",
     "descriptor": "./node-output.asciidoc:16",
     "id": "unsound-code",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 18,
@@ -1874,6 +2055,7 @@ const x = xs[3];
 TypeError: Cannot read properties of undefined (reading 'toFixed')",
     "descriptor": "./node-output.asciidoc:24",
     "id": "unsound-code-output",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": null,
     "lineNumber": 25,
@@ -1902,6 +2084,7 @@ exports[`extractSamples snapshot: noid 1`] = `
     "content": "console.log('Hello', 'TS');",
     "descriptor": "./noid.asciidoc:10",
     "id": "noid-10",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 9,
@@ -1926,6 +2109,7 @@ exports[`extractSamples snapshot: prepend 1`] = `
     "content": "type AB = 'a' | 'b';",
     "descriptor": "./prepend.asciidoc:2",
     "id": "prefix",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 4,
@@ -1945,6 +2129,7 @@ exports[`extractSamples snapshot: prepend 1`] = `
     "content": "const a: AB = 'a';",
     "descriptor": "./prepend.asciidoc:8",
     "id": "combined",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 10,
@@ -1968,6 +2153,7 @@ exports[`extractSamples snapshot: prepend 1`] = `
     "content": "const a: AB = 'a';",
     "descriptor": "./prepend.asciidoc:16",
     "id": "final",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 18,
@@ -1995,6 +2181,7 @@ const shared = "Cher";
 // Cannot redeclare block-scoped variable 'shared'.",
     "descriptor": "./prepend-and-skip.asciidoc:6",
     "id": "prepend-and-skip-6",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 5,
@@ -2017,6 +2204,7 @@ const shared = "Cher";
 // Cannot redeclare block-scoped variable 'shared'.",
     "descriptor": "./prepend-and-skip.asciidoc:13",
     "id": "prepend-and-skip-13",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 12,
@@ -2049,6 +2237,7 @@ export interface Point {
 }",
     "descriptor": "./prepend-as-file.asciidoc:10",
     "id": "prepend-as-file-10",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 9,
@@ -2074,6 +2263,7 @@ const pt: Point = {
 }",
     "descriptor": "./prepend-as-file.asciidoc:21",
     "id": "prepend-as-file-21",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 20,
@@ -2106,6 +2296,7 @@ const pt: Point = {
 }",
     "descriptor": "./prepend-as-file.asciidoc:36",
     "id": "prepend-as-file-36",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "json",
     "lineNumber": 35,
@@ -2129,6 +2320,7 @@ console.log(data.months);
 //               ^? (property) "months": string[]",
     "descriptor": "./prepend-as-file.asciidoc:52",
     "id": "prepend-as-file-52",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 51,
@@ -2163,6 +2355,7 @@ console.log(data.months);
 }",
     "descriptor": "./prepend-as-file.asciidoc:65",
     "id": "prepend-as-file-65",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 64,
@@ -2188,6 +2381,7 @@ import {Flavor} from 'ice-cream';
 scoop(Flavor.VANILLA);  // OK",
     "descriptor": "./prepend-as-file.asciidoc:78",
     "id": "prepend-as-file-78",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 77,
@@ -2216,6 +2410,7 @@ exports[`extractSamples snapshot: prepend-multiple 1`] = `
     "content": "type AB = 'a' | 'b';",
     "descriptor": "./prepend-multiple.asciidoc:4",
     "id": "prepend-multiple-4",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 3,
@@ -2235,6 +2430,7 @@ exports[`extractSamples snapshot: prepend-multiple 1`] = `
     "content": "type ABC = AB | 'c';",
     "descriptor": "./prepend-multiple.asciidoc:10",
     "id": "prepend-multiple-10",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 9,
@@ -2258,6 +2454,7 @@ exports[`extractSamples snapshot: prepend-multiple 1`] = `
     "content": "const c: ABC = 'c';",
     "descriptor": "./prepend-multiple.asciidoc:15",
     "id": "prepend-multiple-15",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 14,
@@ -2296,6 +2493,7 @@ const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
 //       }",
     "descriptor": "./president.asciidoc:7",
     "id": "president-7",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 6,
@@ -2318,6 +2516,7 @@ const president = {...firstLast, ...(hasMiddle ? {middle: 'S'} : {})};
 // Type is { first: string; last: string; middle?: string; }",
     "descriptor": "./president.asciidoc:21",
     "id": "president-21",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 20,
@@ -2347,6 +2546,7 @@ exports[`extractSamples snapshot: program-listing 1`] = `
 { '1,2,3': 2 }</pre>",
     "descriptor": "./program-listing.asciidoc:4",
     "id": "program-listing-4",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "node",
     "lineNumber": 3,
@@ -2367,6 +2567,7 @@ exports[`extractSamples snapshot: program-listing 1`] = `
 const loc = place.latLng;",
     "descriptor": "./program-listing.asciidoc:17",
     "id": "program-listing-17",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 16,
@@ -2389,6 +2590,7 @@ const loc = place.latLng;",
 [ 0, -73.979 ]</pre>",
     "descriptor": "./program-listing.asciidoc:24",
     "id": "program-listing-24",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "node",
     "lineNumber": 23,
@@ -2415,6 +2617,7 @@ const loc = place.latLng;",
 "Capuchi"</pre>",
     "descriptor": "./program-listing.asciidoc:40",
     "id": "program-listing-40",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "node",
     "lineNumber": 39,
@@ -2440,6 +2643,7 @@ hello
 goodbye</pre>",
     "descriptor": "./program-listing.asciidoc:49",
     "id": "program-listing-49",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "node",
     "lineNumber": 48,
@@ -2468,6 +2672,7 @@ exports[`extractSamples snapshot: skip 1`] = `
     "content": "keyof A&B = (keyof A) | (keyof B)",
     "descriptor": "./skip.asciidoc:4",
     "id": "skip-4",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 3,
@@ -2500,6 +2705,7 @@ const resource = await Promise.race([get('example.com'), timeout(500)]);
 //    ^? const resource: number",
     "descriptor": "./top-level-await.asciidoc:8",
     "id": "top-level-await-8",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 7,
@@ -2533,6 +2739,7 @@ const one = 1;
 const two = 2;  // type is 2",
     "descriptor": "./twoslash-assertion.asciidoc:5",
     "id": "twoslash-assertion-5",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 4,
diff --git a/src/test/__snapshots__/markdown.test.ts.snap b/src/test/__snapshots__/markdown.test.ts.snap
index b4fc826..53feb01 100644
--- a/src/test/__snapshots__/markdown.test.ts.snap
+++ b/src/test/__snapshots__/markdown.test.ts.snap
@@ -8,6 +8,7 @@ exports[`markdown should match snapshots: commented-sample 1`] = `
     "content": "function somethingDangerous() {}",
     "descriptor": "./commented-sample.md:6",
     "id": "commented-sample-6",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 5,
@@ -35,6 +36,7 @@ try {
 val  // Type is number | null",
     "descriptor": "./commented-sample.md:11",
     "id": "commented-sample-11",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 10,
@@ -65,6 +67,7 @@ exports[`markdown should match snapshots: doc1 1`] = `
 }",
     "descriptor": "./doc1.md:3",
     "id": "greet-ts",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 4,
@@ -85,6 +88,7 @@ exports[`markdown should match snapshots: doc1 1`] = `
 ",
     "descriptor": "./doc1.md:20",
     "id": "doc1-20",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 19,
@@ -113,6 +117,7 @@ exports[`markdown should match snapshots: multilinetype 1`] = `
 // }",
     "descriptor": "./multilinetype.md:4",
     "id": "multilinetype-4",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 3,
@@ -136,6 +141,7 @@ exports[`markdown should match snapshots: multilinetype 1`] = `
 }",
     "descriptor": "./multilinetype.md:14",
     "id": "multilinetype-14",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 13,
@@ -160,6 +166,7 @@ exports[`markdown should match snapshots: noid 1`] = `
     "content": "console.log('Hello', 'TS');",
     "descriptor": "./noid.md:8",
     "id": "noid-8",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 7,
@@ -184,6 +191,7 @@ exports[`markdown should match snapshots: prepend 1`] = `
     "content": "type AB = 'a' | 'b';",
     "descriptor": "./prepend.md:2",
     "id": "prefix",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 3,
@@ -203,6 +211,7 @@ exports[`markdown should match snapshots: prepend 1`] = `
     "content": "const a: AB = 'a';",
     "descriptor": "./prepend.md:7",
     "id": "combined",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 8,
@@ -226,6 +235,7 @@ exports[`markdown should match snapshots: prepend 1`] = `
     "content": "const a: AB = 'a';",
     "descriptor": "./prepend.md:14",
     "id": "final",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 15,
@@ -250,6 +260,7 @@ exports[`markdown should match snapshots: prepend-multiple 1`] = `
     "content": "type AB = 'a' | 'b';",
     "descriptor": "./prepend-multiple.md:3",
     "id": "prepend-multiple-3",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 2,
@@ -269,6 +280,7 @@ exports[`markdown should match snapshots: prepend-multiple 1`] = `
     "content": "type ABC = AB | 'c';",
     "descriptor": "./prepend-multiple.md:8",
     "id": "prepend-multiple-8",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 7,
@@ -292,6 +304,7 @@ exports[`markdown should match snapshots: prepend-multiple 1`] = `
     "content": "const c: ABC = 'c';",
     "descriptor": "./prepend-multiple.md:12",
     "id": "prepend-multiple-12",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 11,
@@ -323,6 +336,7 @@ exports[`markdown should match snapshots: skip 1`] = `
     "content": "keyof A&B = (keyof A) | (keyof B)",
     "descriptor": "./skip.md:3",
     "id": "skip-3",
+    "inCommentBlock": false,
     "isTSX": false,
     "language": "ts",
     "lineNumber": 2,
diff --git a/src/test/asciidoc.test.ts b/src/test/asciidoc.test.ts
index fda4ed0..c66e81c 100644
--- a/src/test/asciidoc.test.ts
+++ b/src/test/asciidoc.test.ts
@@ -495,6 +495,7 @@ describe('checker', () => {
     './src/test/inputs/augment-dom.asciidoc',
     './src/test/inputs/error-start-of-line.asciidoc',
     './src/test/inputs/check-emit.asciidoc',
+    './src/test/inputs/long-lines.asciidoc',
   ])(
     'asciidoc checker snapshots %p',
     async inputFile => {
diff --git a/src/test/code-sample.test.ts b/src/test/code-sample.test.ts
index 5da3b8c..6d31ac2 100644
--- a/src/test/code-sample.test.ts
+++ b/src/test/code-sample.test.ts
@@ -70,6 +70,7 @@ describe('code-sample', () => {
         prefixesLength: 1,
         language: 'ts',
         id: 'combined',
+        originalContent: `const a: AB = 'a';`,
         content: dedent`
           type AB = 'a' | 'b';
           const a: AB = 'a';`,
@@ -104,6 +105,7 @@ describe('code-sample', () => {
         language: 'ts',
         id: 'mpd-11',
         prefixesLength: 1,
+        originalContent: `type ABC = AB | 'c';`,
         content: dedent`
           type AB = 'a' | 'b';
           type ABC = AB | 'c';`,
@@ -115,6 +117,7 @@ describe('code-sample', () => {
         prefixesLength: 2,
         language: 'ts',
         id: 'mpd-16',
+        originalContent: `const c: ABC = 'c';`,
         content: dedent`
           type AB = 'a' | 'b';
           type ABC = AB | 'c';
@@ -168,6 +171,7 @@ describe('code-sample', () => {
         language: 'ts',
         id: 'b',
         prefixesLength: 3,
+        originalContent: `const p = {} as Person;`,
         content: dedent`
         interface Person {
           name: string;
@@ -220,6 +224,7 @@ describe('code-sample', () => {
         language: 'ts',
         id: 'b',
         prefixesLength: 2,
+        originalContent: `const {name} = p;`,
         content: dedent`
         import _ from 'lodash';
         const p = {name: 'Bob'};
@@ -347,6 +352,7 @@ describe('applyReplacements', () => {
     content: '',
     prefixesLength: 0,
     lineNumber: 4,
+    inCommentBlock: false,
   };
 
   const compressed = dedent`
@@ -378,6 +384,7 @@ describe('applyReplacements', () => {
     ).toEqual([
       {
         ...foo,
+        originalContent: compressed,
         content: full,
       },
     ]);
@@ -403,6 +410,7 @@ describe('applyReplacements', () => {
     ).toEqual([
       {
         ...foo,
+        originalContent: compressed,
         content: full,
       },
       {
diff --git a/src/test/common.ts b/src/test/common.ts
index c0184f7..1b53438 100644
--- a/src/test/common.ts
+++ b/src/test/common.ts
@@ -13,6 +13,8 @@ export const baseSample = {
   skip: false,
   targetFilename: null,
   auxiliaryFiles: [],
+  inCommentBlock: false,
+  originalContent: undefined,
 } satisfies Partial<CodeSample>;
 
 export const baseExtract = {
diff --git a/src/test/inputs/long-lines.asciidoc b/src/test/inputs/long-lines.asciidoc
new file mode 100644
index 0000000..b8b5185
--- /dev/null
+++ b/src/test/inputs/long-lines.asciidoc
@@ -0,0 +1,34 @@
+If your `package.json` has a `prettier` section, literate-ts will check the line lengths of code samples against prettier's `printWidth`:
+
+[source,json]
+----
+{
+  "prettier": {
+    "printWidth": 81
+  }
+}
+----
+
+Line length limits are not enforced against code samples in commented-out blocks, even when they are prepended:
+
+////
+// verifier:prepend-to-following
+[source,ts]
+----
+// This is an extremely long line. This is an extremely long line. This is an extremely long line. This is an extremely long line.
+----
+////
+
+This is the only sample that should produce an error. (literate-ts sets `printWidth` to 100 for itself.)
+
+[source,ts]
+----
+// This is also a long line. 100 characters ought to be enough for anyone. Don't you think that's true?
+----
+
+A program listing is fine, so long as it's less than the `printWidth` after HTML tags are stripped:
+
+++++
+<pre data-type="programlisting">&gt; <strong>["some", "text", "that", "is", "long", "but", "not", "too", "long"].length</strong>
+9</pre>
+++++