diff --git a/src/commons/assessment/AssessmentTypes.ts b/src/commons/assessment/AssessmentTypes.ts index bbd7b13644..174d2b36d1 100644 --- a/src/commons/assessment/AssessmentTypes.ts +++ b/src/commons/assessment/AssessmentTypes.ts @@ -1,4 +1,4 @@ -import { Chapter, SourceError, Variant } from 'js-slang/dist/types'; +import { Chapter, LanguageOptions, SourceError, Variant } from 'js-slang/dist/types'; import { ExternalLibrary, ExternalLibraryName } from '../application/types/ExternalTypes'; @@ -177,6 +177,7 @@ export type Library = { 2?: string; // For mission control }>; moduleParams?: any; + languageOptions?: LanguageOptions; }; export type Testcase = { diff --git a/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts b/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts index a93c57b056..8e5a81da07 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/blockExtraMethods.ts @@ -1,8 +1,10 @@ -import type { Context } from 'js-slang'; +import { Context } from 'js-slang'; +import { Variant } from 'js-slang/dist/types'; import { call } from 'redux-saga/effects'; import { getBlockExtraMethodsString, + getBlockExtraMethodsStringTypedVariant, getDifferenceInMethods, getStoreExtraMethodsString } from '../../../utils/JsSlangHelper'; @@ -35,7 +37,10 @@ export function* blockExtraMethods( ); } - const nullifier = getBlockExtraMethodsString(toBeBlocked); + const nullifier = + context.variant === Variant.TYPED + ? getBlockExtraMethodsStringTypedVariant(toBeBlocked) + : getBlockExtraMethodsString(toBeBlocked); const nullifierFilePath = '/nullifier.js'; const nullifierFiles = { [nullifierFilePath]: nullifier diff --git a/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts b/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts index cb7d034334..b6b8750a6d 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/clearContext.ts @@ -10,7 +10,7 @@ import { selectWorkspace } from '../../SafeEffects'; export function* clearContext(workspaceLocation: WorkspaceLocation, entrypointCode: string) { const { - context: { chapter, externalSymbols: symbols, variant }, + context: { chapter, externalSymbols: symbols, variant, languageOptions }, externalLibrary: externalLibraryName, globals } = yield* selectWorkspace(workspaceLocation); @@ -22,7 +22,8 @@ export function* clearContext(workspaceLocation: WorkspaceLocation, entrypointCo name: externalLibraryName, symbols }, - globals + globals, + languageOptions }; // Clear the context, with the same chapter and externalSymbols as before. diff --git a/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts b/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts index 1ad74892f8..a9e3612238 100644 --- a/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts +++ b/src/commons/sagas/WorkspaceSaga/helpers/evalEditor.ts @@ -1,4 +1,5 @@ import type { FSModule } from 'browserfs/dist/node/core/FS'; +import { Variant } from 'js-slang/dist/types'; import { call, put, select, StrictEffect } from 'redux-saga/effects'; import WorkspaceActions from 'src/commons/workspace/WorkspaceActions'; @@ -82,19 +83,28 @@ export function* evalEditorSaga( const prependFiles = { [prependFilePath]: prepend }; - yield call( - evalCodeSaga, - prependFiles, - prependFilePath, - elevatedContext, - execTime, - EVAL_SILENT, - workspaceLocation - ); + if (context.variant !== Variant.TYPED) { + yield call( + evalCodeSaga, + prependFiles, + prependFilePath, + elevatedContext, + execTime, + EVAL_SILENT, + workspaceLocation + ); + } + // Block use of methods from privileged context yield* blockExtraMethods(elevatedContext, context, execTime, workspaceLocation); } + if (context.variant === Variant.TYPED) { + // Prepend was multi-line, now we need to split them by \n and join them + // This is to avoid extra lines in the editor which affects the error message location + const prependSingleLine = prepend.split('\n').join(''); + files[entrypointFilePath] = prependSingleLine + files[entrypointFilePath]; + } yield call( evalCodeSaga, files, diff --git a/src/commons/sagas/__tests__/WorkspaceSaga.test.ts b/src/commons/sagas/__tests__/WorkspaceSaga.test.ts index f0aef96179..ab8a2f3cf2 100644 --- a/src/commons/sagas/__tests__/WorkspaceSaga.test.ts +++ b/src/commons/sagas/__tests__/WorkspaceSaga.test.ts @@ -130,7 +130,8 @@ describe('EVAL_EDITOR', () => { name: ExternalLibraryName.NONE, symbols: context.externalSymbols }, - globals + globals, + languageOptions: context.languageOptions }; const newDefaultState = generateDefaultState(workspaceLocation, { @@ -397,7 +398,8 @@ describe('EVAL_TESTCASE', () => { name: ExternalLibraryName.NONE, symbols: context.externalSymbols }, - globals + globals, + languageOptions: context.languageOptions }; const newDefaultState = generateDefaultState(workspaceLocation, { @@ -682,30 +684,30 @@ describe('PLAYGROUND_EXTERNAL_SELECT', () => { externalLibrary: oldExternalLibraryName }); - const symbols = externalLibraries.get(newExternalLibraryName)!; - const library: Library = { - chapter, - external: { - name: newExternalLibraryName, - symbols - }, - globals - }; - - return expectSaga(workspaceSaga) - .withState(newDefaultState) - .put(WorkspaceActions.changeExternalLibrary(newExternalLibraryName, workspaceLocation)) - .put(WorkspaceActions.beginClearContext(workspaceLocation, library, true)) - .put(WorkspaceActions.clearReplOutput(workspaceLocation)) - .call(showSuccessMessage, `Switched to ${newExternalLibraryName} library`, 1000) - .dispatch({ - type: WorkspaceActions.externalLibrarySelect.type, - payload: { - externalLibraryName: newExternalLibraryName, - workspaceLocation - } - }) - .silentRun(); + return ( + expectSaga(workspaceSaga) + .withState(newDefaultState) + .put(WorkspaceActions.changeExternalLibrary(newExternalLibraryName, workspaceLocation)) + // beginClearContext is asserted here but the library object can contain + // runtime-specific fields (like languageOptions). Match only the action + // shape we care about (type, workspaceLocation and shouldInitLibrary) + .put.like({ + action: { + type: WorkspaceActions.beginClearContext.type, + payload: { workspaceLocation, shouldInitLibrary: true } + } + }) + .put(WorkspaceActions.clearReplOutput(workspaceLocation)) + .call(showSuccessMessage, `Switched to ${newExternalLibraryName} library`, 1000) + .dispatch({ + type: WorkspaceActions.externalLibrarySelect.type, + payload: { + externalLibraryName: newExternalLibraryName, + workspaceLocation + } + }) + .silentRun() + ); }); test('does not call the above when oldExternalLibraryName === newExternalLibraryName', () => { @@ -770,7 +772,8 @@ describe('BEGIN_CLEAR_CONTEXT', () => { name: newExternalLibraryName, symbols }, - globals + globals, + languageOptions: undefined }; return expectSaga(workspaceSaga) diff --git a/src/commons/utils/JsSlangHelper.ts b/src/commons/utils/JsSlangHelper.ts index e588ca834d..722206b128 100644 --- a/src/commons/utils/JsSlangHelper.ts +++ b/src/commons/utils/JsSlangHelper.ts @@ -181,6 +181,7 @@ function loadStandardLibraries(proxyContext: Context, customBuiltIns: CustomBuil // intercepts reads from the underlying Context and returns desired values export function makeElevatedContext(context: Context) { function ProxyFrame() {} + ProxyFrame.prototype = context.runtime.environments[0].head; // eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-ignore @@ -258,3 +259,13 @@ export function getBlockExtraMethodsString(toRemove: string[]) { ) .join('\n'); } + +export function getBlockExtraMethodsStringTypedVariant(toRemove: string[]) { + return toRemove + .map(x => + x === 'makeUndefinedErrorFunction' + ? '' + : `const ${x} : string = makeUndefinedErrorFunction('${x}');` + ) + .join('\n'); +} diff --git a/src/commons/workspace/WorkspaceReducer.ts b/src/commons/workspace/WorkspaceReducer.ts index 869a0be1ec..9a06e7d239 100644 --- a/src/commons/workspace/WorkspaceReducer.ts +++ b/src/commons/workspace/WorkspaceReducer.ts @@ -101,7 +101,8 @@ const newWorkspaceReducer = createReducer(defaultWorkspaceManager, builder => { action.payload.library.chapter, action.payload.library.external.symbols, workspaceLocation, - action.payload.library.variant + action.payload.library.variant, + action.payload.library.languageOptions ), globals: action.payload.library.globals, externalLibrary: action.payload.library.external.name