Skip to content

Commit e5e5ed6

Browse files
committed
Refactors autolinks files into models/utils layout
Adds caching for refset resolution to improve performance
1 parent f3d43b2 commit e5e5ed6

File tree

28 files changed

+230
-160
lines changed

28 files changed

+230
-160
lines changed

src/autolinks/__tests__/autolinks.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import * as assert from 'assert';
22
import { suite, test } from 'mocha';
33
import { map } from '../../system/iterable';
4-
import type { Autolink, RefSet } from '../autolinks.utils';
5-
import { getAutolinks, getBranchAutolinks } from '../autolinks.utils';
4+
import type { Autolink, RefSet } from '../models/autolinks';
5+
import { getAutolinks, getBranchAutolinks } from '../utils/-webview/autolinks.utils';
66

77
const mockRefSets = (prefixes: string[] = ['']): RefSet[] =>
88
prefixes.map(prefix => [

src/autolinks/autolinks.ts renamed to src/autolinks/autolinksProvider.ts

Lines changed: 33 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Container } from '../container';
77
import type { GitRemote } from '../git/models/remote';
88
import type { RemoteProviderId } from '../git/remotes/remoteProvider';
99
import { getIssueOrPullRequestHtmlIcon, getIssueOrPullRequestMarkdownIcon } from '../git/utils/-webview/icons';
10+
import type { ConfiguredIntegrationsChangeEvent } from '../plus/integrations/authentication/configuredIntegrationService';
1011
import type { HostingIntegration, Integration, IssueIntegration } from '../plus/integrations/integration';
1112
import { IntegrationBase } from '../plus/integrations/integration';
1213
import { remoteProviderIdToIntegrationId } from '../plus/integrations/integrationService';
@@ -18,6 +19,7 @@ import { join, map } from '../system/iterable';
1819
import { Logger } from '../system/logger';
1920
import { escapeMarkdown } from '../system/markdown';
2021
import { getSettledValue, isPromise } from '../system/promise';
22+
import { PromiseCache } from '../system/promiseCache';
2123
import { capitalize, encodeHtmlWeak, getSuperscript } from '../system/string';
2224
import type {
2325
Autolink,
@@ -26,24 +28,28 @@ import type {
2628
EnrichedAutolink,
2729
MaybeEnrichedAutolink,
2830
RefSet,
29-
} from './autolinks.utils';
31+
} from './models/autolinks';
3032
import {
3133
ensureCachedRegex,
3234
getAutolinks,
3335
getBranchAutolinks,
3436
isDynamic,
3537
numRegex,
3638
supportedAutolinkIntegrations,
37-
} from './autolinks.utils';
39+
} from './utils/-webview/autolinks.utils';
3840

3941
const emptyAutolinkMap = Object.freeze(new Map<string, Autolink>());
4042

