diff --git a/craco.config.js b/craco.config.js index a297df211a..2807b94a98 100644 --- a/craco.config.js +++ b/craco.config.js @@ -18,7 +18,7 @@ const cracoConfig = (module.exports = { plugin => plugin.constructor.name === 'InjectManifest' ); if (injectManifestPlugin) { - injectManifestPlugin.config.maximumFileSizeToCacheInBytes = 10 * 1024 * 1024; + injectManifestPlugin.config.maximumFileSizeToCacheInBytes = 15 * 1024 * 1024; } // add rules to pack WASM (for Sourceror) @@ -77,6 +77,9 @@ const cracoConfig = (module.exports = { }) ]; + // Workaround to suppress warnings caused by ts-morph in js-slang + webpackConfig.module.noParse = /typescript\.js$/; + return webpackConfig; } }, diff --git a/package.json b/package.json index 68352e257d..135ef74f6c 100644 --- a/package.json +++ b/package.json @@ -43,7 +43,7 @@ "classnames": "^2.3.2", "flexboxgrid": "^6.3.1", "flexboxgrid-helpers": "^1.1.3", - "js-slang": "^1.0.15", + "js-slang": "^1.0.16", "konva": "^8.3.14", "lodash": "^4.17.21", "lz-string": "^1.4.4", diff --git a/src/commons/application/ApplicationTypes.ts b/src/commons/application/ApplicationTypes.ts index c28cfbfd11..c05580428a 100644 --- a/src/commons/application/ApplicationTypes.ts +++ b/src/commons/application/ApplicationTypes.ts @@ -128,6 +128,12 @@ export const fullJSLanguage: SALanguage = { displayName: 'full JavaScript' }; +export const fullTSLanguage: SALanguage = { + chapter: Chapter.FULL_TS, + variant: Variant.DEFAULT, + displayName: 'full TypeScript' +}; + export const htmlLanguage: SALanguage = { chapter: Chapter.HTML, variant: Variant.DEFAULT, @@ -138,6 +144,8 @@ export const styliseSublanguage = (chapter: Chapter, variant: Variant = Variant. switch (chapter) { case Chapter.FULL_JS: return fullJSLanguage.displayName; + case Chapter.FULL_TS: + return fullTSLanguage.displayName; case Chapter.HTML: return htmlLanguage.displayName; default: diff --git a/src/commons/controlBar/ControlBarChapterSelect.tsx b/src/commons/controlBar/ControlBarChapterSelect.tsx index 903b36d81b..063f0915f0 100644 --- a/src/commons/controlBar/ControlBarChapterSelect.tsx +++ b/src/commons/controlBar/ControlBarChapterSelect.tsx @@ -7,6 +7,7 @@ import React from 'react'; import { defaultLanguages, fullJSLanguage, + fullTSLanguage, htmlLanguage, SALanguage, sourceLanguages, @@ -31,11 +32,13 @@ const chapterListRenderer: ItemListRenderer = ({ itemsParentRef, ren const defaultChoices = defaultLanguages.map(renderItem); const variantChoices = variantLanguages.map(renderItem); const fullJSChoice = renderItem(fullJSLanguage, 0); + const fullTSChoice = renderItem(fullTSLanguage, 0); const htmlChoice = renderItem(htmlLanguage, 0); return ( {defaultChoices} {Constants.playgroundOnly && fullJSChoice} + {Constants.playgroundOnly && fullTSChoice} {Constants.playgroundOnly && htmlChoice} {variantChoices} diff --git a/src/commons/fullJS/FullJSUtils.tsx b/src/commons/fullJS/FullJSUtils.tsx deleted file mode 100644 index 692b80e9a4..0000000000 --- a/src/commons/fullJS/FullJSUtils.tsx +++ /dev/null @@ -1,33 +0,0 @@ -import { showSimpleConfirmDialog, SimpleConfirmDialogProps } from '../utils/DialogHelper'; -import { showWarningMessage } from '../utils/NotificationsHelper'; - -const DISCLAIMER_DIALOG_PROPS: SimpleConfirmDialogProps = { - icon: 'warning-sign', - title: 'You are switching to a unsafe feature!', - contents: ( -

- full JavaScript allows you to run arbitrary JS code beyond source. -
-
- This might pose a security concern if you are not careful and fully aware of what program you - are running! -
-
- Do you still want to proceed? -

- ), - positiveIntent: 'danger', - positiveLabel: 'Proceed', - negativeLabel: 'Cancel' -}; - -const URL_LOAD_INFO: string = - 'For security concerns, users are not allowed to load full JavaScript code from shared links'; - -export function showFullJSDisclaimer(): Promise { - return showSimpleConfirmDialog(DISCLAIMER_DIALOG_PROPS); -} - -export function showFullJSWarningOnUrlLoad(): void { - showWarningMessage(URL_LOAD_INFO); -} diff --git a/src/commons/html/HTMLUtils.tsx b/src/commons/html/HTMLUtils.tsx deleted file mode 100644 index 5a47f010e0..0000000000 --- a/src/commons/html/HTMLUtils.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import { showSimpleConfirmDialog, SimpleConfirmDialogProps } from '../utils/DialogHelper'; - -const DISCLAIMER_DIALOG_PROPS: SimpleConfirmDialogProps = { - icon: 'warning-sign', - title: 'Beware when running shared HTML code!', - contents: ( -

- You are about to access HTML code written by others, which may contain malicious scripts. -
-
- This might pose a security concern if you are not careful and fully aware of what the shared - HTML code contains! -
-
- Do you still want to proceed? -

- ), - positiveIntent: 'danger', - positiveLabel: 'Proceed', - negativeLabel: 'Cancel' -}; - -export function showHTMLDisclaimer(): Promise { - return showSimpleConfirmDialog(DISCLAIMER_DIALOG_PROPS); -} diff --git a/src/commons/sagas/WorkspaceSaga.ts b/src/commons/sagas/WorkspaceSaga.ts index 231c569afd..49f0059896 100644 --- a/src/commons/sagas/WorkspaceSaga.ts +++ b/src/commons/sagas/WorkspaceSaga.ts @@ -42,7 +42,6 @@ import { } from '../application/types/InterpreterTypes'; import { Library, Testcase, TestcaseType, TestcaseTypes } from '../assessment/AssessmentTypes'; import { Documentation } from '../documentation/Documentation'; -import { showFullJSDisclaimer } from '../fullJS/FullJSUtils'; import { SideContentType } from '../sideContent/SideContentTypes'; import { actions } from '../utils/ActionsHelper'; import DisplayBufferService from '../utils/DisplayBufferService'; @@ -58,6 +57,7 @@ import { } from '../utils/JsSlangHelper'; import { showSuccessMessage, showWarningMessage } from '../utils/NotificationsHelper'; import { makeExternalBuiltins as makeSourcerorExternalBuiltins } from '../utils/SourcerorHelper'; +import { showFullJSDisclaimer, showFullTSDisclaimer } from '../utils/WarningDialogHelper'; import { notifyProgramEvaluated } from '../workspace/WorkspaceActions'; import { ADD_HTML_CONSOLE_ERROR, @@ -309,6 +309,8 @@ export default function* WorkspaceSaga(): SagaIterator { const toChangeChapter: boolean = newChapter === Chapter.FULL_JS ? chapterChanged && (yield call(showFullJSDisclaimer)) + : newChapter === Chapter.FULL_TS + ? chapterChanged && (yield call(showFullTSDisclaimer)) : chapterChanged; if (toChangeChapter) { diff --git a/src/commons/sagas/__tests__/WorkspaceSaga.ts b/src/commons/sagas/__tests__/WorkspaceSaga.ts index 0aefdefe75..ce3574fad8 100644 --- a/src/commons/sagas/__tests__/WorkspaceSaga.ts +++ b/src/commons/sagas/__tests__/WorkspaceSaga.ts @@ -4,7 +4,7 @@ import { Chapter, Finished, Variant } from 'js-slang/dist/types'; import { call } from 'redux-saga/effects'; import { expectSaga } from 'redux-saga-test-plan'; import * as matchers from 'redux-saga-test-plan/matchers'; -import * as fullJSUtils from 'src/commons/fullJS/FullJSUtils'; +import { showFullJSDisclaimer, showFullTSDisclaimer } from 'src/commons/utils/WarningDialogHelper'; import { beginInterruptExecution, @@ -16,7 +16,12 @@ import { evalTestcaseFailure, evalTestcaseSuccess } from '../../application/actions/InterpreterActions'; -import { defaultState, fullJSLanguage, OverallState } from '../../application/ApplicationTypes'; +import { + defaultState, + fullJSLanguage, + fullTSLanguage, + OverallState +} from '../../application/ApplicationTypes'; import { externalLibraries, ExternalLibraryName } from '../../application/types/ExternalTypes'; import { BEGIN_DEBUG_PAUSE, @@ -539,9 +544,9 @@ describe('CHAPTER_SELECT', () => { }; return expectSaga(workspaceSaga) - .provide([[matchers.call.fn(fullJSUtils.showFullJSDisclaimer), true]]) + .provide([[matchers.call.fn(showFullJSDisclaimer), true]]) .withState(newDefaultState) - .call(fullJSUtils.showFullJSDisclaimer) + .call(showFullJSDisclaimer) .put(beginClearContext(workspaceLocation, library, false)) .put(clearReplOutput(workspaceLocation)) .call(showSuccessMessage, `Switched to full JavaScript`, 1000) @@ -560,9 +565,9 @@ describe('CHAPTER_SELECT', () => { const newDefaultState = generateDefaultState(workspaceLocation, { context, globals }); return expectSaga(workspaceSaga) - .provide([[matchers.call.fn(fullJSUtils.showFullJSDisclaimer), false]]) + .provide([[matchers.call.fn(showFullJSDisclaimer), false]]) .withState(newDefaultState) - .call(fullJSUtils.showFullJSDisclaimer) + .call(showFullJSDisclaimer) .not.put.actionType(BEGIN_CLEAR_CONTEXT) .not.put.actionType(CLEAR_REPL_OUTPUT) .not.call.fn(showSuccessMessage) @@ -577,6 +582,59 @@ describe('CHAPTER_SELECT', () => { .silentRun(); }); }); + + describe('show disclaimer when fullTS is chosen', () => { + test('correct actions when user proceeds', () => { + const newDefaultState = generateDefaultState(workspaceLocation, { context, globals }); + const library: Library = { + chapter: fullTSLanguage.chapter, + variant: fullTSLanguage.variant, + external: { + name: 'NONE' as ExternalLibraryName, + symbols: context.externalSymbols + }, + globals + }; + + return expectSaga(workspaceSaga) + .provide([[matchers.call.fn(showFullTSDisclaimer), true]]) + .withState(newDefaultState) + .call(showFullTSDisclaimer) + .put(beginClearContext(workspaceLocation, library, false)) + .put(clearReplOutput(workspaceLocation)) + .call(showSuccessMessage, `Switched to full TypeScript`, 1000) + .dispatch({ + type: CHAPTER_SELECT, + payload: { + chapter: fullTSLanguage.chapter, + variant: fullTSLanguage.variant, + workspaceLocation + } + }) + .silentRun(); + }); + + test('correct actions when user cancels', () => { + const newDefaultState = generateDefaultState(workspaceLocation, { context, globals }); + + return expectSaga(workspaceSaga) + .provide([[matchers.call.fn(showFullTSDisclaimer), false]]) + .withState(newDefaultState) + .call(showFullTSDisclaimer) + .not.put.actionType(BEGIN_CLEAR_CONTEXT) + .not.put.actionType(CLEAR_REPL_OUTPUT) + .not.call.fn(showSuccessMessage) + .dispatch({ + type: CHAPTER_SELECT, + payload: { + chapter: fullTSLanguage.chapter, + variant: fullTSLanguage.variant, + workspaceLocation + } + }) + .silentRun(); + }); + }); }); describe('PLAYGROUND_EXTERNAL_SELECT', () => { diff --git a/src/commons/utils/WarningDialogHelper.tsx b/src/commons/utils/WarningDialogHelper.tsx new file mode 100644 index 0000000000..d162bfbbeb --- /dev/null +++ b/src/commons/utils/WarningDialogHelper.tsx @@ -0,0 +1,91 @@ +import { showSimpleConfirmDialog, SimpleConfirmDialogProps } from './DialogHelper'; +import { showWarningMessage } from './NotificationsHelper'; + +// Full JS +const FULL_JS_DISCLAIMER_DIALOG_PROPS: SimpleConfirmDialogProps = { + icon: 'warning-sign', + title: 'You are switching to a unsafe feature!', + contents: ( +

+ full JavaScript allows you to run arbitrary JS code beyond source. +
+
+ This might pose a security concern if you are not careful and fully aware of what program you + are running! +
+
+ Do you still want to proceed? +

+ ), + positiveIntent: 'danger', + positiveLabel: 'Proceed', + negativeLabel: 'Cancel' +}; + +const FULL_JS_URL_LOAD_INFO: string = + 'For security concerns, users are not allowed to load full JavaScript code from shared links'; + +export function showFullJSDisclaimer(): Promise { + return showSimpleConfirmDialog(FULL_JS_DISCLAIMER_DIALOG_PROPS); +} + +export function showFullJSWarningOnUrlLoad(): void { + showWarningMessage(FULL_JS_URL_LOAD_INFO); +} + +// Full TS +const FULL_TS_DISCLAIMER_DIALOG_PROPS: SimpleConfirmDialogProps = { + icon: 'warning-sign', + title: 'You are switching to a unsafe feature!', + contents: ( +

+ full TypeScript allows you to run arbitrary JS code beyond source. +
+
+ This might pose a security concern if you are not careful and fully aware of what program you + are running! +
+
+ Do you still want to proceed? +

+ ), + positiveIntent: 'danger', + positiveLabel: 'Proceed', + negativeLabel: 'Cancel' +}; + +const FULL_TS_URL_LOAD_INFO: string = + 'For security concerns, users are not allowed to load full JavaScript code from shared links'; + +export function showFullTSDisclaimer(): Promise { + return showSimpleConfirmDialog(FULL_TS_DISCLAIMER_DIALOG_PROPS); +} + +export function showFulTSWarningOnUrlLoad(): void { + showWarningMessage(FULL_TS_URL_LOAD_INFO); +} + +// HTML +const HTML_DISCLAIMER_DIALOG_PROPS: SimpleConfirmDialogProps = { + icon: 'warning-sign', + title: 'Beware when running shared HTML code!', + contents: ( +

+ You are about to access HTML code written by others, which may contain malicious scripts. +
+
+ This might pose a security concern if you are not careful and fully aware of what the shared + HTML code contains! +
+
+ Do you still want to proceed? +

+ ), + positiveIntent: 'danger', + positiveLabel: 'Proceed', + negativeLabel: 'Cancel' +}; + +export function showHTMLDisclaimer(): Promise { + return showSimpleConfirmDialog(HTML_DISCLAIMER_DIALOG_PROPS); +} diff --git a/src/pages/playground/Playground.tsx b/src/pages/playground/Playground.tsx index b72d53e78a..df6f6372f5 100644 --- a/src/pages/playground/Playground.tsx +++ b/src/pages/playground/Playground.tsx @@ -26,10 +26,13 @@ import { setEditorSessionId, setSharedbConnected } from 'src/commons/collabEditing/CollabEditingActions'; -import { showFullJSWarningOnUrlLoad } from 'src/commons/fullJS/FullJSUtils'; -import { showHTMLDisclaimer } from 'src/commons/html/HTMLUtils'; import SideContentHtmlDisplay from 'src/commons/sideContent/SideContentHtmlDisplay'; import { useResponsive, useTypedSelector } from 'src/commons/utils/Hooks'; +import { + showFullJSWarningOnUrlLoad, + showFulTSWarningOnUrlLoad, + showHTMLDisclaimer +} from 'src/commons/utils/WarningDialogHelper'; import { addHtmlConsoleError, browseReplHistoryDown, @@ -173,6 +176,8 @@ export async function handleHash(hash: string, props: PlaygroundProps) { const chapter = stringParamToInt(qs.chap) || undefined; if (chapter === Chapter.FULL_JS) { showFullJSWarningOnUrlLoad(); + } else if (chapter === Chapter.FULL_TS) { + showFulTSWarningOnUrlLoad(); } else { if (chapter === Chapter.HTML) { const continueToHtml = await showHTMLDisclaimer(); @@ -656,7 +661,10 @@ const Playground: React.FC = ({ workspaceLocation = 'playground } // (TEMP) Remove tabs for fullJS until support is integrated - if (props.playgroundSourceChapter === Chapter.FULL_JS) { + if ( + props.playgroundSourceChapter === Chapter.FULL_JS || + props.playgroundSourceChapter === Chapter.FULL_TS + ) { return [...tabs, dataVisualizerTab]; } diff --git a/yarn.lock b/yarn.lock index 3a12f62711..f099ea416c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2270,6 +2270,23 @@ resolved "https://registry.yarnpkg.com/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad" integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA== +"@ts-morph/bootstrap@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@ts-morph/bootstrap/-/bootstrap-0.18.0.tgz#23f886caa87575e48b07c9905ddbd56a33ee33f4" + integrity sha512-LmYkdorZT7UonhVuATqj8jFBVz/4ykuFdbrA5szk19OAVxEWaytg7TsngC2waUDLfdc93aJrCF9Gs8Extc1Pfg== + dependencies: + "@ts-morph/common" "~0.18.0" + +"@ts-morph/common@~0.18.0": + version "0.18.1" + resolved "https://registry.yarnpkg.com/@ts-morph/common/-/common-0.18.1.tgz#ca40c3a62c3f9e17142e0af42633ad63efbae0ec" + integrity sha512-RVE+zSRICWRsfrkAw5qCAK+4ZH9kwEFv5h0+/YeHTLieWP7F4wWq4JsKFuNWG+fYh/KF+8rAtgdj5zb2mm+DVA== + dependencies: + fast-glob "^3.2.12" + minimatch "^5.1.0" + mkdirp "^1.0.4" + path-browserify "^1.0.1" + "@tsconfig/node10@^1.0.7": version "1.0.9" resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" @@ -7843,13 +7860,14 @@ js-sdsl@4.3.0, js-sdsl@^4.1.4: resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.3.0.tgz#aeefe32a451f7af88425b11fdb5f58c90ae1d711" integrity sha512-mifzlm2+5nZ+lEcLJMoBK0/IH/bDg8XnJfd/Wq6IP+xoCjLZsTOnV2QpxlVbX9bMnkl5PdEjNtBJ9Cj1NjifhQ== -js-slang@^1.0.15: - version "1.0.15" - resolved "https://registry.yarnpkg.com/js-slang/-/js-slang-1.0.15.tgz#cc7d1c240d1e9c145b8e2a7ed8dc074c89ee68d5" - integrity sha512-wb8zuEU4h3bvBg4MQYC7xLAwBX269hlNnwm8BoVMT2vjV3+GjkTTWzFjc1wfu2Agbl62Ez4sQr1KOtZ8Cgqk4w== +js-slang@^1.0.16: + version "1.0.16" + resolved "https://registry.yarnpkg.com/js-slang/-/js-slang-1.0.16.tgz#8a0d682d95ac19b1b52cb41a2ae24ba9a7b15d9f" + integrity sha512-eTbLUZZhe6ndwzchQUXnb+U4wgUTe/elvrYqCuUCyLwKrJsCa+R5Y8vpPZETjWyj5D0QHP4ByJfvSXMegDyjZw== dependencies: "@babel/parser" "^7.19.4" "@joeychenofficial/alt-ergo-modified" "^2.4.0" + "@ts-morph/bootstrap" "^0.18.0" "@types/estree" "0.0.52" acorn "^8.0.3" acorn-class-fields "^1.0.0" @@ -8434,7 +8452,7 @@ minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: +minimatch@^5.0.1, minimatch@^5.1.0: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== @@ -9123,6 +9141,11 @@ pascal-case@^3.1.2: no-case "^3.0.4" tslib "^2.0.3" +path-browserify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-browserify/-/path-browserify-1.0.1.tgz#d98454a9c3753d5790860f16f68867b9e46be1fd" + integrity sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g== + path-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/path-case/-/path-case-3.0.4.tgz#9168645334eb942658375c56f80b4c0cb5f82c6f"