Add error marker when invalid main function (#9489)

## Before

![image](https://github.com/user-attachments/assets/f6af6721-0896-48b5-8556-9d2a9c19de06)

## After

![image](https://github.com/user-attachments/assets/c59407c8-8244-4906-9d05-713909a19c33)
This commit is contained in:
martmull
2025-01-10 14:29:58 +01:00
committed by GitHub
parent ddcb3dfd28
commit 7b2debf6fb
4 changed files with 123 additions and 4 deletions

View File

@ -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,

View File

@ -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);
});
});

View File

@ -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;
};