From 16c6ea1d26d751f4dd39de3f0ccbf2b2a9117905 Mon Sep 17 00:00:00 2001 From: Nate Kelley Date: Fri, 4 Apr 2025 11:57:07 -0600 Subject: [PATCH] Update validateMetricYaml.tsx --- .../lib/metrics/files/validateMetricYaml.tsx | 84 +++++++++++++------ 1 file changed, 60 insertions(+), 24 deletions(-) diff --git a/web/src/lib/metrics/files/validateMetricYaml.tsx b/web/src/lib/metrics/files/validateMetricYaml.tsx index 1cc758331..22bc9ca34 100644 --- a/web/src/lib/metrics/files/validateMetricYaml.tsx +++ b/web/src/lib/metrics/files/validateMetricYaml.tsx @@ -2,8 +2,7 @@ import React, { useRef } from 'react'; import * as yaml from 'js-yaml'; import * as monaco from 'monaco-editor'; import { type editor } from 'monaco-editor/esm/vs/editor/editor.api'; -import { useEffect } from 'react'; -import MonacoEditor, { OnMount } from '@monaco-editor/react'; +import MonacoEditor from '@monaco-editor/react'; type IMarkerData = editor.IMarkerData; @@ -15,8 +14,22 @@ Siblings: Jane: 25 Jim: 28`; +// Helper function to find line number for a specific key in YAML content +const findLineNumberForKey = (content: string, key: string): number => { + const lines = content.split('\n'); + for (let i = 0; i < lines.length; i++) { + if (lines[i].trim().startsWith(`${key}:`)) { + return i + 1; // Line numbers are 1-based + } + } + return 1; // Default to line 1 if not found +}; + // Linting function that validates YAML content -export const validateMetricYaml = (content: string, monaco: any): IMarkerData[] => { +export const validateMetricYaml = ( + content: string, + monaco: typeof import('monaco-editor') +): IMarkerData[] => { const markers: IMarkerData[] = []; let parsed: any; @@ -24,12 +37,19 @@ export const validateMetricYaml = (content: string, monaco: any): IMarkerData[] try { parsed = yaml.load(content); } catch (error: any) { + // For parse errors, try to extract line information from the error message + let lineNumber = 1; + const lineMatch = error.message.match(/line (\d+)/i); + if (lineMatch && lineMatch[1]) { + lineNumber = parseInt(lineMatch[1], 10); + } + markers.push({ severity: monaco.MarkerSeverity.Error, message: 'Invalid YAML 🤣: ' + error.message, - startLineNumber: 1, + startLineNumber: lineNumber, startColumn: 1, - endLineNumber: 1, + endLineNumber: lineNumber, endColumn: 1 }); return markers; @@ -55,13 +75,14 @@ export const validateMetricYaml = (content: string, monaco: any): IMarkerData[] // Check for any unexpected keys keys.forEach((key) => { if (!allowedKeys.includes(key)) { + const lineNumber = findLineNumberForKey(content, key); markers.push({ severity: monaco.MarkerSeverity.Error, message: `Unexpected key "${key}". Only ${allowedKeys.join(', ')} are allowed.`, - startLineNumber: 1, + startLineNumber: lineNumber, startColumn: 1, - endLineNumber: 1, - endColumn: 1 + endLineNumber: lineNumber, + endColumn: content.split('\n')[lineNumber - 1]?.length || 1 }); } }); @@ -82,39 +103,44 @@ export const validateMetricYaml = (content: string, monaco: any): IMarkerData[] // Validate types for each field if ('Person' in parsed && typeof parsed.Person !== 'string') { + const lineNumber = findLineNumberForKey(content, 'Person'); markers.push({ severity: monaco.MarkerSeverity.Error, message: 'The "Person" field must be a string.', - startLineNumber: 1, + startLineNumber: lineNumber, startColumn: 1, - endLineNumber: 1, - endColumn: 1 + endLineNumber: lineNumber, + endColumn: content.split('\n')[lineNumber - 1]?.length || 1 }); } if ('Place' in parsed && typeof parsed.Place !== 'string') { + const lineNumber = findLineNumberForKey(content, 'Place'); markers.push({ severity: monaco.MarkerSeverity.Error, message: 'The "Place" field must be a string.', - startLineNumber: 1, + startLineNumber: lineNumber, startColumn: 1, - endLineNumber: 1, - endColumn: 1 + endLineNumber: lineNumber, + endColumn: content.split('\n')[lineNumber - 1]?.length || 1 }); } if ('Age' in parsed && typeof parsed.Age !== 'number') { + const lineNumber = findLineNumberForKey(content, 'Age'); markers.push({ severity: monaco.MarkerSeverity.Error, message: 'The "Age" field must be a number.', - startLineNumber: 1, + startLineNumber: lineNumber, startColumn: 1, - endLineNumber: 1, - endColumn: 1 + endLineNumber: lineNumber, + endColumn: content.split('\n')[lineNumber - 1]?.length || 1 }); } if ('Siblings' in parsed) { + const siblingsLineNumber = findLineNumberForKey(content, 'Siblings'); + if ( typeof parsed.Siblings !== 'object' || Array.isArray(parsed.Siblings) || @@ -123,22 +149,32 @@ export const validateMetricYaml = (content: string, monaco: any): IMarkerData[] markers.push({ severity: monaco.MarkerSeverity.Error, message: 'The "Siblings" field must be an object with sibling name-age pairs.', - startLineNumber: 1, + startLineNumber: siblingsLineNumber, startColumn: 1, - endLineNumber: 1, - endColumn: 1 + endLineNumber: siblingsLineNumber, + endColumn: content.split('\n')[siblingsLineNumber - 1]?.length || 1 }); } else { - // Validate that each sibling’s age is a number + // Validate that each sibling's age is a number + const lines = content.split('\n'); Object.entries(parsed.Siblings).forEach(([siblingName, siblingAge]) => { if (typeof siblingAge !== 'number') { + // Look for the sibling in the content + let siblingLineNumber = siblingsLineNumber + 1; // Start after Siblings: + for (let i = siblingsLineNumber; i < lines.length; i++) { + if (lines[i].trim().startsWith(`${siblingName}:`)) { + siblingLineNumber = i + 1; + break; + } + } + markers.push({ severity: monaco.MarkerSeverity.Error, message: `The age for sibling "${siblingName}" must be a number.`, - startLineNumber: 1, + startLineNumber: siblingLineNumber, startColumn: 1, - endLineNumber: 1, - endColumn: 1 + endLineNumber: siblingLineNumber, + endColumn: lines[siblingLineNumber - 1]?.length || 1 }); } });