1/**
2 * @since 4.0.0
3 */
4
5/**
6 * Prefix for virtual doctest collector module requests.
7 *
8 * @category constants
9 * @since 4.0.0
10 */
11export const collectorPrefix = "virtual:effect-doctest/collector?"
12
13/**
14 * Prefix for virtual doctest snippet module requests.
15 *
16 * @category constants
17 * @since 4.0.0
18 */
19export const snippetPrefix = "virtual:effect-doctest/snippet?"
20
21/**
22 * Query parameter used to identify resolved doctest modules.
23 *
24 * @category constants
25 * @since 4.0.0
26 */
27export const resolvedMarker = "effect-doctest"
28
29/**
30 * Describes a virtual doctest module request.
31 *
32 * @category models
33 * @since 4.0.0
34 */
35export interface Request {
36 readonly file: string
37 readonly index?: number | undefined
38 readonly version?: string | undefined
39}
40
41/**
42 * Parses a virtual doctest request with the specified prefix.
43 *
44 * @category protocols
45 * @since 4.0.0
46 */
47export const request = (prefix: string, id: string): Request | undefined => {
48 if (!id.startsWith(prefix)) return undefined
49 const parameters = new URLSearchParams(id.slice(prefix.length))
50 const file = parameters.get("file")
51 const index = parameters.get("index")
52 const version = parameters.get("version")
53 if (file === null || (index !== null && !/^\d+$/.test(index))) {
54 return undefined
55 }
56
57 return {
58 file,
59 index: index === null ? undefined : Number(index),
60 version: version === null ? undefined : version
61 }
62}
63
64/**
65 * Parses the identifier of a resolved doctest module.
66 *
67 * @category protocols
68 * @since 4.0.0
69 */
70export const resolvedRequest = (
71 id: string
72): (Request & { readonly kind: "collector" | "snippet" }) | undefined => {
73 const query = id.indexOf("?")
74 if (query === -1) {
75 return undefined
76 }
77
78 const parameters = new URLSearchParams(id.slice(query + 1))
79 const kind = parameters.get(resolvedMarker)
80 if (kind !== "collector" && kind !== "snippet") {
81 return undefined
82 }
83
84 const index = parameters.get("index")
85 const version = parameters.get("version")
86 if (kind === "snippet" && (index === null || !/^\d+$/.test(index))) {
87 return undefined
88 }
89
90 return {
91 file: id.slice(0, query),
92 index: index === null ? undefined : Number(index),
93 version: version === null ? undefined : version,
94 kind
95 }
96}
97
98/**
99 * Creates the identifier of a resolved doctest module.
100 *
101 * @category protocols
102 * @since 4.0.0
103 */
104export const resolvedId = (kind: "collector" | "snippet", value: Request): string => {
105 const parameters = new URLSearchParams({ [resolvedMarker]: kind })
106 if (value.index !== undefined) {
107 parameters.set("index", String(value.index))
108 }
109
110 if (value.version !== undefined) {
111 parameters.set("version", value.version)
112 }
113
114 return `${value.file}?${parameters}`
115}
116
117/**
118 * Creates a virtual doctest collector module identifier.
119 *
120 * @category protocols
121 * @since 4.0.0
122 */
123export const collectorId = (file: string, version?: string | undefined): string => {
124 const parameters = new URLSearchParams({ file })
125 if (version !== undefined) {
126 parameters.set("version", version)
127 }
128
129 return `${collectorPrefix}${parameters}`
130}
131
132/**
133 * Creates a virtual doctest snippet module identifier.
134 *
135 * @category protocols
136 * @since 4.0.0
137 */
138export const snippetId = (file: string, index: number, version?: string | undefined): string => {
139 const parameters = new URLSearchParams({ file, index: String(index) })
140 if (version !== undefined) {
141 parameters.set("version", version)
142 }
143
144 return `${snippetPrefix}${parameters}`
145}