/** * Copyright (c) 2020-present, Goldman Sachs * * Licensed 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 { clearMarkers, setErrorMarkers } from '@finos/legend-code-editor'; import { DataCubeCodeEditorState, type DataCubeEngine, } from '@finos/legend-data-cube'; import { EngineError, type V1_Lambda } from '@finos/legend-graph'; import { uuid, ActionState, type PlainObject, assertErrorThrown, } from '@finos/legend-shared'; import { action, makeObservable, observable, runInAction } from 'mobx'; export class FreeformExpressionCodeEditorState extends DataCubeCodeEditorState { protected override readonly uuid = uuid(); codeSuffix: string; readonly validationState = ActionState.create(); compilationError?: Error | undefined; override currentlyEditing = false; alertHandler: (error: Error) => void; override model: PlainObject | undefined; compileQueryCheck: () => Promise; queryLambda: () => V1_Lambda; constructor( alertHandler: (error: Error) => void, model: PlainObject | undefined, compileQueryCheck: () => Promise, queryLambda: () => V1_Lambda, engine: DataCubeEngine, ) { super(engine); makeObservable(this, { code: observable, editor: observable.ref, setEditor: action, codeError: observable.ref, compilationError: observable, showError: action, clearError: action, returnType: observable, setReturnType: action, }); this.codePrefix = ''; this.codeSuffix = ''; this.alertHandler = alertHandler; this.model = model; this.compileQueryCheck = compileQueryCheck; this.queryLambda = queryLambda; this.engine = engine; } compileFunction = async (): Promise => { if (this.code.length !== 0) { try { this.currentlyEditing = false; return await this.compileQueryCheck(); } catch (error) { assertErrorThrown(error); if (error instanceof EngineError) { this.validationState.fail(); // correct the source information since we added prefix to the code // and reveal error in the editor if (error.sourceInformation) { error.sourceInformation.startColumn -= error.sourceInformation.startLine === 1 ? this.codePrefix.length : 0; error.sourceInformation.endColumn -= error.sourceInformation.endLine === 1 ? this.codePrefix.length : 0; const fullRange = this.editorModel.getFullModelRange(); if ( error.sourceInformation.startLine < 1 || (error.sourceInformation.startLine === 1 && error.sourceInformation.startColumn < 1) || error.sourceInformation.endLine > fullRange.endLineNumber || (error.sourceInformation.endLine === fullRange.endLineNumber && error.sourceInformation.endColumn > fullRange.endColumn) ) { error.sourceInformation.startColumn = fullRange.startColumn; error.sourceInformation.startLine = fullRange.startLineNumber; error.sourceInformation.endColumn = fullRange.endColumn; error.sourceInformation.endLine = fullRange.endLineNumber; } } this.showError(error); return undefined; } else if (error instanceof Error) { this.setCompilationError(error); } this.alertHandler(error); } } return undefined; }; override get hasErrors(): boolean { return Boolean(this.codeError ?? this.compilationError); } isValid(): boolean { return Boolean( !this.hasErrors && this.code.length !== 0 && !this.currentlyEditing && !this.validationState.isInProgress, ); } setCompilationError(val: Error | undefined) { runInAction(() => { this.compilationError = val; }); } setModel(val: PlainObject | undefined) { this.model = val; } clearQuery() { this.clearError(); this.editor?.getModel()?.setValue(''); } override clearError() { this.codeError = undefined; this.compilationError = undefined; clearMarkers(this.uuid); } showError(error: EngineError) { this.codeError = error; if (error.sourceInformation) { setErrorMarkers( this.editorModel, [ { message: error.message, startLineNumber: error.sourceInformation.startLine, startColumn: error.sourceInformation.startColumn, endLineNumber: error.sourceInformation.endLine, endColumn: error.sourceInformation.endColumn, }, ], this.uuid, ); } } async getReturnType() { this.validationState.inProgress(); // properly reset the error state before revalidating this.clearError(); this.setReturnType(undefined); await this.compileFunction(); this.validationState.complete(); return undefined; } }