Skip to content

Commit

Permalink
[jest-docblock] add docblock.print() (jestjs#4517)
Browse files Browse the repository at this point in the history
* [jest-docblock]: add docblock.print()

* Add missing flow type, clean up

* Fix docs typo

* Fix doc typo

* [WIP] add docblock.parseWithComments()

* Detect newline character, consolidate API

* Fixup types

* Remove default export from jest-docblock

BREAKING CHANGE: Changes jest-docblock to an ES module without a default export,
requires `import * as docblock from 'jest-docbloc'`
  • Loading branch information
azz authored and tabrindle committed Oct 2, 2017
1 parent d450919 commit bfeee26
Show file tree
Hide file tree
Showing 7 changed files with 266 additions and 12 deletions.
14 changes: 13 additions & 1 deletion packages/jest-docblock/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Pragmas can also take arguments:
`jest-docblock` can:
* extract the docblock from some code as a string
* parse a docblock string's pragmas into an object
* print an object and some comments back to a string

## Installation
```sh
Expand All @@ -56,13 +57,18 @@ const code = `
}
`;

const { extract, parse } = require("jest-docblock");
const { extract, parse, parseWithComments, print } = require("jest-docblock");

const docblock = extract(code);
console.log(docblock); // "/**\n * Everything is awesome!\n * \n * @everything is:awesome\n * @flow\n */"

const pragmas = parse(docblock);
console.log(pragmas); // { everything: "is:awesome", flow: "" }

const parsed = parseWithComments(docblock);
console.log(parsed); // { comments: "Everything is awesome!", pragmas: { everything: "is:awesome", flow: "" } }

console.log(print({pragmas, comments: "hi!"})) // /**\n * hi!\n *\n * @everything is:awesome\n * @flow\n */;
```

