@@ -20,7 +20,6 @@ export function serializeAutolink(value: Autolink): Autolink {
20
20
}
21
21
: undefined ,
22
22
id : value . id ,
23
- index : value . index ,
24
23
prefix : value . prefix ,
25
24
url : value . url ,
26
25
alphanumeric : value . alphanumeric ,
@@ -43,23 +42,6 @@ function isCacheable(ref: AutolinkReference | DynamicAutolinkReference): ref is
43
42
return 'prefix' in ref && ref . prefix != null && 'url' in ref && ref . url != null ;
44
43
}
45
44
46
- /**
47
- * Compares autolinks
48
- * @returns non-0 result that means a probability of the autolink `b` is more relevant of the autolink `a`
49
- */
50
- function compareAutolinks ( a : Autolink , b : Autolink ) : number {
51
- // consider that if the number is in the start, it's the most relevant link
52
- if ( b . index === 0 ) return 1 ;
53
- if ( a . index === 0 ) return - 1 ;
54
-
55
- // maybe it worths to use some weight function instead.
56
- return (
57
- b . prefix . length - a . prefix . length ||
58
- b . id . length - a . id . length ||
59
- ( b . index != null && a . index != null ? - ( b . index - a . index ) : 0 )
60
- ) ;
61
- }
62
-
63
45
export function ensureCachedRegex (
64
46
ref : Autolink | CacheableAutolinkReference ,
65
47
outputFormat : 'html' ,
@@ -97,15 +79,29 @@ export function ensureCachedRegex(
97
79
}
98
80
}
99
81
100
- export function ensureCachedBranchNameRegex (
82
+ export function ensureCachedBranchNameRegexes (
101
83
ref : CacheableAutolinkReference ,
102
- ) : asserts ref is RequireSome < CacheableAutolinkReference , 'branchNameRegex' > {
103
- ref . branchNameRegex ??= new RegExp (
104
- `(^|\\-|_|\\.|\\/)(?<prefix>${ ref . prefix } )(?<issueKeyNumber>${
105
- ref . alphanumeric ? '\\w' : '\\d'
106
- } +)(?=$|\\-|_|\\.|\\/)`,
107
- 'gi' ,
108
- ) ;
84
+ ) : asserts ref is RequireSome < CacheableAutolinkReference , 'branchNameRegexes' > {
85
+ if ( ref . prefix ?. length > 0 ) {
86
+ ref . branchNameRegexes ??= [
87
+ // Rule 1: Any prefixed ref followed by a 2+ digit number and either a connector or end-of-string after it
88
+ new RegExp ( `(?<prefix>${ ref . prefix } )(?<issueKeyNumber>\\d{2,})(?:[\\/\\-\\_\\.]|$)` , 'i' ) ,
89
+ ] ;
90
+ } else {
91
+ ref . branchNameRegexes ??= [
92
+ // Rule 2: Any 2+ digit number preceded by feature|feat|fix|bug|bugfix|hotfix|issue|ticket with a connector before it, and either a connector or end-of-string after it
93
+ new RegExp (
94
+ `(?:feature|feat|fix|bug|bugfix|hotfix|issue|ticket)(?:\\/#|-#|_#|\\.#|[\\/\\-\\_\\.#])(?<issueKeyNumber>\\d{2,})(?:[\\/\\-\\_\\.]|$)` ,
95
+ 'i' ,
96
+ ) ,
97
+ // Rule 3.1: Any 3+ digit number preceded by at least two non-slash, non-numeric characters
98
+ new RegExp ( `(?:[^\\d/]{2})(?<issueKeyNumber>\\d{3,})` , 'i' ) ,
99
+ // Rule 3.2: Any 3+ digit number followed by at least two non-slash, non-numeric characters
100
+ new RegExp ( `(?<issueKeyNumber>\\d{3,})(?:[^\\d/]{2})` , 'i' ) ,
101
+ // Rule 3.3: A 3+ digit number is the entire branch name
102
+ new RegExp ( `^(?<issueKeyNumber>\\d{3,})$` , 'i' ) ,
103
+ ] ;
104
+ }
109
105
}
110
106
111
107
export const numRegex = / < n u m > / g;
@@ -135,7 +131,6 @@ export function getAutolinks(message: string, refsets: Readonly<RefSet[]>): Map<
135
131
autolinks . set ( num , {
136
132
provider : provider ,
137
133
id : num ,
138
- index : match . index ,
139
134
prefix : ref . prefix ,
140
135
url : ref . url ?. replace ( numRegex , num ) ,
141
136
alphanumeric : ref . alphanumeric ,
@@ -155,9 +150,16 @@ export function getAutolinks(message: string, refsets: Readonly<RefSet[]>): Map<
155
150
export function getBranchAutolinks ( branchName : string , refsets : Readonly < RefSet [ ] > ) : Map < string , Autolink > {
156
151
const autolinks = new Map < string , Autolink > ( ) ;
157
152
158
- let match ;
159
153
let num ;
160
- for ( const [ provider , refs ] of refsets ) {
154
+ let match ;
155
+ // Sort refsets so that issue integrations are checked first for matches
156
+ const sortedRefSets = [ ...refsets ] . sort ( ( a , b ) => {
157
+ if ( a [ 0 ] ?. id === IssueIntegrationId . Jira || a [ 0 ] ?. id === IssueIntegrationId . Trello ) return - 1 ;
158
+ if ( b [ 0 ] ?. id === IssueIntegrationId . Jira || b [ 0 ] ?. id === IssueIntegrationId . Trello ) return 1 ;
159
+ return 0 ;
160
+ } ) ;
161
+
162
+ for ( const [ provider , refs ] of sortedRefSets ) {
161
163
for ( const ref of refs ) {
162
164
if (
163
165
! isCacheable ( ref ) ||
@@ -167,33 +169,27 @@ export function getBranchAutolinks(branchName: string, refsets: Readonly<RefSet[
167
169
continue ;
168
170
}
169
171
170
- ensureCachedBranchNameRegex ( ref ) ;
171
- const matches = branchName . matchAll ( ref . branchNameRegex ) ;
172
- do {
173
- match = matches . next ( ) ;
174
- if ( ! match . value ?. groups ) break ;
175
-
176
- num = match ?. value ?. groups . issueKeyNumber ;
177
- let index = match . value . index ;
172
+ ensureCachedBranchNameRegexes ( ref ) ;
173
+ for ( const regex of ref . branchNameRegexes ) {
174
+ match = branchName . match ( regex ) ;
175
+ if ( ! match ?. groups ) continue ;
176
+ num = match . groups . issueKeyNumber ;
178
177
const linkUrl = ref . url ?. replace ( numRegex , num ) ;
179
- // strange case (I would say synthetic), but if we parse the link twice, use the most relevant of them
180
- const existingIndex = autolinks . get ( linkUrl ) ?. index ;
181
- if ( existingIndex != null ) {
182
- index = Math . min ( index , existingIndex ) ;
183
- }
184
178
autolinks . set ( linkUrl , {
185
179
...ref ,
186
180
provider : provider ,
187
181
id : num ,
188
- index : index ,
189
182
url : linkUrl ,
190
183
title : ref . title ?. replace ( numRegex , num ) ,
191
184
description : ref . description ?. replace ( numRegex , num ) ,
192
185
descriptor : ref . descriptor ,
193
186
} ) ;
194
- } while ( ! match . done ) ;
187
+
188
+ // Stop at the first match
189
+ return autolinks ;
190
+ }
195
191
}
196
192
}
197
193
198
- return new Map ( [ ... autolinks . entries ( ) ] . sort ( ( a , b ) => compareAutolinks ( a [ 1 ] , b [ 1 ] ) ) ) ;
194
+ return autolinks ;
199
195
}
0 commit comments