src/common/pr-utils/extracting.ts (83 lines of code) (raw):

import { Config } from '../../config'; import { PrItem } from '../github-service'; export type NormalizeOptions = Config['areas'][number]['options']; function capitalize([first, ...rest]: string): string { return `${first.toUpperCase()}${rest.join('')}`; } const VISUALIZE_TOOLS = ['lens', 'tsvb', 'vislib', 'timelion', 'vega', 'visualize']; const VISUALIZE_TITLE_REGEXP = new RegExp(`(in|to) (${VISUALIZE_TOOLS.join('|')})`, 'i'); function createVisualizationTitle(title: string, tool: string): string { if (VISUALIZE_TITLE_REGEXP.test(title)) { return title.replace(VISUALIZE_TITLE_REGEXP, '$1 *$2*'); } return `${title} in *${tool}*`; } function visualizationBracketHandling(title: string, originalTitle?: string): string { const match = title.match(/^\s*\[([^\]]+)\]\s*(.*)/i); const originalBrackets = originalTitle?.match(/^\s*\[([^\]]+)\]/i); if (!match) { if (originalBrackets && VISUALIZE_TOOLS.includes(originalBrackets[1].toLowerCase())) { return createVisualizationTitle(title, originalBrackets[1]); } return title; } if (VISUALIZE_TOOLS.includes(match[1].toLowerCase())) { return createVisualizationTitle(match[2], match[1]); } return match[2]; } /** * This function will normalize the title of a PR, i.e. applies all modifications to it * every PR should get (no matter a specific config). These include: * * - Stripping of all versions in brackets * - All issue numbers mentioned in the title */ export function normalizeTitle( title: string, options: NormalizeOptions = {}, originalTitle?: string ): string { let normalized = title .replace(/\s*\[\d+\.\d+(\.\d+)?\]\s*/gi, ' ') .replace(/\s*\(?#\d{2,}\)?\s*/g, ' '); if (options.bracketHandling === 'strip' || !options.bracketHandling) { normalized = normalized.replace(/\s*\[[^\]]+\]\s*/gi, ' '); } else if (options.bracketHandling === 'visualizations') { normalized = visualizationBracketHandling(normalized, originalTitle); } return capitalize( normalized // Remove some special trailing/leading non word characters, mostly left over from messages we stripped something // in the front/end .replace(/^[\s-:]*/g, '') .replace(/[\s-:]+$/g, '') // Replace any periods at the end of the entry .replace(/[.]+$/g, '') // Replace duplicate whitespace (most likely introduced by some removal above) by a single space .replace(/\s{2,}/g, ' ') // Trim the title .trim() // Put a trailing add/fix into 3rd person singular .replace(/^add\s/i, 'Adds ') .replace(/^fix\s/i, 'Fixes ') ); } /** * Finds and retrieves the actual "release note" details from a PR description (in markdown format). * It will look for: * - paragraphs beginning with release note (or slight variations of that) and the sentence till the end of line. */ export function findReleaseNote(markdown: string): string | undefined { const match = markdown.match( /(?:\n|^)\s*#*\s*release[\s-]?notes?[\s\W]+(.*?)(?:(\r?\n|\r){2}|$|((\r?\n|\r)\s*#+))/is ); return match?.[1].trim() ?? undefined; } export type ReleaseNoteDetails = | { type: 'title'; title: string } | { type: 'releaseNoteTitle'; title: string; originalTitle: string } | { type: 'releaseNoteDetails'; title: string; details: string }; export function extractReleaseNotes( pr: PrItem, options: NormalizeOptions = { bracketHandling: 'strip' } ): ReleaseNoteDetails { const releaseNote = findReleaseNote(pr.body ?? ''); // If the PR did not have any release note in its description just return the normalized title. if (!releaseNote) { return { type: 'title', title: normalizeTitle(pr.title, options) }; } // If the release note details in the PR is too long we'll handle it as a description, and not replace // the title with it, but put it additionally in the release note, so tech writers can compress it. if (releaseNote.length > 120 || releaseNote.includes('\n')) { return { type: 'releaseNoteDetails', title: normalizeTitle(pr.title, options), details: releaseNote, }; } return { type: 'releaseNoteTitle', title: normalizeTitle(releaseNote, options, pr.title), originalTitle: pr.title, }; }