sourcey 3.6.0 → 3.6.2

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 (86) hide show
  1. package/README.md +5 -2
  2. package/dist/adapters/index.d.ts +3 -2
  3. package/dist/adapters/index.d.ts.map +1 -1
  4. package/dist/adapters/index.js +35 -3
  5. package/dist/adapters/types.d.ts +8 -2
  6. package/dist/adapters/types.d.ts.map +1 -1
  7. package/dist/cli.js +28 -34
  8. package/dist/client/search.js +181 -119
  9. package/dist/client/tabs.js +23 -0
  10. package/dist/components/layout/Head.d.ts.map +1 -1
  11. package/dist/components/layout/Head.js +7 -6
  12. package/dist/components/openapi/Introduction.d.ts.map +1 -1
  13. package/dist/components/openapi/Introduction.js +16 -1
  14. package/dist/components/openapi/Security.d.ts.map +1 -1
  15. package/dist/components/openapi/Security.js +18 -2
  16. package/dist/config.d.ts +70 -0
  17. package/dist/config.d.ts.map +1 -1
  18. package/dist/config.js +23 -7
  19. package/dist/core/api-rendering.d.ts +47 -0
  20. package/dist/core/api-rendering.d.ts.map +1 -0
  21. package/dist/core/api-rendering.js +76 -0
  22. package/dist/core/doxygen-loader.d.ts +1 -1
  23. package/dist/core/doxygen-loader.d.ts.map +1 -1
  24. package/dist/core/doxygen-loader.js +37 -19
  25. package/dist/core/godoc-loader.d.ts.map +1 -1
  26. package/dist/core/godoc-loader.js +17 -6
  27. package/dist/core/markdown-loader.d.ts.map +1 -1
  28. package/dist/core/markdown-loader.js +8 -5
  29. package/dist/core/mcp-normalizer.d.ts.map +1 -1
  30. package/dist/core/mcp-normalizer.js +48 -34
  31. package/dist/core/parser.d.ts.map +1 -1
  32. package/dist/core/parser.js +75 -4
  33. package/dist/core/rustdoc-introspector.d.ts +29 -0
  34. package/dist/core/rustdoc-introspector.d.ts.map +1 -0
  35. package/dist/core/rustdoc-introspector.js +236 -0
  36. package/dist/core/rustdoc-loader.d.ts +25 -0
  37. package/dist/core/rustdoc-loader.d.ts.map +1 -0
  38. package/dist/core/rustdoc-loader.js +472 -0
  39. package/dist/core/rustdoc-render.d.ts +82 -0
  40. package/dist/core/rustdoc-render.d.ts.map +1 -0
  41. package/dist/core/rustdoc-render.js +808 -0
  42. package/dist/core/rustdoc-types.d.ts +314 -0
  43. package/dist/core/rustdoc-types.d.ts.map +1 -0
  44. package/dist/core/rustdoc-types.js +18 -0
  45. package/dist/core/search-indexer.js +1 -1
  46. package/dist/core/sourcey-rustdoc/Cargo.lock +705 -0
  47. package/dist/core/sourcey-rustdoc/Cargo.toml +38 -0
  48. package/dist/core/sourcey-rustdoc/README.md +59 -0
  49. package/dist/core/sourcey-rustdoc/src/diagnostics.rs +42 -0
  50. package/dist/core/sourcey-rustdoc/src/doctest.rs +268 -0
  51. package/dist/core/sourcey-rustdoc/src/extract.rs +492 -0
  52. package/dist/core/sourcey-rustdoc/src/lib.rs +58 -0
  53. package/dist/core/sourcey-rustdoc/src/links.rs +126 -0
  54. package/dist/core/sourcey-rustdoc/src/main.rs +256 -0
  55. package/dist/core/sourcey-rustdoc/src/signature.rs +415 -0
  56. package/dist/core/sourcey-rustdoc/src/spec.rs +390 -0
  57. package/dist/core/sourcey-rustdoc/tests/format_version.rs +45 -0
  58. package/dist/dev-server.d.ts.map +1 -1
  59. package/dist/dev-server.js +50 -1
  60. package/dist/index.d.ts +4 -2
  61. package/dist/index.d.ts.map +1 -1
  62. package/dist/index.js +22 -13
  63. package/dist/init.js +2 -2
  64. package/dist/renderer/changelog-feed.d.ts.map +1 -1
  65. package/dist/renderer/changelog-feed.js +27 -19
  66. package/dist/renderer/context.d.ts +1 -0
  67. package/dist/renderer/context.d.ts.map +1 -1
  68. package/dist/renderer/html-builder.d.ts.map +1 -1
  69. package/dist/renderer/html-builder.js +4 -3
  70. package/dist/renderer/llms.js +57 -2
  71. package/dist/site-assembly.d.ts +5 -0
  72. package/dist/site-assembly.d.ts.map +1 -1
  73. package/dist/site-assembly.js +57 -2
  74. package/dist/themes/default/sourcey.css +300 -0
  75. package/dist/utils/code-samples.d.ts +1 -0
  76. package/dist/utils/code-samples.d.ts.map +1 -1
  77. package/dist/utils/code-samples.js +30 -22
  78. package/dist/utils/html.d.ts +10 -0
  79. package/dist/utils/html.d.ts.map +1 -0
  80. package/dist/utils/html.js +152 -0
  81. package/dist/utils/http.d.ts +0 -2
  82. package/dist/utils/http.d.ts.map +1 -1
  83. package/dist/utils/http.js +19 -34
  84. package/dist/utils/markdown.d.ts.map +1 -1
  85. package/dist/utils/markdown.js +102 -37
  86. package/package.json +10 -5
