1.0.2 • Published 2 years ago

@ashnazg/mochadocs v1.0.2

Weekly downloads
-
License
MIT
Repository
-
Last release
2 years ago

title: @ashnazg/mochadocs

sidebar_label: mochadocs

Purpose

I really wanted something like sphinx or doxygen but with the least possible syntax and the most DRY I could think of.

Principles: 1. Readable code is the best documentation. 2. Readable tests is the 2nd best documentation. 3. While real design docs shouldn't be crammed into this format, letting the unit tests file be a significant (hyperlinked) participant reduces how much pure prose has to be written.

Reads Mocha test files and emits a README.md

Default behavior

reads from ./test/ and writes ./README.md The file you're reading was created by running:

const mochadocs = require('@ashnazg/mochadocs');
await mochadocs();

... and there are asserts on this page because you're reading the unit tests.

what's emitted

  1. Lines that begin with /// are emitted as markdown.
  2. describe(...) is parsed to make h2's
  3. it(...) is parsed to make h3's
  4. Lines of code or vanilla comments are also emitted, but wrapped in a pre-block.

Supports Transclusion

A line that is exactly ///{{filename}} will try to cat that filename in.

big Nota Bene: This is relative to the PWD of the process, NOT relative to the file the transclusion is present in.

The Release section on this page was transcluded from another file by saying: ///{{CHANGELOG.md}}

Options

Defaults

if no params given, defaults to:

await mochadocs({path: 'test'});

if you don't need any other options, you can pass it just the path string:

await mochadocs('test');

the full config format is:

await mochadocs({
	path: 'test',
	to: 'README.md',
	libname: '@ashnazg/mochadocs',
	glossary: { // for auto-linking frequently used terms
		acronym1: 'path1',
	},
	indent: '\t\t'
});

Support for space-indented files

Visible code blocks are defined as non-/// lines that start with conf.indent and are between the opening line of the current it(...) and the it-closer, which is the first line of code with less indentation thatn conf.indent.

so for a 2-space file like:

describe('group', function() {
  it('thing', function() {
    visible_code();
  });
  function invisibleHelper() {
    invisible_code();
  };
});

You'd use:

await mochadocs({
  indent: '    ' // four spaces
});

When the indent drops below the conf.indent, code blocks are not emitted til the next it( -- this is so the invisibleHelper's contents don't pollute the unit test's code printout.

Explicit output

you can write the output to a specific place:

await mochadocs({to: 'different.md'});
{
	const lines = (await fs.promises.readFile('different.md', 'utf8')).split('\n');
	assert(lines[length_of_preamble] === '## Reads Mocha test files and emits a README.md');
}

or pass null as the destination and it'll return the markdown file as a list of lines:

{
	const lines = await mochadocs({to: null});
	assert(lines[length_of_preamble] === '## Reads Mocha test files and emits a README.md');
}

Explicit input, file

path can be a filename:

const lines = await mochadocs({path: 'test/basics.js', to: null});
assert(lines[length_of_preamble] === '## Reads Mocha test files and emits a README.md');

Explicit input, dir

... or a directory, in which it'll read all *.js, including subfolders, and process them after sorting them by 'path/filename'.

const lines = await mochadocs({path: 'test', to: null});
assert(lines[length_of_preamble] === '## Reads Mocha test files and emits a README.md');

Replaces require('..')

since in a library's unit tests, require('..') is useful, but not helpful documentation, This is converted to require('LIBNAME').

const lines = await mochadocs({path: 'test', to: null}); // reading the output of section "default"
const hits = lines.filter(line => line === "const mochadocs = require('@ashnazg/mochadocs');");
assert(hits.length === 1);

Configuring require('..') replacement

By default, mochadocs assumes that ./package.json is available and will use that to know your lib's name. if that's not a useful guess (wrong path or wrong name) you can pass in a library name of your choosing as a third param:

const lines = await mochadocs({path: 'test', to: null, libname: 'custom_lib_name'});
const hits = lines.filter(line => line === "const mochadocs = require('custom_lib_name');");
assert(hits.length === 1);

Automatic Glossary

To allow good linkage to other relevant topics without repetitive writing, you can pass in a glossary map and if that key is found in the non-code blocks, it'll be emitted as a link:

lines = await mochadocs({
	to: null,
	glossary: {
		GLOSSARY: 'https://en.wikipedia.org/wiki/Glossary'
	}
});

Glossary Rules

Only links exact words

Given the above config, the word "GLOSSARY" is now a link.

// search term without creating a false positive:
{
	const key = `[GLOS` + `SARY](https://en.wikipedia.org/wiki/Glossary)`;
	const hits = lines.filter(line => line.includes(key));
	assert(hits.length === 1, `${hits.length} hits`);
}

... but GLOSSARYFOO is not, because the replacer respects word boundaries.

{
	const key = `GLOS` + `SARYFOO`;
	const hits = lines.filter(line => line.includes(key));
	assert(hits.length === 1, `${hits.length} hits`);
}

Longer glossary terms take precedence

await mochadocs({
	glossary: {
		GLOSSARY: 'https://en.wikipedia.org/wiki/Glossary',
		SHORT_NAME: '#glossary-rules',
		SHORT_NAME_IN_LONGER_NAME: '#longer-glossary-terms-take-precedence'
	}
});

SHORT_NAME_IN_LONGER_NAME should be a clean link, and its internals should not be affected by the SHORT_NAME term, as replacement is not allowed to go into recursive expansion.

const lines = (await fs.promises.readFile('README.md', 'utf8')).split('\n');
const key = `[SHORT_NAM` + `E_IN_LONGER_NAME](#longer-glossary-terms-take-precedence) should be a clean link, and its internals should not be affected by the [SHORT_NAME](#glossary-rules) term`;
const hits = lines.filter(line => line.includes(key));
assert(hits.length === 1, `${hits.length} hits`);

Existing links are unaffected

this predefined link SHORT_NAME is not further processed.

const lines = (await fs.promises.readFile('README.md', 'utf8')).split('\n');
// here's the unwanted form:
const key = `[[SHORT` + `_NAME](test1)](#existing-links-are-unaffected)`;
const hits = lines.filter(line => line.includes(key));
assert(hits.length === 0, `${hits.length} hits`);

Release 1.0.1

  1. Suppressed codeblocks that aren't inside an it(...)

Release 1.0

  1. Wrote the thing

Bugfixing

short blocks leave trailing pre

const lines = await mochadocs({path: 'samples/trailing-pre-missing.js', to: null});
assert(lines[lines.length-1] === '```', 'simple did not close pre');