starlight-fsharp-oracle 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (89) hide show
  1. package/Directory.Build.props +25 -0
  2. package/Directory.Packages.props +18 -0
  3. package/README.md +80 -0
  4. package/dist/Extensions/StringBuilder.js +28 -0
  5. package/dist/Extensions/TextNode.js +115 -0
  6. package/dist/FSharp.Oracle.Schema/Schema.js +674 -0
  7. package/dist/Generate.js +254 -0
  8. package/dist/Helpers.js +69 -0
  9. package/dist/Plugin.js +139 -0
  10. package/dist/Render/Documentation.js +87 -0
  11. package/dist/Render/Entries.js +148 -0
  12. package/dist/Render/Pages.js +292 -0
  13. package/dist/Render/Primitives.js +114 -0
  14. package/dist/Render/Types.js +30 -0
  15. package/dist/Render.js +12 -0
  16. package/dist/Themes.js +78 -0
  17. package/oracle-bin/FSharp.Compiler.Service.dll +0 -0
  18. package/oracle-bin/FSharp.Core.dll +0 -0
  19. package/oracle-bin/FSharp.DependencyManager.Nuget.dll +0 -0
  20. package/oracle-bin/FSharp.Oracle.Schema.dll +0 -0
  21. package/oracle-bin/FSharp.Oracle.Schema.pdb +0 -0
  22. package/oracle-bin/FSharp.Oracle.Schema.xml +219 -0
  23. package/oracle-bin/Fable.Core.dll +0 -0
  24. package/oracle-bin/Oracle +0 -0
  25. package/oracle-bin/Oracle.deps.json +280 -0
  26. package/oracle-bin/Oracle.dll +0 -0
  27. package/oracle-bin/Oracle.pdb +0 -0
  28. package/oracle-bin/Oracle.runtimeconfig.json +13 -0
  29. package/oracle-bin/Oracle.xml +111 -0
  30. package/oracle-bin/Thoth.Json.Core.Auto.dll +0 -0
  31. package/oracle-bin/Thoth.Json.Core.dll +0 -0
  32. package/oracle-bin/Thoth.Json.System.Text.Json.dll +0 -0
  33. package/oracle-bin/cs/FSharp.Compiler.Service.resources.dll +0 -0
  34. package/oracle-bin/cs/FSharp.Core.resources.dll +0 -0
  35. package/oracle-bin/cs/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  36. package/oracle-bin/de/FSharp.Compiler.Service.resources.dll +0 -0
  37. package/oracle-bin/de/FSharp.Core.resources.dll +0 -0
  38. package/oracle-bin/de/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  39. package/oracle-bin/es/FSharp.Compiler.Service.resources.dll +0 -0
  40. package/oracle-bin/es/FSharp.Core.resources.dll +0 -0
  41. package/oracle-bin/es/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  42. package/oracle-bin/fr/FSharp.Compiler.Service.resources.dll +0 -0
  43. package/oracle-bin/fr/FSharp.Core.resources.dll +0 -0
  44. package/oracle-bin/fr/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  45. package/oracle-bin/it/FSharp.Compiler.Service.resources.dll +0 -0
  46. package/oracle-bin/it/FSharp.Core.resources.dll +0 -0
  47. package/oracle-bin/it/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  48. package/oracle-bin/ja/FSharp.Compiler.Service.resources.dll +0 -0
  49. package/oracle-bin/ja/FSharp.Core.resources.dll +0 -0
  50. package/oracle-bin/ja/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  51. package/oracle-bin/ko/FSharp.Compiler.Service.resources.dll +0 -0
  52. package/oracle-bin/ko/FSharp.Core.resources.dll +0 -0
  53. package/oracle-bin/ko/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  54. package/oracle-bin/pl/FSharp.Compiler.Service.resources.dll +0 -0
  55. package/oracle-bin/pl/FSharp.Core.resources.dll +0 -0
  56. package/oracle-bin/pl/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  57. package/oracle-bin/pt-BR/FSharp.Compiler.Service.resources.dll +0 -0
  58. package/oracle-bin/pt-BR/FSharp.Core.resources.dll +0 -0
  59. package/oracle-bin/pt-BR/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  60. package/oracle-bin/ru/FSharp.Compiler.Service.resources.dll +0 -0
  61. package/oracle-bin/ru/FSharp.Core.resources.dll +0 -0
  62. package/oracle-bin/ru/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  63. package/oracle-bin/tr/FSharp.Compiler.Service.resources.dll +0 -0
  64. package/oracle-bin/tr/FSharp.Core.resources.dll +0 -0
  65. package/oracle-bin/tr/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  66. package/oracle-bin/zh-Hans/FSharp.Compiler.Service.resources.dll +0 -0
  67. package/oracle-bin/zh-Hans/FSharp.Core.resources.dll +0 -0
  68. package/oracle-bin/zh-Hans/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  69. package/oracle-bin/zh-Hant/FSharp.Compiler.Service.resources.dll +0 -0
  70. package/oracle-bin/zh-Hant/FSharp.Core.resources.dll +0 -0
  71. package/oracle-bin/zh-Hant/FSharp.DependencyManager.Nuget.resources.dll +0 -0
  72. package/package.json +38 -0
  73. package/packages/FSharp.Oracle/Extractor/Assembly.fs +160 -0
  74. package/packages/FSharp.Oracle/Extractor/EntityExtractor.fs +608 -0
  75. package/packages/FSharp.Oracle/Extractor/Helpers.fs +150 -0
  76. package/packages/FSharp.Oracle/Extractor/MemberExtractor.fs +172 -0
  77. package/packages/FSharp.Oracle/Extractor/ModuleExtractor.fs +45 -0
  78. package/packages/FSharp.Oracle/Extractor/ParameterExtractor.fs +47 -0
  79. package/packages/FSharp.Oracle/Extractor/SignatureRendering.fs +464 -0
  80. package/packages/FSharp.Oracle/Extractor/ValueExtractor.fs +171 -0
  81. package/packages/FSharp.Oracle/FSharp.Oracle.fsproj +36 -0
  82. package/packages/FSharp.Oracle/Program.fs +66 -0
  83. package/packages/FSharp.Oracle/XmlDoc.fs +463 -0
  84. package/packages/FSharp.Oracle.Schema/FSharp.Oracle.Schema.fsproj +16 -0
  85. package/packages/FSharp.Oracle.Schema/Schema.fs +454 -0
  86. package/packages/Starlight.FSharp.Oracle/components/DocEntry.astro +256 -0
  87. package/packages/Starlight.FSharp.Oracle/components/FSharpDocPage.astro +121 -0
  88. package/packages/Starlight.FSharp.Oracle/components/fsharp-doc.css +58 -0
  89. package/packages/Starlight.FSharp.Oracle/layouts/FSharpDocLayout.astro +27 -0
