Native Flow, use Jest (#767)

* build: Use Flow syntax without comments.

We're switching to Flow annotations - not Flow comments. This
gives documentation.js the ability to self-document without
JSDoc types and improves our compatibility with tools like
prettier.

Fixes #729. Fixes #709
This commit is contained in:
Tom MacWright 2017-05-08 20:46:21 -04:00 committed by GitHub
parent 5b6b3bb8cd
commit 11d9045a00
401 changed files with 8623 additions and 42843 deletions

6
.babelrc Normal file
View File

@ -0,0 +1,6 @@
{
"presets": ["flow"],
"plugins": [
"transform-es2015-modules-commonjs"
]
}

View File

@ -1 +1,5 @@
test/fixture/*
__tests__/fixture/*
src/default_theme/*
coverage/*
lib/*
declarations/*

View File

@ -2,7 +2,7 @@
"root": true,
"parser": "babel-eslint",
"parserOptions": {
"sourceType": "script"
"sourceType": "module"
},
"plugins": [
"flowtype"
@ -31,6 +31,7 @@
},
"extends": [
"eslint:recommended",
"plugin:flowtype/recommended",
"prettier"
],
"env": {

View File

@ -1,7 +1,8 @@
[ignore]
.*node_modules/.cache/.*
.*node_modules/conventional-changelog-core/test/fixtures/.*
.*/test/fixture/.*
.*/__tests__/fixture/.*
./lib/.*
[include]

3
.gitignore vendored
View File

@ -1,3 +1,4 @@
/lib
coverage
.nyc_output
node_modules
/node_modules

View File

@ -2,5 +2,8 @@
"rules": {
"valid-jsdoc": [0],
"no-unused-vars": [0]
},
"env": {
"jest": true
}
}

View File

@ -0,0 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`readme command --readme-file 1`] = `
"# A title
# API
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## foo
A function with documentation.
**Parameters**
- \`a\` {string} blah
Returns **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** answer
## bar
A second function with docs
**Parameters**
- \`b\`
# Another section
"
`;
exports[`readme command updates README.md 1`] = `
"# A title
# API
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
## foo
A function with documentation.
**Parameters**
- \`a\` {string} blah
Returns **[number](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number)** answer
## bar
A second function with docs
**Parameters**
- \`b\`
# Another section
"
`;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

125
__tests__/bin-readme.js Normal file
View File