## API Documentation
Expand All @@ -72,3 +78,9 @@ Extracts a docblock from some file contents. Returns the docblock contained in `

### `parse(docblock: string): {[key: string]: string}`
Parses the pragmas in a docblock string into an object whose keys are the pragma tags and whose values are the arguments to those pragmas.

### `parseWithComments(docblock: string): { comments: string, pragmas: {[key: string]: string} }`
Similar to `parse` except this method also returns the comments from the docblock. Useful when used with `print()`.

### `print({ comments?: string, pragmas?: {[key: string]: string} }): string`
Prints an object of key-value pairs back into a docblock. If `comments` are provided, they will be positioned on the top of the docblock.
5 changes: 4 additions & 1 deletion packages/jest-docblock/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,8 @@
"url": "https://github.com/facebook/jest.git"
},
"license": "MIT",
"main": "build/index.js"
"main": "build/index.js",
"dependencies": {
"detect-newline": "^2.1.0"
}
}
182 changes: 180 additions & 2 deletions packages/jest-docblock/src/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@

'use strict';

const os = require('os');
const docblock = require('../');
import os from 'os';
import * as docblock from '..';

describe('docblock', () => {
it('extracts valid docblock with line comment', () => {
Expand Down Expand Up @@ -212,4 +212,182 @@ describe('docblock', () => {
providesModule: 'apple/banana',
});
});

it('extracts comments from docblock', () => {
const code =
'/**' +
os.EOL +
' * hello world' +
os.EOL +
' * @flow yes' +
os.EOL +
' */';
expect(docblock.parseWithComments(code)).toEqual({
comments: 'hello world',
pragmas: {flow: 'yes'},
});
});

it('extracts multiline comments from docblock', () => {
const code =
'/**' +
os.EOL +
' * hello' +
os.EOL +
' * world' +
os.EOL +
' * @flow yes' +
os.EOL +
' */';
expect(docblock.parseWithComments(code)).toEqual({
comments: 'hello' + os.EOL + 'world',
pragmas: {flow: 'yes'},
});
});

it('extracts comments from beginning and end of docblock', () => {
const code =
'/**' +
os.EOL +
' * hello' +
os.EOL +
' * @flow yes' +
os.EOL +
' * ' +
os.EOL +
' * world' +
os.EOL +
' */';
expect(docblock.parseWithComments(code)).toEqual({
comments: 'hello' + os.EOL + os.EOL + 'world',
pragmas: {flow: 'yes'},
});
});

it('extracts docblock comments as CRLF when docblock contains CRLF', () => {
const code = '/**\r\n * foo\r\n * bar\r\n*/';
expect(docblock.parseWithComments(code)).toEqual({
comments: 'foo\r\nbar',
pragmas: {},
});
});

it('extracts docblock comments as LF when docblock contains LF', () => {
const code = '/**\n * foo\n * bar\n*/';
expect(docblock.parseWithComments(code)).toEqual({
comments: 'foo\nbar',
pragmas: {},
});
});

it('prints docblocks with no pragmas as empty string', () => {
const pragmas = {};
expect(docblock.print({pragmas})).toEqual('');
});

it('prints docblocks with one pragma on one line', () => {
const pragmas = {flow: ''};
expect(docblock.print({pragmas})).toEqual('/** @flow */');
});

it('prints docblocks with multiple pragmas on multiple lines', () => {
const pragmas = {
flow: '',
format: '',
};
expect(docblock.print({pragmas})).toEqual(
'/**' + os.EOL + ' * @flow' + os.EOL + ' * @format' + os.EOL + ' */',
);
});

it('prints docblocks with pragmas', () => {
const pragmas = {
flow: 'foo',
providesModule: 'x/y/z',
};
expect(docblock.print({pragmas})).toEqual(
'/**' +
os.EOL +
' * @flow foo' +
os.EOL +
' * @providesModule x/y/z' +
os.EOL +
' */',
);
});

it('prints docblocks with comments', () => {
const pragmas = {flow: 'foo'};
const comments = 'hello';
expect(docblock.print({comments, pragmas})).toEqual(
'/**' +
os.EOL +
' * hello' +
os.EOL +
' *' +
os.EOL +
' * @flow foo' +
os.EOL +
' */',
);
});

it('prints docblocks with comments and no keys', () => {
const pragmas = {};
const comments = 'Copyright 2004-present Facebook. All Rights Reserved.';
expect(docblock.print({comments, pragmas})).toEqual(
'/**' + os.EOL + ' * ' + comments + os.EOL + ' */',
);
});

it('prints docblocks with multiline comments', () => {
const pragmas = {};
const comments = 'hello' + os.EOL + 'world';
expect(docblock.print({comments, pragmas})).toEqual(
'/**' + os.EOL + ' * hello' + os.EOL + ' * world' + os.EOL + ' */',
);
});

it('prints docblocks that are parseable', () => {
const pragmas = {a: 'b', c: ''};
const comments = 'hello world!';
const formatted = docblock.print({comments, pragmas});
const parsed = docblock.parse(formatted);
expect(parsed).toEqual(pragmas);
});

it('can augment existing docblocks with comments', () => {
const before =
'/**' + os.EOL + ' * Legalese' + os.EOL + ' * @flow' + os.EOL + ' */';
const {comments, pragmas} = docblock.parseWithComments(before);
pragmas.format = '';
const after = docblock.print({comments, pragmas});
expect(after).toEqual(
'/**' +
os.EOL +
' * Legalese' +
os.EOL +
' *' +
os.EOL +
' * @flow' +
os.EOL +
' * @format' +
os.EOL +
' */',
);
});

it('prints docblocks using CRLF if comments contains CRLF', () => {
const pragmas = {};
const comments = 'hello\r\nworld';
const formatted = docblock.print({comments, pragmas});
expect(formatted).toEqual('/**\r\n * hello\r\n * world\r\n */');
});

it('prints docblocks using LF if comments contains LF', () => {
const pragmas = {};
const comments = 'hello\nworld';
const formatted = docblock.print({comments, pragmas});
expect(formatted).toEqual('/**\n * hello\n * world\n */');
});
});
69 changes: 63 additions & 6 deletions packages/jest-docblock/src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@
* @flow
*/

import detectNewline from 'detect-newline';
import {EOL} from 'os';

const commentEndRe = /\*\/$/;
const commentStartRe = /^\/\*\*/;
const docblockRe = /^\s*(\/\*\*?(.|\r?\n)*?\*\/)/;
Expand All @@ -15,14 +18,23 @@ const ltrimRe = /^\s*/;
const multilineRe = /(?:^|\r?\n) *(@[^\r\n]*?) *\r?\n *([^@\r\n\s][^@\r\n]+?) *\r?\n/g;
const propertyRe = /(?:^|\r?\n) *@(\S+) *([^\r\n]*)/g;
const stringStartRe = /(\r?\n|^) *\*/g;
const lineStartRe = /(\r?\n|^) */g;
const wsRe = /[\t ]+/g;

function extract(contents: string): string {
export function extract(contents: string): string {
const match = contents.match(docblockRe);
return match ? match[0].replace(ltrimRe, '') || '' : '';
}

function parse(docblock: string): {[key: string]: string} {
export function parse(docblock: string): {[key: string]: string} {
return parseWithComments(docblock).pragmas;
}

export function parseWithComments(
docblock: string,
): {comments: string, pragmas: {[key: string]: string}} {
const line = detectNewline(docblock) || EOL;

docblock = docblock
.replace(commentStartRe, '')
.replace(commentEndRe, '')
Expand All @@ -34,17 +46,62 @@ function parse(docblock: string): {[key: string]: string} {
let prev = '';
while (prev !== docblock) {
prev = docblock;
docblock = docblock.replace(multilineRe, '\n$1 $2\n');
docblock = docblock.replace(multilineRe, `${line}$1 $2${line}`);
}
docblock = docblock.trim();

const result = Object.create(null);
const comments = docblock.replace(propertyRe, '').replace(lineStartRe, line);
let match;
while ((match = propertyRe.exec(docblock))) {
result[match[1]] = match[2];
}
return result;
return {comments: comments.trim(), pragmas: result};
}

exports.extract = extract;
exports.parse = parse;
export function print({
comments = '',
pragmas = {},
}: {
comments?: string,
pragmas?: {[key: string]: string},
}): string {
const line = detectNewline(comments) || EOL;
const head = '/**';
const start = ' *';
const tail = ' */';

const keys = Object.keys(pragmas);

const printedObject = keys
.map(key => start + ' ' + printKeyValue(key, pragmas[key]) + line)
.join('');

if (!comments) {
if (keys.length === 0) {
return '';
}
if (keys.length === 1) {
return `${head} ${printKeyValue(keys[0], pragmas[keys[0]])}${tail}`;
}
}

const printedComments =
comments
.split(line)
.map(textLine => `${start} ${textLine}`)
.join(line) + line;

return (
head +
line +
(comments ? printedComments : '') +
(comments && keys.length ? start + line : '') +
printedObject +
tail
);
}

function printKeyValue(key, value) {
return `@${key} ${value}`.trim();
}
2 changes: 1 addition & 1 deletion packages/jest-haste-map/src/worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {SerializableError} from 'types/TestResult';
import type {HasteImpl, WorkerMessage, WorkerCallback} from './types';

import path from 'path';
import docblock from 'jest-docblock';
import * as docblock from 'jest-docblock';
import fs from 'graceful-fs';
import H from './constants';
import extractRequires from './lib/extract_requires';
Expand Down
2 changes: 1 addition & 1 deletion packages/jest-runner/src/run_test.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import {
} from 'jest-util';
import jasmine2 from 'jest-jasmine2';
import {getTestEnvironment} from 'jest-config';
import docblock from 'jest-docblock';
import * as docblock from 'jest-docblock';

// The default jest-runner is required because it is the default test runner
// and required implicitly through the `testRunner` ProjectConfig option.
Expand Down
4 changes: 4 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1971,6 +1971,10 @@ detect-indent@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-5.0.0.tgz#3871cc0a6a002e8c3e5b3cf7f336264675f06b9d"

detect-newline@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-2.1.0.tgz#f41f1c10be4b00e87b5f13da680759f2c5bfd3e2"

detective@^4.0.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/detective/-/detective-4.5.0.tgz#6e5a8c6b26e6c7a254b1c6b6d7490d98ec91edd1"
Expand Down

0 comments on commit bfeee26

Please sign in to comment.