diff --git a/www/config.html b/www/config.html index 8e50f340..b4b20c04 100644 --- a/www/config.html +++ b/www/config.html @@ -456,6 +456,23 @@ const lintYamlModel = (model, schemaTools) => { const markers = []; const markedLines = new Set(); + let blockScalarParentIndent = null; + + const isBlockScalarHeader = (text) => { + const t = (text ?? '').trimStart(); + return t.startsWith('|') || t.startsWith('>'); + }; + + const checkChildIndent = (ctx, childIndent, lineNumber) => { + if (!ctx) return; + if (ctx.childIndent == null) { + ctx.childIndent = childIndent; + return; + } + if (childIndent !== ctx.childIndent) { + markLineError(lineNumber, `YAML: inconsistent indentation (expected ${ctx.childIndent} spaces)`, 1); + } + }; const markLineError = (lineNumber, message, startColumn = 1) => { if (markedLines.has(`${lineNumber}:${message}`)) return; @@ -500,6 +517,7 @@ expected: 'object', actual: null, keys: new Map(), + childIndent: null, origin: null, reportedTypeMismatch: false, }]; @@ -566,6 +584,13 @@ if (!line.trim()) continue; const indent = countIndent(line); + if (blockScalarParentIndent !== null && indent <= blockScalarParentIndent) { + blockScalarParentIndent = null; + } + if (blockScalarParentIndent !== null && indent > blockScalarParentIndent) { + continue; // treat as block scalar content + } + if (indent === 0 && (hasTopLevelKey || hasTopLevelList)) { const trimmed = line.trim(); if (trimmed !== '---' && trimmed !== '...' && !trimmed.startsWith('#')) { @@ -581,9 +606,13 @@ const listItem = parseListItem(line); if (listItem) { + if (listItem.indent > 0 && (hasTopLevelKey || hasTopLevelList) && stack.length === 1) { + markLineError(lineNumber, 'YAML: unexpected indentation at document root', 1); + } while (stack.length > 1 && listItem.indent <= stack[stack.length - 1].indent) stack.pop(); const parent = stack[stack.length - 1]; + checkChildIndent(parent, listItem.indent, lineNumber); setActualType(parent, 'array', lineNumber); const itemSchema = schemaTools ? schemaTools.getArrayItemSchema(parent.schema) : null; @@ -595,12 +624,18 @@ expected: itemExpected, actual: null, keys: new Map(), + childIndent: null, origin: {lineNumber, startColumn: listItem.dashIndex + 1, endColumn: listItem.dashIndex + 2}, reportedTypeMismatch: false, }; stack.push(itemCtx); if (!listItem.rest) continue; + if (isBlockScalarHeader(listItem.rest)) { + blockScalarParentIndent = listItem.indent; + checkValueType(itemSchema, 'string', lineNumber, listItem.afterDashIndex + 1, line.length + 1, null); + continue; + } const inline = parseKey(' '.repeat(listItem.afterDashIndex) + listItem.rest); if (inline) { @@ -608,6 +643,7 @@ const kv = inline; while (stack.length > 1 && kv.indent <= stack[stack.length - 1].indent) stack.pop(); const ctx = stack[stack.length - 1]; + checkChildIndent(ctx, kv.indent, lineNumber); setActualType(ctx, 'object', lineNumber); const prev = ctx.keys.get(kv.key); @@ -636,6 +672,9 @@ } const propSchema = schemaTools ? schemaTools.getPropertySchema(ctx.schema, kv.key) : null; + if (isBlockScalarHeader(kv.after)) { + blockScalarParentIndent = kv.indent; + } if (kv.isContainer) { stack.push({ indent: kv.indent, @@ -643,6 +682,7 @@ expected: getExpectedContainerType(propSchema), actual: null, keys: new Map(), + childIndent: null, origin: {lineNumber, startColumn: kv.keyStartIndex + 1, endColumn: kv.keyEndIndex + 1}, reportedTypeMismatch: false, }); @@ -661,10 +701,17 @@ } const kv = parseKey(line); - if (!kv) continue; + if (!kv) { + markLineError(lineNumber, 'YAML: expected a map key (key:) or list item (-)', indent + 1); + continue; + } + if (kv.indent > 0 && (hasTopLevelKey || hasTopLevelList) && stack.length === 1) { + markLineError(lineNumber, 'YAML: unexpected indentation at document root', 1); + } while (stack.length > 1 && kv.indent <= stack[stack.length - 1].indent) stack.pop(); const ctx = stack[stack.length - 1]; + checkChildIndent(ctx, kv.indent, lineNumber); setActualType(ctx, 'object', lineNumber); const prev = ctx.keys.get(kv.key); @@ -700,12 +747,17 @@ expected: getExpectedContainerType(propSchema), actual: null, keys: new Map(), + childIndent: null, origin: {lineNumber, startColumn: kv.keyStartIndex + 1, endColumn: kv.keyEndIndex + 1}, reportedTypeMismatch: false, }); continue; } + if (isBlockScalarHeader(kv.after)) { + blockScalarParentIndent = kv.indent; + } + if (!propSchema) continue; const actual = classifyYamlScalar(kv.after).type;