bito-code-review[bot] commented on code in PR #37868:
URL: https://github.com/apache/superset/pull/37868#discussion_r2795925857


##########
superset-frontend/packages/superset-ui-chart-controls/src/utils/safeEchartOptionsParser.ts:
##########
@@ -0,0 +1,477 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 { parse } from 'acorn';
+import type { Node } from 'acorn';
+import type { z } from 'zod';
+import {
+  customEChartOptionsSchema,
+  titleSchema,
+  legendSchema,
+  gridSchema,
+  axisSchema,
+  tooltipSchema,
+  dataZoomSchema,
+  toolboxSchema,
+  visualMapSchema,
+  seriesSchema,
+  graphicElementSchema,
+  axisPointerSchema,
+  textStyleSchema,
+  type CustomEChartOptions,
+} from './echartOptionsSchema';
+
+// 
=============================================================================
+// Custom Error Class
+// 
=============================================================================
+
+/**
+ * Custom error class for EChart options parsing errors
+ */
+export class EChartOptionsParseError extends Error {
+  public readonly errorType:
+    | 'parse_error'
+    | 'security_error'
+    | 'validation_error';
+
+  public readonly validationErrors: string[];
+
+  public readonly location?: { line: number; column: number };
+
+  constructor(
+    message: string,
+    errorType:
+      | 'parse_error'
+      | 'security_error'
+      | 'validation_error' = 'parse_error',
+    validationErrors: string[] = [],
+    location?: { line: number; column: number },
+  ) {
+    super(message);
+    this.name = 'EChartOptionsParseError';
+    this.errorType = errorType;
+    this.validationErrors = validationErrors;
+    this.location = location;
+  }
+}
+
+// 
=============================================================================
+// Partial Validation Helper
+// 
=============================================================================
+
+/**
+ * Maps top-level property names to their Zod schemas for partial validation
+ */
+const propertySchemas: Record<string, z.ZodTypeAny> = {
+  title: titleSchema,
+  legend: legendSchema,
+  grid: gridSchema,
+  xAxis: axisSchema,
+  yAxis: axisSchema,
+  tooltip: tooltipSchema,
+  dataZoom: dataZoomSchema,
+  toolbox: toolboxSchema,
+  visualMap: visualMapSchema,
+  series: seriesSchema,
+  graphic: graphicElementSchema,
+  axisPointer: axisPointerSchema,
+  textStyle: textStyleSchema,
+};
+
+/**
+ * Validates each property individually and returns only valid properties.
+ * This allows partial validation where invalid properties are filtered out
+ * while valid ones are kept. Throws an error if any validation issues are 
found.
+ */
+function validatePartial(data: Record<string, unknown>): CustomEChartOptions {
+  const result: Record<string, unknown> = {};
+  const validationErrors: string[] = [];
+
+  for (const [key, value] of Object.entries(data)) {
+    if (value === undefined) continue;
+
+    const schema = propertySchemas[key];
+
+    if (schema) {
+      // For properties with known schemas, validate them
+      if (Array.isArray(value)) {
+        // Validate array items individually
+        const validItems = value
+          .map((item, index) => {
+            const itemResult = schema.safeParse(item);
+            if (itemResult.success) {
+              return itemResult.data;
+            }
+            validationErrors.push(
+              `Invalid array item in "${key}[${index}]": 
${itemResult.error.issues.map(e => e.message).join(', ')}`,
+            );
+            return null;
+          })
+          .filter(item => item !== null);
+
+        if (validItems.length > 0) {
+          result[key] = validItems;
+        }
+      } else {
+        // Validate single object
+        const propResult = schema.safeParse(value);
+        if (propResult.success) {
+          result[key] = propResult.data;
+        } else {
+          validationErrors.push(
+            `Invalid property "${key}": ${propResult.error.issues.map(e => 
e.message).join(', ')}`,
+          );
+        }
+      }
+    } else {
+      // For primitive properties (animation, backgroundColor, etc.), validate 
with full schema
+      const primitiveResult =
+        customEChartOptionsSchema.shape[
+          key as keyof typeof customEChartOptionsSchema.shape
+        ]?.safeParse(value);
+
+      if (primitiveResult?.success) {
+        result[key] = primitiveResult.data;
+      } else if (primitiveResult) {
+        validationErrors.push(
+          `Invalid property "${key}": ${primitiveResult.error?.issues.map(e => 
e.message).join(', ') ?? 'Invalid value'}`,
+        );
+      }
+      // Unknown properties are silently ignored
+    }
+  }
+
+  if (validationErrors.length > 0) {
+    throw new EChartOptionsParseError(
+      'EChart options validation failed',
+      'validation_error',
+      validationErrors,
+    );
+  }
+
+  return result as CustomEChartOptions;
+}
+
+// 
=============================================================================
+// AST Safety Validation
+// 
=============================================================================
+
+/**
+ * Safe AST node types that are allowed in EChart options.
+ * These represent static data structures without executable code.
+ */
+const SAFE_NODE_TYPES = new Set([
+  'ObjectExpression',
+  'ArrayExpression',
+  'Literal',
+  'Property',
+  'Identifier',
+  'UnaryExpression',
+  'TemplateLiteral',
+  'TemplateElement',
+]);
+
+const ALLOWED_UNARY_OPERATORS = new Set(['-', '+']);
+
+const DANGEROUS_IDENTIFIERS = new Set([
+  'eval',
+  'Function',
+  'constructor',
+  'prototype',
+  '__proto__',
+  'window',
+  'document',
+  'globalThis',
+  'process',
+  'require',
+  'import',
+  'module',
+  'exports',
+]);
+
+/**
+ * Recursively validates that an AST node contains only safe constructs.
+ * Throws an error if any unsafe patterns are detected.
+ */
+function validateNode(node: Node, path: string[] = []): void {
+  if (!node || typeof node !== 'object') {
+    return;
+  }
+
+  const nodeType = node.type;
+
+  if (!SAFE_NODE_TYPES.has(nodeType)) {
+    throw new Error(
+      `Unsafe node type "${nodeType}" at path: ${path.join('.')}. ` +
+        `Only static data structures are allowed.`,
+    );
+  }
+
+  switch (nodeType) {
+    case 'Identifier': {
+      const identNode = node as Node & { name: string };
+      if (DANGEROUS_IDENTIFIERS.has(identNode.name)) {
+        throw new Error(
+          `Dangerous identifier "${identNode.name}" detected at path: 
${path.join('.')}`,
+        );
+      }
+      break;

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Security: Restrict bare identifiers in AST 
validation</b></div>
   <div id="fix">
   
   The validateNode function permits bare identifiers like 'Math' or 'foo' in 
value positions, which could allow non-static data and introduce security 
risks. Since the parser is designed for static object literals, only literal 
identifiers (undefined, null, true, false, NaN, Infinity) should be allowed. 
This gap isn't covered by existing tests and could lead to unexpected string 
values in parsed output.
   </div>
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3194c9</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/packages/superset-ui-core/src/components/AsyncAceEditor/index.tsx:
##########
@@ -501,3 +501,9 @@ export const ConfigEditor = AsyncAceEditor([
   'mode/yaml',
   'theme/github',
 ]);
+
+export const JSEditor = AsyncAceEditor([
+  'mode/json',
+  'mode/javascript',
+  'theme/github',
+]);

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Incorrect default mode for JSEditor</b></div>
   <div id="fix">
   
   The JSEditor is configured with 'mode/json' first, causing the default mode 
to be 'json', but given the name 'JSEditor', it appears intended for JavaScript 
editing. Consider reordering the modes to start with 'mode/javascript' for 
correct default behavior, similar to how other editors prioritize their primary 
mode.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
    export const JSEditor = AsyncAceEditor([
      'mode/javascript',
      'mode/json',
      'theme/github',
    ]);
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3194c9</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/packages/superset-ui-chart-controls/src/utils/echartOptionsSchema.ts:
##########
@@ -0,0 +1,837 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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.
+ */
+
+/**
+ * Unified ECharts Options Schema
+ *
+ * This file serves as the single source of truth for:
+ * 1. Runtime validation (Zod schema)
+ * 2. TypeScript types (inferred from Zod)
+ *
+ * Reference: https://echarts.apache.org/en/option.html
+ */
+
+import { z } from 'zod';
+
+// 
=============================================================================
+// Common Schemas
+// 
=============================================================================
+
+/** Color value - hex, rgb, rgba, or named color */
+const colorSchema = z.string();
+
+/** Numeric or percentage string (e.g., '50%') */
+const numberOrPercentSchema = z.union([z.number(), z.string()]);
+
+/** Line type */
+const lineTypeSchema = z.union([
+  z.enum(['solid', 'dashed', 'dotted']),
+  z.array(z.number()),
+]);
+
+/** Font weight */
+const fontWeightSchema = z.union([
+  z.enum(['normal', 'bold', 'bolder', 'lighter']),
+  z.number().min(100).max(900),
+]);
+
+/** Font style */
+const fontStyleSchema = z.enum(['normal', 'italic', 'oblique']);
+
+/** Symbol type */
+const symbolTypeSchema = z.string();
+
+// 
=============================================================================
+// Text Style Schema
+// 
=============================================================================
+
+export const textStyleSchema = z.object({
+  color: colorSchema.optional(),
+  fontStyle: fontStyleSchema.optional(),
+  fontWeight: fontWeightSchema.optional(),
+  fontFamily: z.string().optional(),
+  fontSize: z.number().optional(),
+  lineHeight: z.number().optional(),
+  backgroundColor: z.union([colorSchema, z.literal('transparent')]).optional(),
+  borderColor: colorSchema.optional(),
+  borderWidth: z.number().optional(),
+  borderType: lineTypeSchema.optional(),
+  borderRadius: z.union([z.number(), z.array(z.number())]).optional(),
+  padding: z.union([z.number(), z.array(z.number())]).optional(),
+  shadowColor: colorSchema.optional(),
+  shadowBlur: z.number().optional(),
+  shadowOffsetX: z.number().optional(),
+  shadowOffsetY: z.number().optional(),
+  width: numberOrPercentSchema.optional(),
+  height: numberOrPercentSchema.optional(),
+  textBorderColor: colorSchema.optional(),
+  textBorderWidth: z.number().optional(),

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Missing ECharts textStyle Property</b></div>
   <div id="fix">
   
   The textStyleSchema is missing the textBorderType property, which ECharts 
supports for controlling text border dash styles. This could cause validation 
to reject valid chart configurations that use textBorderType.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
     textBorderWidth: z.number().optional(),
     textBorderType: lineTypeSchema.optional(),
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3194c9</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



##########
superset-frontend/src/explore/components/controls/JSEditorControl.tsx:
##########
@@ -0,0 +1,103 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you 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 { useMemo } from 'react';
+import AutoSizer from 'react-virtualized-auto-sizer';
+import ControlHeader, {
+  ControlHeaderProps,
+} from 'src/explore/components/ControlHeader';
+import { styled } from '@apache-superset/core';
+import {
+  ControlComponentProps,
+  safeParseEChartOptions,
+  EChartOptionsParseError,
+} from '@superset-ui/chart-controls';
+import { EditorHost } from 'src/core/editors';
+import { useDebounceValue } from 'src/hooks/useDebounceValue';
+
+const Container = styled.div`
+  border: 1px solid ${({ theme }) => theme.colorBorder};
+  border-radius: ${({ theme }) => theme.borderRadius}px;
+  overflow: hidden;
+`;
+
+const ErrorMessage = styled.div`
+  color: ${({ theme }) => theme.colorErrorText};
+`;
+
+export default function JSEditorControl({
+  name,
+  label,
+  description,
+  renderTrigger,
+  hovered,
+  tooltipOnClick,
+  onChange,
+  value,
+}: ControlHeaderProps & ControlComponentProps<string>) {
+  const debouncedValue = useDebounceValue(value);
+  const error = useMemo(() => {
+    try {
+      safeParseEChartOptions(debouncedValue ?? '');
+      return null;
+    } catch (err) {
+      if (err instanceof EChartOptionsParseError) {
+        return err;
+      }
+      throw err;
+    }
+  }, [debouncedValue]);
+  const headerProps = {
+    name,
+    label: label ?? name,
+    description,
+    renderTrigger,
+    validationErrors: error?.message ? [error.message] : undefined,
+    hovered,
+    tooltipOnClick,
+  };
+
+  return (
+    <>
+      <ControlHeader {...headerProps} />
+      <Container>
+        <AutoSizer disableHeight>
+          {({ width }) => (
+            <EditorHost
+              id="echart-js-editor"
+              value={value ?? ''}
+              onChange={val => onChange?.(val)}
+              language="javascript"
+              tabSize={2}
+              lineNumbers
+              width={`${width}px`}
+              height="250px"
+            />
+          )}
+        </AutoSizer>
+      </Container>
+      {error && (
+        <ErrorMessage>
+          {error.validationErrors.map((err, idx) => (
+            <div key={idx}>{err}</div>
+          ))}

Review Comment:
   <div>
   
   
   <div id="suggestion">
   <div id="issue"><b>Error Display Logic Bug</b></div>
   <div id="fix">
   
   The error display logic fails to show messages for parse_error and 
security_error types, as validationErrors is empty in those cases. This leaves 
users without feedback for syntax or security issues. Update to fall back to 
error.message when validationErrors is empty.
   </div>
   
   
   <details>
   <summary>
   <b>Code suggestion</b>
   </summary>
   <blockquote>Check the AI-generated fix before applying</blockquote>
   <div id="code">
   
   
   ````suggestion
             {error.validationErrors.length > 0 ? 
error.validationErrors.map((err, idx) => (
               <div key={idx}>{err}</div>
             )) : <div>{error.message}</div>}
   ````
   
   </div>
   </details>
   
   
   
   </div>
   
   
   
   
   <small><i>Code Review Run #3194c9</i></small>
   </div>
   
   ---
   Should Bito avoid suggestions like this for future reviews? (<a 
href=https://alpha.bito.ai/home/ai-agents/review-rules>Manage Rules</a>)
   - [ ] Yes, avoid them



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]


---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]

Reply via email to