Skip to content

Commit

Permalink
Adds support for regex in autolinks
Browse files Browse the repository at this point in the history
  • Loading branch information
felipecrs committed Oct 10, 2022
1 parent 203e98f commit 9923d02
Show file tree
Hide file tree
Showing 6 changed files with 76 additions and 29 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
### Added

- Adds an `Open in Commit Graph` action to the hovers and commit quick pick menus
- Adds support for configuring autolinks with regular expressions

### Changed

Expand All @@ -23,6 +24,7 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this p
- Fixes Commit Details view showing incorrect actions for uncommitted changes
- Fixes prioritization of multiple PRs associated with the same commit to choose a merged PR over others
- Fixes Graph not showing account banners when access is not allowed and trial banners were previously dismissed
- Fixes [#2251] - Gerrit change-ids should be parsed in the whole commit message body

## [12.2.2] - 2022-09-06

Expand Down
40 changes: 35 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2383,15 +2383,45 @@
"default": null,
"items": {
"type": "object",
"required": [
"prefix",
"url"
"oneOf": [
{
"required": [
"prefix",
"url"
]
},
{
"required": [
"regex",
"url"
],
"allOf": [
{
"not": {
"required": [
"alphanumeric"
]
}
},
{
"not": {
"required": [
"ignoreCase"
]
}
}
]
}
],
"properties": {
"prefix": {
"type": "string",
"description": "Specifies the short prefix to use to generate autolinks for the external resource"
},
"regex": {
"type": "string",
"description": "Specifies the regular expression used to match autolinks for the external resource, where the first group matches the reference number"
},
"title": {
"type": [
"string",
Expand All @@ -2406,12 +2436,12 @@
},
"alphanumeric": {
"type": "boolean",
"description": "Specifies whether alphanumeric characters should be allowed in `<num>`",
"description": "When using a prefix, specifies whether alphanumeric characters should be allowed in `<num>`",
"default": false
},
"ignoreCase": {
"type": "boolean",
"description": "Specifies whether case should be ignored when matching the prefix",
"description": "When using a prefix, specifies whether case should be ignored when matching the prefix",
"default": false
}
},
Expand Down
51 changes: 32 additions & 19 deletions src/annotations/autolinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ const numRegex = /<num>/g;
export interface Autolink {
provider?: RemoteProviderReference;
id: string;
prefix: string;
prefix?: string;
title?: string;
url: string;

Expand Down Expand Up @@ -61,11 +61,15 @@ export interface DynamicAutolinkReference {
}

function isDynamic(ref: AutolinkReference | DynamicAutolinkReference): ref is DynamicAutolinkReference {
return !('prefix' in ref) && !('url' in ref);
return (!('prefix' in ref) || !('regex' in ref)) && !('url' in ref);
}

function isCacheable(ref: AutolinkReference | DynamicAutolinkReference): ref is CacheableAutolinkReference {
return 'prefix' in ref && ref.prefix != null && 'url' in ref && ref.url != null;
return (
(('prefix' in ref && ref.prefix != null) || ('regex' in ref && ref.regex != null)) &&
'url' in ref &&
ref.url != null
);
}

export class Autolinks implements Disposable {
Expand All @@ -88,12 +92,13 @@ export class Autolinks implements Disposable {
// Since VS Code's configuration objects are live we need to copy them to avoid writing back to the configuration
this._references =
autolinks
?.filter(a => a.prefix && a.url)
?.filter(a => (a.prefix || a.regex) && a.url)
/**
* Only allow properties defined by {@link AutolinkReference}
*/
?.map(a => ({
prefix: a.prefix,
regex: a.regex,
url: a.url,
title: a.title,
alphanumeric: a.alphanumeric,
Expand Down Expand Up @@ -268,7 +273,7 @@ export class Autolinks implements Disposable {
ref: CacheableAutolinkReference | DynamicAutolinkReference,
): ref is CacheableAutolinkReference | DynamicAutolinkReference {
if (isDynamic(ref)) return true;
if (!ref.prefix || !ref.url) return false;
if ((!ref.prefix && !ref.regex) || !ref.url) return false;
if (ref.tokenize !== undefined || ref.tokenize === null) return true;

try {
Expand Down Expand Up @@ -422,22 +427,30 @@ function ensureCachedRegex(ref: CacheableAutolinkReference, outputFormat: 'html'
// Regexes matches the ref prefix followed by a token (e.g. #1234)
if (outputFormat === 'markdown' && ref.messageMarkdownRegex == null) {
// Extra `\\\\` in `\\\\\\[` is because the markdown is escaped
ref.messageMarkdownRegex = new RegExp(
`(?<=^|\\s|\\(|\\\\\\[)(${escapeRegex(encodeHtmlWeak(escapeMarkdown(ref.prefix)))}(${
ref.alphanumeric ? '\\w' : '\\d'
}+))\\b`,
ref.ignoreCase ? 'gi' : 'g',
);
ref.messageMarkdownRegex = ref.regex
? new RegExp(`((${ref.regex}))`, 'g')
: new RegExp(
`(?<=^|\\s|\\(|\\\\\\[)(${escapeRegex(encodeHtmlWeak(escapeMarkdown(ref.prefix!)))}(${
ref.alphanumeric ? '\\w' : '\\d'
}+))\\b`,
ref.ignoreCase ? 'gi' : 'g',
);
} else if (outputFormat === 'html' && ref.messageHtmlRegex == null) {
ref.messageHtmlRegex = new RegExp(
`(?<=^|\\s|\\(|\\[)(${escapeRegex(encodeHtmlWeak(ref.prefix))}(${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
ref.ignoreCase ? 'gi' : 'g',
);
ref.messageHtmlRegex = ref.regex
? new RegExp(`((${ref.regex}))`, 'g')
: new RegExp(
`(?<=^|\\s|\\(|\\[)(${escapeRegex(encodeHtmlWeak(ref.prefix!))}(${
ref.alphanumeric ? '\\w' : '\\d'
}+))\\b`,
ref.ignoreCase ? 'gi' : 'g',
);
} else if (ref.messageRegex == null) {
ref.messageRegex = new RegExp(
`(?<=^|\\s|\\(|\\[)(${escapeRegex(ref.prefix)}(${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
ref.ignoreCase ? 'gi' : 'g',
);
ref.messageRegex = ref.regex
? new RegExp(`((${ref.regex}))`, 'g')
: new RegExp(
`(?<=^|\\s|\\(|\\[)(${escapeRegex(ref.prefix!)}(${ref.alphanumeric ? '\\w' : '\\d'}+))\\b`,
ref.ignoreCase ? 'gi' : 'g',
);
}

return true;
Expand Down
5 changes: 4 additions & 1 deletion src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,15 @@ export const enum AutolinkType {
}

export interface AutolinkReference {
prefix: string;
url: string;
title?: string;

prefix?: string;
alphanumeric?: boolean;
ignoreCase?: boolean;

regex?: string;

type?: AutolinkType;
description?: string;
}
Expand Down
3 changes: 1 addition & 2 deletions src/git/remotes/gerrit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,9 @@ export class GerritRemote extends RemoteProvider {
if (this._autolinks === undefined) {
this._autolinks = [
{
prefix: 'Change-Id: ',
regex: '(I[a-z0-9]{40})',
url: `${this.baseReviewUrl}/q/<num>`,
title: `Open Change #<num> on ${this.name}`,
alphanumeric: true,

description: `Change #<num> on ${this.name}`,
},
Expand Down
4 changes: 2 additions & 2 deletions src/views/nodes/autolinkedItemNode.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export class AutolinkedItemNode extends ViewNode<ViewsWithCommits> {
if (!isIssueOrPullRequest(this.item)) {
const { provider } = this.item;

const item = new TreeItem(`${this.item.prefix}${this.item.id}`, TreeItemCollapsibleState.None);
const item = new TreeItem(`${this.item.prefix ?? ''}${this.item.id}`, TreeItemCollapsibleState.None);
item.description = provider?.name ?? 'Custom';
item.iconPath = new ThemeIcon(
this.item.type == null
Expand All @@ -53,7 +53,7 @@ export class AutolinkedItemNode extends ViewNode<ViewsWithCommits> {
: this.item.type === AutolinkType.PullRequest
? 'Autolinked Pull Request'
: 'Autolinked Issue'
} ${this.item.prefix}${this.item.id}`
} ${this.item.prefix ?? ''}${this.item.id}`
} \\\n[${this.item.url}](${this.item.url}${this.item.title != null ? ` "${this.item.title}"` : ''})`,
);
return item;
Expand Down

0 comments on commit 9923d02

Please sign in to comment.