41-
export class Autolinks implements Disposable {
42-
protected _disposable: Disposable | undefined;
43+
export class AutolinksProvider implements Disposable {
44+
private _disposable: Disposable | undefined;
4345
private _references: CacheableAutolinkReference[] = [];
46+
private _refsetCache = new PromiseCache<string | undefined, RefSet[]>({ accessTTL: 1000 * 60 * 60 });
4447

4548
constructor(private readonly container: Container) {
46-
this._disposable = Disposable.from(configuration.onDidChange(this.onConfigurationChanged, this));
49+
this._disposable = Disposable.from(
50+
configuration.onDidChange(this.onConfigurationChanged, this),
51+
container.integrations.onDidChangeConfiguredIntegrations(this.onConfiguredIntegrationsChanged, this),
52+
);
4753

4854
this.setAutolinksFromConfig();
4955
}
@@ -55,9 +61,14 @@ export class Autolinks implements Disposable {
5561
private onConfigurationChanged(e?: ConfigurationChangeEvent) {
5662
if (configuration.changed(e, 'autolinks')) {
5763
this.setAutolinksFromConfig();
64+
this._refsetCache.clear();
5865
}
5966
}
6067

68+
private onConfiguredIntegrationsChanged(_e: ConfiguredIntegrationsChangeEvent) {
69+
this._refsetCache.clear();
70+
}
71+
6172
private setAutolinksFromConfig() {
6273
const autolinks = configuration.get('autolinks');
6374
// Since VS Code's configuration objects are live we need to copy them to avoid writing back to the configuration
@@ -118,57 +129,40 @@ export class Autolinks implements Disposable {
118129
}
119130

120131
/** Collects custom-configured autolink references into @param refsets */
121-
private collectCustomAutolinks(
122-
remote: GitRemote | undefined,
123-
refsets: RefSet[],
124-
options?: { excludeCustom?: boolean },
125-
): void {
126-
if (this._references.length && (remote?.provider == null || !options?.excludeCustom)) {
132+
private collectCustomAutolinks(refsets: RefSet[]): void {
133+
if (this._references.length) {
127134
refsets.push([undefined, this._references]);
128135
}
129136
}
130137

131-
private async getRefSets(remote?: GitRemote, options?: { excludeCustom?: boolean }) {
132-
const refsets: RefSet[] = [];
138+
private async getRefSets(remote?: GitRemote) {
139+
return this._refsetCache.get(remote?.remoteKey, async () => {
140+
const refsets: RefSet[] = [];
133141

134-
await this.collectIntegrationAutolinks(remote, refsets);
135-
this.collectRemoteAutolinks(remote, refsets);
136-
this.collectCustomAutolinks(remote, refsets, options);
142+
await this.collectIntegrationAutolinks(remote, refsets);
143+
this.collectRemoteAutolinks(remote, refsets);
144+
this.collectCustomAutolinks(refsets);
137145

138-
return refsets;
146+
return refsets;
147+
});
139148
}
140149

141150
/** @returns A sorted list of autolinks. the first match is the most relevant */
142-
async getBranchAutolinks(
143-
branchName: string,
144-
remote?: GitRemote,
145-
options?: { excludeCustom?: boolean },
146-
): Promise<Map<string, Autolink>> {
147-
const refsets = await this.getRefSets(remote, options);
151+
async getBranchAutolinks(branchName: string, remote?: GitRemote): Promise<Map<string, Autolink>> {
152+
const refsets = await this.getRefSets(remote);
148153
if (refsets.length === 0) return emptyAutolinkMap;
149154

150155
return getBranchAutolinks(branchName, refsets);
151156
}
152157

153-
async getAutolinks(message: string, remote?: GitRemote): Promise<Map<string, Autolink>>;
154-
async getAutolinks(
155-
message: string,
156-
remote: GitRemote,
157-
// eslint-disable-next-line @typescript-eslint/unified-signatures
158-
options?: { excludeCustom?: boolean },
159-
): Promise<Map<string, Autolink>>;
160-
@debug<Autolinks['getAutolinks']>({
158+
@debug<AutolinksProvider['getAutolinks']>({
161159
args: {
162160
0: '<message>',
163161
1: false,
164162
},
165163
})
166-
async getAutolinks(
167-
message: string,
168-
remote?: GitRemote,
169-
options?: { excludeCustom?: boolean },
170-
): Promise<Map<string, Autolink>> {
171-
const refsets = await this.getRefSets(remote, options);
164+
async getAutolinks(message: string, remote?: GitRemote): Promise<Map<string, Autolink>> {
165+
const refsets = await this.getRefSets(remote);
172166
if (refsets.length === 0) return emptyAutolinkMap;
173167

174168
return getAutolinks(message, refsets);
@@ -191,7 +185,7 @@ export class Autolinks implements Disposable {
191185
autolinks: Map<string, Autolink>,
192186
remote: GitRemote | undefined,
193187
): Promise<Map<string, EnrichedAutolink> | undefined>;
194-
@debug<Autolinks['getEnrichedAutolinks']>({
188+
@debug<AutolinksProvider['getEnrichedAutolinks']>({
195189
args: {
196190
0: messageOrAutolinks =>
197191
typeof messageOrAutolinks === 'string' ? '<message>' : `autolinks=${messageOrAutolinks.size}`,
@@ -264,7 +258,7 @@ export class Autolinks implements Disposable {
264258
return enrichedAutolinks;
265259
}
266260

267-
@debug<Autolinks['linkify']>({
261+
@debug<AutolinksProvider['linkify']>({
268262
args: {
269263
0: '<text>',
270264
2: remotes => remotes?.length,

src/autolinks/index.ts

Lines changed: 0 additions & 2 deletions
This file was deleted.

src/autolinks/models/autolinks.ts

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
import type { IssueOrPullRequest } from '../../git/models/issueOrPullRequest';
2+
import type { ProviderReference } from '../../git/models/remoteProvider';
3+
import type { ResourceDescriptor } from '../../plus/integrations/integration';
4+
import type { MaybePausedResult } from '../../system/promise';
5+
6+
export type AutolinkType = 'issue' | 'pullrequest';
7+
export type AutolinkReferenceType = 'commit' | 'branch';
8+
9+
export interface AutolinkReference {
10+
/** Short prefix to match to generate autolinks for the external resource */
11+
readonly prefix: string;
12+
/** URL of the external resource to link to */
13+
readonly url: string;
14+
/** Whether alphanumeric characters should be allowed in `<num>` */
15+
readonly alphanumeric: boolean;
16+
/** Whether case should be ignored when matching the prefix */
17+
readonly ignoreCase: boolean;
18+
readonly title: string | undefined;
19+
20+
readonly type?: AutolinkType;
21+
readonly referenceType?: AutolinkReferenceType;
22+
readonly description?: string;
23+
readonly descriptor?: ResourceDescriptor;
24+
}
25+
26+
export interface Autolink extends AutolinkReference {
27+
provider?: ProviderReference;
28+
id: string;
29+
index?: number;
30+
31+
tokenize?:
32+
| ((
33+
text: string,
34+
outputFormat: 'html' | 'markdown' | 'plaintext',
35+
tokenMapping: Map<string, string>,
36+
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
37+
prs?: Set<string>,
38+
footnotes?: Map<number, string>,
39+
) => string)
40+
| null;
41+
}
42+
43+
export type EnrichedAutolink = [
44+
issueOrPullRequest: Promise<IssueOrPullRequest | undefined> | undefined,
45+
autolink: Autolink,
46+
];
47+
48+
export type MaybeEnrichedAutolink = readonly [
49+
issueOrPullRequest: MaybePausedResult<IssueOrPullRequest | undefined> | undefined,
50+
autolink: Autolink,
51+
];
52+
53+
export interface CacheableAutolinkReference extends AutolinkReference {
54+
tokenize?:
55+
| ((
56+
text: string,
57+
outputFormat: 'html' | 'markdown' | 'plaintext',
58+
tokenMapping: Map<string, string>,
59+
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
60+
prs?: Set<string>,
61+
footnotes?: Map<number, string>,
62+
) => string)
63+
| null;
64+
65+
messageHtmlRegex?: RegExp;
66+
messageMarkdownRegex?: RegExp;
67+
messageRegex?: RegExp;
68+
branchNameRegex?: RegExp;
69+
}
70+
71+
export interface DynamicAutolinkReference {
72+
tokenize?:
73+
| ((
74+
text: string,
75+
outputFormat: 'html' | 'markdown' | 'plaintext',
76+
tokenMapping: Map<string, string>,
77+
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
78+
prs?: Set<string>,
79+
footnotes?: Map<number, string>,
80+
) => string)
81+
| null;
82+
parse: (text: string, autolinks: Map<string, Autolink>) => void;
83+
}
84+
85+
export type RefSet = [
86+
ProviderReference | undefined,
87+
(AutolinkReference | DynamicAutolinkReference)[] | CacheableAutolinkReference[],
88+
];

src/autolinks/autolinks.utils.ts renamed to src/autolinks/utils/-webview/autolinks.utils.ts

Lines changed: 10 additions & 91 deletions
Original file line numberDiff line numberDiff line change
@@ -1,75 +1,13 @@
1-
import { IssueIntegrationId } from '../constants.integrations';
2-
import type { IssueOrPullRequest } from '../git/models/issueOrPullRequest';
3-
import type { ProviderReference } from '../git/models/remoteProvider';
4-
import type { ResourceDescriptor } from '../plus/integrations/integration';
5-
import { escapeMarkdown } from '../system/markdown';
6-
import type { MaybePausedResult } from '../system/promise';
7-
import { encodeHtmlWeak, escapeRegex } from '../system/string';
8-
9-
export type AutolinkType = 'issue' | 'pullrequest';
10-
export type AutolinkReferenceType = 'commit' | 'branch';
11-
12-
export interface AutolinkReference {
13-
/** Short prefix to match to generate autolinks for the external resource */
14-
readonly prefix: string;
15-
/** URL of the external resource to link to */
16-
readonly url: string;
17-
/** Whether alphanumeric characters should be allowed in `<num>` */
18-
readonly alphanumeric: boolean;
19-
/** Whether case should be ignored when matching the prefix */
20-
readonly ignoreCase: boolean;
21-
readonly title: string | undefined;
22-
23-
readonly type?: AutolinkType;
24-
readonly referenceType?: AutolinkReferenceType;
25-
readonly description?: string;
26-
readonly descriptor?: ResourceDescriptor;
27-
}
28-
29-
export interface Autolink extends AutolinkReference {
30-
provider?: ProviderReference;
31-
id: string;
32-
index?: number;
33-
34-
tokenize?:
35-
| ((
36-
text: string,
37-
outputFormat: 'html' | 'markdown' | 'plaintext',
38-
tokenMapping: Map<string, string>,
39-
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
40-
prs?: Set<string>,
41-
footnotes?: Map<number, string>,
42-
) => string)
43-
| null;
44-
}
45-
46-
export type EnrichedAutolink = [
47-
issueOrPullRequest: Promise<IssueOrPullRequest | undefined> | undefined,
48-
autolink: Autolink,
49-
];
50-
51-
export type MaybeEnrichedAutolink = readonly [
52-
issueOrPullRequest: MaybePausedResult<IssueOrPullRequest | undefined> | undefined,
53-
autolink: Autolink,
54-
];
55-
56-
export interface CacheableAutolinkReference extends AutolinkReference {
57-
tokenize?:
58-
| ((
59-
text: string,
60-
outputFormat: 'html' | 'markdown' | 'plaintext',
61-
tokenMapping: Map<string, string>,
62-
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
63-
prs?: Set<string>,
64-
footnotes?: Map<number, string>,
65-
) => string)
66-
| null;
67-
68-
messageHtmlRegex?: RegExp;
69-
messageMarkdownRegex?: RegExp;
70-
messageRegex?: RegExp;
71-
branchNameRegex?: RegExp;
72-
}
1+
import { IssueIntegrationId } from '../../../constants.integrations';
2+
import { escapeMarkdown } from '../../../system/markdown';
3+
import { encodeHtmlWeak, escapeRegex } from '../../../system/string';
4+
import type {
5+
Autolink,
6+
AutolinkReference,
7+
CacheableAutolinkReference,
8+
DynamicAutolinkReference,
9+
RefSet,
10+
} from '../../models/autolinks';
7311

7412
export function serializeAutolink(value: Autolink): Autolink {
7513
const serialized: Autolink = {
@@ -95,20 +33,6 @@ export function serializeAutolink(value: Autolink): Autolink {
9533
return serialized;
9634
}
9735

98-
export interface DynamicAutolinkReference {
99-
tokenize?:
100-
| ((
101-
text: string,
102-
outputFormat: 'html' | 'markdown' | 'plaintext',
103-
tokenMapping: Map<string, string>,
104-
enrichedAutolinks?: Map<string, MaybeEnrichedAutolink>,
105-
prs?: Set<string>,
106-
footnotes?: Map<number, string>,
107-
) => string)
108-
| null;
109-
parse: (text: string, autolinks: Map<string, Autolink>) => void;
110-
}
111-
11236
export const supportedAutolinkIntegrations = [IssueIntegrationId.Jira];
11337

11438
export function isDynamic(ref: AutolinkReference | DynamicAutolinkReference): ref is DynamicAutolinkReference {
@@ -119,11 +43,6 @@ function isCacheable(ref: AutolinkReference | DynamicAutolinkReference): ref is
11943
return 'prefix' in ref && ref.prefix != null && 'url' in ref && ref.url != null;
12044
}
12145

122-
export type RefSet = [
123-
ProviderReference | undefined,
124-
(AutolinkReference | DynamicAutolinkReference)[] | CacheableAutolinkReference[],
125-
];
126-
12746
/**
12847
* Compares autolinks
12948
* @returns non-0 result that means a probability of the autolink `b` is more relevant of the autolink `a`

0 commit comments

Comments
 (0)