Huge News!Announcing our $40M Series B led by Abstract Ventures.Learn More
Socket
Sign inDemoInstall
Socket

atomdoc

Package Overview
Dependencies
Maintainers
7
Versions
17
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

atomdoc - npm Package Compare versions

Comparing version 1.1.0 to 1.2.0

2

package.json
{
"name": "atomdoc",
"version": "1.1.0",
"version": "1.2.0",
"description": "An atomdoc parser",

@@ -5,0 +5,0 @@ "main": "./src/atomdoc.js",

@@ -18,3 +18,3 @@ # AtomDoc parser

It has only one method, `parse`, which takes no options.
It has only one method, `parse`:

@@ -29,2 +29,5 @@ ```coffee

doc = AtomDoc.parse(docString)
# Alternatively, you can avoid parsing "Returns" statements in documentation (useful for class-level documentation):
doc = AtomDoc.parse(docString, {parseReturns: false})
```

@@ -31,0 +34,0 @@

@@ -22,333 +22,373 @@ const marked = require('marked')

// * `docString` a string from the documented object to be parsed
// * `options` an optional {Object} with the following optional keys:
// * `parseReturns`. A {Boolean} describing whether "Returns" statements
// should be parsed or not. Defaults to true.
//
// Returns a {Doc} object
const parse = function (docString) {
const lexer = new marked.Lexer()
const tokens = lexer.lex(docString)
const firstToken = tokens[0]
const parse = function (docString, {parseReturns} = {}) {
if (parseReturns == null) {
parseReturns = true
}
if (!firstToken || (firstToken.type !== 'paragraph')) {
throw new Error('Doc string must start with a paragraph!')
return new Parser(parseReturns).parse(docString)
}
class Parser {
constructor (parseReturns) {
this.parseReturns = parseReturns
this.stopOnSectionBoundaries = this.stopOnSectionBoundaries.bind(this)
}
const doc = new Doc(docString)
parse (docString) {
const lexer = new marked.Lexer()
const tokens = lexer.lex(docString)
const firstToken = tokens[0]
Object.assign(doc, parseSummaryAndDescription(tokens))
while (tokens.length) {
let args, events, examples, returnValues, titledArgs
if ((titledArgs = parseTitledArgumentsSection(tokens))) {
if (doc.titledArguments == null) doc.titledArguments = []
doc.titledArguments.push(titledArgs)
} else if ((args = parseArgumentsSection(tokens))) {
doc.arguments = args
} else if ((events = parseEventsSection(tokens))) {
doc.events = events
} else if ((examples = parseExamplesSection(tokens))) {
doc.examples = examples
} else if ((returnValues = parseReturnValues(tokens, true))) {
doc.setReturnValues(returnValues)
} else {
// These tokens are basically in no-mans land. We'll add them to the
// description so they dont get lost.
const extraDescription = generateDescription(tokens, stopOnSectionBoundaries)
doc.description += `\n\n${extraDescription}`
if (!firstToken || (firstToken.type !== 'paragraph')) {
throw new Error('Doc string must start with a paragraph!')
}
}
return doc
}
const doc = new Doc(docString)
const parseSummaryAndDescription = function (tokens, tokenCallback) {
if (tokenCallback == null) { tokenCallback = stopOnSectionBoundaries }
let summary = ''
let description = ''
let visibility = 'Private'
Object.assign(doc, this.parseSummaryAndDescription(tokens))
let rawVisibility = null
let rawSummary = tokens[0].text
if (rawSummary) {
const visibilityMatch = VisibilityRegex.exec(rawSummary)
if (visibilityMatch) {
visibility = visibilityMatch[1]
rawVisibility = visibilityMatch[0]
if (rawVisibility) rawSummary = rawSummary.replace(rawVisibility, '')
while (tokens.length) {
let args, events, examples, returnValues, titledArgs
if ((titledArgs = this.parseTitledArgumentsSection(tokens))) {
if (doc.titledArguments == null) doc.titledArguments = []
doc.titledArguments.push(titledArgs)
} else if ((args = this.parseArgumentsSection(tokens))) {
doc.arguments = args
} else if ((events = this.parseEventsSection(tokens))) {
doc.events = events
} else if ((examples = this.parseExamplesSection(tokens))) {
doc.examples = examples
} else if (this.parseReturns && (returnValues = this.parseReturnValues(tokens, true))) {
doc.setReturnValues(returnValues)
} else {
// These tokens are basically in no-mans land. We'll add them to the
// description so they dont get lost.
const extraDescription = generateDescription(tokens, this.stopOnSectionBoundaries)
doc.description += `\n\n${extraDescription}`
}
}
}
if (isReturnValue(rawSummary)) {
const returnValues = parseReturnValues(tokens, false)
return {summary, description, visibility, returnValues}
} else {
summary = rawSummary
description = generateDescription(tokens, tokenCallback)
if (rawVisibility) description = description.replace(rawVisibility, '')
return {description, summary, visibility}
return doc
}
}
const parseArgumentsSection = function (tokens) {
const firstToken = tokens[0]
if (firstToken && firstToken.type === 'heading') {
if (firstToken.text !== 'Arguments' || firstToken.depth !== SpecialHeadingDepth) return
} else if (firstToken && firstToken.type === 'list_start') {
if (!isAtArgumentList(tokens)) { return }
} else {
return
}
parseSummaryAndDescription (tokens, tokenCallback) {
if (tokenCallback == null) { tokenCallback = this.stopOnSectionBoundaries }
let summary = ''
let description = ''
let visibility = 'Private'
let args = null
let rawVisibility = null
let rawSummary = tokens[0].text
if (rawSummary) {
const visibilityMatch = VisibilityRegex.exec(rawSummary)
if (visibilityMatch) {
visibility = visibilityMatch[1]
rawVisibility = visibilityMatch[0]
if (rawVisibility) rawSummary = rawSummary.replace(rawVisibility, '')
}
}
if (firstToken.type === 'list_start') {
args = parseArgumentList(tokens)
} else {
tokens.shift() // consume the header
// consume any BS before the args list
generateDescription(tokens, stopOnSectionBoundaries)
args = parseArgumentList(tokens)
if (isReturnValue(rawSummary)) {
const returnValues = this.parseReturnValues(tokens, false)
return {summary, description, visibility, returnValues}
} else {
summary = rawSummary
description = generateDescription(tokens, tokenCallback)
if (rawVisibility) description = description.replace(rawVisibility, '')
return {description, summary, visibility}
}
}
return args
}
parseArgumentsSection (tokens) {
const firstToken = tokens[0]
if (firstToken && firstToken.type === 'heading') {
if (firstToken.text !== 'Arguments' || firstToken.depth !== SpecialHeadingDepth) return
} else if (firstToken && firstToken.type === 'list_start') {
if (!isAtArgumentList(tokens)) { return }
} else {
return
}
const parseTitledArgumentsSection = function (tokens) {
const firstToken = tokens[0]
if (!firstToken || firstToken.type !== 'heading') return
if (!firstToken.text.startsWith('Arguments:') ||
firstToken.depth !== SpecialHeadingDepth
) {
return
}
let args = null
return {
title: tokens.shift().text.replace('Arguments:', '').trim(),
description: generateDescription(tokens, stopOnSectionBoundaries),
arguments: parseArgumentList(tokens)
if (firstToken.type === 'list_start') {
args = this.parseArgumentList(tokens)
} else {
tokens.shift() // consume the header
// consume any BS before the args list
generateDescription(tokens, this.stopOnSectionBoundaries)
args = this.parseArgumentList(tokens)
}
return args
}
}
const parseEventsSection = function (tokens) {
let firstToken = tokens[0]
if (
!firstToken ||
firstToken.type !== 'heading' ||
firstToken.text !== 'Events' ||
firstToken.depth !== SpecialHeadingDepth
) { return }
parseTitledArgumentsSection (tokens) {
const firstToken = tokens[0]
if (!firstToken || firstToken.type !== 'heading') return
if (!firstToken.text.startsWith('Arguments:') ||
firstToken.depth !== SpecialHeadingDepth
) {
return
}
const eventHeadingDepth = SpecialHeadingDepth + 1
// We consume until there is a heading of h3 which denotes the beginning of an event.
const stopTokenCallback = function (token, tokens) {
if ((token.type === 'heading') && (token.depth === eventHeadingDepth)) {
return false
return {
title: tokens.shift().text.replace('Arguments:', '').trim(),
description: generateDescription(tokens, this.stopOnSectionBoundaries),
arguments: this.parseArgumentList(tokens)
}
return stopOnSectionBoundaries(token, tokens)
}
const events = []
tokens.shift() // consume the header
parseEventsSection (tokens) {
let firstToken = tokens[0]
if (
!firstToken ||
firstToken.type !== 'heading' ||
firstToken.text !== 'Events' ||
firstToken.depth !== SpecialHeadingDepth
) { return }
while (tokens.length) {
const eventHeadingDepth = SpecialHeadingDepth + 1
// We consume until there is a heading of h3 which denotes the beginning of an event.
generateDescription(tokens, stopTokenCallback)
firstToken = tokens[0]
if (
firstToken &&
firstToken.type === 'heading' &&
firstToken.depth === eventHeadingDepth
) {
tokens.shift() // consume the header
const {summary, description, visibility} = parseSummaryAndDescription(
tokens, stopTokenCallback)
const name = firstToken.text
let args = parseArgumentList(tokens)
if (args.length === 0) args = null
events.push({name, summary, description, visibility, arguments: args})
} else {
break
const stopTokenCallback = (token, tokens) => {
if ((token.type === 'heading') && (token.depth === eventHeadingDepth)) {
return false
}
return this.stopOnSectionBoundaries(token, tokens)
}
}
if (events.length) { return events }
}
const events = []
tokens.shift() // consume the header
const parseExamplesSection = function (tokens) {
let firstToken = tokens[0]
if (
!firstToken ||
firstToken.type !== 'heading' ||
firstToken.text !== 'Examples' ||
firstToken.depth !== SpecialHeadingDepth
) { return }
while (tokens.length) {
// We consume until there is a heading of h3 which denotes the beginning of an event.
generateDescription(tokens, stopTokenCallback)
const examples = []
tokens.shift() // consume the header
while (tokens.length) {
const description = generateDescription(tokens, function (token, tokens) {
if (token.type === 'code') return false
return stopOnSectionBoundaries(token, tokens)
})
firstToken = tokens[0]
if (firstToken.type === 'code') {
const example = {
description,
lang: firstToken.lang,
code: firstToken.text,
raw: generateCode(tokens)
firstToken = tokens[0]
if (
firstToken &&
firstToken.type === 'heading' &&
firstToken.depth === eventHeadingDepth
) {
tokens.shift() // consume the header
const {summary, description, visibility} = this.parseSummaryAndDescription(
tokens, stopTokenCallback)
const name = firstToken.text
let args = this.parseArgumentList(tokens)
if (args.length === 0) args = null
events.push({name, summary, description, visibility, arguments: args})
} else {
break
}
examples.push(example)
} else {
break
}
if (events.length) { return events }
}
if (examples.length) { return examples }
}
parseExamplesSection (tokens) {
let firstToken = tokens[0]
if (
!firstToken ||
firstToken.type !== 'heading' ||
firstToken.text !== 'Examples' ||
firstToken.depth !== SpecialHeadingDepth
) { return }
const parseReturnValues = function (tokens, consumeTokensAfterReturn) {
let normalizedString
if (consumeTokensAfterReturn == null) { consumeTokensAfterReturn = false }
const firstToken = tokens[0]
if (
!firstToken ||
!['paragraph', 'text'].includes(firstToken.type) ||
!isReturnValue(firstToken.text)
) { return }
const examples = []
tokens.shift() // consume the header
// there might be a `Public: ` in front of the return.
const returnsMatches = ReturnsRegex.exec(firstToken.text)
if (consumeTokensAfterReturn) {
normalizedString = generateDescription(tokens, () => true)
if (returnsMatches[1]) {
normalizedString = normalizedString.replace(returnsMatches[1], '')
while (tokens.length) {
const description = generateDescription(tokens, (token, tokens) => {
if (token.type === 'code') return false
return this.stopOnSectionBoundaries(token, tokens)
})
firstToken = tokens[0]
if (firstToken.type === 'code') {
const example = {
description,
lang: firstToken.lang,
code: firstToken.text,
raw: generateCode(tokens)
}
examples.push(example)
} else {
break
}
}
} else {
const token = tokens.shift()
normalizedString = token.text
if (returnsMatches[1]) {
normalizedString = normalizedString.replace(returnsMatches[1], '')
}
normalizedString = normalizedString.replace(/\s{2,}/g, ' ')
if (examples.length) { return examples }
}
let returnValues = null
parseReturnValues (tokens, consumeTokensAfterReturn) {
let normalizedString
if (consumeTokensAfterReturn == null) { consumeTokensAfterReturn = false }
const firstToken = tokens[0]
if (
!firstToken ||
!['paragraph', 'text'].includes(firstToken.type) ||
!isReturnValue(firstToken.text)
) { return }
while (normalizedString) {
const nextIndex = normalizedString.indexOf('Returns', 1)
let returnString = normalizedString
if (nextIndex > -1) {
returnString = normalizedString.substring(0, nextIndex)
normalizedString = normalizedString.substring(nextIndex, normalizedString.length)
// there might be a `Public: ` in front of the return.
const returnsMatches = ReturnsRegex.exec(firstToken.text)
if (consumeTokensAfterReturn) {
normalizedString = generateDescription(tokens, () => true)
if (returnsMatches[1]) {
normalizedString = normalizedString.replace(returnsMatches[1], '')
}
} else {
normalizedString = null
const token = tokens.shift()
normalizedString = token.text
if (returnsMatches[1]) {
normalizedString = normalizedString.replace(returnsMatches[1], '')
}
normalizedString = normalizedString.replace(/\s{2,}/g, ' ')
}
if (returnValues == null) { returnValues = [] }
returnValues.push({
type: getLinkMatch(returnString),
description: returnString.trim()
})
let returnValues = null
while (normalizedString) {
const nextIndex = normalizedString.indexOf('Returns', 1)
let returnString = normalizedString
if (nextIndex > -1) {
returnString = normalizedString.substring(0, nextIndex)
normalizedString = normalizedString.substring(nextIndex, normalizedString.length)
} else {
normalizedString = null
}
if (returnValues == null) { returnValues = [] }
returnValues.push({
type: getLinkMatch(returnString),
description: returnString.trim()
})
}
return returnValues
}
return returnValues
}
// Parses argument lists like this one:
//
// * `something` A {Bool}
// * `somethingNested` A nested object
parseArgumentList (tokens) {
let depth = 0
let args = []
let argumentsList = null
const argumentsListStack = []
let argument = null
const argumentStack = []
// Parses argument lists like this one:
//
// * `something` A {Bool}
// * `somethingNested` A nested object
const parseArgumentList = function (tokens) {
let depth = 0
let args = []
let argumentsList = null
const argumentsListStack = []
let argument = null
const argumentStack = []
while (tokens.length && (tokens[0].type === 'list_start' || depth)) {
const token = tokens[0]
switch (token.type) {
case 'list_start':
// This list might not be a argument list. Check...
const parseAsArgumentList = isAtArgumentList(tokens)
if (parseAsArgumentList) {
depth++
if (argumentsList) argumentsListStack.push(argumentsList)
argumentsList = []
tokens.shift()
} else if (argument) {
// If not, consume the list as part of the description
if (!argument.text) argument.text = []
argument.text.push(`\n${generateList(tokens)}`)
}
break
while (tokens.length && (tokens[0].type === 'list_start' || depth)) {
const token = tokens[0]
switch (token.type) {
case 'list_start':
// This list might not be a argument list. Check...
const parseAsArgumentList = isAtArgumentList(tokens)
if (parseAsArgumentList) {
depth++
if (argumentsList) argumentsListStack.push(argumentsList)
argumentsList = []
case 'list_item_start':
case 'loose_item_start':
if (argument) { argumentStack.push(argument) }
argument = {}
tokens.shift()
} else if (argument) {
// If not, consume the list as part of the description
break
case 'code':
if (!argument.text) argument.text = []
argument.text.push(`\n${generateList(tokens)}`)
}
break
argument.text.push(`\n${generateCode(tokens)}`)
break
case 'list_item_start':
case 'loose_item_start':
if (argument) { argumentStack.push(argument) }
argument = {}
tokens.shift()
break
case 'text':
if (!argument.text) argument.text = []
argument.text.push(token.text)
tokens.shift()
break
case 'code':
if (!argument.text) argument.text = []
argument.text.push(`\n${generateCode(tokens)}`)
break
case 'list_item_end':
case 'loose_item_end':
if (argument) {
Object.assign(argument,
this.parseListItem(argument.text.join(' ').replace(/ \n/g, '\n')))
argumentsList.push(argument)
delete argument.text
}
case 'text':
if (!argument.text) argument.text = []
argument.text.push(token.text)
tokens.shift()
break
argument = argumentStack.pop()
tokens.shift()
break
case 'list_item_end':
case 'loose_item_end':
if (argument) {
Object.assign(argument,
parseListItem(argument.text.join(' ').replace(/ \n/g, '\n')))
argumentsList.push(argument)
delete argument.text
}
case 'list_end':
depth--
if (argument) {
argument.children = argumentsList
argumentsList = argumentsListStack.pop()
} else {
args = argumentsList
}
tokens.shift()
break
argument = argumentStack.pop()
tokens.shift()
break
default: tokens.shift()
}
}
case 'list_end':
depth--
if (argument) {
argument.children = argumentsList
argumentsList = argumentsListStack.pop()
} else {
args = argumentsList
}
tokens.shift()
break
return args
}
default: tokens.shift()
parseListItem (argumentString) {
let isOptional
let name = null
let type = null
let description = argumentString
const nameMatches = ArgumentListItemRegex.exec(argumentString)
if (nameMatches) {
name = nameMatches[1]
description = description.replace(nameMatches[0], '')
type = getLinkMatch(description)
isOptional = !!nameMatches[3]
}
return {name, description, type, isOptional}
}
return args
}
stopOnSectionBoundaries (token, tokens) {
if (['paragraph', 'text'].includes(token.type)) {
if (this.parseReturns && isReturnValue(token.text)) {
return false
}
} else if (token.type === 'heading') {
if (token.depth === SpecialHeadingDepth && SpecialHeadings.test(token.text)) {
return false
}
} else if (token.type === 'list_start') {
let listToken = null
for (listToken of tokens) {
if (listToken.type === 'text') break
}
const parseListItem = function (argumentString) {
let isOptional
let name = null
let type = null
let description = argumentString
// Check if list is an arguments list. If it starts with `someVar`, it is.
if (listToken && ArgumentListItemRegex.test(listToken.text)) return false
}
const nameMatches = ArgumentListItemRegex.exec(argumentString)
if (nameMatches) {
name = nameMatches[1]
description = description.replace(nameMatches[0], '')
type = getLinkMatch(description)
isOptional = !!nameMatches[3]
return true
}
return {name, description, type, isOptional}
}

@@ -380,24 +420,2 @@

const stopOnSectionBoundaries = function (token, tokens) {
if (['paragraph', 'text'].includes(token.type)) {
if (isReturnValue(token.text)) {
return false
}
} else if (token.type === 'heading') {
if (token.depth === SpecialHeadingDepth && SpecialHeadings.test(token.text)) {
return false
}
} else if (token.type === 'list_start') {
let listToken = null
for (listToken of tokens) {
if (listToken.type === 'text') break
}
// Check if list is an arguments list. If it starts with `someVar`, it is.
if (listToken && ArgumentListItemRegex.test(listToken.text)) return false
}
return true
}
// Will read / consume tokens down to a special section (args, events, examples)

@@ -404,0 +422,0 @@ const generateDescription = function (tokens, tokenCallback) {

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc