Skip to content

Commit ddd16d2

Browse files
committed
Fix extract annotations tests
1 parent 9067ae2 commit ddd16d2

2 files changed

Lines changed: 98 additions & 44 deletions

File tree

packages/codehike/src/code/extract-annotations.test.ts

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { expect, test } from "vitest"
1+
import { expect, test, vi } from "vitest"
22
import { splitAnnotationsAndCode } from "./extract-annotations.js"
33

44
async function t(comment: string) {
@@ -153,6 +153,36 @@ test("multiple start/end pairs of same name", async () => {
153153
expect(r0.toLineNumber).toBeLessThan(r1.fromLineNumber)
154154
})
155155

156+
test("same-name nested start/end pairs preserve nesting", async () => {
157+
const code = [
158+
"// !focus(start)",
159+
"const outer = 1",
160+
"// !focus(start)",
161+
"const inner = 2",
162+
"// !focus(end)",
163+
"const outerTail = 3",
164+
"// !focus(end)",
165+
"const after = 4",
166+
].join("\n")
167+
const { annotations } = await splitAnnotationsAndCode(
168+
code,
169+
"javascript",
170+
"!",
171+
)
172+
173+
expect(annotations).toHaveLength(2)
174+
expect(annotations[0].name).toEqual("focus")
175+
expect(annotations[0].ranges[0]).toEqual({
176+
fromLineNumber: 1,
177+
toLineNumber: 3,
178+
})
179+
expect(annotations[1].name).toEqual("focus")
180+
expect(annotations[1].ranges[0]).toEqual({
181+
fromLineNumber: 2,
182+
toLineNumber: 2,
183+
})
184+
})
185+
156186
test("different annotation names with start/end", async () => {
157187
const code = [
158188
"// !focus(start)",
@@ -195,6 +225,31 @@ test("start/end removes comment lines from code", async () => {
195225
expect(lines[2]).toContain("let c = 3")
196226
})
197227

228+
test("adjacent start/end markers are ignored instead of creating empty ranges", async () => {
229+
const warn = vi.spyOn(console, "warn").mockImplementation(() => {})
230+
try {
231+
const code = [
232+
"// !focus(start)",
233+
"// !focus(end)",
234+
"const x = 1",
235+
].join("\n")
236+
237+
const { code: resultCode, annotations } = await splitAnnotationsAndCode(
238+
code,
239+
"javascript",
240+
"!",
241+
)
242+
243+
expect(resultCode).toEqual("const x = 1")
244+
expect(annotations).toHaveLength(0)
245+
expect(warn).toHaveBeenCalledWith(
246+
"Code Hike warning: Empty !focus start/end annotation range",
247+
)
248+
} finally {
249+
warn.mockRestore()
250+
}
251+
})
252+
198253
test("start/end works with Python comments", async () => {
199254
const code = [
200255
"x = 1",

packages/codehike/src/code/extract-annotations.tsx

Lines changed: 42 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -22,70 +22,69 @@ function getLineFromRange(range: any): number {
2222
}
2323

2424
function processStartEndMarkers(annotations: Annotation[]): Annotation[] {
25-
const regular: Annotation[] = []
26-
const starts: { name: string; query: string; line: number }[] = []
27-
const ends: { name: string; query: string; line: number }[] = []
25+
const stacks = new Map<
26+
string,
27+
{ name: string; query: string; line: number; order: number }[]
28+
>()
29+
const orderedAnnotations: { order: number; annotation: Annotation }[] = []
2830

29-
for (const a of annotations) {
31+
for (const [order, a] of annotations.entries()) {
3032
const q = a.query ?? ""
3133
if (q.startsWith(START_MARKER)) {
32-
starts.push({
34+
const start = {
3335
name: a.name,
3436
query: q.slice(START_MARKER.length),
3537
line: getLineFromRange(a.ranges[0]),
36-
})
38+
order,
39+
}
40+
const stack = stacks.get(a.name) ?? []
41+
stack.push(start)
42+
stacks.set(a.name, stack)
3743
} else if (q.startsWith(END_MARKER)) {
38-
ends.push({
39-
name: a.name,
40-
query: q.slice(END_MARKER.length),
41-
line: getLineFromRange(a.ranges[0]),
44+
const stack = stacks.get(a.name)
45+
const start = stack?.pop()
46+
if (!start) {
47+
console.warn(
48+
`Code Hike warning: Unmatched !${a.name}(end) annotation`,
49+
)
50+
continue
51+
}
52+
53+
const endLine = getLineFromRange(a.ranges[0]) - 1
54+
if (endLine < start.line) {
55+
console.warn(
56+
`Code Hike warning: Empty !${a.name} start/end annotation range`,
57+
)
58+
continue
59+
}
60+
61+
orderedAnnotations.push({
62+
order: start.order,
63+
annotation: {
64+
name: start.name,
65+
query: start.query,
66+
ranges: [{ fromLineNumber: start.line, toLineNumber: endLine }],
67+
},
4268
})
4369
} else {
44-
regular.push(a)
70+
orderedAnnotations.push({ order, annotation: a })
4571
}
4672
}
4773

48-
if (starts.length === 0 && ends.length === 0) {
74+
if (orderedAnnotations.length === annotations.length) {
4975
return annotations
5076
}
5177

52-
const paired: Annotation[] = []
53-
const usedEnds = new Set<number>()
54-
55-
for (const start of starts) {
56-
// find the first unused end with the same name that comes after the start
57-
const endIndex = ends.findIndex(
58-
(e, i) =>
59-
!usedEnds.has(i) &&
60-
e.name === start.name &&
61-
e.line >= start.line,
62-
)
63-
if (endIndex === -1) {
78+
for (const stack of stacks.values()) {
79+
for (const start of stack) {
6480
console.warn(
6581
`Code Hike warning: Unmatched !${start.name}(start) annotation`,
6682
)
67-
continue
68-
}
69-
usedEnds.add(endIndex)
70-
const end = ends[endIndex]
71-
paired.push({
72-
name: start.name,
73-
query: start.query,
74-
ranges: [
75-
{ fromLineNumber: start.line, toLineNumber: end.line - 1 },
76-
],
77-
})
78-
}
79-
80-
for (let i = 0; i < ends.length; i++) {
81-
if (!usedEnds.has(i)) {
82-
console.warn(
83-
`Code Hike warning: Unmatched !${ends[i].name}(end) annotation`,
84-
)
8583
}
8684
}
8785

88-
return [...regular, ...paired]
86+
orderedAnnotations.sort((a, b) => a.order - b.order)
87+
return orderedAnnotations.map((entry) => entry.annotation)
8988
}
9089

9190
async function extractCommentAnnotations(

0 commit comments

Comments
 (0)