feat(recipe-studio, validators): tweak OXC validator with lint suppression support and improve error normalization logic
This commit is contained in:
parent
93063c3212
commit
5e5feb5c00
3 changed files with 155 additions and 116 deletions
|
|
@ -267,6 +267,7 @@ def _run_oxc_batch(
|
|||
"error_count": 1,
|
||||
"error_message": "Invalid OXC result entry.",
|
||||
"severity": None,
|
||||
"code": None,
|
||||
"labels": [],
|
||||
"codeframe": None,
|
||||
"warning_count": 0,
|
||||
|
|
@ -277,6 +278,7 @@ def _run_oxc_batch(
|
|||
error_count_raw = item.get("error_count")
|
||||
message_raw = item.get("error_message")
|
||||
severity_raw = item.get("severity")
|
||||
code_raw = item.get("code")
|
||||
labels_raw = item.get("labels")
|
||||
codeframe_raw = item.get("codeframe")
|
||||
warning_count_raw = item.get("warning_count")
|
||||
|
|
@ -286,6 +288,7 @@ def _run_oxc_batch(
|
|||
"error_count": int(error_count_raw) if isinstance(error_count_raw, int) else 0,
|
||||
"error_message": str(message_raw or ""),
|
||||
"severity": str(severity_raw) if isinstance(severity_raw, str) else None,
|
||||
"code": str(code_raw) if isinstance(code_raw, str) else None,
|
||||
"labels": labels_raw if isinstance(labels_raw, list) else [],
|
||||
"codeframe": str(codeframe_raw) if isinstance(codeframe_raw, str) else None,
|
||||
"warning_count": int(warning_count_raw)
|
||||
|
|
@ -303,6 +306,7 @@ def _fallback_results(row_count: int, message: str) -> list[dict[str, Any]]:
|
|||
"error_count": 1,
|
||||
"error_message": message,
|
||||
"severity": None,
|
||||
"code": None,
|
||||
"labels": [],
|
||||
"codeframe": None,
|
||||
"warning_count": 0,
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ const VALIDATION_MODES = new Set(["syntax", "lint", "syntax+lint"]);
|
|||
const CODE_SHAPES = new Set(["auto", "module", "snippet"]);
|
||||
const SNIPPET_PREFIX = "(() => {\n";
|
||||
const SNIPPET_SUFFIX = "\n})();\nexport {};\n";
|
||||
const OXLINT_SUPPRESSED_RULES = ["no-unused-vars", "no-new-array"];
|
||||
const TOOL_DIR = dirname(fileURLToPath(import.meta.url));
|
||||
|
||||
function mapLang(value) {
|
||||
|
|
@ -112,6 +113,7 @@ function remapDiagnosticOffsets(diagnostic, offset) {
|
|||
function normalizeParserError(error) {
|
||||
if (typeof error === "string") {
|
||||
return {
|
||||
code: null,
|
||||
message: error.trim() || "Unknown parser error",
|
||||
severity: null,
|
||||
labels: [],
|
||||
|
|
@ -120,12 +122,14 @@ function normalizeParserError(error) {
|
|||
}
|
||||
if (!error || typeof error !== "object") {
|
||||
return {
|
||||
code: null,
|
||||
message: "Unknown parser error",
|
||||
severity: null,
|
||||
labels: [],
|
||||
codeframe: null,
|
||||
};
|
||||
}
|
||||
const code = typeof error.code === "string" ? error.code : null;
|
||||
const message = String(error.message || error.reason || "").trim() || "Unknown parser error";
|
||||
const severity = typeof error.severity === "string" ? error.severity : null;
|
||||
const labels = Array.isArray(error.labels)
|
||||
|
|
@ -146,6 +150,7 @@ function normalizeParserError(error) {
|
|||
: [];
|
||||
const codeframe = typeof error.codeframe === "string" ? error.codeframe : null;
|
||||
return {
|
||||
code,
|
||||
message,
|
||||
severity,
|
||||
labels,
|
||||
|
|
@ -190,6 +195,7 @@ function normalizeLintDiagnostic(diagnostic) {
|
|||
|
||||
const code = typeof diagnostic.code === "string" ? diagnostic.code : null;
|
||||
return {
|
||||
code,
|
||||
message: code ? `${code}: ${message}` : message,
|
||||
severity,
|
||||
labels,
|
||||
|
|
@ -203,6 +209,7 @@ function makeResult({
|
|||
warningCount = 0,
|
||||
message = "",
|
||||
severity = null,
|
||||
code = null,
|
||||
labels = [],
|
||||
codeframe = null,
|
||||
}) {
|
||||
|
|
@ -212,6 +219,7 @@ function makeResult({
|
|||
warning_count: Number.isInteger(warningCount) ? warningCount : 0,
|
||||
error_message: String(message || ""),
|
||||
severity: typeof severity === "string" ? severity : null,
|
||||
code: typeof code === "string" ? code : null,
|
||||
labels: Array.isArray(labels) ? labels : [],
|
||||
codeframe: typeof codeframe === "string" ? codeframe : null,
|
||||
};
|
||||
|
|
@ -225,6 +233,7 @@ function syntaxResultFromErrors(errors) {
|
|||
warningCount: 0,
|
||||
message: errors.slice(0, 3).map((error) => error.message).join(" | "),
|
||||
severity: first ? first.severity : null,
|
||||
code: first ? first.code : null,
|
||||
labels: first ? first.labels : [],
|
||||
codeframe: first ? first.codeframe : null,
|
||||
});
|
||||
|
|
@ -377,7 +386,13 @@ function runLintBatch(entries) {
|
|||
}
|
||||
|
||||
const oxlintBin = join(TOOL_DIR, "node_modules", ".bin", "oxlint");
|
||||
const exec = spawnSync(oxlintBin, ["--format", "json", tempDir], {
|
||||
const oxlintArgs = [
|
||||
...OXLINT_SUPPRESSED_RULES.flatMap((rule) => ["-A", rule]),
|
||||
"--format",
|
||||
"json",
|
||||
tempDir,
|
||||
];
|
||||
const exec = spawnSync(oxlintBin, oxlintArgs, {
|
||||
encoding: "utf8",
|
||||
cwd: TOOL_DIR,
|
||||
});
|
||||
|
|
@ -452,6 +467,7 @@ function runLintBatch(entries) {
|
|||
.map((diag) => diag.message)
|
||||
.join(" | "),
|
||||
severity: top ? top.severity : null,
|
||||
code: top ? top.code : null,
|
||||
labels: top ? top.labels : [],
|
||||
codeframe: top ? top.codeframe : null,
|
||||
}),
|
||||
|
|
@ -555,4 +571,3 @@ main().catch((error) => {
|
|||
process.stderr.write(String(error?.stack || error));
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -284,9 +284,32 @@ export function ExecutionsView({
|
|||
return formatDuration(selectedExecution.createdAt, selectedExecution.finishedAt);
|
||||
}, [selectedExecution]);
|
||||
const showSummaryCards = selectedExecution?.status === "completed";
|
||||
const showProgressPanel =
|
||||
selectedExecution?.status === "completed" ||
|
||||
(selectedExecution ? isExecutionInProgress(selectedExecution.status) : false);
|
||||
const hasProgressSnapshot = Boolean(
|
||||
selectedExecution?.progress &&
|
||||
(typeof selectedExecution.progress.done === "number" ||
|
||||
typeof selectedExecution.progress.total === "number" ||
|
||||
typeof selectedExecution.progress.percent === "number" ||
|
||||
typeof selectedExecution.progress.rate === "number" ||
|
||||
typeof selectedExecution.progress.eta_sec === "number"),
|
||||
) || Boolean(
|
||||
selectedExecution?.column_progress &&
|
||||
(typeof selectedExecution.column_progress.done === "number" ||
|
||||
typeof selectedExecution.column_progress.total === "number" ||
|
||||
typeof selectedExecution.column_progress.percent === "number"),
|
||||
) || Boolean(
|
||||
selectedExecution?.batch &&
|
||||
(typeof selectedExecution.batch.idx === "number" ||
|
||||
typeof selectedExecution.batch.total === "number"),
|
||||
);
|
||||
const selectedStatus = selectedExecution?.status ?? null;
|
||||
const isSelectedExecutionInProgress = selectedStatus
|
||||
? isExecutionInProgress(selectedStatus)
|
||||
: false;
|
||||
const showProgressPanel = Boolean(selectedExecution) && (
|
||||
selectedStatus === "completed" ||
|
||||
isSelectedExecutionInProgress ||
|
||||
hasProgressSnapshot
|
||||
);
|
||||
const progressComplete = selectedExecution?.status === "completed";
|
||||
const progressPercent = selectedExecution?.progress?.percent ?? (progressComplete ? 100 : 0);
|
||||
const batchTotal = selectedExecution?.batch?.total ?? null;
|
||||
|
|
@ -400,118 +423,115 @@ export function ExecutionsView({
|
|||
</div>
|
||||
)}
|
||||
|
||||
{(selectedExecution.status === "completed" ||
|
||||
isExecutionInProgress(selectedExecution.status)) && (
|
||||
<Tabs value={detailTab} onValueChange={setDetailTab}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<TabsList className="border border-border/60 bg-card/40">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="columns">Columns</TabsTrigger>
|
||||
<TabsTrigger value="data">Data</TabsTrigger>
|
||||
<TabsTrigger value="raw">Raw</TabsTrigger>
|
||||
</TabsList>
|
||||
{canCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onCancelExecution(selectedExecution.id)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<TabsContent value="overview">
|
||||
<ExecutionOverviewTab
|
||||
execution={selectedExecution}
|
||||
showSummaryCards={showSummaryCards}
|
||||
recordsMetric={recordsMetric}
|
||||
totalMetric={totalMetric}
|
||||
runDuration={runDuration}
|
||||
columnCount={columnCount}
|
||||
llmColumnCount={llmColumnCount}
|
||||
nullRate={nullRate}
|
||||
sideEffects={sideEffects}
|
||||
lowUniquenessColumns={lowUniquenessColumns}
|
||||
modelUsageRows={modelUsageRows}
|
||||
terminalLines={terminalLines}
|
||||
terminalRef={terminalRef}
|
||||
onTerminalScroll={(event) => {
|
||||
const element = event.currentTarget;
|
||||
const distanceFromBottom =
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
shouldStickTerminalToBottomRef.current =
|
||||
distanceFromBottom <= TERMINAL_STICKY_BOTTOM_THRESHOLD_PX;
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="columns">
|
||||
<ExecutionColumnsTab analysisColumns={analysisColumns} />
|
||||
</TabsContent>
|
||||
<TabsContent value="data">
|
||||
<ExecutionDataTab
|
||||
execution={selectedExecution}
|
||||
datasetColumnNames={datasetColumnNames}
|
||||
hiddenDatasetColumns={hiddenDatasetColumns}
|
||||
canPageDataset={canPageDataset}
|
||||
currentDatasetPage={currentDatasetPage}
|
||||
totalPages={totalPages}
|
||||
tableColumns={tableColumns}
|
||||
datasetRowsForTable={datasetRowsForTable}
|
||||
visibleDatasetColumnNames={visibleDatasetColumnNames}
|
||||
expandedDatasetRows={expandedDatasetRows}
|
||||
selectedExecutionIdSafe={selectedExecutionIdSafe}
|
||||
onSetHiddenColumns={(updater) => {
|
||||
<Tabs value={detailTab} onValueChange={setDetailTab}>
|
||||
<div className="flex items-center justify-between gap-2">
|
||||
<TabsList className="border border-border/60 bg-card/40">
|
||||
<TabsTrigger value="overview">Overview</TabsTrigger>
|
||||
<TabsTrigger value="columns">Columns</TabsTrigger>
|
||||
<TabsTrigger value="data">Data</TabsTrigger>
|
||||
<TabsTrigger value="raw">Raw</TabsTrigger>
|
||||
</TabsList>
|
||||
{canCancel && (
|
||||
<Button
|
||||
type="button"
|
||||
size="sm"
|
||||
variant="outline"
|
||||
onClick={() => onCancelExecution(selectedExecution.id)}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<TabsContent value="overview">
|
||||
<ExecutionOverviewTab
|
||||
execution={selectedExecution}
|
||||
showSummaryCards={showSummaryCards}
|
||||
recordsMetric={recordsMetric}
|
||||
totalMetric={totalMetric}
|
||||
runDuration={runDuration}
|
||||
columnCount={columnCount}
|
||||
llmColumnCount={llmColumnCount}
|
||||
nullRate={nullRate}
|
||||
sideEffects={sideEffects}
|
||||
lowUniquenessColumns={lowUniquenessColumns}
|
||||
modelUsageRows={modelUsageRows}
|
||||
terminalLines={terminalLines}
|
||||
terminalRef={terminalRef}
|
||||
onTerminalScroll={(event) => {
|
||||
const element = event.currentTarget;
|
||||
const distanceFromBottom =
|
||||
element.scrollHeight - element.scrollTop - element.clientHeight;
|
||||
shouldStickTerminalToBottomRef.current =
|
||||
distanceFromBottom <= TERMINAL_STICKY_BOTTOM_THRESHOLD_PX;
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="columns">
|
||||
<ExecutionColumnsTab analysisColumns={analysisColumns} />
|
||||
</TabsContent>
|
||||
<TabsContent value="data">
|
||||
<ExecutionDataTab
|
||||
execution={selectedExecution}
|
||||
datasetColumnNames={datasetColumnNames}
|
||||
hiddenDatasetColumns={hiddenDatasetColumns}
|
||||
canPageDataset={canPageDataset}
|
||||
currentDatasetPage={currentDatasetPage}
|
||||
totalPages={totalPages}
|
||||
tableColumns={tableColumns}
|
||||
datasetRowsForTable={datasetRowsForTable}
|
||||
visibleDatasetColumnNames={visibleDatasetColumnNames}
|
||||
expandedDatasetRows={expandedDatasetRows}
|
||||
selectedExecutionIdSafe={selectedExecutionIdSafe}
|
||||
onSetHiddenColumns={(updater) => {
|
||||
const selectedId = selectedExecution.id;
|
||||
setHiddenDatasetColumnsByExecution((current) => {
|
||||
const currentColumns = current[selectedId] ?? [];
|
||||
return {
|
||||
...current,
|
||||
[selectedId]: updater(currentColumns),
|
||||
};
|
||||
});
|
||||
}}
|
||||
onPrevPage={() => {
|
||||
if (selectedExecution.kind === "preview") {
|
||||
const selectedId = selectedExecution.id;
|
||||
setHiddenDatasetColumnsByExecution((current) => {
|
||||
const currentColumns = current[selectedId] ?? [];
|
||||
return {
|
||||
...current,
|
||||
[selectedId]: updater(currentColumns),
|
||||
};
|
||||
});
|
||||
}}
|
||||
onPrevPage={() => {
|
||||
if (selectedExecution.kind === "preview") {
|
||||
const selectedId = selectedExecution.id;
|
||||
setPreviewDatasetPageByExecution((current) => ({
|
||||
...current,
|
||||
[selectedId]: Math.max(1, currentDatasetPage - 1),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
onLoadDatasetPage(selectedExecution.id, currentDatasetPage - 1);
|
||||
}}
|
||||
onNextPage={() => {
|
||||
if (selectedExecution.kind === "preview") {
|
||||
const selectedId = selectedExecution.id;
|
||||
setPreviewDatasetPageByExecution((current) => ({
|
||||
...current,
|
||||
[selectedId]: Math.min(totalPages, currentDatasetPage + 1),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
onLoadDatasetPage(selectedExecution.id, currentDatasetPage + 1);
|
||||
}}
|
||||
onToggleRowExpanded={(rowId) => {
|
||||
setExpandedDatasetRowsByExecution((current) => {
|
||||
const rows = current[selectedExecution.id] ?? {};
|
||||
return {
|
||||
...current,
|
||||
[selectedExecution.id]: {
|
||||
...rows,
|
||||
[rowId]: !rows[rowId],
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="raw">
|
||||
<ExecutionRawTab rawExecution={rawExecution} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
)}
|
||||
setPreviewDatasetPageByExecution((current) => ({
|
||||
...current,
|
||||
[selectedId]: Math.max(1, currentDatasetPage - 1),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
onLoadDatasetPage(selectedExecution.id, currentDatasetPage - 1);
|
||||
}}
|
||||
onNextPage={() => {
|
||||
if (selectedExecution.kind === "preview") {
|
||||
const selectedId = selectedExecution.id;
|
||||
setPreviewDatasetPageByExecution((current) => ({
|
||||
...current,
|
||||
[selectedId]: Math.min(totalPages, currentDatasetPage + 1),
|
||||
}));
|
||||
return;
|
||||
}
|
||||
onLoadDatasetPage(selectedExecution.id, currentDatasetPage + 1);
|
||||
}}
|
||||
onToggleRowExpanded={(rowId) => {
|
||||
setExpandedDatasetRowsByExecution((current) => {
|
||||
const rows = current[selectedExecution.id] ?? {};
|
||||
return {
|
||||
...current,
|
||||
[selectedExecution.id]: {
|
||||
...rows,
|
||||
[rowId]: !rows[rowId],
|
||||
},
|
||||
};
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</TabsContent>
|
||||
<TabsContent value="raw">
|
||||
<ExecutionRawTab rawExecution={rawExecution} />
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue