Add error marker when invalid main function (#9489)
## Before  ## After 
This commit is contained in:
@ -8,6 +8,7 @@ import { WorkflowCodeAction } from '@/workflow/types/Workflow';
|
||||
import { WorkflowStepHeader } from '@/workflow/workflow-steps/components/WorkflowStepHeader';
|
||||
import { setNestedValue } from '@/workflow/workflow-steps/workflow-actions/utils/setNestedValue';
|
||||
|
||||
import { Monaco } from '@monaco-editor/react';
|
||||
import { CmdEnterActionButton } from '@/action-menu/components/CmdEnterActionButton';
|
||||
import { ServerlessFunctionExecutionResult } from '@/serverless-functions/components/ServerlessFunctionExecutionResult';
|
||||
import { INDEX_FILE_PATH } from '@/serverless-functions/constants/IndexFilePath';
|
||||
@ -26,13 +27,13 @@ import { WORKFLOW_SERVERLESS_FUNCTION_TAB_LIST_COMPONENT_ID } from '@/workflow/w
|
||||
import { WorkflowVariablePicker } from '@/workflow/workflow-variables/components/WorkflowVariablePicker';
|
||||
import { useTheme } from '@emotion/react';
|
||||
import styled from '@emotion/styled';
|
||||
import { Monaco } from '@monaco-editor/react';
|
||||
import { editor } from 'monaco-editor';
|
||||
import { AutoTypings } from 'monaco-editor-auto-typings';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { useRecoilState, useRecoilValue } from 'recoil';
|
||||
import { CodeEditor, IconCode, IconPlayerPlay, isDefined } from 'twenty-ui';
|
||||
import { useDebouncedCallback } from 'use-debounce';
|
||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||
|
||||
const StyledContainer = styled.div`
|
||||
display: flex;
|
||||
@ -299,6 +300,7 @@ export const WorkflowEditActionFormServerlessFunction = ({
|
||||
language={'typescript'}
|
||||
onChange={handleCodeChange}
|
||||
onMount={handleEditorDidMount}
|
||||
setMarkers={getWrongExportedFunctionMarkers}
|
||||
options={{
|
||||
readOnly: actionOptions.readonly,
|
||||
domReadOnly: actionOptions.readonly,
|
||||
|
||||
@ -0,0 +1,30 @@
|
||||
import { getWrongExportedFunctionMarkers } from '@/workflow/workflow-steps/workflow-actions/utils/getWrongExportedFunctionMarkers';
|
||||
|
||||
describe('getWrongExportedFunctionMarkers', () => {
|
||||
it('should return marker when no exported function', () => {
|
||||
const value = 'const main = async () => {}';
|
||||
const result = getWrongExportedFunctionMarkers(value);
|
||||
expect(result.length).toEqual(1);
|
||||
expect(result[0].message).toEqual(
|
||||
'An exported "main" arrow function is required.',
|
||||
);
|
||||
});
|
||||
|
||||
it('should return marker when no wrong exported function', () => {
|
||||
const value = 'export const wrongMain = async () => {}';
|
||||
const result = getWrongExportedFunctionMarkers(value);
|
||||
expect(result.length).toEqual(1);
|
||||
});
|
||||
|
||||
it('should return no marker when valid exported function', () => {
|
||||
const value = 'export const main = async () => {}';
|
||||
const result = getWrongExportedFunctionMarkers(value);
|
||||
expect(result.length).toEqual(0);
|
||||
});
|
||||
|
||||
it('should return handle multiple spaces', () => {
|
||||
const value = 'export const main = async () => {}';
|
||||
const result = getWrongExportedFunctionMarkers(value);
|
||||
expect(result.length).toEqual(0);
|
||||
});
|
||||
});
|
||||
@ -0,0 +1,60 @@
|
||||
import { isDefined } from 'twenty-ui';
|
||||
|
||||
const getSubstringCoordinate = (
|
||||
text: string,
|
||||
substring: string,
|
||||
): { line: number; column: number } | null => {
|
||||
const lines = text.split('\n');
|
||||
|
||||
for (let lineIndex = 0; lineIndex < lines.length; lineIndex++) {
|
||||
const columnIndex = lines[lineIndex].indexOf(substring);
|
||||
if (columnIndex !== -1) {
|
||||
return {
|
||||
line: lineIndex + 1, // 1-based line number
|
||||
column: columnIndex + 1, // 1-based column number
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export const getWrongExportedFunctionMarkers = (value: string) => {
|
||||
const validRegex = /export\s+const\s+main\s*=/g;
|
||||
const invalidRegex = /export\s+const\s+\S*/g;
|
||||
const exportRegex = /export\s+const/g;
|
||||
const validMatch = value.match(validRegex);
|
||||
const invalidMatch = value.match(invalidRegex);
|
||||
const exportMatch = value.match(exportRegex);
|
||||
const markers = [];
|
||||
|
||||
if (!validMatch && !!invalidMatch) {
|
||||
const coordinates = getSubstringCoordinate(value, invalidMatch[0]);
|
||||
if (isDefined(coordinates)) {
|
||||
const endColumn = invalidMatch[0].length + coordinates.column;
|
||||
markers.push({
|
||||
severity: 8, //MarkerSeverity.Error,
|
||||
message: 'Exported arrow function should be named "main"',
|
||||
code: 'export const main',
|
||||
startLineNumber: coordinates.line,
|
||||
startColumn: coordinates.column,
|
||||
endLineNumber: 1,
|
||||
endColumn,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (!exportMatch) {
|
||||
markers.push({
|
||||
severity: 8, //MarkerSeverity.Error,
|
||||
message: 'An exported "main" arrow function is required.',
|
||||
code: 'export const main',
|
||||
startLineNumber: 1,
|
||||
startColumn: 1,
|
||||
endLineNumber: 1,
|
||||
endColumn: 1,
|
||||
});
|
||||
}
|
||||
|
||||
return markers;
|
||||
};
|
||||
@ -1,11 +1,14 @@
|
||||
import { useTheme, css } from '@emotion/react';
|
||||
import Editor, { EditorProps } from '@monaco-editor/react';
|
||||
import Editor, { EditorProps, Monaco } from '@monaco-editor/react';
|
||||
import { codeEditorTheme } from '@ui/input';
|
||||
import { isDefined } from '@ui/utilities';
|
||||
import styled from '@emotion/styled';
|
||||
import { useState } from 'react';
|
||||
import { editor } from 'monaco-editor';
|
||||
|
||||
type CodeEditorProps = Omit<EditorProps, 'onChange'> & {
|
||||
onChange?: (value: string) => void;
|
||||
setMarkers?: (value: string) => editor.IMarkerData[];
|
||||
withHeader?: boolean;
|
||||
};
|
||||
|
||||
@ -35,12 +38,31 @@ export const CodeEditor = ({
|
||||
language,
|
||||
onMount,
|
||||
onChange,
|
||||
setMarkers,
|
||||
onValidate,
|
||||
height = 450,
|
||||
withHeader = false,
|
||||
options,
|
||||
}: CodeEditorProps) => {
|
||||
const theme = useTheme();
|
||||
const [monaco, setMonaco] = useState<Monaco | undefined>(undefined);
|
||||
const [editor, setEditor] = useState<
|
||||
editor.IStandaloneCodeEditor | undefined
|
||||
>(undefined);
|
||||
|
||||
const setModelMarkers = (
|
||||
editor: editor.IStandaloneCodeEditor | undefined,
|
||||
monaco: Monaco | undefined,
|
||||
) => {
|
||||
const model = editor?.getModel();
|
||||
if (!isDefined(model)) {
|
||||
return;
|
||||
}
|
||||
const customMarkers = setMarkers?.(model.getValue());
|
||||
if (isDefined(customMarkers)) {
|
||||
monaco?.editor.setModelMarkers(model, 'customMarker', customMarkers);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledEditor
|
||||
@ -49,17 +71,22 @@ export const CodeEditor = ({
|
||||
value={value}
|
||||
language={language}
|
||||
onMount={(editor, monaco) => {
|
||||
setMonaco(monaco);
|
||||
setEditor(editor);
|
||||
monaco.editor.defineTheme('codeEditorTheme', codeEditorTheme(theme));
|
||||
monaco.editor.setTheme('codeEditorTheme');
|
||||
|
||||
onMount?.(editor, monaco);
|
||||
setModelMarkers(editor, monaco);
|
||||
}}
|
||||
onChange={(value) => {
|
||||
if (isDefined(value)) {
|
||||
onChange?.(value);
|
||||
setModelMarkers(editor, monaco);
|
||||
}
|
||||
}}
|
||||
onValidate={onValidate}
|
||||
onValidate={(markers) => {
|
||||
onValidate?.(markers);
|
||||
}}
|
||||
options={{
|
||||
overviewRulerLanes: 0,
|
||||
scrollbar: {
|
||||
|
||||
Reference in New Issue
Block a user