import * as core from '@actions/core' import {BugPattern, FindbugsResult, SourceLine} from './spotbugs' import parser from 'fast-xml-parser' import fs from 'fs' import BufferEncoding from 'buffer' import * as path from 'path' import {Annotation, AnnotationLevel} from './github' import {fromString as htmlToText, HtmlToTextOptions} from 'html-to-text' import decode from 'unescape' import {memoizeWith, identity, indexBy, chain} from 'ramda' const HTML_TO_TEXT_OPTIONS: HtmlToTextOptions = { wordwrap: false, preserveNewlines: false, uppercaseHeadings: false } const XML_PARSE_OPTIONS = { allowBooleanAttributes: true, ignoreAttributes: false, attributeNamePrefix: '' } function asArray<T>(arg: T[] | T | undefined): T[] { return !arg ? [] : Array.isArray(arg) ? arg : [arg] } export function annotationsForPath(resultFile: string): Annotation[] { core.info(`Creating annotations for ${resultFile}`) const root: string = process.env['GITHUB_WORKSPACE'] || '' const result: FindbugsResult = parser.parse( fs.readFileSync(resultFile, 'UTF-8' as BufferEncoding), XML_PARSE_OPTIONS ) const violations = asArray(result?.BugCollection?.BugInstance) const bugPatterns: {[type: string]: BugPattern} = indexBy( a => a.type, asArray(result?.BugCollection?.BugPattern) ) core.info(`${resultFile} has ${violations.length} violations`) const getFilePath: (sourcePath: string) => string | undefined = memoizeWith( identity, (sourcePath: string) => asArray(result?.BugCollection?.Project?.SrcDir).find(SrcDir => { const combinedPath = path.join(SrcDir, sourcePath) const fileExists = fs.existsSync(combinedPath) core.debug(`${combinedPath} ${fileExists ? 'does' : 'does not'} exists`) return fileExists }) ) return chain(BugInstance => { const annotationsForBug: Annotation[] = [] const sourceLines = asArray(BugInstance.SourceLine) const primarySourceLine: SourceLine | undefined = sourceLines.length > 1 ? sourceLines.find(sl => sl.primary) : sourceLines[0] const SrcDir: string | undefined = primarySourceLine?.sourcepath && getFilePath(primarySourceLine?.sourcepath) if (primarySourceLine?.start && SrcDir) { const annotation: Annotation = { annotation_level: AnnotationLevel.warning, path: path.relative( root, path.join(SrcDir, primarySourceLine?.sourcepath) ), start_line: Number(primarySourceLine?.start || 1), end_line: Number( primarySourceLine?.end || primarySourceLine?.start || 1 ), title: BugInstance.type, message: BugInstance.LongMessage, raw_details: htmlToText( decode(bugPatterns[BugInstance.type].Details), HTML_TO_TEXT_OPTIONS ) } annotationsForBug.push(annotation) } else { core.debug( `Skipping bug instance because source line start or source directory are missing` ) } return annotationsForBug }, violations) }