/* * Copyright 2024 CodeRabbit AI Inc * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import React, { useState, useEffect } from "react" import AceEditor from "react-ace" import "ace-builds/src-noconflict/theme-github" import "ace-builds/src-noconflict/ext-language_tools" import "ace-builds/webpack-resolver" import "ace-builds/src-noconflict/mode-yaml" import jsYaml from "js-yaml" import Ajv from "ajv" const ajv = new Ajv({ allErrors: true }) import Schema from "../../../static/schema/schema.v2.json" const validate = ajv.compile(Schema.definitions.schema) const initialValue = `# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json language: "en-US" early_access: false reviews: profile: "chill" request_changes_workflow: false high_level_summary: true poem: true review_status: true collapse_walkthrough: false auto_review: enabled: true drafts: false chat: auto_reply: true ` export default function YamlEditor() { const [value, setValue] = useState(initialValue) const [annotations, setAnnotations] = useState([]) useEffect(() => { setValue(initialValue) validateAndSetAnnotations(initialValue) }, []) function validateAndSetAnnotations(yaml) { try { const doc = jsYaml.load(yaml, { strict: true }) const isValid = validate(doc) if (!isValid && validate.errors) { setAnnotations( validate.errors.map(err => { const instancePathArr = err?.instancePath?.split("/") const key = instancePathArr && instancePathArr[instancePathArr.length - 1] return { row: err.instancePath ? getLineNumber(yaml, err.instancePath) : 0, column: 0, text: `${key}: ${err.message} ${ err?.params?.allowedValues ? `Allowed values: ${err.params.allowedValues.join(", ")}` : "" }`, type: "error", } }), ) } else { setAnnotations([]) } } catch (err) { const instancePathArr = err?.instancePath?.split("/") const key = instancePathArr && instancePathArr[instancePathArr.length - 1] setAnnotations([ { row: err.instancePath ? getLineNumber(yaml, err.instancePath) : 0, column: 0, text: `${key}: ${err.message} ${ err?.params?.allowedValues ? `Allowed values: ${err.params.allowedValues.join(", ")}` : "" }` || "YAML parsing error", type: "error", }, ]) } } function getLineNumber(yaml, instancePath) { const lines = yaml.split("\n") const pathParts = instancePath.split("/").filter(Boolean) let currentObj = jsYaml.load(yaml) let lineNumber = 0 const lastPathPart = pathParts[pathParts.length - 1] const lastPathPartIndex = pathParts.length - 1 for (let i = 0; i < lines.length; i++) { if (lines[i].trim().startsWith(pathParts[0] + ":")) { // Found the top-level field lineNumber = i currentObj = currentObj[pathParts[0]] for (let j = 1; j < lastPathPartIndex; j++) { // Go through the nested fields for (let k = lineNumber + 1; k < lines.length; k++) { if (lines[k].trim().startsWith(pathParts[j] + ":")) { lineNumber = k currentObj = currentObj[pathParts[j]] break } } } // look for the last path part with array syntax as well as object syntax for (let l = lineNumber + 1; l < lines.length; l++) { if (lines[l].trim().startsWith(`- ${lastPathPart}:`)) { lineNumber = l break } else if (lines[l].trim().startsWith(lastPathPart + ":")) { lineNumber = l break } } break } } return lineNumber } function onChange(newValue) { setValue(newValue) validateAndSetAnnotations(newValue) } return (