@ 260902.1
current
import { spawnSync } from 'node:child_process'
import Base from 'gitlint-parser-base'

const revertRE = /Revert "(.*)"$/
const workingRE = /Working on v([\d]+)\.([\d]+).([\d]+)$/
const releaseRE = /([\d]{4})-([\d]{2})-([\d]{2}),? Version/
const reviewedByRE = /^Reviewed-By: (.*)$/
const fixesRE = /^Fixes: (.*)$/
const prUrlRE = /^PR-URL: (.*)$/
const refsRE = /^Refs?: (.*)$/

export default class Parser extends Base {
  constructor (str, validator) {
    super(str, validator)
    this.subsystems = []
    this.fixes = []
    this.prUrl = null
    this.refs = []
    this.reviewers = []
    this._metaStart = 0
    this._metaEnd = 0
    this._parse()
  }

  _setMetaStart (n) {
    if (this._metaStart) return
    this._metaStart = n
  }

  _setMetaEnd (n) {
    if (n < this._metaEnd) return
    this._metaEnd = n
  }

  _parseTrailers (body) {
    const interpretTrailers = commitMessage => spawnSync('git', [
      'interpret-trailers', '--only-trailers', '--only-input', '--no-divider'
    ], {
      encoding: 'utf-8',
      input: `'dummy subject\n\n${commitMessage.join('\n')}\n`
    }).stdout

    let originalTrailers
    try {
      originalTrailers = interpretTrailers(body).trim()
    } catch (err) {
      console.warn('git is not available, trailers detection might be a bit ' +
                   'off which is acceptable in most cases', err)
      return body
    }
    const trailerFreeBody = body.slice(1) // clone, and remove the first empty line
    const stillInTrailers = () => {
      const result = interpretTrailers(trailerFreeBody)
      return result.length && originalTrailers.startsWith(result.trim())
    }
    for (let i = trailerFreeBody.length - 1; stillInTrailers(); i--) {
      // Remove last line until git no longer detects any trailers
      trailerFreeBody.pop()
    }
    this._metaStart = trailerFreeBody.length + 1 // the subject line needs to be counted
    for (let i = trailerFreeBody.length - 1; trailerFreeBody[i] === ''; i--) {
      // Remove additional empty line(s)
      trailerFreeBody.pop()
    }
    this._metaEnd = body.length - 1
    this.trailerFreeBody = trailerFreeBody
    return (this.trailers = originalTrailers.split('\n'))
  }

  _parse () {
    const revert = this.isRevert()
    if (!revert) {
      this.subsystems = getSubsystems(this.title || '')
    } else {
      const matches = this.title.match(revertRE)
      if (matches) {
        const title = matches[1]
        this.subsystems = getSubsystems(title)
      }
    }

    const trailers = this._parseTrailers(this.body)

    for (let i = 0; i < trailers.length; i++) {
      const line = trailers[i]
      const reviewedBy = reviewedByRE.exec(line)
      if (reviewedBy) {
        this._setMetaStart(i)
        this._setMetaEnd(i)
        this.reviewers.push(reviewedBy[1])
        continue
      }

      const fixes = fixesRE.exec(line)
      if (fixes) {
        this._setMetaStart(i)
        this._setMetaEnd(i)
        this.fixes.push(fixes[1])
        continue
      }

      const prUrl = prUrlRE.exec(line)
      if (prUrl) {
        this._setMetaStart(i)
        this._setMetaEnd(i)
        this.prUrl = prUrl[1]
        continue
      }

      const refs = refsRE.exec(line)
      if (refs) {
        this._setMetaStart(i)
        this._setMetaEnd(i)
        this.refs.push(refs[1])
        continue
      }

      if (this._metaStart && !this._metaEnd) { this._setMetaEnd(i) }
    }
  }

  isRevert () {
    return revertRE.test(this.title)
  }

  isWorkingCommit () {
    return workingRE.test(this.title)
  }

  isReleaseCommit () {
    return releaseRE.test(this.title)
  }

  toJSON () {
    return {
      sha: this.sha,
      title: this.title,
      subsystems: this.subsystems,
      author: this.author,
      date: this.date,
      fixes: this.fixes,
      refs: this.refs,
      prUrl: this.prUrl,
      reviewers: this.reviewers,
      body: this.body,
      trailers: this.trailers,
      trailerFreeBody: this.trailerFreeBody,
      revert: this.isRevert(),
      release: this.isReleaseCommit(),
      working: this.isWorkingCommit(),
      metadata: {
        start: this._metaStart,
        end: this._metaEnd
      }
    }
  }
}

function getSubsystems (str) {
  str = str || ''
  const colon = str.indexOf(':')
  if (colon === -1) {
    return []
  }

  const subStr = str.slice(0, colon)
  const subs = subStr.split(',')
  return subs.map((item) => {
    return item.trim()
  })
}