Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
130 changes: 130 additions & 0 deletions src/jsdoc/example-tag-parser.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/** @Flow */

type Example = {
raw: string,
description?: string,
code?: Array,
returns?: Object
};

const token = {
CODE: 'CODE',
DESC_DELIMITER: 'DESC_DELIMITER',
RETURNS_DELIMITER: 'RETURNS_DELIMITER',
COMMENT: 'COMMENT',
};

type Token = $Keys<typeof token>;

const status = {
IN_CODE: 'IN_CODE',
IN_DESCRIPTION: 'IN_DESCRIPTION',
IN_RETURNS: 'IN_RETURNS',
NONE: 'NONE',
};

type Status = $Keys<typeof status>;

function isComment(str: string): Boolean {
return (str.startsWith('//'));
}

function isCode(str: string): Boolean {
return !isComment(str);
}

function isDescriptionDelimiter(str: string): Boolean {
return (str === '//-');
}

function isReturnsDelimiter(str: string): Boolean {
return (str === '//=>');
}

function tokenize(str: string): Token {
if (isCode(str)) return token.CODE;
if (isDescriptionDelimiter(str)) return token.DESC_DELIMITER;
if (isReturnsDelimiter(str)) return token.RETURNS_DELIMITER;
return token.COMMENT;
}

function stripComment(str: string): string {
return (isComment(str)) ? str.replace('//', '') : str;
}

function updateExample(line, field, example) {
line = stripComment(line).trim();
if (example[field]) {
example[field] += `\n${line}`;
}
else {
example[field] = line;
}
}

function parseToken(currentToken, line, currentStatus, example): Status {
switch (currentToken) {
case token.DESC_DELIMITER:
if (currentStatus === status.NONE) currentStatus = status.IN_DESCRIPTION;
else if (currentStatus === status.IN_DESCRIPTION) currentStatus = status.NONE; // end desc block
else throw new Error(`${currentToken} can't appear after code or returns block`);
break;
case token.RETURNS_DELIMITER:
if (currentStatus === status.NONE || currentStatus === status.IN_CODE) currentStatus = status.IN_RETURNS;
else if (currentStatus === status.IN_RETURNS) currentStatus = status.NONE; // end returns block
else throw new Error(`${currentToken} must appear after code block`);
break;
case token.CODE:
if (currentStatus === status.NONE || currentStatus === status.IN_CODE) updateExample(line, 'code', example);
else throw new Error(`${currentToken} can't appear inside desc or returns block`);
break;
case token.COMMENT:
if (currentStatus === status.IN_DESCRIPTION) updateExample(line, 'description', example);
else if (currentStatus === status.IN_RETURNS) updateExample(line, 'returns', example);
else throw new Error(`${currentToken} must appear inside desc or returns block`);
break;
default:
throw new Error('Unrecognized token');
}
return currentStatus;
}

/**
* An example of an "example" tag
*
* * @example
* //-
* // Adds two numbers
* //-
* add(2, 3);
* //=>
* // 5
* //=>
*
* @returns {Example} Returns the parsed "example" tag
* {
* raw: '//-\n// Adds two numbers\n//-\nadd(2, 3);\n//=>\n// 5\n//=>',
* description: 'Adds two numbers',
* code: 'add(2, 3);',
* returns: '5'
* }
*/
export default function parse(exampleRaw: string): Example {
const example: Example = {};
let currentStatus: Status = status.NONE;
example.raw = exampleRaw;
try {
const lines = exampleRaw.split('\n');
for (let line of lines) {
line = line.trim();
if (!line) continue;
const currentToken = tokenize(line);
currentStatus = parseToken(currentToken, line, currentStatus, example);
}
}
catch (e) {
// That's fine. The example probably doesn't comply with our standard
}

return example;
}
43 changes: 34 additions & 9 deletions src/jsdoc/parser.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,16 @@
import esprima from 'esprima';
import doctrine from 'doctrine';
import walk from 'esprima-walk';
import exampleTagParser from './example-tag-parser';

export type ParsedDocs = {
name: string,
description: string,
args?: Array,
returns?: Object,
name: string,
description: string,
args?: Array,
returns?: Object,
access?: string,
examples?: Array,
static?: Boolean
};

const parsedData: Array<ParsedDocs> = [];
Expand Down Expand Up @@ -51,16 +55,34 @@ function handleFunctionType(node: Object) {
const args = [];
let description = '';
let returns = {};
let isStatic = false;
let access = 'public';
let examples = [];
if (node.leadingComments && node.leadingComments.length) {
const commentsAst = getCommentsAST(node);
description = commentsAst.description;

for (const tag of commentsAst.tags) {
if (tag.title === 'param') {
args.push(formatTag(tag));
}
if (tag.title === 'returns') {
returns = formatTag(tag);
switch (tag.title) {
case 'param':
args.push(formatTag(tag));
break;
case 'returns' :
returns = formatTag(tag);
break;
case 'static':
isStatic = true;
break;
case 'private':
case 'protected':
access = tag.title;
break;
case 'access':
access = tag.access;
break;
case 'example':
examples.push(exampleTagParser(tag.description));
break;
}
}
}
Expand All @@ -71,6 +93,9 @@ function handleFunctionType(node: Object) {
description,
args,
returns,
access,
examples,
static: isStatic,
};
parsedData.push(item);
}
Expand Down