Skip to content

Commit

Permalink
Add error marker when invalid main function (#9489)
Browse files Browse the repository at this point in the history
  • Loading branch information
martmull authored Jan 10, 2025
1 parent ddcb3df commit 7b2debf
Show file tree
Hide file tree
Showing 4 changed files with 123 additions and 4 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -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;
Expand Down Expand Up @@ -299,6 +300,7 @@ export const WorkflowEditActionFormServerlessFunction = ({
language={'typescript'}
onChange={handleCodeChange}
onMount={handleEditorDidMount}
setMarkers={getWrongExportedFunctionMarkers}
options={{
readOnly: actionOptions.readonly,
domReadOnly: actionOptions.readonly,
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
});
});
Original file line number Diff line number Diff line change
@@ -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;
};
33 changes: 30 additions & 3 deletions packages/twenty-ui/src/input/code-editor/components/CodeEditor.tsx
Original file line number Diff line number Diff line change
@@ -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;
};

Expand Down Expand Up @@ -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
Expand All @@ -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: {
Expand Down

0 comments on commit 7b2debf

Please sign in to comment.