@@ -0,0 +1,66 @@
1
+ module FSharp.Oracle.Program
2
+
3
+ open System
4
+ open FSharp.Compiler.CodeAnalysis
5
+ open FSharp.Oracle.Schema
6
+ open FSharp.Oracle.Extractor
7
+ open Thoth.Json.System.Text.Json
8
+ open Thoth.Json.Core.Auto
9
+
10
+ [<EntryPoint>]
11
+ let main argv =
12
+ let rec parseArgs outputBase dlls =
13
+ function
14
+ | "--output-base" :: value :: rest -> parseArgs value dlls rest
15
+ | path :: rest -> parseArgs outputBase (path :: dlls) rest
16
+ | [] -> outputBase, List.rev dlls |> List.toArray
17
+
18
+ let outputBase, dllPaths = parseArgs "api" [] (argv |> Array.toList)
19
+
20
+ match dllPaths with
21
+ | [||] ->
22
+ eprintfn "Usage: fsharp-docs-oracle [--output-base <base>] <path/to/Assembly.dll> [...]"
23
+ eprintfn "Output: JSON IR written to stdout"
24
+ 1
25
+ | _ ->
26
+ let checker = FSharpChecker.Create()
27
+
28
+ // Gather every .dll in the same directories as the specified assemblies
29
+ // so FCS can resolve transitive dependencies. Also include the current
30
+ // .NET runtime directory because framework assemblies (e.g.
31
+ // System.Text.RegularExpressions) are not copied to the publish output.
32
+ let allDllPaths =
33
+ let runtimeDir =
34
+ System.Runtime.InteropServices.RuntimeEnvironment.GetRuntimeDirectory()
35
+
36
+ [|
37
+ yield! IO.Directory.GetFiles(runtimeDir, "*.dll")
38
+
39
+ yield!
40
+ dllPaths
41
+ |> Array.collect (fun path ->
42
+ let dir = IO.Path.GetDirectoryName(path)
43
+ IO.Directory.GetFiles(dir, "*.dll")
44
+ )
45
+ |]
46
+ |> Array.map IO.Path.GetFullPath
47
+ |> Array.distinct
48
+
49
+ let assemblies =
50
+ dllPaths
51
+ |> Array.map (extractAssembly checker allDllPaths outputBase)
52
+ |> Array.toList
53
+
54
+ let root =
55
+ {
56
+ Assemblies = assemblies
57
+ }
58
+
59
+ let json =
60
+ root
61
+ |> Encode.Auto.generateEncoder(losslessOption = true)
62
+ |> Encode.toString 4
63
+
64
+ Console.WriteLine(json)
65
+
66
+ 0
@@ -0,0 +1,463 @@
1
+ module Oracle.XmlDoc
2
+
3
+ // XML doc formatter adapted from Ionide.XmlDocFormatter
4
+ // https://github.com/ionide/Ionide.XmlDocFormatter/blob/develop/src/Ionide.XmlDocFormatter.fs
5
+
6
+ open System
7
+ open System.Text.RegularExpressions
8
+ open FSharp.Oracle.Schema
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // Formatter config and tag infrastructure
12
+ // ---------------------------------------------------------------------------
13
+
14
+ type private FormatterConfig =
15
+ {
16
+ Paragraph: string -> string
17
+ CodeBlock: string -> string -> string
18
+ InlineCode: string -> string
19
+ HandleMicrosoftOrList: string -> string
20
+ BeforeTransform: string -> string
21
+ AfterTransform: string -> string
22
+ Example: string -> string
23
+ Block: string -> string
24
+ // Reference / link formatters
25
+ MemberRef: string -> string
26
+ LangwordRef: string -> string
27
+ ExternalLink: string -> string -> string
28
+ }
29
+
30
+ let private tagPattern (tagName: string) =
31
+ sprintf
32
+ """(?'void_element'<%s(?'void_attributes'\s+[^\/>]+)?\/>)|(?'non_void_element'<%s(?'non_void_attributes'\s+[^>]+)?>(?'non_void_innerText'(?:(?!<%s>)(?!<\/%s>)[\s\S])*)<\/%s\s*>)"""
33
+ tagName
34
+ tagName
35
+ tagName
36
+ tagName
37
+ tagName
38
+
39
+ type private TagInfo =
40
+ | VoidElement of attributes: Map<string, string>
41
+ | NonVoidElement of innerText: string * attributes: Map<string, string>
42
+
43
+ let private extractTextFromQuote (quotedText: string) =
44
+ quotedText.Substring(1, quotedText.Length - 2)
45
+
46
+ let private extractMemberText (text: string) =
47
+ let pattern = "(?'member_type'[a-z]{1}:)?(?'member_text'.*)"
48
+ let m = Regex.Match(text, pattern, RegexOptions.IgnoreCase)
49
+
50
+ if m.Groups.["member_text"].Success then
51
+ m.Groups.["member_text"].Value
52
+ else
53
+ text
54
+
55
+ let private getAttributes (attributes: Group) =
56
+ if attributes.Success then
57
+ let pattern = """(?'key'\S+)=(?'value''[^']*'|"[^"]*")"""
58
+
59
+ Regex.Matches(attributes.Value, pattern, RegexOptions.IgnoreCase)
60
+ |> Seq.cast<Match>
61
+ |> Seq.map (fun m -> m.Groups.["key"].Value, extractTextFromQuote m.Groups.["value"].Value)
62
+ |> Map.ofSeq
63
+ else
64
+ Map.empty
65
+
66
+ let rec private applyFormatter
67
+ (info:
68
+ {|
69
+ TagName: string
70
+ Formatter: TagInfo -> string option
71
+ |})
72
+ text
73
+ =
74
+ let pattern = tagPattern info.TagName
75
+
76
+ match Regex.Match(text, pattern, RegexOptions.IgnoreCase) with
77
+ | m when m.Success ->
78
+ if m.Groups.["void_element"].Success then
79
+ let attributes = getAttributes m.Groups.["void_attributes"]
80
+
81
+ match VoidElement attributes |> info.Formatter with
82
+ | Some replacement ->
83
+ text.Replace(m.Groups.["void_element"].Value, replacement)
84
+ |> applyFormatter info
85
+ | None -> text
86
+
87
+ elif m.Groups.["non_void_element"].Success then
88
+ let innerText = m.Groups.["non_void_innerText"].Value
89
+ let attributes = getAttributes m.Groups.["non_void_attributes"]
90
+
91
+ match NonVoidElement(innerText, attributes) |> info.Formatter with
92
+ | Some replacement ->
93
+ text.Replace(m.Groups.["non_void_element"].Value, replacement)
94
+ |> applyFormatter info
95
+ | None -> text
96
+ else
97
+ text
98
+ | _ -> text
99
+
100
+ // ---------------------------------------------------------------------------
101
+ // Individual tag formatters
102
+ // ---------------------------------------------------------------------------
103
+
104
+ let private codeBlock (config: FormatterConfig) =
105
+ applyFormatter
106
+ {|
107
+ TagName = "code"
108
+ Formatter =
109
+ function
110
+ | VoidElement _ -> None
111
+ | NonVoidElement(innerText, attributes) ->
112
+ let lang = attributes |> Map.tryFind "lang" |> Option.defaultValue "fsharp"
113
+
114
+ let innerText = innerText.TrimEnd()
115
+
116
+ let normalizedText =
117
+ match
118
+ innerText.StartsWith("\n", StringComparison.Ordinal),
119
+ innerText.EndsWith("\n", StringComparison.Ordinal)
120
+ with
121
+ | true, true -> $"%s{innerText}"
122
+ | true, false -> $"%s{innerText}\n"
123
+ | false, true -> $"\n%s{innerText}"
124
+ | false, false -> $"\n%s{innerText}\n"
125
+
126
+ config.CodeBlock lang normalizedText |> Some
127
+ |}
128
+
129
+ let private example (config: FormatterConfig) =
130
+ applyFormatter
131
+ {|
132
+ TagName = "example"
133
+ Formatter =
134
+ function
135
+ | VoidElement _ -> None
136
+ | NonVoidElement(innerText, _) -> config.Example innerText |> Some
137
+ |}
138
+
139
+ let private codeInline (config: FormatterConfig) =
140
+ applyFormatter
141
+ {|
142
+ TagName = "c"
143
+ Formatter =
144
+ function
145
+ | VoidElement _ -> None
146
+ | NonVoidElement(innerText, _) -> config.InlineCode innerText |> Some
147
+ |}
148
+
149
+ let private anchor (config: FormatterConfig) =
150
+ applyFormatter
151
+ {|
152
+ TagName = "a"
153
+ Formatter =
154
+ function
155
+ | VoidElement attributes ->
156
+ match Map.tryFind "href" attributes with
157
+ | Some href -> Some(config.ExternalLink href href)
158
+ | None -> None
159
+ | NonVoidElement(innerText, attributes) ->
160
+ match Map.tryFind "href" attributes with
161
+ | Some href -> Some(config.ExternalLink href innerText)
162
+ | None -> Some(config.InlineCode innerText)
163
+ |}
164
+
165
+ let private paragraph (config: FormatterConfig) =
166
+ applyFormatter
167
+ {|
168
+ TagName = "para"
169
+ Formatter =
170
+ function
171
+ | VoidElement _ -> None
172
+ | NonVoidElement(innerText, _) -> config.Paragraph innerText |> Some
173
+ |}
174
+
175
+ let private block (config: FormatterConfig) =
176
+ applyFormatter
177
+ {|
178
+ TagName = "block"
179
+ Formatter =
180
+ function
181
+ | VoidElement _ -> None
182
+ | NonVoidElement(innerText, _) -> config.Block innerText |> Some
183
+ |}
184
+
185
+ let private see (config: FormatterConfig) =
186
+ applyFormatter
187
+ {|
188
+ TagName = "see"
189
+ Formatter =
190
+ let fromAttrs (attrs: Map<string, string>) =
191
+ match Map.tryFind "cref" attrs with
192
+ | Some cref -> Some(config.MemberRef(extractMemberText cref))
193
+ | None ->
194
+ match Map.tryFind "langword" attrs with
195
+ | Some langword -> Some(config.LangwordRef langword)
196
+ | None -> None
197
+
198
+ function
199
+ | VoidElement attributes -> fromAttrs attributes
200
+ | NonVoidElement(innerText, attributes) ->
201
+ if String.IsNullOrWhiteSpace innerText then
202
+ fromAttrs attributes
203
+ else
204
+ match Map.tryFind "href" attributes with
205
+ | Some externalUrl -> Some(config.ExternalLink externalUrl innerText)
206
+ | None -> Some(config.InlineCode innerText)
207
+ |}
208
+
209
+ let private paramRef (config: FormatterConfig) =
210
+ applyFormatter
211
+ {|
212
+ TagName = "paramref"
213
+ Formatter =
214
+ function
215
+ | VoidElement attributes ->
216
+ match Map.tryFind "name" attributes with
217
+ | Some name -> Some(config.InlineCode name)
218
+ | None -> None
219
+ | NonVoidElement(innerText, attributes) ->
220
+ if String.IsNullOrWhiteSpace innerText then
221
+ match Map.tryFind "name" attributes with
222
+ | Some name -> Some(config.InlineCode name)
223
+ | None -> None
224
+ else
225
+ Some(config.InlineCode innerText)
226
+ |}
227
+
228
+ let private typeParamRef (config: FormatterConfig) =
229
+ applyFormatter
230
+ {|
231
+ TagName = "typeparamref"
232
+ Formatter =
233
+ function
234
+ | VoidElement attributes ->
235
+ match Map.tryFind "name" attributes with
236
+ | Some name -> Some(config.InlineCode name)
237
+ | None -> None
238
+ | NonVoidElement(innerText, attributes) ->
239
+ if String.IsNullOrWhiteSpace innerText then
240
+ match Map.tryFind "name" attributes with
241
+ | Some name -> Some(config.InlineCode name)
242
+ | None -> None
243
+ else
244
+ Some(config.InlineCode innerText)
245
+ |}
246
+
247
+ let private unescapeSpecialCharacters (text: string) =
248
+ text
249
+ .Replace("&lt;", "<")
250
+ .Replace("&gt;", ">")
251
+ .Replace("&quot;", "\"")
252
+ .Replace("&apos;", "'")
253
+ .Replace("&amp;", "&")
254
+
255
+ // ---------------------------------------------------------------------------
256
+ // Configs and main entry points
257
+ // ---------------------------------------------------------------------------
258
+
259
+ let private markdownConfig =
260
+ {
261
+ BeforeTransform = id
262
+ AfterTransform = unescapeSpecialCharacters
263
+ Paragraph = fun content -> Environment.NewLine + content + Environment.NewLine
264
+ CodeBlock = fun lang code -> "```" + lang + code + "```"
265
+ InlineCode = fun code -> "`" + code + "`"
266
+ HandleMicrosoftOrList = id
267
+ Example =
268
+ fun content ->
269
+ Environment.NewLine
270
+ + Environment.NewLine
271
+ + "**Example:**"
272
+ + Environment.NewLine
273
+ + Environment.NewLine
274
+ + content
275
+ Block = fun content -> Environment.NewLine + content + Environment.NewLine
276
+ MemberRef = fun name -> $"``{name}``"
277
+ LangwordRef = fun word -> $"`{word}`"
278
+ ExternalLink = fun href text -> $"[`{text}`]({href})"
279
+ }
280
+
281
+ let private formatToMarkdown (text: string) =
282
+ text
283
+ |> markdownConfig.BeforeTransform
284
+ |> paragraph markdownConfig
285
+ |> example markdownConfig
286
+ |> block markdownConfig
287
+ |> codeInline markdownConfig
288
+ |> codeBlock markdownConfig
289
+ |> see markdownConfig
290
+ |> paramRef markdownConfig
291
+ |> typeParamRef markdownConfig
292
+ |> anchor markdownConfig
293
+ |> markdownConfig.HandleMicrosoftOrList
294
+ |> markdownConfig.AfterTransform
295
+
296
+ // ---------------------------------------------------------------------------
297
+ // Public API
298
+ // ---------------------------------------------------------------------------
299
+
300
+ let private emptyXmlDoc =
301
+ {
302
+ Summary = None
303
+ Remarks = None
304
+ Returns = None
305
+ Params = []
306
+ Examples = []
307
+ }
308
+
309
+ let private extractFirst (tagName: string) (text: string) : string option =
310
+ match Regex.Match(text, tagPattern tagName, RegexOptions.IgnoreCase) with
311
+ | m when m.Success && m.Groups.["non_void_element"].Success ->
312
+ m.Groups.["non_void_innerText"].Value
313
+ |> formatToMarkdown
314
+ |> fun s -> s.Trim()
315
+ |> Some
316
+ | _ -> None
317
+
318
+ let private extractAll (tagName: string) (text: string) : string list =
319
+ Regex.Matches(text, tagPattern tagName, RegexOptions.IgnoreCase)
320
+ |> Seq.cast<Match>
321
+ |> Seq.choose (fun m ->
322
+ if m.Groups.["non_void_element"].Success then
323
+ m.Groups.["non_void_innerText"].Value
324
+ |> formatToMarkdown
325
+ |> fun s -> s.Trim()
326
+ |> Some
327
+ else
328
+ None
329
+ )
330
+ |> Seq.toList
331
+
332
+ let private extractParams (text: string) : XmlDocParam list =
333
+ Regex.Matches(text, tagPattern "param", RegexOptions.IgnoreCase)
334
+ |> Seq.cast<Match>
335
+ |> Seq.choose (fun m ->
336
+ if m.Groups.["non_void_element"].Success then
337
+ let attrs = getAttributes m.Groups.["non_void_attributes"]
338
+ let inner = m.Groups.["non_void_innerText"].Value
339
+
340
+ match Map.tryFind "name" attrs with
341
+ | Some name ->
342
+ Some
343
+ {
344
+ Name = name
345
+ Doc = formatToMarkdown inner |> fun s -> s.Trim()
346
+ }
347
+ | None -> None
348
+ else
349
+ None
350
+ )
351
+ |> Seq.toList
352
+
353
+ /// Parse the inner XML content of a <member> element into the structured
354
+ /// XmlDoc IR type, with all inline tags converted to Markdown.
355
+ let private parseXmlContent (text: string) : XmlDoc =
356
+ if String.IsNullOrWhiteSpace text then
357
+ emptyXmlDoc
358
+ else
359
+ {
360
+ Summary = extractFirst "summary" text
361
+ Remarks = extractFirst "remarks" text
362
+ Returns = extractFirst "returns" text
363
+ Params = extractParams text
364
+ Examples = extractAll "example" text
365
+ }
366
+
367
+ // ---------------------------------------------------------------------------
368
+ // XML documentation file loader
369
+ // ---------------------------------------------------------------------------
370
+
371
+ /// Escape `<` and `>` characters that appear inside XML attribute values.
372
+ /// The F# compiler sometimes generates invalid XML for anonymous record
373
+ /// type signatures (e.g. `name="M:Foo(<>f__AnonymousType...)`).
374
+ let private fixXmlAttributeChars (xml: string) : string =
375
+ let sb = System.Text.StringBuilder(xml.Length)
376
+ let mutable inTag = false
377
+ let mutable inQuotes = false
378
+ let mutable i = 0
379
+
380
+ while i < xml.Length do
381
+ let c = xml.[i]
382
+
383
+ if not inTag then
384
+ if c = '<' then
385
+ inTag <- true
386
+
387
+ sb.Append(c) |> ignore
388
+ elif inQuotes then
389
+ if c = '"' then
390
+ inQuotes <- false
391
+ sb.Append(c) |> ignore
392
+ elif c = '<' then
393
+ sb.Append("&lt;") |> ignore
394
+ elif c = '>' then
395
+ sb.Append("&gt;") |> ignore
396
+ else
397
+ sb.Append(c) |> ignore
398
+ else
399
+ if c = '"' then
400
+ inQuotes <- true
401
+ elif c = '>' then
402
+ inTag <- false
403
+
404
+ sb.Append(c) |> ignore
405
+
406
+ i <- i + 1
407
+
408
+ sb.ToString()
409
+
410
+ /// Load the XML documentation file that lives next to a compiled DLL and
411
+ /// return a lookup map from the standard XML-doc member ID (e.g.
412
+ /// "M:My.Namespace.func(System.Int32)") to the inner XML content of that
413
+ /// <member> element.
414
+ let loadXmlDocFile (dllPath: string) : Map<string, string> =
415
+ let xmlPath = System.IO.Path.ChangeExtension(dllPath, ".xml")
416
+
417
+ if not (System.IO.File.Exists xmlPath) then
418
+ Map.empty
419
+ else
420
+ let xmlText = System.IO.File.ReadAllText(xmlPath)
421
+
422
+ let doc =
423
+ try
424
+ System.Xml.Linq.XDocument.Parse(xmlText)
425
+ with
426
+ | :? System.Xml.XmlException as ex ->
427
+ eprintfn "ERROR: Failed to parse XML documentation file: %s" xmlPath
428
+ let lines = xmlText.Split('\n')
429
+ let lineIdx = ex.LineNumber - 1
430
+ let startLine = max 0 (lineIdx - 2)
431
+ let endLine = min (lines.Length - 1) (lineIdx + 2)
432
+
433
+ for i in startLine .. endLine do
434
+ let marker = if i = lineIdx then ">>> " else " "
435
+ eprintfn "%sLine %d: %s" marker (i + 1) lines.[i]
436
+
437
+ reraise()
438
+
439
+ doc.Descendants(System.Xml.Linq.XName.Get "member")
440
+ |> Seq.choose (fun m ->
441
+ let nameAttr = m.Attribute(System.Xml.Linq.XName.Get "name")
442
+
443
+ if nameAttr <> null then
444
+ let innerXml = m.Nodes() |> Seq.map (fun n -> n.ToString()) |> String.concat "\n"
445
+
446
+ Some(nameAttr.Value, innerXml)
447
+ else
448
+ None
449
+ )
450
+ |> Map.ofSeq
451
+
452
+ /// Look up and parse the XML doc for a given member signature.
453
+ /// Returns an empty XmlDoc when the signature is not found in the map.
454
+ let xmlDocOf (xmlDocMap: Map<string, string>) (xmlDocSig: string) : XmlDoc =
455
+ xmlDocMap
456
+ |> Map.tryFind xmlDocSig
457
+ |> Option.map parseXmlContent
458
+ |> Option.defaultValue emptyXmlDoc
459
+
460
+ /// Look up just the summary for a module-level signature.
461
+ /// Used for Module.XmlDoc which is stored as a plain string in the IR.
462
+ let moduleDocOf (xmlDocMap: Map<string, string>) (xmlDocSig: string) : string option =
463
+ xmlDocMap |> Map.tryFind xmlDocSig |> Option.bind (extractFirst "summary")
@@ -0,0 +1,16 @@
1
+ <Project Sdk="Microsoft.NET.Sdk">
2
+
3
+ <PropertyGroup>
4
+ <OutputType>Library</OutputType>
5
+ <TargetFramework>net10.0</TargetFramework>
6
+ </PropertyGroup>
7
+
8
+ <ItemGroup>
9
+ <Compile Include="Schema.fs" />
10
+ </ItemGroup>
11
+
12
+ <ItemGroup>
13
+ <PackageReference Include="Thoth.Json.Core" />
14
+ </ItemGroup>
15
+
16
+ </Project>