Skip to content

Commit 9ec7ef7

Browse files
committed
Fix some leakages in linking.js & unit test
findLinkedNode - do not mutate function argument - simplify regex to string equality check findClosestNode - Return undefined if no or equal-scoring nodes are found addLinkBack - Fix schema.attributes || [] with || {} - use a new .clone() method of CMNode to clarify intent resolveLinks - use the .clone()
1 parent 382a1f1 commit 9ec7ef7

3 files changed

Lines changed: 471 additions & 18 deletions

File tree

src/ssg/lib/ContentModelNode.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ class ContentModelNode {
66
this.title = this.fsNode.name
77
this.date = new Date(this.fsNode.stats?.birthtime || Date.now())
88
}
9+
10+
// Shallow copy that preserves the prototype without re-running the constructor
11+
clone() {
12+
const prototype = Object.getPrototypeOf(this)
13+
const shell = Object.create(prototype)
14+
return Object.assign(shell, this)
15+
}
916
}
1017

1118
module.exports = ContentModelNode

src/ssg/lib/linking.js

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
const _ = require('lodash')
22

33
function findLinkedNode(sourceNode, allNodes, linkPath) {
4-
const leafSlug = linkPath.pop()
5-
const leafRe = new RegExp(`^${leafSlug}$`, 'i')
6-
const leafMatches = allNodes.filter(p => p.slug.match(leafRe))
4+
const leafSlug = linkPath[linkPath.length - 1]
5+
const leafLower = leafSlug.toLowerCase()
6+
const leafMatches = allNodes.filter(p => {
7+
return p.slug.toLowerCase() === leafLower
8+
})
79

810
if (!leafMatches.length) {
911
return undefined
@@ -13,16 +15,17 @@ function findLinkedNode(sourceNode, allNodes, linkPath) {
1315
return leafMatches[0]
1416
}
1517

16-
if (!linkPath.length) {
18+
if (linkPath.length === 1) {
1719
return findClosestNode(sourceNode, leafMatches)
1820
}
1921

20-
const paths = linkPath.reverse()
22+
const ancestorSlugs = linkPath.slice(0, -1).reverse()
2123
return leafMatches.find(node => {
2224
let ctx = node.context
23-
for (const path of paths) {
25+
for (const ancestor of ancestorSlugs) {
26+
const pathLower = ancestor.toLowerCase()
2427
ctx = ctx.throwUntil(item => {
25-
return item.slug?.match(new RegExp(`^${path}$`, 'i'))
28+
return item.slug?.toLowerCase() === pathLower
2629
})
2730
}
2831
return !!ctx.items.length
@@ -31,8 +34,9 @@ function findLinkedNode(sourceNode, allNodes, linkPath) {
3134

3235
function findClosestNode(sourceNode, candidates) {
3336
const sourceContext = [...sourceNode.context.items, sourceNode]
34-
let bestMatch = candidates[0]
35-
let bestScore = -1
37+
let bestMatch
38+
let bestScore = 0
39+
let tied = false
3640
for (const candidate of candidates) {
3741
const candidateContext = candidate.context.items
3842
const maxDepth = Math.min(
@@ -49,14 +53,17 @@ function findClosestNode(sourceNode, candidates) {
4953
if (score > bestScore) {
5054
bestScore = score
5155
bestMatch = candidate
56+
tied = false
57+
} else if (score === bestScore) {
58+
tied = true
5259
}
5360
}
54-
return bestMatch
61+
return tied ? undefined : bestMatch
5562
}
5663

5764
function addLinkBack(sourceNode, targetNode, key) {
5865
if (sourceNode.schema) {
59-
Object.keys(sourceNode.schema.attributes || []).forEach(schemaKey => {
66+
Object.keys(sourceNode.schema.attributes || {}).forEach(schemaKey => {
6067
const schemaValue = sourceNode.schema.attributes[schemaKey]
6168
const isSchemaValueArray = Array.isArray(schemaValue)
6269
const re = new RegExp(`^\\+(${targetNode.contentType}|):${key}$`)
@@ -69,9 +76,9 @@ function addLinkBack(sourceNode, targetNode, key) {
6976
const existingCount = sourceNode.getLinks().filter(link => {
7077
return link.keyPath.length === 2 && link.keyPath[0] === schemaKey
7178
}).length
72-
sourceNode.addLink([schemaKey, existingCount], Object.assign({}, targetNode))
79+
sourceNode.addLink([schemaKey, existingCount], targetNode.clone())
7380
} else {
74-
sourceNode.addLink([schemaKey], Object.assign({}, targetNode))
81+
sourceNode.addLink([schemaKey], targetNode.clone())
7582
}
7683
}
7784
})
@@ -107,19 +114,19 @@ function resolveLinks(node, nodes) {
107114
if (!valueItem.linkPath) {
108115
continue
109116
}
110-
const linkedNode = findLinkedNode(node, nodes, [...valueItem.linkPath])
117+
const linkedNode = findLinkedNode(node, nodes, valueItem.linkPath)
111118
if (linkedNode) {
112-
node.addLink([key, i], Object.assign({}, linkedNode))
119+
node.addLink([key, i], linkedNode.clone())
113120
linkedNode.addLinkBack(node, key)
114121
}
115122
}
116123
} else {
117124
if (!value?.linkPath) {
118125
return
119126
}
120-
const linkedNode = findLinkedNode(node, nodes, [...value.linkPath])
127+
const linkedNode = findLinkedNode(node, nodes, value.linkPath)
121128
if (linkedNode) {
122-
node.addLink([key], Object.assign({}, linkedNode))
129+
node.addLink([key], linkedNode.clone())
123130
linkedNode.addLinkBack(node, key)
124131
}
125132
}
@@ -128,7 +135,8 @@ function resolveLinks(node, nodes) {
128135

129136
module.exports = {
130137
findLinkedNode,
138+
findClosestNode,
131139
addLinkBack,
132140
serializeLinks,
133141
resolveLinks
134-
}
142+
}

0 commit comments

Comments
 (0)