@doccident/doccident 0.0.2 → 0.0.3

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/README.md CHANGED
@@ -1,179 +1,147 @@
1
- [![npm version](https://badge.fury.io/js/doccident.svg)](http://badge.fury.io/js/doccident)
2
- [![Build Status](https://travis-ci.org/Widdershin/doccident.svg?branch=master)](https://travis-ci.org/Widdershin/doccident)
3
- [![Greenkeeper badge](https://badges.greenkeeper.io/Widdershin/doccident.svg)](https://greenkeeper.io/)
4
-
5
- * * *
6
-
7
1
  # doccident
8
2
 
9
- Test all the code in your markdown docs!
10
-
11
- Why on earth?
12
- ---
13
-
14
- As an open source developer, there are few things more embarrassing than a user opening an issue to inform you that your README example is broken! With `doccident`, you can rest easy knowing that your example code is *actually runnable*.
3
+ [![npm version](https://badge.fury.io/js/doccident.svg)](http://badge.fury.io/js/doccident)
15
4
 
16
- Installation
17
- ---
18
- Just `npm install @doccident/doccident` and run `doccident`. It will run all of the Javascript code examples tucked away in your markdown, and let you know if any blow up.
5
+ **Test the code examples in your Markdown documentation.**
19
6
 
20
- Okay, how do I use it?
21
- ---
7
+ ## Overview
22
8
 
23
- Let's try it on this repo!
9
+ As an open source developer, few things are more frustrating for users than encountering broken examples in a README. `doccident` ensures your documentation remains accurate by treating your examples as testable code. It parses your Markdown files, extracts JavaScript and TypeScript code blocks, and executes them in a sandboxed environment to verify they run without errors.
24
10
 
25
- ```js
26
- var a = 5;
11
+ > **Note**: `doccident` primarily verifies that your code *runs* without throwing exceptions. While you can add assertions to your examples to test correctness, its main goal is to ensure your documentation examples are valid and runnable.
27
12
 
28
- var b = 10;
13
+ ## Installation
29
14
 
30
- console.log(a + c);
15
+ ```bash
16
+ npm install --save-dev @doccident/doccident
31
17
  ```
32
18
 
33
- There's a problem with that example. `doccident` finds it for us:
19
+ ## Usage
20
+
21
+ Run `doccident` in your project root. By default, it recursively checks all `.md` and `.markdown` files (excluding `node_modules`).
34
22
 
35
23
  ```bash
36
- $ doccident
37
- x..
24
+ npx doccident
25
+ ```
38
26
 
39
- Failed - README.md:32:17
40
- evalmachine.<anonymous>:7
41
- console.log(a + c);
42
- ^
27
+ You can also target specific files or directories:
43
28
 
44
- ReferenceError: c is not defined
29
+ ```bash
30
+ npx doccident docs/**/*.md
45
31
  ```
46
32
 
47
- Awesome! No excuse for broken documentation ever again, right? :wink:
33
+ ### Language Support & Recipes
48
34
 
49
- We can also run specific files or folders by running `doccident` with a glob, like `doccident docs/**/*.md`. By default `doccident` will recursively run all the `.md` or `.markdown` files starting with the current directory, with the exception of the `node_modules` directory.
35
+ `doccident` executes code inside `js`, `javascript`, `es6`, `ts`, or `typescript` fenced code blocks. It automatically transforms modern JavaScript and TypeScript using **esbuild** before execution.
50
36
 
51
- Note: `doccident` doesn't actually attempt to provide any guarantee that your code worked, only that it didn't explode in a horrible fashion. If you would like to use `doccident` for actually testing the correctness of your code, you can add some `assert`s to your examples.
37
+ #### JavaScript
52
38
 
53
- `doccident` is not a replacement for your test suite. It's designed to run with your CI build and give you peace of mind that all of your examples are at least vaguely runnable.
39
+ Use `js`, `javascript`, or `es6` for JavaScript examples.
54
40
 
55
- So how do I write those examples?
56
- ---
41
+ **Recipe:**
57
42
 
58
- In your markdown files, anything inside of code blocks with 'js' or 'es6' will be run. E.g:
43
+ 1. Use `js` fenced code blocks.
44
+ 2. Write standard JavaScript (ES6+ supported).
45
+ 3. Use `require` to load dependencies defined in your configuration.
59
46
 
60
47
  ```js
61
- console.log("Yay, tests in my docs");
48
+ const { sum } = require('./math-utils');
49
+ const result = sum(1, 2);
62
50
  ```
63
51
 
64
- ```es6
65
- const a = 5;
66
- console.log({a, foo: 'test'});
52
+ #### TypeScript
53
+
54
+ Use `ts` or `typescript` for TypeScript examples.
55
+
56
+ **Recipe:**
57
+
58
+ 1. Use `ts` fenced code blocks.
59
+ 2. Include type annotations to demonstrate correct usage.
60
+ 3. `doccident` strips types during execution, so your examples serve as both documentation and functional tests.
61
+
62
+ ```ts
63
+ interface User {
64
+ id: number;
65
+ name: string;
66
+ }
67
+
68
+ const user: User = { id: 1, name: 'Doccident' };
69
+ console.log(user.name);
67
70
  ```
68
71
 
69
- I have a code example I don't want tested!
70
- ---
71
- You can tell `doccident` to skip examples by adding `<!-- skip-example -->` before the example. E.g:
72
+ ### Skipping Examples
73
+
74
+ To skip a specific code block, add the `<!-- skip-example -->` comment immediately before it:
72
75
 
73
76
  <!-- skip-example -->
74
77
  ```js
75
- // not a runnable example
76
-
77
- var foo = download(...);
78
+ // This code will not be executed
79
+ fetch('https://example.com');
78
80
  ```
79
81
 
80
- How do requires work? And other setup logic?
81
- ---
82
+ ## Configuration
83
+
84
+ Create a `.doccident-setup.js` file in your project root to configure the test environment.
82
85
 
83
- You can `require` any needed modules or example helpers in `.doccident-setup.js`. E.g:
86
+ ### Injecting Dependencies (`require`)
84
87
 
85
- <!-- skip-example -->
86
- ```js
88
+ If your examples use external libraries, provide them here. This allows your examples to `require` modules just like users would:
89
+
90
+ ```javascript
87
91
  // .doccident-setup.js
88
92
  module.exports = {
89
93
  require: {
90
- Rx: require('rx')
91
- },
92
-
93
- globals: {
94
- $: require('jquery')
94
+ // Make 'my-library' available when examples call require('my-library')
95
+ 'my-library': require('./index.js'),
96
+ 'lodash': require('lodash')
95
97
  }
96
- }
98
+ };
97
99
  ```
98
100
 
99
- Anything exported under `require` will then be used by any examples that `require` that key.
100
- You must explicitly configure all of the dependencies used in your examples.
101
+ ### Global Variables
101
102
 
102
- Anything exported under `globals` will be available globally across all examples.
103
+ Define global variables available to all snippets:
103
104
 
104
- You can also specify a regexRequire section to handle anything more complex than an exact string match!
105
-
106
- <!-- skip-example -->
107
- ```js
108
- // .doccident-setup.js
105
+ ```javascript
109
106
  module.exports = {
110
- require: {
111
- Rx: require('rx')
112
- },
113
-
114
- regexRequire: {
115
- 'rx/(.*)': function (fullPath, matchedModuleName) {
116
- return require('./dist/' + matchedModuleName);
117
- }
107
+ globals: {
108
+ $: require('jquery'),
109
+ window: {}
118
110
  }
119
- }
111
+ };
120
112
  ```
121
113
 
122
- Do I have to enable es6 support?
123
- ---
114
+ ### Advanced Configuration
124
115
 
125
- Nope, ES6 support is on by default. You can disable `babel` support
126
- in your `.doccident-setup.js` file.
127
- This will speed things up drastically:
116
+ * **`regexRequire`**: Handle dynamic requires that match a pattern.
117
+ * **`beforeEach`**: Function to run before each snippet (e.g., to reset global state).
118
+ * **`transformCode`**: Pre-process code before execution (e.g., to strip out display-only syntax).
128
119
 
129
- <!-- skip-example -->
130
- ```js
131
- //.doccident-setup.js
132
- module.exports = {
133
- babel: false
134
- }
135
- ```
120
+ ## Architecture and Approach
136
121
 
137
- What if I have global state that needs to be reset after my examples run?
138
- ---
139
- <!-- skip-example -->
140
- ```js
141
- //.doccident-setup.js
142
- module.exports = {
143
- beforeEach: function () {
144
- // reset your awesome global state
145
- }
146
- }
147
- ```
122
+ `doccident` is designed to be a lightweight, flexible testing harness for documentation. The codebase is modularized to separate parsing, execution, and reporting, ensuring maintainability and extensibility.
148
123
 
149
- You can specify a function to be run before each example in your `.doccident-setup.js`.
124
+ ### Core Modules
150
125
 
151
- What if I want to remove custom syntax from examples before processing?
152
- ---
126
+ 1. **Parser (`src/parse-code-snippets-from-markdown.ts`)**
127
+ * Reads Markdown content line-by-line.
128
+ * Uses a robust state machine to identify code fences and control comments (like `skip-example`).
129
+ * Extracts valid snippets into structured objects containing code, file paths, and line numbers.
130
+ * **TypeScript Support**: Now recognizes `ts` and `typescript` blocks in addition to JS.
153
131
 
154
- <!-- skip-example -->
155
- ```js
156
- //.doccident-setup.js
157
- module.exports = {
158
- transformCode(code) {
159
- // Remove ... from code syntax
160
- return code.replace(/\.\.\./g, "");
161
- }
162
- }
163
- ```
132
+ 2. **Test Runner (`src/doctest.ts`)**
133
+ * The orchestrator of the application.
134
+ * Iterates through parsed snippets and manages the execution lifecycle.
135
+ * **Sandboxing**: Uses Node.js's `vm` module (`runInNewContext`) to execute code in isolation. This prevents examples from polluting the global scope of the runner itself, while allowing controlled injection of dependencies via the configuration.
136
+ * **Transformation**: Uses **esbuild** to compile modern JavaScript and TypeScript code down to a compatible format before execution. This ensures that modern syntax and type annotations run correctly in all environments, and is significantly faster than the previous Babel implementation.
137
+
138
+ 3. **Reporter (`src/reporter.ts`)**
139
+ * Collects execution results (pass, fail, skip).
140
+ * Formats output using `chalk` for readability.
141
+ * **Error Mapping**: Crucially, it maps execution errors back to the specific line number in the original Markdown file, making it easy to identify exactly which line in your documentation caused the failure.
142
+
143
+ 4. **Types & Utils**
144
+ * Shared interfaces (`src/types.ts`) ensure type safety across the application.
145
+ * Utility functions (`src/utils.ts`) provide common helpers.
164
146
 
165
- Who uses doccident?
166
- ---
167
-
168
- All of these projects either run `doccident` with `npm test` or as part of their CI process:
169
-
170
- * [lodash](https://github.com/lodash/lodash)
171
- * [Moment](https://github.com/moment/momentjs.com)
172
- * [RxJS](https://github.com/ReactiveX/RxJS)
173
- * [most](https://github.com/cujojs/most)
174
- * [xstream](https://github.com/staltz/xstream)
175
- * [cyclejs/time](https://github.com/cyclejs/time)
176
- * [rx.schedulers](https://github.com/Reactive-Extensions/rx.schedulers)
177
- * [rx.priorityqueue](https://github.com/Reactive-Extensions/rx.priorityqueue)
178
- * [rx.disposables](https://github.com/Reactive-Extensions/rx.disposables)
179
- * [rx-undoable](https://github.com/Widdershin/rx-undoable)
147
+ This separation of concerns allows `doccident` to be easily extended—for example, by adding new parsers for different documentation formats or custom reporters for CI environments.
package/bin/cmd.js CHANGED
@@ -5,9 +5,9 @@
5
5
 
6
6
  const { version } = require('../package');
7
7
  const doctest = require('..');
8
- const program = require('commander');
8
+ const { program } = require('commander');
9
9
  const path = require('path');
10
- const glob = require('glob');
10
+ const fg = require('fast-glob');
11
11
  const fs = require('fs');
12
12
  const { pathToFileURL } = require('url');
13
13
 
@@ -30,15 +30,18 @@ program
30
30
  .name('doccident')
31
31
  .description('Test all the code in your markdown docs!')
32
32
  .version(version, '-v, --version', 'output the current version')
33
+ .argument('[glob]', 'glob pattern for files to test')
33
34
  .helpOption('-h, --help', 'output usage informations')
34
35
  .option('-c, --config <path>', 'custom config location', path.join(process.cwd(), '/.doccident-setup.js'))
35
36
  .option('--test-output', 'output the test results to the console')
36
37
  .parse(process.argv);
37
38
 
39
+ const options = program.opts();
40
+
38
41
  // Parse config file
39
42
  (async () => {
40
- if (program.config) {
41
- const configPath = path.resolve(program.config);
43
+ if (options.config) {
44
+ const configPath = path.resolve(options.config);
42
45
 
43
46
  if (fs.existsSync(configPath)) {
44
47
  try {
@@ -51,33 +54,31 @@ program
51
54
  }
52
55
  }
53
56
 
54
- if (program.testOutput) {
57
+ if (options.testOutput) {
55
58
  config.testOutput = true;
56
59
  }
57
60
 
58
61
  // Resolve files
59
- glob(
60
- program.args[0] || DEFAULT_GLOB,
61
- {
62
- ignore: [...config.ignore, ...DEFAULT_IGNORE]
63
- },
64
- (err, files) => {
65
-
66
- if (err) {
67
- console.trace(err);
62
+ try {
63
+ const files = await fg(
64
+ program.args[0] || DEFAULT_GLOB,
65
+ {
66
+ ignore: [...config.ignore, ...DEFAULT_IGNORE]
68
67
  }
68
+ );
69
69
 
70
- // Run tests
71
- const results = doctest.runTests(files, config);
70
+ // Run tests
71
+ const results = doctest.runTests(files, config);
72
72
 
73
- console.log('\n');
74
- doctest.printResults(results);
73
+ console.log('\n');
74
+ doctest.printResults(results);
75
75
 
76
- // Exit with error-code if any test failed
77
- const failures = results.filter(result => result.status === 'fail');
78
- if (failures.length > 0) {
79
- process.exit(1);
80
- }
76
+ // Exit with error-code if any test failed
77
+ const failures = results.filter(result => result.status === 'fail');
78
+ if (failures.length > 0) {
79
+ process.exit(1);
81
80
  }
82
- );
81
+ } catch (err) {
82
+ console.trace(err);
83
+ }
83
84
  })();
package/dist/doctest.js CHANGED
@@ -1,57 +1,58 @@
1
- /* eslint-disable no-console */
2
1
  "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
3
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.printResults = void 0;
4
7
  exports.runTests = runTests;
5
- exports.printResults = printResults;
6
- var fs_1 = require("fs");
7
- var vm_1 = require("vm");
8
- var core_1 = require("@babel/core");
9
- var preset_env_1 = require("@babel/preset-env");
10
- var chalk_1 = require("chalk");
11
- function flatten(arr) {
12
- return Array.prototype.concat.apply([], arr);
13
- }
14
- var parse_code_snippets_from_markdown_1 = require("./parse-code-snippets-from-markdown");
8
+ const fs_1 = require("fs");
9
+ const vm_1 = require("vm");
10
+ const esbuild_1 = require("esbuild");
11
+ const chalk_1 = __importDefault(require("chalk"));
12
+ const utils_1 = require("./utils");
13
+ const parse_code_snippets_from_markdown_1 = __importDefault(require("./parse-code-snippets-from-markdown"));
14
+ const reporter_1 = require("./reporter");
15
+ Object.defineProperty(exports, "printResults", { enumerable: true, get: function () { return reporter_1.printResults; } });
15
16
  function runTests(files, config) {
16
- var results = files
17
+ const results = files
17
18
  .map(read)
18
19
  .map(parse_code_snippets_from_markdown_1.default)
19
20
  .map(testFile(config));
20
- return flatten(results);
21
+ return (0, utils_1.flatten)(results);
21
22
  }
22
23
  function read(fileName) {
23
- return { contents: (0, fs_1.readFileSync)(fileName, "utf8"), fileName: fileName };
24
+ return { contents: (0, fs_1.readFileSync)(fileName, "utf8"), fileName };
24
25
  }
25
26
  function makeTestSandbox(config) {
26
27
  function sandboxRequire(moduleName) {
27
- for (var regexRequire in config.regexRequire) {
28
- var regex = new RegExp(regexRequire);
29
- var match = regex.exec(moduleName);
30
- var handler = config.regexRequire[regexRequire];
28
+ for (const regexRequire in config.regexRequire) {
29
+ const regex = new RegExp(regexRequire);
30
+ const match = regex.exec(moduleName);
31
+ const handler = config.regexRequire[regexRequire];
31
32
  if (match) {
32
- return handler.apply(void 0, match);
33
+ return handler(...match);
33
34
  }
34
35
  }
35
- if (config.require[moduleName] === undefined) {
36
+ if (config.require === undefined || config.require[moduleName] === undefined) {
36
37
  throw moduleNotFoundError(moduleName);
37
38
  }
38
39
  return config.require[moduleName];
39
40
  }
40
- var sandboxConsole = {
41
- log: function () { return null; },
41
+ const sandboxConsole = {
42
+ log: () => null,
42
43
  };
43
- var sandboxGlobals = { require: sandboxRequire, console: config.testOutput ? console : sandboxConsole };
44
- var sandbox = Object.assign({}, sandboxGlobals, config.globals);
44
+ const sandboxGlobals = { require: sandboxRequire, console: config.testOutput ? console : sandboxConsole };
45
+ const sandbox = Object.assign({}, sandboxGlobals, config.globals);
45
46
  return sandbox;
46
47
  }
47
48
  function testFile(config) {
48
49
  return function testFileWithConfig(args) {
49
- var codeSnippets = args.codeSnippets;
50
- var fileName = args.fileName;
51
- var shareCodeInFile = args.shareCodeInFile;
52
- var results;
50
+ const codeSnippets = args.codeSnippets;
51
+ const fileName = args.fileName;
52
+ const shareCodeInFile = args.shareCodeInFile;
53
+ let results;
53
54
  if (shareCodeInFile) {
54
- var sandbox = makeTestSandbox(config);
55
+ const sandbox = makeTestSandbox(config);
55
56
  results = codeSnippets.map(test(config, fileName, sandbox));
56
57
  }
57
58
  else {
@@ -60,99 +61,58 @@ function testFile(config) {
60
61
  return results;
61
62
  };
62
63
  }
63
- function test(config, filename, sandbox) {
64
- return function (codeSnippet) {
64
+ function test(config, _filename, sandbox) {
65
+ return (codeSnippet) => {
65
66
  if (codeSnippet.skip) {
66
- return { status: "skip", codeSnippet: codeSnippet, stack: "" };
67
+ return { status: "skip", codeSnippet, stack: "" };
67
68
  }
68
- var success = false;
69
- var stack = "";
70
- var code = codeSnippet.code;
69
+ let success = false;
70
+ let stack = "";
71
+ let code = codeSnippet.code;
71
72
  if (config.transformCode) {
72
73
  try {
73
74
  code = config.transformCode(code);
74
75
  }
75
76
  catch (e) {
76
- return { status: "fail", codeSnippet: codeSnippet, stack: "Encountered an error while transforming snippet: \n" + e.stack };
77
+ return { status: "fail", codeSnippet, stack: "Encountered an error while transforming snippet: \n" + e.stack };
77
78
  }
78
79
  }
79
- var perSnippetSandbox;
80
+ let perSnippetSandbox;
80
81
  if (sandbox === undefined) {
81
82
  perSnippetSandbox = makeTestSandbox(config);
82
83
  }
83
84
  if (config.beforeEach) {
84
85
  config.beforeEach();
85
86
  }
86
- var options = {
87
- presets: [preset_env_1.default],
88
- };
89
87
  try {
90
- if (config.babel !== false) {
91
- code = (0, core_1.transformSync)(code, options).code;
92
- }
88
+ const result = (0, esbuild_1.transformSync)(code, {
89
+ loader: 'ts',
90
+ format: 'cjs',
91
+ target: 'node12'
92
+ });
93
+ code = result.code || "";
93
94
  (0, vm_1.runInNewContext)(code, perSnippetSandbox || sandbox);
94
95
  success = true;
95
96
  }
96
97
  catch (e) {
97
98
  stack = e.stack || "";
98
99
  }
99
- var status = success ? "pass" : "fail";
100
+ const status = success ? "pass" : "fail";
100
101
  process.stdout.write(success ? chalk_1.default.green(".") : chalk_1.default.red("x"));
101
- return { status: status, codeSnippet: codeSnippet, stack: stack };
102
+ return { status, codeSnippet, stack };
102
103
  };
103
104
  }
104
- function printResults(results) {
105
- results.filter(function (result) { return result.status === "fail"; }).forEach(printFailure);
106
- var passingCount = results.filter(function (result) { return result.status === "pass"; })
107
- .length;
108
- var failingCount = results.filter(function (result) { return result.status === "fail"; })
109
- .length;
110
- var skippingCount = results.filter(function (result) { return result.status === "skip"; })
111
- .length;
112
- function successfulRun() {
113
- return failingCount === 0;
114
- }
115
- console.log(chalk_1.default.green("Passed: " + passingCount));
116
- if (skippingCount > 0) {
117
- console.log(chalk_1.default.yellow("Skipped: " + skippingCount));
118
- }
119
- if (successfulRun()) {
120
- console.log(chalk_1.default.green("\nSuccess!"));
121
- }
122
- else {
123
- console.log(chalk_1.default.red("Failed: " + failingCount));
124
- }
125
- }
126
- function printFailure(result) {
127
- console.log(chalk_1.default.red("Failed - ".concat(markDownErrorLocation(result))));
128
- var stackDetails = relevantStackDetails(result.stack);
129
- console.log(stackDetails);
130
- var variableNotDefined = stackDetails.match(/(\w+) is not defined/);
131
- if (variableNotDefined) {
132
- var variableName = variableNotDefined[1];
133
- console.log("You can declare ".concat(chalk_1.default.blue(variableName), " in the ").concat(chalk_1.default.blue("globals"), " section in ").concat(chalk_1.default.grey(".doccident-setup.js")));
134
- console.log("\nFor example:\n".concat(chalk_1.default.grey("// .doccident-setup.js"), "\nmodule.exports = {\n globals: {\n ").concat(chalk_1.default.blue(variableName), ": ...\n }\n}\n "));
135
- }
136
- }
137
- function relevantStackDetails(stack) {
138
- var match = stack.match(/([\w\W]*?)at eval/) ||
139
- // eslint-disable-next-line no-useless-escape
140
- stack.match(/([\w\W]*)at [\w*\/]*?doctest.js/);
141
- if (match !== null) {
142
- return match[1];
143
- }
144
- return stack;
145
- }
146
105
  function moduleNotFoundError(moduleName) {
147
- return new Error("\nAttempted to require '".concat(chalk_1.default.blue(moduleName), "' but was not found in config.\nYou need to include it in the require section of your ").concat(chalk_1.default.grey(".doccident-setup.js"), " file.\n\nFor example:\n").concat(chalk_1.default.grey("// .doccident-setup.js"), "\nmodule.exports = {\n require: {\n ").concat(chalk_1.default.blue("'".concat(moduleName, "': require('").concat(moduleName, "')")), "\n }\n}\n "));
106
+ return new Error(`
107
+ Attempted to require '${chalk_1.default.blue(moduleName)}' but was not found in config.
108
+ You need to include it in the require section of your ${chalk_1.default.grey(".doccident-setup.js")} file.
109
+
110
+ For example:
111
+ ${chalk_1.default.grey("// .doccident-setup.js")}
112
+ module.exports = {
113
+ require: {
114
+ ${chalk_1.default.blue(`'${moduleName}': require('${moduleName}')`)}
115
+ }
148
116
  }
149
- function markDownErrorLocation(result) {
150
- var match = result.stack.match(/eval.*<.*>:(\d+):(\d+)/);
151
- if (match) {
152
- var mdLineNumber = parseInt(match[1], 10);
153
- var columnNumber = parseInt(match[2], 10);
154
- var lineNumber = result.codeSnippet.lineNumber + mdLineNumber;
155
- return "".concat(result.codeSnippet.fileName, ":").concat(lineNumber, ":").concat(columnNumber);
156
- }
157
- return "".concat(result.codeSnippet.fileName, ":").concat(result.codeSnippet.lineNumber);
117
+ `);
158
118
  }
@@ -1,35 +1,29 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- var isStartOfSnippet = function (line) {
4
- return line.trim().match(/```\W*(JavaScript|js|es6)\s?$/i);
5
- };
6
- var isEndOfSnippet = function (line) { return line.trim() === "```"; };
7
- var isSkip = function (line) { return line.trim() === "<!-- skip-example -->"; };
8
- var isCodeSharedInFile = function (line) {
9
- return line.trim() === "<!-- share-code-between-examples -->";
10
- };
3
+ const isStartOfSnippet = (line) => line.trim().match(/```\W*(JavaScript|js|es6|ts|typescript)\s?$/i);
4
+ const isEndOfSnippet = (line) => line.trim() === "```";
5
+ const isSkip = (line) => line.trim() === "<!-- skip-example -->";
6
+ const isCodeSharedInFile = (line) => line.trim() === "<!-- share-code-between-examples -->";
11
7
  function startNewSnippet(snippets, fileName, lineNumber) {
12
- var skip = snippets.skip;
8
+ const skip = snippets.skip;
13
9
  snippets.skip = false;
14
10
  return Object.assign(snippets, {
15
11
  snippets: snippets.snippets.concat([
16
- { code: "", fileName: fileName, lineNumber: lineNumber, complete: false, skip: skip }
12
+ { code: "", fileName, lineNumber, complete: false, skip: skip ?? false }
17
13
  ])
18
14
  });
19
15
  }
20
16
  function addLineToLastSnippet(line) {
21
17
  return function addLine(snippets) {
22
- var lastSnippet = snippets.snippets[snippets.snippets.length - 1];
18
+ const lastSnippet = snippets.snippets[snippets.snippets.length - 1];
23
19
  if (lastSnippet && !lastSnippet.complete) {
24
20
  lastSnippet.code += line + "\n";
25
21
  }
26
22
  return snippets;
27
23
  };
28
24
  }
29
- function endSnippet(snippets, fileName, lineNumber) {
30
- // eslint-disable-next-line no-console
31
- console.log("endSnippet", snippets, fileName, lineNumber);
32
- var lastSnippet = snippets.snippets[snippets.snippets.length - 1];
25
+ function endSnippet(snippets, _fileName, _lineNumber) {
26
+ const lastSnippet = snippets.snippets[snippets.snippets.length - 1];
33
27
  if (lastSnippet) {
34
28
  lastSnippet.complete = true;
35
29
  }
@@ -59,27 +53,25 @@ function parseLine(line) {
59
53
  return addLineToLastSnippet(line);
60
54
  }
61
55
  function parseCodeSnippets(args) {
62
- var contents = args.contents;
63
- var fileName = args.fileName;
64
- var initialState = {
56
+ const contents = args.contents;
57
+ const fileName = args.fileName;
58
+ const initialState = {
65
59
  snippets: [],
66
60
  shareCodeInFile: false,
67
61
  complete: false
68
62
  };
69
- var results = contents
63
+ const results = contents
70
64
  .split("\n")
71
65
  .map(parseLine)
72
- .reduce(function (snippets, lineAction, index) {
73
- return lineAction(snippets, fileName, index + 1);
74
- }, initialState);
75
- var codeSnippets = results.snippets;
76
- var lastSnippet = codeSnippets[codeSnippets.length - 1];
66
+ .reduce((snippets, lineAction, index) => lineAction(snippets, fileName, index + 1), initialState);
67
+ const codeSnippets = results.snippets;
68
+ const lastSnippet = codeSnippets[codeSnippets.length - 1];
77
69
  if (lastSnippet && !lastSnippet.complete) {
78
70
  throw new Error("Snippet parsing was incomplete");
79
71
  }
80
72
  return {
81
- fileName: fileName,
82
- codeSnippets: codeSnippets,
73
+ fileName,
74
+ codeSnippets,
83
75
  shareCodeInFile: results.shareCodeInFile
84
76
  };
85
77
  }
@@ -0,0 +1,78 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.printResults = printResults;
7
+ /* eslint-disable no-console */
8
+ const chalk_1 = __importDefault(require("chalk"));
9
+ function printResults(results) {
10
+ results.filter((result) => result.status === "fail").forEach(printFailure);
11
+ const passingCount = results.filter((result) => result.status === "pass")
12
+ .length;
13
+ const failingCount = results.filter((result) => result.status === "fail")
14
+ .length;
15
+ const skippingCount = results.filter((result) => result.status === "skip")
16
+ .length;
17
+ function successfulRun() {
18
+ return failingCount === 0;
19
+ }
20
+ console.log(chalk_1.default.green("Passed: " + passingCount));
21
+ if (skippingCount > 0) {
22
+ console.log(chalk_1.default.yellow("Skipped: " + skippingCount));
23
+ }
24
+ if (successfulRun()) {
25
+ console.log(chalk_1.default.green("\nSuccess!"));
26
+ }
27
+ else {
28
+ console.log(chalk_1.default.red("Failed: " + failingCount));
29
+ }
30
+ }
31
+ function printFailure(result) {
32
+ console.log(chalk_1.default.red(`Failed - ${markDownErrorLocation(result)}`));
33
+ const stackDetails = relevantStackDetails(result.stack);
34
+ console.log(stackDetails);
35
+ const variableNotDefined = stackDetails.match(/(\w{1,256}) is not defined/);
36
+ if (variableNotDefined) {
37
+ const variableName = variableNotDefined[1];
38
+ console.log(`You can declare ${chalk_1.default.blue(variableName)} in the ${chalk_1.default.blue("globals")} section in ${chalk_1.default.grey(".doccident-setup.js")}`);
39
+ console.log(`
40
+ For example:
41
+ ${chalk_1.default.grey("// .doccident-setup.js")}
42
+ module.exports = {
43
+ globals: {
44
+ ${chalk_1.default.blue(variableName)}: ...
45
+ }
46
+ }
47
+ `);
48
+ }
49
+ }
50
+ function relevantStackDetails(stack) {
51
+ const evalIndex = stack.indexOf("at eval");
52
+ if (evalIndex !== -1) {
53
+ return stack.substring(0, evalIndex);
54
+ }
55
+ const lines = stack.split("\n");
56
+ for (let i = 0; i < lines.length; i++) {
57
+ const line = lines[i];
58
+ if (line.includes("doctest.js") && line.trim().startsWith("at ")) {
59
+ return lines.slice(0, i).join("\n") + "\n";
60
+ }
61
+ }
62
+ return stack;
63
+ }
64
+ function markDownErrorLocation(result) {
65
+ const lines = result.stack.split("\n");
66
+ for (const line of lines) {
67
+ if (line.includes("eval")) {
68
+ const match = line.match(/<([^><]+)>:(\d+):(\d+)/);
69
+ if (match) {
70
+ const mdLineNumber = parseInt(match[2], 10);
71
+ const columnNumber = parseInt(match[3], 10);
72
+ const lineNumber = result.codeSnippet.lineNumber + mdLineNumber;
73
+ return `${result.codeSnippet.fileName}:${lineNumber}:${columnNumber}`;
74
+ }
75
+ }
76
+ }
77
+ return `${result.codeSnippet.fileName}:${result.codeSnippet.lineNumber}`;
78
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/dist/utils.js ADDED
@@ -0,0 +1,6 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.flatten = flatten;
4
+ function flatten(arr) {
5
+ return Array.prototype.concat.apply([], arr);
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@doccident/doccident",
3
- "version": "0.0.2",
3
+ "version": "0.0.3",
4
4
  "description": "Test all the code in your markdown docs!",
5
5
  "main": "dist/doctest.js",
6
6
  "files": [
@@ -12,8 +12,8 @@
12
12
  },
13
13
  "scripts": {
14
14
  "build": "tsc",
15
- "lint": "eslint . --ext .ts",
16
- "test": "jest",
15
+ "lint": "eslint .",
16
+ "test": "vitest run",
17
17
  "precommit": "npm run lint && npm run test",
18
18
  "clean": "rm -rf dist",
19
19
  "prepublishOnly": "npm run clean && npm run build"
@@ -37,29 +37,22 @@
37
37
  },
38
38
  "homepage": "https://github.com/BillaudCipher/doccident",
39
39
  "devDependencies": {
40
- "@babel/preset-typescript": "^7.27.1",
41
40
  "@eslint/eslintrc": "^3.3.1",
42
41
  "@eslint/js": "^9.23.0",
43
42
  "@types/node": "^22.15.17",
44
43
  "@typescript-eslint/eslint-plugin": "^8.32.0",
45
44
  "@typescript-eslint/parser": "^8.32.0",
45
+ "@vitest/coverage-v8": "^4.0.16",
46
46
  "eslint": "^9.24.0",
47
47
  "eslint-plugin-import": "^2.31.0",
48
- "jest": "^29.7.0",
49
- "typescript": "^5.8.3"
48
+ "typescript": "^5.8.3",
49
+ "vitest": "^4.0.16"
50
50
  },
51
51
  "dependencies": {
52
- "@babel/core": "^7.27.1",
53
- "@babel/preset-env": "^7.27.2",
54
- "chalk": "^2.4.2",
55
- "commander": "^4.1.1",
56
- "glob": "^7.2.3",
52
+ "chalk": "^4.1.2",
53
+ "commander": "^14.0.2",
54
+ "esbuild": "^0.27.2",
55
+ "fast-glob": "^3.3.3",
57
56
  "globals": "^16.1.0"
58
- },
59
- "babel": {
60
- "presets": [
61
- "@babel/preset-env",
62
- "@babel/preset-typescript"
63
- ]
64
57
  }
65
58
  }