@bfra.me/doc-sync 0.1.0 → 0.1.1

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.
@@ -169,15 +169,12 @@ function renderMDXDocument(frontmatter: MDXFrontmatter, content: string): string
169
169
  * Prevents XSS by escaping potentially dangerous content
170
170
  */
171
171
  export function sanitizeContent(content: string): string {
172
- // Use comprehensive sanitization from utils
173
172
  return sanitizeForMDX(content)
174
173
  }
175
174
 
176
175
  /**
177
176
  * Sanitizes content within MDX while preserving JSX components
178
- * Only escapes content that appears to be user-provided text
179
- * Now includes sanitization of JSX component attributes to prevent XSS
180
- * Uses safe, non-backtracking parsing to prevent ReDoS
177
+ * Sanitizes JSX component attributes to prevent XSS while leaving closing tags unchanged
181
178
  */
182
179
  export function sanitizeTextContent(content: string): string {
183
180
  const jsxTags = parseJSXTags(content)
@@ -189,12 +186,7 @@ export function sanitizeTextContent(content: string): string {
189
186
  parts.push(sanitizeContent(content.slice(lastIndex, index)))
190
187
  }
191
188
 
192
- if (isClosing) {
193
- parts.push(tag)
194
- } else {
195
- parts.push(sanitizeJSXTag(tag))
196
- }
197
-
189
+ parts.push(isClosing ? tag : sanitizeJSXTag(tag))
198
190
  lastIndex = index + tag.length
199
191
  }
200
192
 
@@ -225,23 +217,32 @@ export function validateMDXSyntax(mdx: string): Result<true, SyncError> {
225
217
  return ok(true)
226
218
  }
227
219
 
