1+ import slugify from 'slugify' ;
12import { IssueIntegrationId } from '../constants.integrations' ;
23import type { IssueOrPullRequest } from '../git/models/issue' ;
34import type { ProviderReference } from '../git/models/remoteProvider' ;
45import type { ResourceDescriptor } from '../plus/integrations/integration' ;
5- import { flatMap } from '../system/iterable' ;
6+ import { flatMap , forEach } from '../system/iterable' ;
67import { escapeMarkdown } from '../system/markdown' ;
78import type { MaybePausedResult } from '../system/promise' ;
89import { encodeHtmlWeak , escapeRegex } from '../system/string' ;
@@ -117,7 +118,7 @@ export function isDynamic(ref: AutolinkReference | DynamicAutolinkReference): re
117118 return ! ( 'prefix' in ref ) && ! ( 'url' in ref ) ;
118119}
119120
120- function isCacheable ( ref : AutolinkReference | DynamicAutolinkReference ) : ref is CacheableAutolinkReference {
121+ export function isCacheable ( ref : AutolinkReference | DynamicAutolinkReference ) : ref is CacheableAutolinkReference {
121122 return 'prefix' in ref && ref . prefix != null && 'url' in ref && ref . url != null ;
122123}
123124
@@ -131,17 +132,18 @@ export type RefSet = [
131132 * @returns non-0 result that means a probability of the autolink `b` is more relevant of the autolink `a`
132133 */
133134function compareAutolinks ( a : Autolink , b : Autolink ) : number {
135+ // believe that link with prefix is definitely more relevant that just a number
134136 if ( b . prefix . length - a . prefix . length ) {
135137 return b . prefix . length - a . prefix . length ;
136138 }
139+ // if custom priority provided, let's consider it first
137140 if ( a . priority || b . priority ) {
138141 if ( ( b . priority ?? '' ) > ( a . priority ?? '' ) ) {
139142 return 1 ;
140143 }
141144 if ( ( b . priority ?? '' ) < ( a . priority ?? '' ) ) {
142145 return - 1 ;
143146 }
144- return 0 ;
145147 }
146148 // consider that if the number is in the start, it's the most relevant link
147149 if ( b . index === 0 ) return 1 ;
@@ -184,6 +186,7 @@ function ensureCachedRegex(ref: CacheableAutolinkReference, outputFormat: 'html'
184186 ref . ignoreCase ? 'gi' : 'g' ,
185187 ) ;
186188 if ( ! ref . prefix && ! ref . alphanumeric ) {
189+ // use a different regex for non-prefixed refs
187190 ref . branchNameRegex =
188191 / (?< numberChunkBeginning > ^ | \/ | - | _ ) (?< numberChunk > (?< issueKeyNumber > \d + ) ( ( ( - | \. | _ ) \d + ) { 0 , 1 } ) ) (?< numberChunkEnding > $ | \/ | - | _ ) / gi;
189192 } else {
@@ -245,27 +248,26 @@ export function getAutolinks(message: string, refsets: Readonly<RefSet[]>) {
245248 return autolinks ;
246249}
247250
248- function calculatePriority (
249- input : string ,
251+ /** returns lexicographic priority value ready to sort */
252+ export function calculatePriority (
250253 issueKey : string ,
254+ edgeDistance : number ,
251255 numberGroup : string ,
252- index : number ,
253256 chunkIndex : number = 0 ,
254257) : string {
255- const edgeDistance = Math . min ( index , input . length - index + numberGroup . length - 1 ) ;
256258 const isSingleNumber = issueKey === numberGroup ;
257259 return `
258- ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) + chunkIndex ) } :
259- ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) - edgeDistance ) } :
260- ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) + Number ( isSingleNumber ) ) }
260+ ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) + chunkIndex ) } :\
261+ ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) - edgeDistance ) } :\
262+ ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) + Number ( isSingleNumber ) ) } :\
263+ ${ String . fromCharCode ( 'a' . charCodeAt ( 0 ) + Number ( issueKey ) ) }
261264 ` ;
262265}
263266
264267export function getBranchAutolinks ( branchName : string , refsets : Readonly < RefSet [ ] > ) {
265268 const autolinks = new Map < string , Autolink > ( ) ;
266269
267270 let match ;
268- let num ;
269271 for ( const [ provider , refs ] of refsets ) {
270272 for ( const ref of refs ) {
271273 if (
@@ -278,16 +280,19 @@ export function getBranchAutolinks(branchName: string, refsets: Readonly<RefSet[
278280
279281 ensureCachedRegex ( ref , 'plaintext' ) ;
280282 let chunks = [ branchName ] ;
283+ // use more complex logic for refs with no prefix
281284 const nonPrefixedRef = ! ref . prefix && ! ref . alphanumeric ;
282285 if ( nonPrefixedRef ) {
283286 chunks = branchName . split ( '/' ) ;
284287 }
285288 let chunkIndex = 0 ;
286- const chunkMap = new Map < string , number > ( ) ;
289+ const chunkIndexMap = new Map < string , number > ( ) ;
287290 let skip = false ;
288- const matches = flatMap ( chunks , chunk => {
289- const releaseMatch = / ^ r e l e a s e ( s ? ) ( (?< releaseNum > - [ \d . - ] + ) ? ) $ / gm. exec ( chunk ) ;
291+ // know chunk indexes, skip release-like chunks or chunk pairs like release-1 or release/1
292+ let matches : IterableIterator < RegExpExecArray > | undefined = flatMap ( chunks , chunk => {
293+ const releaseMatch = / ^ ( v | v e r ? | v e r s i o n s ? | r e l e a s e s ? ) ( (?< releaseNum > [ \d . - ] + ) ? ) $ / gm. exec ( chunk ) ;
290294 if ( releaseMatch ) {
295+ // number in the next chunk should be ignored
291296 if ( ! releaseMatch . groups ?. releaseNum ) {
292297 skip = true ;
293298 }
@@ -298,48 +303,63 @@ export function getBranchAutolinks(branchName: string, refsets: Readonly<RefSet[
298303 return [ ] ;
299304 }
300305 const match = chunk . matchAll ( ref . branchNameRegex ) ;
301- chunkMap . set ( chunk , chunkIndex ++ ) ;
306+ chunkIndexMap . set ( chunk , chunkIndex ++ ) ;
302307 return match ;
303308 } ) ;
309+ /** additional matches list to skip numbers that are mentioned inside the ref title */
310+ const refTitlesMatches : IterableIterator < RegExpExecArray > [ ] = [ ] ;
311+ /** indicates that we should remove any matched link from the map */
312+ let unwanted = false ;
304313 do {
305- match = matches . next ( ) ;
306- if ( ! match . value ?. groups ) break ;
314+ match = matches ?. next ( ) ;
315+ if ( match ?. done && refTitlesMatches . length ) {
316+ // check ref titles on unwanted matches
317+ matches = refTitlesMatches . shift ( ) ;
318+ unwanted = true ;
319+ continue ;
320+ }
321+ if ( ! match ?. value ?. groups ) break ;
307322
308- num = match ?. value ?. groups . issueKeyNumber ;
323+ const { issueKeyNumber : issueKey , numberChunk = issueKey } = match . value . groups ;
324+ const input = match . value . input ;
309325 let index = match . value . index ;
310- const linkUrl = ref . url ?. replace ( numRegex , num ) ;
326+ const entryEdgeDistance = Math . min ( index , input . length - index - numberChunk . length - 1 ) ;
327+
328+ const linkUrl = ref . url ?. replace ( numRegex , issueKey ) ;
311329 // strange case (I would say synthetic), but if we parse the link twice, use the most relevant of them
312330 const existingIndex = autolinks . get ( linkUrl ) ?. index ;
313331 if ( existingIndex != null ) {
314332 index = Math . min ( index , existingIndex ) ;
315333 }
316- console . log (
317- JSON . stringify ( match . value ) ,
318- match . value . groups . numberChunk ,
319- match . value . groups . numberChunkBeginning ,
320- match . value . input ,
321- match . value . groups . issueKeyNumber ,
322- ) ;
323- autolinks . set ( linkUrl , {
324- ...ref ,
325- provider : provider ,
326- id : num ,
327- index : index ,
328- url : linkUrl ,
329- priority : nonPrefixedRef
330- ? calculatePriority (
331- match . value . input ,
332- num ,
333- match . value . groups . numberChunk ,
334- index ,
335- chunkMap . get ( match . value . input ) ,
336- )
337- : undefined ,
338- title : ref . title ?. replace ( numRegex , num ) ,
339- description : ref . description ?. replace ( numRegex , num ) ,
340- descriptor : ref . descriptor ,
341- } ) ;
342- } while ( ! match . done ) ;
334+
335+ // fill refTitlesMatches for non-prefixed refs
336+ if ( ! unwanted && nonPrefixedRef && ref . title ) {
337+ refTitlesMatches . push ( slugify ( ref . title ) . matchAll ( ref . branchNameRegex ) ) ;
338+ }
339+
340+ if ( ! unwanted ) {
341+ autolinks . set ( linkUrl , {
342+ ...ref ,
343+ provider : provider ,
344+ id : issueKey ,
345+ index : index ,
346+ url : linkUrl ,
347+ priority : nonPrefixedRef
348+ ? calculatePriority (
349+ issueKey ,
350+ entryEdgeDistance ,
351+ match . value . groups . numberChunk ,
352+ chunkIndexMap . get ( match . value . input ) ,
353+ )
354+ : undefined ,
355+ title : ref . title ?. replace ( numRegex , issueKey ) ,
356+ description : ref . description ?. replace ( numRegex , issueKey ) ,
357+ descriptor : ref . descriptor ,
358+ } ) ;
359+ } else {
360+ autolinks . delete ( linkUrl ) ;
361+ }
362+ } while ( true ) ;
343363 }
344364 }
345365
0 commit comments