Because - We need a linter for the FML so that we can fetch errors for feature configs This commit - First pass at an FML linter that will fetch errors from the FML and parse them into `Diagnostic`s - We do not have the endpoint yet that is going to fetch the errors (#9480) - Separates out some shared helpers for both schema linter and fml linter - Rename `schema.ts` to `validators.ts` so we can include the fml linting in the same file. This can be separated out later if need be
This commit is contained in:
Родитель
c42cbe90d7
Коммит
e019771d55
|
@ -11,9 +11,12 @@ import React, { useCallback, useMemo, useRef } from "react";
|
|||
import { useController, useFormContext } from "react-hook-form";
|
||||
import { FeatureValueEditorProps } from "src/components/PageEditBranches/FormBranches/FormFeatureValue/props";
|
||||
import {
|
||||
fmlLinter,
|
||||
schemaAutocomplete,
|
||||
schemaLinter,
|
||||
} from "src/components/PageEditBranches/FormBranches/FormFeatureValue/schema";
|
||||
} from "src/components/PageEditBranches/FormBranches/FormFeatureValue/validators";
|
||||
|
||||
const allowFmlLinting = false;
|
||||
|
||||
export default function RichFeatureValueEditor({
|
||||
featureConfig,
|
||||
|
@ -81,8 +84,11 @@ export default function RichFeatureValueEditor({
|
|||
];
|
||||
|
||||
if (schema) {
|
||||
extensions.push(linter(schemaLinter(schema)));
|
||||
|
||||
if (allowFmlLinting) {
|
||||
extensions.push(linter(fmlLinter()));
|
||||
} else {
|
||||
extensions.push(linter(schemaLinter(schema)));
|
||||
}
|
||||
const completionSource = schemaAutocomplete(schema);
|
||||
if (completionSource) {
|
||||
extensions.push(
|
||||
|
|
|
@ -3,10 +3,11 @@ import { EditorState, EditorStateConfig } from "@codemirror/state";
|
|||
import { basicSetup } from "codemirror";
|
||||
import {
|
||||
detectDraft,
|
||||
fmlLinter,
|
||||
schemaAutocomplete,
|
||||
schemaLinter,
|
||||
simpleObjectSchema,
|
||||
} from "src/components/PageEditBranches/FormBranches/FormFeatureValue/schema";
|
||||
} from "src/components/PageEditBranches/FormBranches/FormFeatureValue/validators";
|
||||
import { z } from "zod";
|
||||
|
||||
const SIMPLE_SCHEMA: z.infer<typeof simpleObjectSchema> = {
|
||||
|
@ -329,3 +330,37 @@ describe("detectDraft", () => {
|
|||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("fmlLinter", () => {
|
||||
it.each(["", " ", "\t", "\n", " \n \t "])(
|
||||
"does not return fml errors for an empty document",
|
||||
(doc) => {
|
||||
const linter = fmlLinter();
|
||||
const state = createEditorState({ doc: JSON.stringify(doc) });
|
||||
const diagnostics = linter({ state });
|
||||
expect(diagnostics).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it.each([`{"foo": {"error"}`, `{"error": {"bingo"}`])(
|
||||
"returns FML errors",
|
||||
(doc) => {
|
||||
const linter = fmlLinter();
|
||||
const message = { message: "oh no!" };
|
||||
const state = createEditorState({ doc: JSON.stringify(doc) });
|
||||
const diagnostics = linter({ state });
|
||||
expect(diagnostics).toContainEqual(expect.objectContaining(message));
|
||||
},
|
||||
);
|
||||
|
||||
it.each([`{"foo": {"bar"}`, `{"foo": {"mimsy"}`])(
|
||||
"does not returns FML errors",
|
||||
(doc) => {
|
||||
const linter = fmlLinter();
|
||||
const message = { message: "oh no!" };
|
||||
const state = createEditorState({ doc: JSON.stringify(doc) });
|
||||
const diagnostics = linter({ state });
|
||||
expect(diagnostics).not.toContainEqual(expect.objectContaining(message));
|
||||
},
|
||||
);
|
||||
});
|
|
@ -6,6 +6,7 @@ import { Text } from "@codemirror/state";
|
|||
import { EditorView } from "@codemirror/view";
|
||||
import jsonToAst from "json-to-ast";
|
||||
import { z } from "zod";
|
||||
|
||||
/**
|
||||
* A simplified schema that is generated by a feature manifest entry with only a
|
||||
* simple list of variables.
|
||||
|
@ -201,12 +202,7 @@ export function schemaLinter(schema: Record<string, unknown>) {
|
|||
pos.end.column - 1,
|
||||
);
|
||||
|
||||
diagnostics.push({
|
||||
from,
|
||||
to,
|
||||
message,
|
||||
severity: "error",
|
||||
});
|
||||
diagnostics.push(createDiagnostic(from, to, message));
|
||||
}
|
||||
|
||||
reportFloatValues(view.state.doc, rootNode, diagnostics);
|
||||
|
@ -222,6 +218,23 @@ function documentPosition(doc: Text, line: number, column: number): number {
|
|||
return doc.line(line).from + column;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse an error into a Diagnostic
|
||||
*/
|
||||
function createDiagnostic(
|
||||
from: number,
|
||||
to: number,
|
||||
message: string,
|
||||
severity = "error",
|
||||
): Diagnostic {
|
||||
return {
|
||||
from: from,
|
||||
to: to,
|
||||
message: message,
|
||||
severity: severity,
|
||||
} as Diagnostic;
|
||||
}
|
||||
|
||||
interface FindNodeResult {
|
||||
value: jsonToAst.ValueNode;
|
||||
key?: jsonToAst.IdentifierNode;
|
||||
|
@ -280,12 +293,13 @@ function reportFloatValues(
|
|||
case "Literal":
|
||||
if (typeof node.value === "number" && ~~node.value !== node.value) {
|
||||
const loc = node.loc!;
|
||||
diagnostics.push({
|
||||
from: documentPosition(doc, loc.start.line, loc.start.column),
|
||||
to: documentPosition(doc, loc.start.line, loc.start.column),
|
||||
severity: "error",
|
||||
message: "Floats are not supported",
|
||||
});
|
||||
diagnostics.push(
|
||||
createDiagnostic(
|
||||
documentPosition(doc, loc.start.line, loc.start.column),
|
||||
documentPosition(doc, loc.start.line, loc.start.column),
|
||||
"Floats are not supported",
|
||||
),
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
@ -332,3 +346,27 @@ export function schemaAutocomplete(schema: Record<string, unknown>) {
|
|||
return null;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse from FML errors to Diagnostics
|
||||
*/
|
||||
function parseFmlErrors(view: TestableEditorView): Diagnostic[] {
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
|
||||
const doc = view.state.doc;
|
||||
const text = doc.toString();
|
||||
const from = text.indexOf("error");
|
||||
if (from === -1) return diagnostics;
|
||||
|
||||
const to = from + "error".length;
|
||||
|
||||
diagnostics.push(createDiagnostic(from, to, "oh no!"));
|
||||
return diagnostics;
|
||||
}
|
||||
|
||||
export function fmlLinter() {
|
||||
return function (view: TestableEditorView) {
|
||||
// TODO call endpoint to retrieve errors, remove temp error highlighting
|
||||
return parseFmlErrors(view);
|
||||
};
|
||||
}
|
Загрузка…
Ссылка в новой задаче