@nshipster/sosumi 1.0.4 → 1.0.6

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nshipster/sosumi",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "Making Apple docs AI-readable",
5
5
  "license": "MIT",
6
6
  "author": "NSHipster",
@@ -5,7 +5,9 @@
5
5
  import type {
6
6
  AppleDocJSON,
7
7
  ContentItem,
8
+ Declaration,
8
9
  IndexContentItem,
10
+ Platform,
9
11
  PossibleValueItem,
10
12
  PropertyItem,
11
13
  TopicSection,
@@ -66,6 +68,8 @@ export async function renderFromJSON(
66
68
  }
67
69
  }
68
70
 
71
+ markdown += renderDeprecationNotice(jsonData, jsonData.references, options.externalOrigin)
72
+
69
73
  // Add declaration
70
74
  if (jsonData.primaryContentSections) {
71
75
  const declarationSection = jsonData.primaryContentSections.find(
@@ -192,6 +196,10 @@ function generateFrontMatterFromJSON(jsonData: AppleDocJSON, sourceUrl: string):
192
196
  frontMatter.source = sourceUrl
193
197
  frontMatter.timestamp = new Date().toISOString()
194
198
 
199
+ if (isSymbolDeprecated(jsonData)) {
200
+ frontMatter.deprecated = "true"
201
+ }
202
+
195
203
  // Convert to YAML format
196
204
  const yamlLines = Object.entries(frontMatter).map(([key, value]) => `${key}: ${value}`)
197
205
  return `---\n${yamlLines.join("\n")}\n---\n\n`
@@ -227,10 +235,25 @@ function generateBreadcrumbs(sourceUrl: string, externalOrigin?: string): string
227
235
  return `${breadcrumbs}\n\n`
228
236
  }
229
237
 
238
+ /**
239
+ * Map a DocC language code to a Markdown code-fence identifier. DocC uses
240
+ * `occ` for Objective-C internally, but Markdown highlighters expect `objc`.
241
+ */
242
+ function normalizeFenceLanguage(syntax: string): string {
243
+ return syntax === "occ" ? "objc" : syntax
244
+ }
245
+
246
+ /**
247
+ * Tab titles DocC uses for language-variant code examples. Used to detect
248
+ * whether a `tabNavigator` is a language switch (so we can default to Swift)
249
+ * versus some other tabbed content we should leave alone.
250
+ */
251
+ const LANGUAGE_TAB_TITLES = new Set(["Swift", "Objective-C"])
252
+
230
253
  /**
231
254
  * Render declaration sections
232
255
  */
233
- function renderDeclarations(declarations: Array<{ tokens?: Array<{ text?: string }> }>): string {
256
+ function renderDeclarations(declarations: Declaration[]): string {
234
257
  let markdown = ""
235
258
 
236
259
  for (const decl of declarations) {
@@ -242,7 +265,8 @@ function renderDeclarations(declarations: Array<{ tokens?: Array<{ text?: string
242
265
  .join("")
243
266
  .trim()
244
267
 
245
- markdown += `\`\`\`swift\n${code}\n\`\`\`\n\n`
268
+ const fence = decl.languages?.includes("occ") ? "objc" : "swift"
269
+ markdown += `\`\`\`${fence}\n${code}\n\`\`\`\n\n`
246
270
  }
247
271
  }
248
272
 
@@ -399,7 +423,7 @@ function renderContentArray(
399
423
  } else {
400
424
  code = String(item.code || "")
401
425
  }
402
- const syntax = item.syntax || "swift"
426
+ const syntax = normalizeFenceLanguage(item.syntax || "swift")
403
427
 
404
428
  markdown += `\`\`\`${syntax}\n${code}\n\`\`\`\n\n`
405
429
  } else if (item.type === "unorderedList") {
@@ -435,14 +459,29 @@ function renderContentArray(
435
459
  ? renderContentArray(item.content, references, depth + 1, externalOrigin)
436
460
  : ""
437
461
  const cleanContent = asideContent.trim().replace(/\n/g, "\n> ")
438
- markdown += `> [!${calloutType}]\n> ${cleanContent}\n\n`
462
+ const deprecatedLabel = style.toLowerCase() === "deprecated" ? "**Deprecated**\n>\n> " : ""
463
+ markdown += `> [!${calloutType}]\n> ${deprecatedLabel}${cleanContent}\n\n`
439
464
  } else if (item.type === "table") {
440
465
  markdown += renderTable(item, references, depth, externalOrigin)
441
466
  } else if (item.type === "tabNavigator" && item.tabs?.length) {
442
- for (const tab of item.tabs) {
443
- const label = tab.title?.trim()
444
- if (label) {
445
- markdown += `**${label}**\n\n`
467
+ // Only filter when every tab is a known language tab — otherwise
468
+ // `tabNavigator` may carry non-language content (e.g. platform or
469
+ // theme variants) that we shouldn't drop.
470
+ const allLanguageTabs = item.tabs.every(
471
+ (tab) => tab.title != null && LANGUAGE_TAB_TITLES.has(tab.title.trim()),
472
+ )
473
+ let tabsToRender = item.tabs
474
+ if (allLanguageTabs) {
475
+ const swiftTabs = item.tabs.filter((tab) => tab.title?.trim() === "Swift")
476
+ tabsToRender = swiftTabs.length > 0 ? swiftTabs : item.tabs
477
+ }
478
+ const showLabel = tabsToRender.length > 1
479
+ for (const tab of tabsToRender) {
480
+ if (showLabel) {
481
+ const label = tab.title?.trim()
482
+ if (label) {
483
+ markdown += `**${label}**\n\n`
484
+ }
446
485
  }
447
486
  if (tab.content?.length) {
448
487
  markdown += renderContentArray(tab.content, references, depth + 1, externalOrigin)
@@ -594,7 +633,8 @@ function renderTopicSections(
594
633
  ? reference.abstract.map((a: { text: string }) => a.text).join("")
595
634
  : ""
596
635
 
597
- markdown += `- [${title}](${url})`
636
+ const deprecatedLabel = reference?.deprecated ? " *(Deprecated)*" : ""
637
+ markdown += `- [${title}](${url})${deprecatedLabel}`
598
638
  if (abstract) {
599
639
  markdown += ` ${abstract}`
600
640
  }
@@ -694,6 +734,102 @@ function renderSeeAlso(
694
734
  return markdown
695
735
  }
696
736
 
737
+ /**
738
+ * Whether the symbol page represents a deprecated API.
739
+ */
740
+ function isSymbolDeprecated(jsonData: AppleDocJSON): boolean {
741
+ return getDeprecationNoticeBody(jsonData, jsonData.references) !== null
742
+ }
743
+
744
+ /**
745
+ * Collect a deprecation message from platform availability metadata.
746
+ */
747
+ function getDeprecationMessageFromPlatforms(platforms?: Platform[]): string | null {
748
+ if (!platforms?.length) {
749
+ return null
750
+ }
751
+
752
+ const deprecatedPlatforms = platforms.filter(
753
+ (platform) => platform.deprecated === true || platform.deprecatedAt,
754
+ )
755
+ if (deprecatedPlatforms.length === 0) {
756
+ return null
757
+ }
758
+
759
+ const uniqueMessages = [
760
+ ...new Set(
761
+ deprecatedPlatforms
762
+ .map((platform) => platform.message?.trim())
763
+ .filter((message): message is string => Boolean(message)),
764
+ ),
765
+ ]
766
+
767
+ if (uniqueMessages.length === 1) {
768
+ return uniqueMessages[0]
769
+ }
770
+
771
+ if (uniqueMessages.length > 1) {
772
+ return deprecatedPlatforms
773
+ .filter((platform) => platform.message)
774
+ .map((platform) => `**${platform.name}:** ${platform.message}`)
775
+ .join("\n")
776
+ }
777
+
778
+ return DEFAULT_DEPRECATION_MESSAGE
779
+ }
780
+
781
+ const DEFAULT_DEPRECATION_MESSAGE = "This symbol is deprecated."
782
+
783
+ /**
784
+ * Resolve deprecation notice text from DocC JSON (without rendering the callout).
785
+ */
786
+ function getDeprecationNoticeBody(
787
+ jsonData: AppleDocJSON,
788
+ references?: Record<string, ContentItem>,
789
+ externalOrigin?: string,
790
+ ): string | null {
791
+ if (jsonData.deprecationSummary?.length) {
792
+ const summary = renderContentArray(
793
+ jsonData.deprecationSummary,
794
+ references,
795
+ 0,
796
+ externalOrigin,
797
+ ).trim()
798
+ if (summary) {
799
+ return summary
800
+ }
801
+ }
802
+
803
+ const platformMessage = getDeprecationMessageFromPlatforms(jsonData.metadata?.platforms)
804
+ if (platformMessage) {
805
+ return platformMessage
806
+ }
807
+
808
+ const identifier = jsonData.identifier?.url
809
+ if (identifier && references?.[identifier]?.deprecated === true) {
810
+ return DEFAULT_DEPRECATION_MESSAGE
811
+ }
812
+
813
+ return null
814
+ }
815
+
816
+ /**
817
+ * Render a deprecation callout for deprecated symbol pages.
818
+ */
819
+ function renderDeprecationNotice(
820
+ jsonData: AppleDocJSON,
821
+ references?: Record<string, ContentItem>,
822
+ externalOrigin?: string,
823
+ ): string {
824
+ const body = getDeprecationNoticeBody(jsonData, references, externalOrigin)
825
+ if (!body) {
826
+ return ""
827
+ }
828
+
829
+ const quotedBody = body.replace(/\n/g, "\n> ")
830
+ return `> [!WARNING]\n> **Deprecated**\n>\n> ${quotedBody}\n\n`
831
+ }
832
+
697
833
  /**
698
834
  * Map aside style to GitHub-style callout
699
835
  */
package/src/lib/types.ts CHANGED
@@ -75,6 +75,9 @@ export interface ContentItem {
75
75
 
76
76
  // Abstract content
77
77
  abstract?: TextFragment[]
78
+
79
+ /** Present on reference entries for deprecated symbols. */
80
+ deprecated?: boolean
78
81
  }
79
82
 
80
83
  // ============================================================================
@@ -86,6 +89,7 @@ export interface ContentItem {
86
89
  */
87
90
  export interface Declaration {
88
91
  tokens?: Token[]
92
+ languages?: string[]
89
93
  }
90
94
 
91
95
  /**
@@ -260,6 +264,10 @@ export interface Platform {
260
264
  name: string
261
265
  introducedAt: string
262
266
  beta?: boolean
267
+ deprecated?: boolean
268
+ deprecatedAt?: string
269
+ message?: string
270
+ unavailable?: boolean
263
271
  }
264
272
 
265
273
  /**
@@ -296,6 +304,8 @@ export interface AppleDocJSON {
296
304
 
297
305
  // Content
298
306
  abstract?: Array<{ text: string; type: string }>
307
+ /** DocC deprecation notice shown at the top of deprecated symbol pages. */
308
+ deprecationSummary?: ContentItem[]
299
309
  sections?: ContentItem[]
300
310
 
301
311
  // Primary content sections