function parse_block (source, opts) {
  var trim = opts.trim
    ? function trim (s) { return s.trim() }
    : function trim (s) { return s }

  var source_str = source
    .map(function (line) { return trim(line.source) })
    .join('\n')

  source_str = trim(source_str)

  var start = source[0].number

  // merge source lines into tags
  // we assume tag starts with "@"
  source = source
    .reduce(function (tags, line) {
      line.source = trim(line.source)

      if (line.source.match(/^\s*@(\w+)/)) {
        tags.push({source: [line.source], line: line.number})
      } else {
        var tag = tags[tags.length - 1]
        if (opts.join !== undefined && opts.join !== false && opts.join !== 0 &&
            !line.startWithStar && tag.source.length > 0) {
          var source
          if (typeof opts.join === 'string') {
            source = opts.join + line.source.replace(/^\s+/, '')
          } else if (typeof opts.join === 'number') {
            source = line.source
          } else {
            source = ' ' + line.source.replace(/^\s+/, '')
          }
          tag.source[tag.source.length - 1] += source
        } else {
          tag.source.push(line.source)
        }
      }

      return tags
    }, [{source: []}])
    .map(function (tag) {
      tag.source = trim(tag.source.join('\n'))
      return tag
    })

  // Block description
  var description = source.shift()

  // skip if no descriptions and no tags
  if (description.source === '' && source.length === 0) {
    return null
  }

  var tags = source.reduce(function (tags, tag) {
    var tag_node = parse_tag(tag.source, opts.parsers)

    if (!tag_node) { return tags }

    tag_node.line = tag.line
    tag_node.source = tag.source

    if (opts.dotted_names && tag_node.name.indexOf('.') !== -1) {
      var parent_name
      var parent_tag
      var parent_tags = tags
      var parts = tag_node.name.split('.')

      while (parts.length > 1) {
        parent_name = parts.shift()
        parent_tag = find(parent_tags, {
          tag: tag_node.tag,
          name: parent_name
        })

        if (!parent_tag) {
          parent_tag = {
            tag: tag_node.tag,
            line: Number(tag_node.line),
            name: parent_name,
            type: '',
            description: ''
          }
          parent_tags.push(parent_tag)
        }

        parent_tag.tags = parent_tag.tags || []
        parent_tags = parent_tag.tags
      }

      tag_node.name = parts[0]
      parent_tags.push(tag_node)
      return tags
    }

    return tags.concat(tag_node)
  }, [])

  return {
    tags: tags,
    line: start,
    description: description.source,
    source: source_str
  }
}
function parse_block (source, opts) {
  var trim = opts.trim
    ? function trim (s) { return s.trim() }
    : function trim (s) { return s }

  var source_str = source
    .map(function (line) { return trim(line.source) })
    .join('\n')

  source_str = trim(source_str)

  var start = source[0].number

  
  
  source = source
    .reduce(function (tags, line) {
      line.source = trim(line.source)

      if (line.source.match(/^\s*@(\w+)/)) {
        tags.push({source: [line.source], line: line.number})
      } else {
        var tag = tags[tags.length - 1]
        if (opts.join !== undefined && opts.join !== false && opts.join !== 0 &&
            !line.startWithStar && tag.source.length > 0) {
          var source
          if (typeof opts.join === 'string') {
            source = opts.join + line.source.replace(/^\s+/, '')
          } else if (typeof opts.join === 'number') {
            source = line.source
          } else {
            source = ' ' + line.source.replace(/^\s+/, '')
          }
          tag.source[tag.source.length - 1] += source
        } else {
          tag.source.push(line.source)
        }
      }

      return tags
    }, [{source: []}])
    .map(function (tag) {
      tag.source = trim(tag.source.join('\n'))
      return tag
    })

  
  var description = source.shift()

  
  if (description.source === '' && source.length === 0) {
    return null
  }

  var tags = source.reduce(function (tags, tag) {
    var tag_node = parse_tag(tag.source, opts.parsers)

    if (!tag_node) { return tags }

    tag_node.line = tag.line
    tag_node.source = tag.source

    if (opts.dotted_names && tag_node.name.indexOf('.') !== -1) {
      var parent_name
      var parent_tag
      var parent_tags = tags
      var parts = tag_node.name.split('.')

      while (parts.length > 1) {
        parent_name = parts.shift()
        parent_tag = find(parent_tags, {
          tag: tag_node.tag,
          name: parent_name
        })

        if (!parent_tag) {
          parent_tag = {
            tag: tag_node.tag,
            line: Number(tag_node.line),
            name: parent_name,
            type: '',
            description: ''
          }
          parent_tags.push(parent_tag)
        }

        parent_tag.tags = parent_tag.tags || []
        parent_tags = parent_tag.tags
      }

      tag_node.name = parts[0]
      parent_tags.push(tag_node)
      return tags
    }

    return tags.concat(tag_node)
  }, [])

  return {
    tags: tags,
    line: start,
    description: description.source,
    source: source_str
  }
}
function parse_block ( source , opts ) { var trim = opts . trim ? function trim ( s ) { return s . trim ( ) } : function trim ( s ) { return s } var source_str = source . map ( function ( line ) { return trim ( line . source ) } ) . join ( '\n' ) source_str = trim ( source_str ) var start = source [ 0 ] . number // merge source lines into tags // we assume tag starts with "@" source = source . reduce ( function ( tags , line ) { line . source = trim ( line . source ) if ( line . source . match ( / ^\s*@(\w+) / ) ) { tags . push ( { source : [ line . source ] , line : line . number } ) } else { var tag = tags [ tags . length - 1 ] if ( opts . join !== undefined && opts . join !== false && opts . join !== 0 && ! line . startWithStar && tag . source . length > 0 ) { var source if ( typeof opts . join === 'string' ) { source = opts . join + line . source . replace ( / ^\s+ / , '' ) } else if ( typeof opts . join === 'number' ) { source = line . source } else { source = ' ' + line . source . replace ( / ^\s+ / , '' ) } tag . source [ tag . source . length - 1 ] += source } else { tag . source . push ( line . source ) } } return tags } , [ { source : [ ] } ] ) . map ( function ( tag ) { tag . source = trim ( tag . source . join ( '\n' ) ) return tag } ) // Block description var description = source . shift ( ) // skip if no descriptions and no tags if ( description . source === '' && source . length === 0 ) { return null } var tags = source . reduce ( function ( tags , tag ) { var tag_node = parse_tag ( tag . source , opts . parsers ) if ( ! tag_node ) { return tags } tag_node . line = tag . line tag_node . source = tag . source if ( opts . dotted_names && tag_node . name . indexOf ( '.' ) !== - 1 ) { var parent_name var parent_tag var parent_tags = tags var parts = tag_node . name . split ( '.' ) while ( parts . length > 1 ) { parent_name = parts . shift ( ) parent_tag = find ( parent_tags , { tag : tag_node . tag , name : parent_name } ) if ( ! parent_tag ) { parent_tag = { tag : tag_node . tag , line : Number ( tag_node . line ) , name : parent_name , type : '' , description : '' } parent_tags . push ( parent_tag ) } parent_tag . tags = parent_tag . tags || [ ] parent_tags = parent_tag . tags } tag_node . name = parts [ 0 ] parent_tags . push ( tag_node ) return tags } return tags . concat ( tag_node ) } , [ ] ) return { tags : tags , line : start , description : description . source , source : source_str } }
Parses comment block (array of String lines)
Parses comment block ( array of String lines )