Skip to content

Commit 6e297b9

Browse files
authored
Merge pull request #3586 from ilandikov/refactor/dynamic-destination-path
refactor: reduce memory usage with dynamic destination path
2 parents 89b494f + edec45d commit 6e297b9

File tree

7 files changed

+38
-66
lines changed

7 files changed

+38
-66
lines changed

src/Scripting/TasksFile.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { type CachedMetadata, type FrontMatterCache, type Reference, getAllTags, parseFrontMatterTags } from 'obsidian';
2-
import type { Link } from '../Task/Link';
3-
import { LinkResolver } from '../Task/LinkResolver';
2+
import { Link } from '../Task/Link';
43

54
export type OptionalTasksFile = TasksFile | undefined;
65

@@ -36,7 +35,7 @@ export class TasksFile {
3635
}
3736

3837
private createLinks(obsidianRawLinks: Reference[] | undefined) {
39-
return obsidianRawLinks?.map((link) => LinkResolver.getInstance().resolve(link, this.path)) ?? [];
38+
return obsidianRawLinks?.map((link) => new Link(link, this.path)) ?? [];
4039
}
4140

4241
/**

src/Task/Link.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
import type { Reference } from 'obsidian';
22
import type { TasksFile } from '../Scripting/TasksFile';
3+
import { LinkResolver } from './LinkResolver';
34

45
export class Link {
56
private readonly rawLink: Reference;
67
private readonly pathContainingLink: string;
7-
private readonly _destinationPath: string | null;
88

99
/**
1010
* @param {Reference} rawLink - The raw link from Obsidian cache.
1111
* @param {string} pathContainingLink - The path of the file where this link is located.
12-
* @param {string | undefined} destinationPath - The path of the note being linked tio.
1312
*/
14-
constructor(rawLink: Reference, pathContainingLink: string, destinationPath?: string) {
13+
constructor(rawLink: Reference, pathContainingLink: string) {
1514
this.rawLink = rawLink;
1615
this.pathContainingLink = pathContainingLink;
17-
this._destinationPath = destinationPath ?? null;
1816
}
1917

2018
/**
@@ -80,7 +78,7 @@ export class Link {
8078
* See {@link LinkResolver} docs for more info.
8179
*/
8280
public get destinationPath(): string | null {
83-
return this._destinationPath;
81+
return LinkResolver.getInstance().getDestinationPath(this.rawLink, this.pathContainingLink) ?? null;
8482
}
8583

8684
/**

src/Task/LinkResolver.ts

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
import type { Reference } from 'obsidian';
2-
import { Link } from './Link';
32

43
export type GetFirstLinkpathDestFn = (rawLink: Reference, sourcePath: string) => string | null;
54

65
const defaultGetFirstLinkpathDestFn = (_rawLink: Reference, _sourcePath: string) => null;
76

87
/**
9-
* An abstraction to populate {@link Link.destinationPath}.
8+
* An abstraction to implement {@link Link.destinationPath}.
109
*
1110
* See also:
1211
* - `src/main.ts` - search for `LinkResolver.getInstance()`
@@ -25,13 +24,8 @@ export class LinkResolver {
2524
public resetGetFirstLinkpathDestFn() {
2625
this.getFirstLinkpathDestFn = defaultGetFirstLinkpathDestFn;
2726
}
28-
29-
public resolve(rawLink: Reference, pathContainingLink: string) {
30-
return new Link(
31-
rawLink,
32-
pathContainingLink,
33-
this.getFirstLinkpathDestFn(rawLink, pathContainingLink) ?? undefined,
34-
);
27+
public getDestinationPath(rawLink: Reference, pathContainingLink: string) {
28+
return this.getFirstLinkpathDestFn(rawLink, pathContainingLink) ?? undefined;
3529
}
3630

3731
/**

src/Task/ListItem.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,7 @@ import type { TasksFile } from '../Scripting/TasksFile';
33
import type { Task } from './Task';
44
import type { TaskLocation } from './TaskLocation';
55
import { TaskRegularExpressions } from './TaskRegularExpressions';
6-
import type { Link } from './Link';
7-
import { LinkResolver } from './LinkResolver';
6+
import { Link } from './Link';
87

98
export class ListItem {
109
// The original line read from file.
@@ -211,7 +210,7 @@ export class ListItem {
211210
public get outlinks(): Readonly<Link[]> {
212211
return this.rawLinksInFileBody
213212
.filter((link) => link.position.start.line === this.lineNumber)
214-
.map((link) => LinkResolver.getInstance().resolve(link, this.file.path));
213+
.map((link) => new Link(link, this.file.path));
215214
}
216215

217216
/**

tests/Scripting/TaskProperties.test.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33
*/
44

55
import moment from 'moment';
6-
import type { Reference } from 'obsidian';
76
import { Status } from '../../src/Statuses/Status';
87

98
import { TaskBuilder } from '../TestingTools/TaskBuilder';
@@ -159,9 +158,7 @@ describe('task', () => {
159158

160159
it('links', () => {
161160
// This is getting annoying, having to do this repeatedly.
162-
LinkResolver.getInstance().setGetFirstLinkpathDestFn((rawLink: Reference, sourcePath: string) =>
163-
getFirstLinkpathDest(rawLink, sourcePath),
164-
);
161+
LinkResolver.getInstance().setGetFirstLinkpathDestFn(getFirstLinkpathDest);
165162

166163
const tasks = readTasksFromSimulatedFile(links_everywhere);
167164
verifyFieldDataFromTasksForReferenceDocs(tasks, [

tests/Task/Link.test.ts

Lines changed: 6 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { Reference } from 'obsidian';
21
import { TasksFile } from '../../src/Scripting/TasksFile';
32
import { Link } from '../../src/Task/Link';
43
import internal_heading_links from '../Obsidian/__test_data__/internal_heading_links.json';
@@ -7,7 +6,6 @@ import link_in_task_markdown_link from '../Obsidian/__test_data__/link_in_task_m
76
import link_in_task_wikilink from '../Obsidian/__test_data__/link_in_task_wikilink.json';
87
import link_is_broken from '../Obsidian/__test_data__/link_is_broken.json';
98

10-
import link_in_file_body from '../Obsidian/__test_data__/link_in_file_body.json';
119
import links_everywhere from '../Obsidian/__test_data__/links_everywhere.json';
1210
import { allCacheSampleData } from '../Obsidian/AllCacheSampleData';
1311
import type { SimulatedFile } from '../Obsidian/SimulatedFile';
@@ -20,7 +18,11 @@ import { getFirstLinkpathDest, getFirstLinkpathDestFromData } from '../__mocks__
2018
function getLink(data: any, index: number) {
2119
const rawLink = data.cachedMetadata.links[index];
2220
const destinationPath = getFirstLinkpathDestFromData(data, rawLink);
23-
return new Link(rawLink, data.filePath, destinationPath);
21+
22+
const resolver = LinkResolver.getInstance();
23+
resolver.setGetFirstLinkpathDestFn(() => destinationPath);
24+
25+
return new Link(rawLink, data.filePath);
2426
}
2527

2628
describe('linkClass', () => {
@@ -261,31 +263,6 @@ describe('linkClass', () => {
261263
// []() and [alias]() are not detected by the obsidian parser as a link
262264
});
263265

264-
describe('destinationPath tests', () => {
265-
it('should accept and return destinationPath', () => {
266-
const data = link_in_file_body;
267-
const rawLink = data.cachedMetadata.links[0];
268-
expect(rawLink.original).toEqual('[[yaml_tags_is_empty]]');
269-
expect(rawLink.link).toEqual('yaml_tags_is_empty');
270-
271-
const destinationPath = 'Test Data/yaml_tags_is_empty.md';
272-
const link = new Link(rawLink, data.filePath, destinationPath);
273-
274-
expect(link.destinationPath).toEqual(destinationPath);
275-
});
276-
277-
it('should return null path if destinationPath not supplied', () => {
278-
const data = link_in_file_body;
279-
const rawLink = data.cachedMetadata.links[0];
280-
expect(rawLink.original).toEqual('[[yaml_tags_is_empty]]');
281-
expect(rawLink.link).toEqual('yaml_tags_is_empty');
282-
283-
const link = new Link(rawLink, data.filePath);
284-
285-
expect(link.destinationPath).toBeNull();
286-
});
287-
});
288-
289266
describe('linksTo() tests', () => {
290267
it('matches filenames', () => {
291268
const link = getLink(links_everywhere, 0);
@@ -329,9 +306,7 @@ describe('linkClass', () => {
329306

330307
describe('visualise links', () => {
331308
beforeAll(() => {
332-
LinkResolver.getInstance().setGetFirstLinkpathDestFn((rawLink: Reference, sourcePath: string) => {
333-
return getFirstLinkpathDest(rawLink, sourcePath);
334-
});
309+
LinkResolver.getInstance().setGetFirstLinkpathDestFn(getFirstLinkpathDest);
335310
});
336311

337312
afterAll(() => {

tests/Task/LinkResolver.test.ts

Lines changed: 21 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { Reference } from 'obsidian';
22
import link_in_file_body from '../Obsidian/__test_data__/link_in_file_body.json';
33
import { LinkResolver } from '../../src/Task/LinkResolver';
4+
import { Link } from '../../src/Task/Link';
45

56
describe('LinkResolver', () => {
67
let rawLink: Reference;
@@ -10,40 +11,49 @@ describe('LinkResolver', () => {
1011
});
1112

1213
it('should resolve a link via local instance', () => {
13-
const resolver = new LinkResolver();
14-
const link = resolver.resolve(rawLink, link_in_file_body.filePath);
14+
const link = new Link(rawLink, link_in_file_body.filePath);
1515

1616
expect(link.originalMarkdown).toEqual('[[yaml_tags_is_empty]]');
1717
expect(link.destinationPath).toBeNull();
1818
});
1919

2020
it('should resolve a link via global instance', () => {
21-
const link = LinkResolver.getInstance().resolve(rawLink, link_in_file_body.filePath);
21+
const link = new Link(rawLink, link_in_file_body.filePath);
2222

2323
expect(link.originalMarkdown).toEqual('[[yaml_tags_is_empty]]');
2424
expect(link.destinationPath).toBeNull();
2525
});
2626

2727
it('should allow a function to be supplied, to find the destination of a link', () => {
28-
const resolver = new LinkResolver();
29-
resolver.setGetFirstLinkpathDestFn((_rawLink: Reference, _sourcePath: string) => 'Hello World.md');
28+
const resolver = LinkResolver.getInstance();
29+
resolver.setGetFirstLinkpathDestFn(() => 'Hello World.md');
3030

31-
const link = resolver.resolve(rawLink, link_in_file_body.filePath);
31+
const link = new Link(rawLink, link_in_file_body.filePath);
3232
expect(link.destinationPath).toEqual('Hello World.md');
3333
});
3434

3535
it('should allow the global instance to be reset', () => {
3636
const globalInstance = LinkResolver.getInstance();
37-
globalInstance.setGetFirstLinkpathDestFn(
38-
(_rawLink: Reference, _sourcePath: string) => 'From Global Instance.md',
39-
);
37+
globalInstance.setGetFirstLinkpathDestFn(() => 'From Global Instance.md');
4038

41-
const link1 = globalInstance.resolve(rawLink, link_in_file_body.filePath);
39+
const link1 = new Link(rawLink, link_in_file_body.filePath);
4240
expect(link1.destinationPath).toEqual('From Global Instance.md');
4341

4442
globalInstance.resetGetFirstLinkpathDestFn();
4543

46-
const link2 = globalInstance.resolve(rawLink, link_in_file_body.filePath);
44+
const link2 = new Link(rawLink, link_in_file_body.filePath);
4745
expect(link2.destinationPath).toBeNull();
4846
});
47+
48+
it('resetting global instance affects pre-existing links', () => {
49+
const globalInstance = LinkResolver.getInstance();
50+
globalInstance.setGetFirstLinkpathDestFn(() => 'From Global Instance.md');
51+
52+
const link1 = new Link(rawLink, link_in_file_body.filePath);
53+
expect(link1.destinationPath).toEqual('From Global Instance.md');
54+
55+
globalInstance.resetGetFirstLinkpathDestFn();
56+
57+
expect(link1.destinationPath).toBeNull();
58+
});
4959
});

0 commit comments

Comments
 (0)