220
+ /**
221
+ * Checks if a tag name is likely a TypeScript generic parameter rather than a JSX component
222
+ * Single uppercase letters (T, E, K, V, etc.) are common generic type parameters
223
+ */
224
+ function isTypeScriptGeneric(tag: string): boolean {
225
+ const tagNameMatch = tag.match(/<\/?([A-Z][a-zA-Z0-9]*)/)
226
+ const tagName = tagNameMatch?.[1]
227
+ return tagName !== undefined && tagName.length === 1
228
+ }
229
+
228
230
  function checkForUnclosedTags(mdx: string): string[] {
229
231
  const unclosed: string[] = []
230
232
  const tagStack: string[] = []
231
233
 
232
- // Remove code blocks from content before checking for JSX tags
233
- // This prevents TypeScript generics like Result<T, E> from being
234
- // misinterpreted as unclosed JSX tags
234
+ // Remove code blocks and inline code to prevent TypeScript generics like Result<T, E>
235
+ // from being misinterpreted as JSX tags
235
236
  const codeBlocks = extractCodeBlocks(mdx)
236
- let contentWithoutCodeBlocks = mdx
237
+ let contentWithoutCode = mdx
237
238
  for (const block of codeBlocks) {
238
- // Replace code block with empty lines to preserve line numbers
239
239
  const lineCount = block.split('\n').length
240
240
  const placeholder = '\n'.repeat(lineCount)
241
- contentWithoutCodeBlocks = contentWithoutCodeBlocks.replace(block, placeholder)
241
+ contentWithoutCode = contentWithoutCode.replace(block, placeholder)
242
242
  }
243
243
 
244
- const jsxTags = parseJSXTags(contentWithoutCodeBlocks)
244
+ const allJSXTags = parseJSXTags(contentWithoutCode)
245
+ const jsxTags = allJSXTags.filter(({tag}) => !isTypeScriptGeneric(tag))
245
246
 
246
247
  for (const {tag, isClosing, isSelfClosing} of jsxTags) {
247
248
  const tagNameMatch = isClosing
package/src/index.ts CHANGED
@@ -71,6 +71,75 @@ export type {
71
71
  ValidationWarning,
72
72
  } from './orchestrator'
73
73
 
74
+ // Re-export parsers
75
+ export {
76
+ analyzePublicAPI,
77
+ analyzeTypeScriptContent,
78
+ analyzeTypeScriptFile,
79
+ assertPackageAPI,
80
+ assertPackageInfo,
81
+ assertParseError,
82
+ buildDocSlug,
83
+ createProject,
84
+ extractDocsConfig,
85
+ extractExportedFunctions,
86
+ extractExportedTypes,
87
+ extractJSDocInfo,
88
+ extractPackageAPI,
89
+ extractReExports,
90
+ findEntryPoint,
91
+ findExportedSymbols,
92
+ findReadmePath,
93
+ findSection,
94
+ flattenSections,
95
+ getExportedSymbolInfo,
96
+ getExportsByKind,
97
+ getPackageScope,
98
+ getSectionsByLevel,
99
+ getTableOfContents,
100
+ getUnscopedName,
101
+ hasJSDoc,
102
+ isDocConfigSource,
103
+ isExportedFunction,
104
+ isExportedType,
105
+ isJSDocInfo,
106
+ isJSDocParam,
107
+ isJSDocTag,
108
+ isMDXFrontmatter,
109
+ isPackageAPI,
110
+ isPackageInfo,
111
+ isParseError,
112
+ isReadmeContent,
113
+ isReadmeSection,
114
+ isReExport,
115
+ isSafeContent,
116
+ isSafeFilePath,
117
+ isSymbolExported,
118
+ isSyncError,
119
+ isValidHeadingLevel,
120
+ isValidPackageName,
121
+ isValidSemver,
122
+ parseJSDoc,
123
+ parsePackageComplete,
124
+ parsePackageJson,
125
+ parsePackageJsonContent,
126
+ parseReadme,
127
+ parseReadmeFile,
128
+ parseSourceContent,
129
+ parseSourceFile,
130
+ } from './parsers'
131
+
132
+ export type {
133
+ ExportAnalyzerOptions,
134
+ JSDocableDeclaration,
135
+ PackageInfoOptions,
136
+ PackageJsonSchema,
137
+ PublicAPIAnalysis,
138
+ ReadmeParserOptions,
139
+ ResolvedExport,
140
+ TypeScriptParserOptions,
141
+ } from './parsers'
142
+
74
143
  export type {
75
144
  CLIOptions,
76
145
  DocConfig,
@@ -102,6 +171,19 @@ export type {
102
171
 
103
172
  export {SENTINEL_MARKERS} from './types'
104
173
 
174
+ // Re-export utils
175
+ export {
176
+ createHeadingPattern,
177
+ extractCodeBlocks,
178
+ findEmptyMarkdownLinks,
179
+ hasComponent,
180
+ parseJSXAttributes,
181
+ parseJSXTags,
182
+ sanitizeAttribute,
183
+ sanitizeForMDX,
184
+ sanitizeJSXTag,
185
+ } from './utils'
186
+
105
187
  // Re-export watcher
106
188
  export {
107
189
  categorizeFile,
@@ -51,11 +51,11 @@ export function hasComponent(content: string, componentName: string): boolean {
51
51
  }
52
52
 
53
53
  /**
54
- * Extract code blocks from markdown content using unified/remark (safe, no regex)
54
+ * Extract code blocks and inline code from markdown content using unified/remark (safe, no regex)
55
55
  * This approach uses AST parsing instead of regex to avoid ReDoS vulnerabilities
56
56
  *
57
57
  * @param content - The markdown content to parse
58
- * @returns Array of code block strings with their language identifiers
58
+ * @returns Array of code block strings (fenced blocks with backticks, inline code with backticks)
59
59
  *
60
60
  * @example
61
61
  * ```ts
@@ -91,6 +91,10 @@ export function extractCodeBlocks(content: string): readonly string[] {
91
91
  const value = node.value ?? ''
92
92
  blocks.push(`\`\`\`${lang}\n${value}\n\`\`\``)
93
93
  }
94
+ if (node.type === 'inlineCode') {
95
+ const value = node.value ?? ''
96
+ blocks.push(`\`${value}\``)
97
+ }
94
98
  if (Array.isArray(node.children)) {
95
99
  for (const child of node.children) {
96
100
  visit(child)