@@ -0,0 +1,38 @@
1
+ [package]
2
+ name = "sourcey-rustdoc"
3
+ version = "0.1.1"
4
+ edition = "2021"
5
+ description = "Convert nightly rustdoc JSON into a stable RustdocSpec snapshot. Powers sourcey's rustdoc() source adapter; works standalone for any tool that wants a stable Rust API surface representation."
6
+ license = "AGPL-3.0-only"
7
+ repository = "https://github.com/sourcey/sourcey"
8
+ homepage = "https://sourcey.com"
9
+ documentation = "https://sourcey.com/docs/adapters/rustdoc"
10
+ readme = "README.md"
11
+ keywords = ["rustdoc", "documentation", "json", "rust-api"]
12
+ categories = ["development-tools", "command-line-utilities"]
13
+ rust-version = "1.85"
14
+
15
+ [lib]
16
+ name = "sourcey_rustdoc"
17
+ path = "src/lib.rs"
18
+
19
+ [[bin]]
20
+ name = "sourcey-rustdoc"
21
+ path = "src/main.rs"
22
+
23
+ [dependencies]
24
+ rustdoc-types = "=0.57"
25
+ rustdoc-json = "0.9"
26
+ serde = { version = "1", features = ["derive"] }
27
+ serde_json = "1"
28
+ anyhow = "1"
29
+ thiserror = "1"
30
+ clap = { version = "4", features = ["derive"] }
31
+ pulldown-cmark = "0.10"
32
+ time = { version = "0.3", features = ["formatting"] }
33
+ toml = "0.8"
34
+ flate2 = "1"
35
+
36
+ [profile.release]
37
+ lto = "thin"
38
+ codegen-units = 1
@@ -0,0 +1,59 @@
1
+ # sourcey-rustdoc
2
+
3
+ [![crates.io](https://img.shields.io/crates/v/sourcey-rustdoc.svg)](https://crates.io/crates/sourcey-rustdoc)
4
+ [![docs.rs](https://docs.rs/sourcey-rustdoc/badge.svg)](https://docs.rs/sourcey-rustdoc)
5
+
6
+ Convert nightly rustdoc JSON into a stable `RustdocSpec` snapshot. Powers
7
+ the [sourcey](https://sourcey.com) `rustdoc()` source adapter; published
8
+ standalone so any tool that wants a stable representation of a Rust crate's
9
+ API surface can consume the same schema.
10
+
11
+ Most users install sourcey via npm (`npx sourcey init`) and never touch
12
+ this crate directly. It is published for:
13
+
14
+ - alternative renderers that want a stable Rust-side schema
15
+ - semver checkers and public-API analyzers
16
+ - CI tools that drive snapshot generation from Rust
17
+
18
+ If you just want sourcey for your project, see
19
+ [sourcey.com/docs/adapters/rustdoc](https://sourcey.com/docs/adapters/rustdoc).
20
+
21
+ ## CLI
22
+
23
+ ```
24
+ sourcey-rustdoc --manifest <path> [options] [--output <file>|-]
25
+ ```
26
+
27
+ Key flags:
28
+
29
+ - `--manifest <path>` Path to the Cargo manifest (file or directory).
30
+ - `--crate <name>` Crate name to document; repeat for several. Defaults to
31
+ the manifest's own package.
32
+ - `--features <name>` Enable a feature; repeat for several.
33
+ - `--all-features` Enable all features.
34
+ - `--no-default-features` Disable default features.
35
+ - `--include-private` Include `pub(crate)` and private items.
36
+ - `--include-hidden` Include `#[doc(hidden)]` items.
37
+ - `--target <triple>` Build docs for a specific target triple.
38
+ - `--toolchain <name>` rustup toolchain to invoke; default `nightly`.
39
+ - `--cap-lints <level>` Pass through to rustdoc; default `warn`.
40
+ - `--output <path>|-` Where to write the snapshot. `-` writes to stdout.
41
+ - `--strict` Exit non-zero on any error-level diagnostic.
42
+
43
+ ## Snapshot format
44
+
45
+ See `src/spec.rs`. The schema is versioned by the top-level `version` field
46
+ (currently `1`) and pinned to a specific rustdoc JSON `format_version` via
47
+ the `rustdoc-types` dependency. The `format_version` carried in the
48
+ snapshot lets consumers verify they read a compatible artifact.
49
+
50
+ ## First-run compile cost
51
+
52
+ The first invocation runs `cargo build --release` for this crate and all of
53
+ its dependencies (`rustdoc-types`, `rustdoc-json`, `serde`, `clap`,
54
+ `pulldown-cmark`, ...). On a warm cargo cache this is fast; on a cold
55
+ machine expect 30–60s. Subsequent invocations are near-instant.
56
+
57
+ Snapshot mode in the TS adapter sidesteps this entirely by reading a
58
+ committed `rustdoc.json`, so projects that build their docs site in CI
59
+ without a Rust toolchain are unaffected.
@@ -0,0 +1,42 @@
1
+ use crate::spec::{Diagnostic, DiagnosticSeverity};
2
+
3
+ pub mod codes {
4
+ pub const FORMAT_VERSION_MISMATCH: &str = "RUSTDOC_FORMAT_VERSION_MISMATCH";
5
+ pub const INTRA_DOC_LINK_UNRESOLVED: &str = "RUSTDOC_INTRA_DOC_LINK_UNRESOLVED";
6
+ pub const MISSING_HTML_ROOT_URL: &str = "RUSTDOC_MISSING_HTML_ROOT_URL";
7
+ pub const CARGO_FAILED: &str = "RUSTDOC_CARGO_FAILED";
8
+ pub const INVALID_SNAPSHOT_SCHEMA: &str = "RUSTDOC_INVALID_SNAPSHOT_SCHEMA";
9
+ }
10
+
11
+ pub fn error(code: &str, message: impl Into<String>) -> Diagnostic {
12
+ Diagnostic {
13
+ severity: DiagnosticSeverity::Error,
14
+ code: code.to_string(),
15
+ message: message.into(),
16
+ crate_name: None,
17
+ file: None,
18
+ line: None,
19
+ }
20
+ }
21
+
22
+ pub fn warning(code: &str, message: impl Into<String>) -> Diagnostic {
23
+ Diagnostic {
24
+ severity: DiagnosticSeverity::Warning,
25
+ code: code.to_string(),
26
+ message: message.into(),
27
+ crate_name: None,
28
+ file: None,
29
+ line: None,
30
+ }
31
+ }
32
+
33
+ pub fn info(code: &str, message: impl Into<String>) -> Diagnostic {
34
+ Diagnostic {
35
+ severity: DiagnosticSeverity::Info,
36
+ code: code.to_string(),
37
+ message: message.into(),
38
+ crate_name: None,
39
+ file: None,
40
+ line: None,
41
+ }
42
+ }
@@ -0,0 +1,268 @@
1
+ use pulldown_cmark::{CodeBlockKind, Event, Parser, Tag, TagEnd};
2
+
3
+ use crate::spec::Doctest;
4
+
5
+ /// Tokens that explicitly mark a fence as non-rust. Anything else is treated
6
+ /// as a rust doctest with the tokens collected as fence attributes,
7
+ /// matching rustdoc's own behavior.
8
+ const NON_RUST_LANGS: &[&str] = &[
9
+ "text",
10
+ "ignore-but-not-rust",
11
+ "json",
12
+ "toml",
13
+ "yaml",
14
+ "html",
15
+ "bash",
16
+ "sh",
17
+ "shell",
18
+ "console",
19
+ "diff",
20
+ "ini",
21
+ "markdown",
22
+ "md",
23
+ ];
24
+
25
+ /// Tokens that are recognised by rustdoc as fence attributes (kept as
26
+ /// attributes, not consumed as a language identifier).
27
+ const KNOWN_FENCE_ATTRS: &[&str] = &[
28
+ "ignore",
29
+ "no_run",
30
+ "should_panic",
31
+ "compile_fail",
32
+ "allow_fail",
33
+ "standalone_crate",
34
+ "edition2015",
35
+ "edition2018",
36
+ "edition2021",
37
+ "edition2024",
38
+ ];
39
+
40
+ /// Parse the docstring markdown and return every code fence as a Doctest.
41
+ /// Doctests in rustdoc are any ` ``` ` fence whose language token is empty, `rust`,
42
+ /// or starts with `rust,`. We also surface non-rust fences so the renderer can
43
+ /// label them; the caller filters on `lang`/`fence_attributes`.
44
+ pub fn extract_doctests(docs_markdown: &str) -> Vec<Doctest> {
45
+ let mut out = Vec::new();
46
+ let parser = Parser::new(docs_markdown);
47
+ let mut active: Option<ActiveFence> = None;
48
+ let mut ordinal: u32 = 0;
49
+ for ev in parser {
50
+ match ev {
51
+ Event::Start(Tag::CodeBlock(CodeBlockKind::Fenced(info))) => {
52
+ let info = info.into_string();
53
+ active = Some(ActiveFence::new(&info));
54
+ }
55
+ Event::Start(Tag::CodeBlock(CodeBlockKind::Indented)) => {
56
+ active = Some(ActiveFence::default_rust());
57
+ }
58
+ Event::Text(text) => {
59
+ if let Some(fence) = active.as_mut() {
60
+ fence.body.push_str(&text);
61
+ }
62
+ }
63
+ Event::End(TagEnd::CodeBlock) => {
64
+ if let Some(fence) = active.take() {
65
+ if let Some(doctest) = fence.into_doctest(ordinal) {
66
+ out.push(doctest);
67
+ ordinal += 1;
68
+ }
69
+ }
70
+ }
71
+ _ => {}
72
+ }
73
+ }
74
+ out
75
+ }
76
+
77
+ struct ActiveFence {
78
+ lang: String,
79
+ fence_attributes: Vec<String>,
80
+ body: String,
81
+ is_rust: bool,
82
+ }
83
+
84
+ impl ActiveFence {
85
+ fn new(info: &str) -> Self {
86
+ let parts: Vec<String> = info
87
+ .split(',')
88
+ .map(str::trim)
89
+ .filter(|s| !s.is_empty())
90
+ .map(|s| s.to_string())
91
+ .collect();
92
+ if parts.is_empty() {
93
+ return Self {
94
+ lang: "rust".to_string(),
95
+ fence_attributes: Vec::new(),
96
+ body: String::new(),
97
+ is_rust: true,
98
+ };
99
+ }
100
+ let first = &parts[0];
101
+ let first_lc = first.to_lowercase();
102
+ // Explicit non-rust language: not a doctest.
103
+ if NON_RUST_LANGS.contains(&first_lc.as_str()) {
104
+ return Self {
105
+ lang: first.clone(),
106
+ fence_attributes: parts[1..].to_vec(),
107
+ body: String::new(),
108
+ is_rust: false,
109
+ };
110
+ }
111
+ // Explicit `rust` lang: drop it, keep the rest as attributes.
112
+ if first_lc == "rust" {
113
+ return Self {
114
+ lang: "rust".to_string(),
115
+ fence_attributes: parts[1..].to_vec(),
116
+ body: String::new(),
117
+ is_rust: true,
118
+ };
119
+ }
120
+ // First token is a known rust attribute (e.g. `no_run`): all tokens
121
+ // are attributes; implicit rust lang.
122
+ if KNOWN_FENCE_ATTRS.contains(&first_lc.as_str()) {
123
+ return Self {
124
+ lang: "rust".to_string(),
125
+ fence_attributes: parts.clone(),
126
+ body: String::new(),
127
+ is_rust: true,
128
+ };
129
+ }
130
+ // Unknown first token (e.g. `mermaid`): treat as a foreign language,
131
+ // not a doctest.
132
+ Self {
133
+ lang: first.clone(),
134
+ fence_attributes: parts[1..].to_vec(),
135
+ body: String::new(),
136
+ is_rust: false,
137
+ }
138
+ }
139
+
140
+ fn default_rust() -> Self {
141
+ Self {
142
+ lang: "rust".to_string(),
143
+ fence_attributes: Vec::new(),
144
+ body: String::new(),
145
+ is_rust: true,
146
+ }
147
+ }
148
+
149
+ fn into_doctest(self, ordinal: u32) -> Option<Doctest> {
150
+ if !self.is_rust {
151
+ return None;
152
+ }
153
+ let (display_code, executable_code) = split_hidden_lines(&self.body);
154
+ let implicit_main_wrap = !contains_top_level_main(&executable_code);
155
+ Some(Doctest {
156
+ lang: self.lang,
157
+ fence_attributes: self.fence_attributes,
158
+ display_code,
159
+ executable_code,
160
+ implicit_main_wrap,
161
+ source: None,
162
+ ordinal,
163
+ })
164
+ }
165
+ }
166
+
167
+ fn split_hidden_lines(body: &str) -> (String, String) {
168
+ let mut display = String::new();
169
+ let mut executable = String::new();
170
+ for line in body.lines() {
171
+ let trimmed = line.trim_start();
172
+ if let Some(rest) = trimmed.strip_prefix("# ") {
173
+ let indent_len = line.len() - trimmed.len();
174
+ executable.push_str(&line[..indent_len]);
175
+ executable.push_str(rest);
176
+ executable.push('\n');
177
+ continue;
178
+ }
179
+ if trimmed == "#" {
180
+ executable.push('\n');
181
+ continue;
182
+ }
183
+ if let Some(rest) = trimmed.strip_prefix("##") {
184
+ let indent_len = line.len() - trimmed.len();
185
+ display.push_str(&line[..indent_len]);
186
+ display.push('#');
187
+ display.push_str(rest);
188
+ display.push('\n');
189
+ executable.push_str(&line[..indent_len]);
190
+ executable.push('#');
191
+ executable.push_str(rest);
192
+ executable.push('\n');
193
+ continue;
194
+ }
195
+ display.push_str(line);
196
+ display.push('\n');
197
+ executable.push_str(line);
198
+ executable.push('\n');
199
+ }
200
+ (display, executable)
201
+ }
202
+
203
+ fn contains_top_level_main(code: &str) -> bool {
204
+ let mut depth: i32 = 0;
205
+ for line in code.lines() {
206
+ let trimmed = line.trim_start();
207
+ if depth == 0 && (trimmed.starts_with("fn main(") || trimmed.starts_with("fn main ")) {
208
+ return true;
209
+ }
210
+ for ch in line.chars() {
211
+ match ch {
212
+ '{' => depth += 1,
213
+ '}' => depth -= 1,
214
+ _ => {}
215
+ }
216
+ }
217
+ }
218
+ false
219
+ }
220
+
221
+ #[cfg(test)]
222
+ mod tests {
223
+ use super::*;
224
+
225
+ #[test]
226
+ fn extracts_rust_fences() {
227
+ let docs = "Example\n\n```\nlet x = 1;\n```\n";
228
+ let doctests = extract_doctests(docs);
229
+ assert_eq!(doctests.len(), 1);
230
+ assert!(doctests[0].display_code.contains("let x = 1;"));
231
+ }
232
+
233
+ #[test]
234
+ fn skips_text_fences() {
235
+ let docs = "```text\nnot rust\n```\n";
236
+ assert!(extract_doctests(docs).is_empty());
237
+ }
238
+
239
+ #[test]
240
+ fn captures_fence_attributes() {
241
+ let docs = "```rust,no_run,should_panic\nlet x = 1;\n```\n";
242
+ let doctests = extract_doctests(docs);
243
+ assert_eq!(doctests[0].fence_attributes, vec!["no_run", "should_panic"]);
244
+ }
245
+
246
+ #[test]
247
+ fn strips_hidden_lines_from_display() {
248
+ let docs = "```\n# use foo::Bar;\nlet x = Bar::new();\n```\n";
249
+ let doctests = extract_doctests(docs);
250
+ assert!(!doctests[0].display_code.contains("# use foo::Bar"));
251
+ assert!(doctests[0].display_code.contains("let x = Bar::new();"));
252
+ assert!(doctests[0].executable_code.contains("use foo::Bar;"));
253
+ }
254
+
255
+ #[test]
256
+ fn detects_explicit_main() {
257
+ let docs = "```\nfn main() {\n let _ = 1;\n}\n```\n";
258
+ let doctests = extract_doctests(docs);
259
+ assert!(!doctests[0].implicit_main_wrap);
260
+ }
261
+
262
+ #[test]
263
+ fn detects_implicit_main() {
264
+ let docs = "```\nlet x = 1;\nassert_eq!(x, 1);\n```\n";
265
+ let doctests = extract_doctests(docs);
266
+ assert!(doctests[0].implicit_main_wrap);
267
+ }
268
+ }