@ -0,0 +1,125 @@
var path = require('path'),
os = require('os'),
exec = require('child_process').exec,
tmp = require('tmp'),
fs = require('fs-extra');
function documentation(args, options, parseJSON) {
return new Promise((resolve, reject) => {
if (!options.cwd) {
options.cwd = __dirname;
}
options.maxBuffer = 1024 * 1024;
args.unshift(
'node ' + path.join(__dirname, '..', 'bin', 'documentation.js')
);
exec(args.join(' '), options, (err, res) => {
resolve(res);
});
});
}
describe('readme command', function() {
var fixtures = path.join(__dirname, 'fixture/readme');
var sourceFile = path.join(fixtures, 'index.js');
var d;
var removeCallback;
beforeEach(() => {
var dirEntry = tmp.dirSync({ unsafeCleanup: true });
d = dirEntry.name;
fs.copySync(
path.join(fixtures, 'README.input.md'),
path.join(d, 'README.md')
);
fs.copySync(path.join(fixtures, 'index.js'), path.join(d, 'index.js'));
});
// run tests after setting up temp dir
test('--diff-only: changes needed', async function() {
var before = fs.readFileSync(path.join(d, 'README.md'), 'utf-8');
try {
await documentation(['readme index.js --diff-only -s API'], {
cwd: d
});
} catch (err) {
var after = fs.readFileSync(path.join(d, 'README.md'), 'utf-8');
expect(err).toBeTruthy();
expect(err.code).not.toBe(0);
expect(after).toEqual(before);
}
});
test('updates README.md', async function() {
await documentation(['readme index.js -s API'], { cwd: d });
var outputPath = path.join(d, 'README.md');
expect(fs.readFileSync(outputPath, 'utf-8')).toMatchSnapshot();
});
test('--readme-file', async function() {
fs.copySync(
path.join(fixtures, 'README.input.md'),
path.join(d, 'other.md')
);
await documentation(['readme index.js -s API --readme-file other.md'], {
cwd: d
});
var actual = fs.readFileSync(path.join(d, 'other.md'), 'utf8');
expect(actual).toMatchSnapshot();
});
test('--diff-only: changes NOT needed', function() {
fs.copySync(
path.join(fixtures, 'README.output.md'),
path.join(d, 'uptodate.md')
);
return documentation(
['readme index.js --diff-only -s API --readme-file uptodate.md'],
{ cwd: d }
).then(stdout => {
// t.match(stdout, 'is up to date.');
});
});
test('-s: not found', async function() {
fs.copySync(
path.join(fixtures, 'README.output.md'),
path.join(d, 'uptodate.md')
);
try {
await documentation(
['readme index.js --diff-only -s NOTFOUND --readme-file uptodate.md'],
{ cwd: d }
);
} catch (err) {
expect(err).toBeTruthy();
}
});
test('requires -s option', async function() {
try {
await documentation(['readme index.js'], { cwd: d });
} catch (err) {
expect(err).toBeTruthy();
expect(err.code !== 0).toBeTruthy();
expect(err.stderr.match(/Missing required argument/)).toBeTruthy();
}
});
var badFixturePath = path.join(__dirname, 'fixture/bad/syntax.input');
test('errors on invalid syntax', async function() {
try {
await documentation(
['readme ' + badFixturePath + ' -s API --parseExtension input'],
{ cwd: d }
);
} catch (err) {
expect(err).toBeTruthy();
expect(err.code !== 0).toBeTruthy();
}
});
});

View File

@ -0,0 +1,161 @@
var path = require('path');
var os = require('os');
var get = require('./utils').get;
var spawn = require('child_process').spawn;
var fs = require('fs');
var pEvent = require('p-event');
function documentation(args, options) {
if (!options) {
options = {};
}
if (!options.cwd) {
options.cwd = __dirname;
}
options.maxBuffer = 1024 * 1024;
args.unshift(path.join(__dirname, '..', 'bin', 'documentation.js'));
return spawn('node', args, options);
}
function normalize(result) {
result.forEach(function(item) {
item.context.file = '[path]';
});
return result;
}
const timeout = 20000;
test('harness', function() {
var docProcess = documentation(['serve', 'fixture/simple.input.js']);
expect(docProcess).toBeTruthy();
docProcess.kill();
});
test(
'provides index.html',
function() {
var docProcess = documentation(['serve', 'fixture/simple.input.js']);
return pEvent(docProcess.stdout, 'data').then(function(data) {
var portNumber = data
.toString()
.match(/documentation.js serving on port (\d+)/);
expect(portNumber).toBeTruthy();
return get(`http://localhost:${portNumber[1]}/`).then(function(text) {
expect(text.match(/<html>/)).toBeTruthy();
docProcess.kill();
});
});
},
timeout
);
test(
'accepts port argument',
function() {
var docProcess = documentation([
'serve',
'fixture/simple.input.js',
'--port=4004'
]);
return pEvent(docProcess.stdout, 'data').then(function(data) {
var portNumber = data
.toString()
.match(/documentation.js serving on port (\d+)/);
expect(portNumber).toBeTruthy();
return get(`http://localhost:${portNumber[1]}/`).then(function(text) {
expect(text.match(/<html>/)).toBeTruthy();
docProcess.kill();
});
});
},
timeout
);
test(
'--watch',
function(done) {
var tmpFile = path.join(os.tmpdir(), '/simple.js');
fs.writeFileSync(tmpFile, '/** a function */function apples() {}');
var docProcess = documentation(['serve', tmpFile, '--watch']);
pEvent(docProcess.stdout, 'data').then(function(data) {
var portNumber = data
.toString()
.match(/documentation.js serving on port (\d+)/);
expect(portNumber).toBeTruthy();
return get(`http://localhost:${portNumber[1]}/`).then(function(text) {
expect(text.match(/apples/)).toBeTruthy();
fs.writeFileSync(tmpFile, '/** a function */function bananas() {}');
function doGet() {
get(`http://localhost:${portNumber[1]}/`).then(function(text) {
if (text.match(/bananas/)) {
docProcess.kill();
done();
} else {
setTimeout(doGet, 100);
}
});
}
doGet();
});
});
},
timeout
);
test(
'--watch',
function(done) {
var tmpDir = os.tmpdir();
var a = path.join(tmpDir, '/simple.js');
var b = path.join(tmpDir, '/required.js');
fs.writeFileSync(a, 'require("./required")');
fs.writeFileSync(b, '/** soup */function soup() {}');
var docProcess = documentation(['serve', a, '--watch']);
docProcess.stdout.once('data', function(data) {
var portNumber = data
.toString()
.match(/documentation.js serving on port (\d+)/);
expect(portNumber).toBeTruthy();
get(`http://localhost:${portNumber[1]}/`).then(function(text) {
expect(text.match(/soup/)).toBeTruthy();
fs.writeFileSync(b, '/** nuts */function nuts() {}');
function doGet() {
get(`http://localhost:${portNumber[1]}/`).then(function(text) {
if (text.match(/nuts/)) {
docProcess.kill();
done();
} else {
setTimeout(doGet, 100);
}
});
}
doGet();
});
});
},
timeout
);
test(
'error page',
function() {
var tmpDir = os.tmpdir();
var a = path.join(tmpDir, '/simple.js');
fs.writeFileSync(a, '**');
var docProcess = documentation(['serve', a, '--watch']);
return pEvent(docProcess.stdout, 'data').then(function(data) {
var portNumber = data
.toString()
.match(/documentation.js serving on port (\d+)/);
expect(portNumber).toBeTruthy();
return get(`http://localhost:${portNumber[1]}/`).then(function(text) {
expect(text.match(/Unexpected token/)).toBeTruthy();
docProcess.kill();
});
});
},
timeout
);

