diff --git a/docs/configure-coderabbit.md b/docs/configure-coderabbit.md index e44c3936..c468ca19 100644 --- a/docs/configure-coderabbit.md +++ b/docs/configure-coderabbit.md @@ -39,24 +39,6 @@ You can add a `.coderabbit.yaml` configuration file to the root of your repositories. Below is a sample YAML file that can be used as a starting point and changed as needed: -```yaml -# 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 -``` - Write your configuration file in the below editor to validate: ```mdx-code-block diff --git a/src/components/YamlEditor/YamlEditor.jsx b/src/components/YamlEditor/YamlEditor.jsx deleted file mode 100644 index a60564ba..00000000 --- a/src/components/YamlEditor/YamlEditor.jsx +++ /dev/null @@ -1,103 +0,0 @@ -import { React, useState } 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"; - -export default function YamlEditor() { - const [annotations, setAnnotations] = useState([]); - const [value, setValue] = useState( - "# yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json\n" - ); - const validate = ajv.compile(Schema.definitions.schema); - function getRowFromPath(path) { - // Convert path to row number (0-based) - return path.split("/").length - 1; - } - function getLineNumber(yaml, path) { - const lines = yaml.split("\n"); - const pathParts = path.split("/").filter(Boolean); - let currentObj = jsYaml.load(yaml); - let lineNumber = 0; - - for (const part of pathParts) { - for (let i = lineNumber; i < lines.length; i++) { - if (lines[i].trim().startsWith(part + ":")) { - lineNumber = i; - break; - } - } - currentObj = currentObj[part]; - } - - return lineNumber; - } - function onChange(newValue) { - setValue(newValue); - try { - const doc = jsYaml.load(newValue, { strict: true }); - const valid = validate(doc); - - if (!valid && validate.errors) { - setAnnotations( - validate.errors.map((err) => ({ - row: err.instancePath - ? getLineNumber(newValue, err.instancePath) - : 0, - column: 0, - text: `${err.keyword}: ${err.message} ${ - err?.params?.allowedValues - ? `Allowed values: ${err.params.allowedValues.join(", ")}` - : "" - }`, - type: "error", - })) - ); - } else { - setAnnotations([]); - } - } catch (err) { - setAnnotations([ - { - row: err.instancePath ? getLineNumber(newValue, err.instancePath) : 0, - column: 0, - text: - `${err.keyword}: ${err.message} ${ - err?.params?.allowedValues - ? `Allowed values: ${err.params.allowedValues.join(", ")}` - : "" - }` || "YAML parsing error", - type: "error", - }, - ]); - } - } - return ( - - ); -} diff --git a/src/components/YamlEditor/YamlEditor.tsx b/src/components/YamlEditor/YamlEditor.tsx new file mode 100644 index 00000000..2308f8c9 --- /dev/null +++ b/src/components/YamlEditor/YamlEditor.tsx @@ -0,0 +1,160 @@ +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 ( +
+ +
+
+ ); +}