Skip to content

Commit d1bb53b

Browse files
committed
feat: add code validator for function worker
1 parent 4a410c8 commit d1bb53b

File tree

3 files changed

+120
-5
lines changed

3 files changed

+120
-5
lines changed

src/client/components/CodeEditor/main.tsx

Lines changed: 78 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,87 @@
1-
import Editor, { Monaco } from '@monaco-editor/react';
2-
import React from 'react';
1+
import Editor, { Monaco, OnMount } from '@monaco-editor/react';
2+
import React, { useRef } from 'react';
33
import { useSettingsStore } from '../../store/settings';
44
import { useEvent } from '../../hooks/useEvent';
55
import { sandboxGlobal } from './lib/sandbox';
6+
import { ValidatorFn } from './validator/fetch';
67

78
interface CodeEditorProps {
89
height?: string | number;
910
value?: string;
1011
readOnly?: boolean;
1112
onChange?: (code: string) => void;
13+
codeValidator?: ValidatorFn[];
1214
}
1315

1416
export const CodeEditor: React.FC<CodeEditorProps> = React.memo((props) => {
15-
const { readOnly = false } = props;
17+
const { readOnly = false, codeValidator = [] } = props;
1618
const colorScheme = useSettingsStore((state) => state.colorScheme);
1719
const theme = colorScheme === 'dark' ? 'vs-dark' : 'light';
20+
const editorRef = useRef<any>(null);
21+
const monacoRef = useRef<Monaco | null>(null);
22+
23+
const validateCode = useEvent(
24+
(code: string): { isValid: boolean; errors: string[] } => {
25+
for (const validator of codeValidator) {
26+
const result = validator(code);
27+
if (!result.isValid) {
28+
return result;
29+
}
30+
}
31+
32+
return { isValid: true, errors: [] };
33+
}
34+
);
35+
36+
const handleEditorDidMount: OnMount = useEvent((editor, monaco) => {
37+
editorRef.current = editor;
38+
monacoRef.current = monaco;
39+
40+
monaco.languages.typescript.javascriptDefaults.addExtraLib(
41+
sandboxGlobal,
42+
'global.ts'
43+
);
44+
45+
const validateAndReport = () => {
46+
const model = editor.getModel();
47+
if (!model) {
48+
return;
49+
}
50+
51+
const code = model.getValue();
52+
const validation = validateCode(code);
53+
54+
if (!validation.isValid) {
55+
const markers = validation.errors.map((error, index) => ({
56+
severity: monaco.MarkerSeverity.Error,
57+
startLineNumber: 1,
58+
startColumn: 1,
59+
endLineNumber: 1,
60+
endColumn: 1,
61+
message: error,
62+
source: 'tianji-validator',
63+
}));
64+
65+
monaco.editor.setModelMarkers(model, 'tianji-validator', markers);
66+
} else {
67+
// Clear custom markers if validation passes
68+
monaco.editor.setModelMarkers(model, 'tianji-validator', []);
69+
}
70+
};
71+
72+
// Initial validation
73+
if (props.value) {
74+
setTimeout(validateAndReport, 100);
75+
}
76+
77+
// Listen for content changes
78+
const model = editor.getModel();
79+
if (model) {
80+
model.onDidChangeContent(() => {
81+
setTimeout(validateAndReport, 300); // Debounce validation
82+
});
83+
}
84+
});
1885

1986
const handleEditorWillMount = useEvent((monaco: Monaco) => {
2087
monaco.languages.typescript.javascriptDefaults.addExtraLib(
@@ -27,14 +94,21 @@ export const CodeEditor: React.FC<CodeEditorProps> = React.memo((props) => {
2794
<Editor
2895
height={props.height}
2996
theme={theme}
30-
defaultLanguage="javascript"
97+
defaultLanguage="typescript"
3198
value={props.value}
3299
options={{
33100
tabSize: 2,
34101
readOnly,
102+
automaticLayout: true,
103+
scrollBeyondLastLine: false,
104+
wordWrap: 'on',
105+
wrappingIndent: 'indent',
106+
formatOnPaste: true,
107+
formatOnType: true,
35108
}}
36109
onChange={(val) => props.onChange?.(val ?? '')}
37110
beforeMount={handleEditorWillMount}
111+
onMount={handleEditorDidMount}
38112
/>
39113
);
40114
});
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
export type ValidatorFn = (code: string) => ValidatorResult;
2+
3+
export interface ValidatorResult {
4+
isValid: boolean;
5+
errors: string[];
6+
}
7+
8+
export function fetchValidator(code: string): ValidatorResult {
9+
const errors: string[] = [];
10+
11+
// Check for fetch function declaration using multiple patterns
12+
const fetchPatterns = [
13+
/function\s+fetch\s*\(/, // function fetch(
14+
/const\s+fetch\s*=\s*function/, // const fetch = function
15+
/const\s+fetch\s*=\s*\(/, // const fetch = (
16+
/let\s+fetch\s*=\s*function/, // let fetch = function
17+
/let\s+fetch\s*=\s*\(/, // let fetch = (
18+
/var\s+fetch\s*=\s*function/, // var fetch = function
19+
/var\s+fetch\s*=\s*\(/, // var fetch = (
20+
/export\s+function\s+fetch\s*\(/, // export function fetch(
21+
/export\s+const\s+fetch\s*=/, // export const fetch =
22+
];
23+
24+
const hasFetchFunction = fetchPatterns.some((pattern) => pattern.test(code));
25+
26+
if (!hasFetchFunction) {
27+
errors.push(
28+
'A fetch function must be declared. Example: function fetch() { ... }'
29+
);
30+
}
31+
32+
return {
33+
isValid: errors.length === 0,
34+
errors,
35+
};
36+
}

src/client/components/worker/WorkerEditForm.tsx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
DialogHeader,
3333
DialogTitle,
3434
} from '@/components/ui/dialog';
35+
import { fetchValidator, ValidatorFn } from '../CodeEditor/validator/fetch';
3536

3637
const formSchema = z
3738
.object({
@@ -67,6 +68,8 @@ interface WorkerEditFormProps {
6768
onSubmit: (values: WorkerEditFormValues) => Promise<void>;
6869
}
6970

71+
const codeValidator: ValidatorFn[] = [fetchValidator];
72+
7073
export const WorkerEditForm: React.FC<WorkerEditFormProps> = React.memo(
7174
(props) => {
7275
const { t } = useTranslation();
@@ -180,9 +183,10 @@ export const WorkerEditForm: React.FC<WorkerEditFormProps> = React.memo(
180183
</FormLabel>
181184
<FormControl>
182185
<CodeEditor
183-
height={400}
186+
height={680}
184187
value={field.value}
185188
onChange={field.onChange}
189+
codeValidator={codeValidator}
186190
/>
187191
</FormControl>
188192
<FormDescription>
@@ -306,6 +310,7 @@ export const WorkerEditForm: React.FC<WorkerEditFormProps> = React.memo(
306310
height="100%"
307311
value={form.watch('code')}
308312
onChange={(value) => form.setValue('code', value)}
313+
codeValidator={codeValidator}
309314
/>
310315
</FullscreenModal>
311316

0 commit comments

Comments
 (0)