Skip to content

Commit 73bc33d

Browse files
committed
fix: handle YAML parsing edge cases in CustomModesManager
- Add BOM (Byte Order Mark) stripping for UTF-8 and UTF-16 - Normalize invisible characters including non-breaking spaces - Replace fancy quotes and dashes with standard characters - Remove zero-width characters that can cause parsing issues - Add comprehensive test coverage for all edge cases This fixes the YAML parsing limitations documented in PR #237 by implementing proper preprocessing before parsing YAML content.
1 parent 3598e3c commit 73bc33d

File tree

2 files changed

+510
-4
lines changed

2 files changed

+510
-4
lines changed

src/core/config/CustomModesManager.ts

Lines changed: 87 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -73,12 +73,95 @@ export class CustomModesManager {
7373
return exists ? roomodesPath : undefined
7474
}
7575

76+
/**
77+
* Strip BOM (Byte Order Mark) from the beginning of a string
78+
*/
79+
private stripBOM(content: string): string {
80+
// Common BOMs: UTF-8, UTF-16 BE, UTF-16 LE
81+
const boms = [
82+
"\uFEFF", // UTF-8 BOM
83+
"\uFFFE", // UTF-16 LE BOM (reversed)
84+
]
85+
86+
let result = content
87+
for (const bom of boms) {
88+
if (result.startsWith(bom)) {
89+
result = result.slice(bom.length)
90+
}
91+
}
92+
93+
return result
94+
}
95+
96+
/**
97+
* Clean invisible and problematic characters from YAML content
98+
*/
99+
private cleanInvisibleCharacters(content: string): string {
100+
// Replace non-breaking spaces with regular spaces
101+
let cleaned = content.replace(/\u00A0/g, " ")
102+
103+
// Replace other invisible characters that can cause issues
104+
// Zero-width space, zero-width non-joiner, zero-width joiner
105+
cleaned = cleaned.replace(/[\u200B\u200C\u200D]/g, "")
106+
107+
// Replace various dash characters with standard hyphen
108+
cleaned = cleaned.replace(/[\u2010-\u2015\u2212]/g, "-")
109+
110+
// Replace various quote characters with standard quotes
111+
cleaned = cleaned.replace(/[\u2018\u2019]/g, "'")
112+
cleaned = cleaned.replace(/[\u201C\u201D]/g, '"')
113+
114+
return cleaned
115+
}
116+
117+
/**
118+
* Parse YAML content with enhanced error handling and preprocessing
119+
*/
120+
private parseYamlSafely(content: string, filePath: string): any {
121+
// Clean the content
122+
let cleanedContent = this.stripBOM(content)
123+
cleanedContent = this.cleanInvisibleCharacters(cleanedContent)
124+
125+
try {
126+
return yaml.parse(cleanedContent)
127+
} catch (error) {
128+
const errorMsg = error instanceof Error ? error.message : String(error)
129+
console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, errorMsg)
130+
131+
// Show user-friendly error message for .roomodes files
132+
if (filePath.endsWith(ROOMODES_FILENAME)) {
133+
const lineMatch = errorMsg.match(/at line (\d+)/)
134+
const line = lineMatch ? lineMatch[1] : "unknown"
135+
vscode.window.showErrorMessage(
136+
`Invalid YAML in .roomodes file at line ${line}. Please check for:\n` +
137+
`• Proper indentation (use spaces, not tabs)\n` +
138+
`• Matching quotes and brackets\n` +
139+
`• Valid YAML syntax`,
140+
)
141+
}
142+
143+
throw error
144+
}
145+
}
146+
76147
private async loadModesFromFile(filePath: string): Promise<ModeConfig[]> {
77148
try {
78149
const content = await fs.readFile(filePath, "utf-8")
79-
const settings = yaml.parse(content)
150+
const settings = this.parseYamlSafely(content, filePath)
80151
const result = customModesSettingsSchema.safeParse(settings)
152+
81153
if (!result.success) {
154+
console.error(`[CustomModesManager] Schema validation failed for ${filePath}:`, result.error)
155+
156+
// Show user-friendly error for .roomodes files
157+
if (filePath.endsWith(ROOMODES_FILENAME)) {
158+
const issues = result.error.issues
159+
.map((issue) => `• ${issue.path.join(".")}: ${issue.message}`)
160+
.join("\n")
161+
162+
vscode.window.showErrorMessage(`Invalid custom modes format in .roomodes:\n${issues}`)
163+
}
164+
82165
return []
83166
}
84167

@@ -153,7 +236,7 @@ export class CustomModesManager {
153236
let config: any
154237

155238
try {
156-
config = yaml.parse(content)
239+
config = this.parseYamlSafely(content, settingsPath)
157240
} catch (error) {
158241
console.error(error)
159242
vscode.window.showErrorMessage(errorMessage)
@@ -335,9 +418,9 @@ export class CustomModesManager {
335418
let settings
336419

337420
try {
338-
settings = yaml.parse(content)
421+
settings = this.parseYamlSafely(content, filePath)
339422
} catch (error) {
340-
console.error(`[CustomModesManager] Failed to parse YAML from ${filePath}:`, error)
423+
// Error already logged in parseYamlSafely
341424
settings = { customModes: [] }
342425
}
343426

0 commit comments

Comments
 (0)