407
__tests__/bin.js Normal file
View File

@ -0,0 +1,407 @@
/* global jasmine */
var path = require('path'),
os = require('os'),
exec = require('child_process').exec,
tmp = require('tmp'),
fs = require('fs-extra');
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
function documentation(args, options, parseJSON) {
if (!options) {
options = {};
}
if (!options.cwd) {
options.cwd = __dirname;
}
options.maxBuffer = 1024 * 1024;
args.unshift('node ' + path.join(__dirname, '..', 'bin', 'documentation.js'));
return new Promise((resolve, reject) => {
exec(args.join(' '), options, function(err, stdout, stderr) {
if (err) {
err.stderr = stderr;
return reject(err);
}
if (parseJSON === false) {
resolve(stdout);
} else {
try {
resolve(JSON.parse(stdout));
} catch (e) {
reject(e);
}
}
});
});
}
function normalize(result) {
result.forEach(function(item) {
item.context.file = '[path]';
});
return result;
}
test('documentation binary', async function() {
const data = await documentation(['build fixture/simple.input.js'], {});
expect(data.length).toBe(1);
});
test('defaults to parsing package.json main', async function() {
const data = await documentation(['build'], {
cwd: path.join(__dirname, '..')
});
expect(data.length).toBeTruthy();
});
test('polyglot mode', async function() {
const data = await documentation([
'build fixture/polyglot/blend.cpp --polyglot'
]);
expect(normalize(data)).toMatchSnapshot();
});
test('accepts config file', async function() {
const data = await documentation([
'build fixture/sorting/input.js -c fixture/config.json'
]);
expect(normalize(data)).toMatchSnapshot();
});
test('accepts config file - reports failures', async function() {
try {
await documentation(
['build fixture/sorting/input.js -c fixture/config-bad.yml'],
{},
false
);
} catch (stderr) {
expect(stderr).toMatchSnapshot();
}
});
test('accepts config file - reports parse failures', async function() {
try {
await documentation(
['build fixture/sorting/input.js -c fixture/config-malformed.json'],
{},
false
);
} catch (stderr) {
expect(stderr.stderr.match(/SyntaxError/g)).toBeTruthy();
}
});
test('--shallow option', async function() {
const data = await documentation([
'build --shallow fixture/internal.input.js'
]);
expect(data.length).toBe(0);
});
test('external modules option', async function() {
const data = await documentation([
'build fixture/external.input.js ' +
'--external=external --external=external/node_modules'
]);
expect(data.length).toBe(2);
});
test('when a file is specified both in a glob and explicitly, it is only documented once', async function() {
const data = await documentation([
'build fixture/simple.input.js fixture/simple.input.*'
]);
expect(data.length).toBe(1);
});
test('extension option', async function() {
const data = await documentation([
'build fixture/extension/index.otherextension ' +
'--requireExtension=otherextension --parseExtension=otherextension'
]);
expect(data.length).toBe(1);
});
/*
* This tests that parseExtension adds extensions to smartGlob's
* look through directories.
*/
test('polyglot + parseExtension + smartGlob', async function() {
const data = await documentation([
'build fixture/polyglot ' + '--polyglot --parseExtension=cpp'
]);
expect(data.length).toBe(1);
});
test('extension option', function() {
return documentation(['build fixture/extension.jsx']);
});
test('invalid arguments', function() {
test('bad -f option', async function() {
try {
await documentation(
['build -f DOES-NOT-EXIST fixture/internal.input.js'],
{},
false
);
} catch (err) {
expect(err).toBeTruthy();
}
});
test('html with no destination', function() {
return documentation(['build -f html fixture/internal.input.js'], function(
err
) {
expect(
err
.toString()
.match(
/The HTML output mode requires a destination directory set with -o/
)
).toBeTruthy();
});
});
test('bad command', async function() {
try {
await documentation(['-f html fixture/internal.input.js'], {}, false);
} catch (err) {
expect(err.code).toBeTruthy();
}
});
});
test('--config', async function() {
var dst = path.join(os.tmpdir(), (Date.now() + Math.random()).toString());
fs.mkdirSync(dst);
var outputIndex = path.join(dst, 'index.html');
var expectedOutputPath = path.join(
__dirname,
'fixture/html/nested.config-output.html'
);
const data = await documentation(
[
'build -c fixture/html/documentation.yml -f html fixture/html/nested.input.js -o ' +
dst
],
{},
false
);
var output = fs.readFileSync(outputIndex, 'utf8');
expect(output).toMatchSnapshot();
});
test('--version', async function() {
const output = await documentation(['--version'], {}, false);
expect(output).toBeTruthy();
});
describe('lint command', function() {
test('generates lint output', async function() {
try {
await documentation(['lint fixture/lint/lint.input.js'], {}, false);
} catch (err) {
var data = err.stderr.toString().split('\n').slice(2).join('\n');
expect(data).toMatchSnapshot();
}
});
test('generates no output on a good file', async function() {
const data = await documentation(
['lint fixture/simple.input.js'],
{},
false
);
expect(data).toBe('');
});
test('exposes syntax error on a bad file', async function() {
try {
await documentation(
['lint fixture/bad/syntax.input', '--parseExtension input'],
{},
false
);
} catch (err) {
expect(err.code > 0).toBeTruthy();
}
});
test('lint with no inputs', async function() {
try {
await documentation(
['lint'],
{
cwd: path.join(__dirname, 'fixture/bad')
},
false
);
} catch (err) {
expect(err.code > 0).toBeTruthy();
}
});
});
test('given no files', async function() {
try {
await documentation(['build']);
} catch (err) {
expect(
err
.toString()
.match(
/documentation was given no files and was not run in a module directory/
)
).toBeTruthy();
}
});
test('with an invalid command', async function() {
try {
await documentation(['invalid'], {}, false);
} catch (err) {
expect(err).toBeTruthy();
}
});
test('--access flag', async function() {
const data = await documentation(
['build --shallow fixture/internal.input.js -a public'],
{},
false
);
expect(data).toBe('[]');
});
test('--private flag', async function() {
const data = await documentation(
['build fixture/internal.input.js --private'],
{},
false
);
expect(data.length > 2).toBeTruthy();
});
test('--infer-private flag', async function() {
const data = await documentation(
['build fixture/infer-private.input.js --infer-private ^_'],
{},
false
);
// This uses JSON.parse with a reviver used as a visitor.
JSON.parse(data, function(n, v) {
// Make sure we do not see any names that match `^_`.
if (n === 'name') {
expect(typeof v).toBe('string');
expect(!/_$/.test(v)).toBeTruthy();
}
return v;
});
});
test('write to file', async function() {
var dst = path.join(os.tmpdir(), (Date.now() + Math.random()).toString());
const data = await documentation(
['build --shallow fixture/internal.input.js -o ' + dst],
{},
false
);
expect(data).toBe('');
expect(fs.existsSync(dst)).toBeTruthy();
});
test('write to html', async function() {
var dstDir = path.join(os.tmpdir(), (Date.now() + Math.random()).toString());
fs.mkdirSync(dstDir);
const data = await documentation(
['build --shallow fixture/internal.input.js -f html -o ' + dstDir],
{},
false
);
expect(data).toBe('');
expect(fs.existsSync(path.join(dstDir, 'index.html'))).toBeTruthy();
});
test('write to html with custom theme', async function() {
var dstDir = path.join(os.tmpdir(), (Date.now() + Math.random()).toString());
fs.mkdirSync(dstDir);
const data = await documentation(
[
'build -t fixture/custom_theme --shallow fixture/internal.input.js -f html -o ' +
dstDir
],
{},
false
);
expect(data).toBe('');
expect(fs.readFileSync(path.join(dstDir, 'index.html'), 'utf8')).toBeTruthy();
});
test('write to html, highlightAuto', function() {
var fixture = 'fixture/auto_lang_hljs/multilanguage.input.js',
config = 'fixture/auto_lang_hljs/config.yml',
dstDir = path.join(os.tmpdir(), (Date.now() + Math.random()).toString());
fs.mkdirSync(dstDir);
return documentation(
['build --shallow ' + fixture + ' -c ' + config + ' -f html -o ' + dstDir],
{},
false
).then(() => {
var result = fs.readFileSync(path.join(dstDir, 'index.html'), 'utf8');
expect(
result.indexOf('<span class="hljs-number">42</span>') > 0
).toBeTruthy();
expect(
result.indexOf('<span class="hljs-selector-attr">[data-foo]</span>') > 0
).toBeTruthy();
expect(
result.indexOf('<span class="hljs-attr">data-foo</span>') > 0
).toBeTruthy();
});
});
test('fatal error', async function() {
try {
await documentation(
['build --shallow fixture/bad/syntax.input --parseExtension input'],
{},
false
);
} catch (err) {
expect(err.toString().match(/Unexpected token/)).toBeTruthy();
}
});
test('build --document-exported', async function() {
const data = await documentation(
['build fixture/document-exported.input.js --document-exported -f md'],
{},
false
);
expect(data).toMatchSnapshot();
});
test('build large file without error (no deoptimized styling error)', function() {
var dstFile =
path.join(os.tmpdir(), (Date.now() + Math.random()).toString()) + '.js';
var contents = '';
for (var i = 0; i < 4e4; i++) {
contents += '/* - */\n';
}
fs.writeFileSync(dstFile, contents, 'utf8');
return documentation(['build ' + dstFile], {}, false).then(() => {
fs.unlinkSync(dstFile);
});
});

View File

@ -8,7 +8,6 @@ require('./polyglot/blend.cpp');
* I am in `external.input.js`.
*/
function foo() {
'use strict';
return 'bar';
}

View File

@ -1,9 +1,7 @@
// @flow
'use strict';
/** x */
let x: (T) => string;
let x: T => string;
/** x2 */
let x2: (a: T) => string;

View File

@ -1,5 +1,3 @@
'use strict';
/**
* Creates a new Klass
* @extends Stream.Writable

View File

@ -1,5 +1,3 @@
'use strict';
var otherDep = require('external2');
/**

Some files were not shown because too many files have changed in this diff Show More