fix: remove JS comments revisited (#41952)
* fix: restrict removeJSComments to test-evaluator To prevent @babel from being included everywhere, this only calls removeJSComments inside the test-evaluator * test: add removeJSComments test
This commit is contained in:
committed by
GitHub
parent
341fe76f0f
commit
2eac236e39
@ -2,7 +2,9 @@ import chai from 'chai';
|
|||||||
import '@babel/polyfill';
|
import '@babel/polyfill';
|
||||||
import { toString as __toString } from 'lodash-es';
|
import { toString as __toString } from 'lodash-es';
|
||||||
import { format as __format } from '../../utils/format';
|
import { format as __format } from '../../utils/format';
|
||||||
import curriculumHelpers from '../../utils/curriculum-helpers';
|
import curriculumHelpers, {
|
||||||
|
removeJSComments
|
||||||
|
} from '../../utils/curriculum-helpers';
|
||||||
|
|
||||||
const __utils = (() => {
|
const __utils = (() => {
|
||||||
const MAX_LOGS_SIZE = 64 * 1024;
|
const MAX_LOGS_SIZE = 64 * 1024;
|
||||||
@ -56,8 +58,12 @@ const __utils = (() => {
|
|||||||
/* Run the test if there is one. If not just evaluate the user code */
|
/* Run the test if there is one. If not just evaluate the user code */
|
||||||
self.onmessage = async e => {
|
self.onmessage = async e => {
|
||||||
/* eslint-disable no-unused-vars */
|
/* eslint-disable no-unused-vars */
|
||||||
const code = (e.data?.code?.contents || '').slice();
|
let code = (e.data?.code?.contents || '').slice();
|
||||||
const editableContents = (e.data?.code?.editableContents || '').slice();
|
code = e.data?.removeComments ? removeJSComments(code) : code;
|
||||||
|
let editableContents = (e.data?.code?.editableContents || '').slice();
|
||||||
|
editableContents = e.data?.removeComments
|
||||||
|
? removeJSComments(editableContents)
|
||||||
|
: editableContents;
|
||||||
|
|
||||||
const assert = chai.assert;
|
const assert = chai.assert;
|
||||||
const __helpers = curriculumHelpers;
|
const __helpers = curriculumHelpers;
|
||||||
@ -74,7 +80,9 @@ self.onmessage = async e => {
|
|||||||
try {
|
try {
|
||||||
// Logging is proxyed after the build to catch console.log messages
|
// Logging is proxyed after the build to catch console.log messages
|
||||||
// generated during testing.
|
// generated during testing.
|
||||||
testResult = eval(`${e.data.build}
|
testResult = eval(`${
|
||||||
|
e.data?.removeComments ? removeJSComments(e.data.build) : e.data.build
|
||||||
|
}
|
||||||
__utils.flushLogs();
|
__utils.flushLogs();
|
||||||
__userCodeWasExecuted = true;
|
__userCodeWasExecuted = true;
|
||||||
__utils.toggleProxyLogger(true);
|
__utils.toggleProxyLogger(true);
|
||||||
|
@ -172,7 +172,7 @@ class ShowClassic extends Component {
|
|||||||
updateChallengeMeta({
|
updateChallengeMeta({
|
||||||
...challengeMeta,
|
...challengeMeta,
|
||||||
title,
|
title,
|
||||||
removeComments,
|
removeComments: removeComments !== false,
|
||||||
challengeType,
|
challengeType,
|
||||||
helpCategory
|
helpCategory
|
||||||
});
|
});
|
||||||
|
@ -87,14 +87,13 @@ export function* executeChallengeSaga({
|
|||||||
const protect = isLoopProtected(challengeMeta);
|
const protect = isLoopProtected(challengeMeta);
|
||||||
const buildData = yield buildChallengeData(challengeData, {
|
const buildData = yield buildChallengeData(challengeData, {
|
||||||
preview: false,
|
preview: false,
|
||||||
removeComments: challengeMeta.removeComments,
|
|
||||||
protect
|
protect
|
||||||
});
|
});
|
||||||
const document = yield getContext('document');
|
const document = yield getContext('document');
|
||||||
const testRunner = yield call(
|
const testRunner = yield call(
|
||||||
getTestRunner,
|
getTestRunner,
|
||||||
buildData,
|
buildData,
|
||||||
{ proxyLogger },
|
{ proxyLogger, removeComments: challengeMeta.removeComments },
|
||||||
document
|
document
|
||||||
);
|
);
|
||||||
const testResults = yield executeTests(testRunner, tests);
|
const testResults = yield executeTests(testRunner, tests);
|
||||||
@ -202,7 +201,6 @@ function* previewChallengeSaga({ flushLogs = true } = {}) {
|
|||||||
const protect = isLoopProtected(challengeMeta);
|
const protect = isLoopProtected(challengeMeta);
|
||||||
const buildData = yield buildChallengeData(challengeData, {
|
const buildData = yield buildChallengeData(challengeData, {
|
||||||
preview: true,
|
preview: true,
|
||||||
removeComments: challengeMeta.removeComments,
|
|
||||||
protect
|
protect
|
||||||
});
|
});
|
||||||
// evaluate the user code in the preview frame or in the worker
|
// evaluate the user code in the preview frame or in the worker
|
||||||
@ -210,7 +208,10 @@ function* previewChallengeSaga({ flushLogs = true } = {}) {
|
|||||||
const document = yield getContext('document');
|
const document = yield getContext('document');
|
||||||
yield call(updatePreview, buildData, document, proxyLogger);
|
yield call(updatePreview, buildData, document, proxyLogger);
|
||||||
} else if (isJavaScriptChallenge(challengeData)) {
|
} else if (isJavaScriptChallenge(challengeData)) {
|
||||||
const runUserCode = getTestRunner(buildData, { proxyLogger });
|
const runUserCode = getTestRunner(buildData, {
|
||||||
|
proxyLogger,
|
||||||
|
removeComments: challengeMeta.removeComments
|
||||||
|
});
|
||||||
// without a testString the testRunner just evaluates the user's code
|
// without a testString the testRunner just evaluates the user's code
|
||||||
yield call(runUserCode, null, previewTimeout);
|
yield call(runUserCode, null, previewTimeout);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
import frameRunnerData from '../../../../../config/client/frame-runner.json';
|
import frameRunnerData from '../../../../../config/client/frame-runner.json';
|
||||||
// eslint-disable-next-line import/no-unresolved
|
// eslint-disable-next-line import/no-unresolved
|
||||||
import testEvaluatorData from '../../../../../config/client/test-evaluator.json';
|
import testEvaluatorData from '../../../../../config/client/test-evaluator.json';
|
||||||
import { removeJSComments } from '../../../utils/curriculum-helpers';
|
|
||||||
|
|
||||||
const { filename: runner } = frameRunnerData;
|
const { filename: runner } = frameRunnerData;
|
||||||
const { filename: testEvaluator } = testEvaluatorData;
|
const { filename: testEvaluator } = testEvaluatorData;
|
||||||
@ -107,16 +106,16 @@ const testRunners = {
|
|||||||
[challengeTypes.backend]: getDOMTestRunner,
|
[challengeTypes.backend]: getDOMTestRunner,
|
||||||
[challengeTypes.pythonProject]: getDOMTestRunner
|
[challengeTypes.pythonProject]: getDOMTestRunner
|
||||||
};
|
};
|
||||||
export function getTestRunner(buildData, { proxyLogger }, document) {
|
export function getTestRunner(buildData, runnerConfig, document) {
|
||||||
const { challengeType } = buildData;
|
const { challengeType } = buildData;
|
||||||
const testRunner = testRunners[challengeType];
|
const testRunner = testRunners[challengeType];
|
||||||
if (testRunner) {
|
if (testRunner) {
|
||||||
return testRunner(buildData, proxyLogger, document);
|
return testRunner(buildData, runnerConfig, document);
|
||||||
}
|
}
|
||||||
throw new Error(`Cannot get test runner for challenge type ${challengeType}`);
|
throw new Error(`Cannot get test runner for challenge type ${challengeType}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getJSTestRunner({ build, sources }, proxyLogger) {
|
function getJSTestRunner({ build, sources }, { proxyLogger, removeComments }) {
|
||||||
const code = {
|
const code = {
|
||||||
contents: sources.index,
|
contents: sources.index,
|
||||||
editableContents: sources.editableContents
|
editableContents: sources.editableContents
|
||||||
@ -126,12 +125,15 @@ function getJSTestRunner({ build, sources }, proxyLogger) {
|
|||||||
|
|
||||||
return (testString, testTimeout, firstTest = true) => {
|
return (testString, testTimeout, firstTest = true) => {
|
||||||
return testWorker
|
return testWorker
|
||||||
.execute({ build, testString, code, sources, firstTest }, testTimeout)
|
.execute(
|
||||||
|
{ build, testString, code, sources, firstTest, removeComments },
|
||||||
|
testTimeout
|
||||||
|
)
|
||||||
.on('LOG', proxyLogger).done;
|
.on('LOG', proxyLogger).done;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getDOMTestRunner(buildData, proxyLogger, document) {
|
async function getDOMTestRunner(buildData, { proxyLogger }, document) {
|
||||||
await new Promise(resolve =>
|
await new Promise(resolve =>
|
||||||
createTestFramer(document, resolve, proxyLogger)(buildData)
|
createTestFramer(document, resolve, proxyLogger)(buildData)
|
||||||
);
|
);
|
||||||
@ -165,27 +167,16 @@ export function buildJSChallenge({ files }, options) {
|
|||||||
.map(pipeLine);
|
.map(pipeLine);
|
||||||
return Promise.all(finalFiles)
|
return Promise.all(finalFiles)
|
||||||
.then(checkFilesErrors)
|
.then(checkFilesErrors)
|
||||||
.then(files => {
|
.then(files => ({
|
||||||
let build = files
|
challengeType: challengeTypes.js,
|
||||||
|
build: files
|
||||||
.reduce(
|
.reduce(
|
||||||
(body, file) => [...body, file.head, file.contents, file.tail],
|
(body, file) => [...body, file.head, file.contents, file.tail],
|
||||||
[]
|
[]
|
||||||
)
|
)
|
||||||
.join('\n');
|
.join('\n'),
|
||||||
let sources = buildSourceMap(files);
|
sources: buildSourceMap(files)
|
||||||
if (options?.removeComments !== false) {
|
}));
|
||||||
build = removeJSComments(build);
|
|
||||||
sources = {
|
|
||||||
...sources,
|
|
||||||
index: removeJSComments(sources.index)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
challengeType: challengeTypes.js,
|
|
||||||
build,
|
|
||||||
sources
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function buildBackendChallenge({ url }) {
|
export function buildBackendChallenge({ url }) {
|
||||||
|
@ -52,6 +52,11 @@ describe('removeJSComments', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('leaves malformed JS unchanged', () => {
|
||||||
|
const actual = '/ unclosed regex';
|
||||||
|
expect(removeJSComments(actual)).toBe(actual);
|
||||||
|
});
|
||||||
|
|
||||||
it('does not remove a url found in JS code', () => {
|
it('does not remove a url found in JS code', () => {
|
||||||
expect(removeJSComments(jsCodeWithUrl)).toBe(jsCodeWithUrlUnchanged);
|
expect(removeJSComments(jsCodeWithUrl)).toBe(jsCodeWithUrlUnchanged);
|
||||||
});
|
});
|
||||||
|
@ -519,7 +519,7 @@ async function createTestRunner(
|
|||||||
buildChallenge,
|
buildChallenge,
|
||||||
solutionFromNext
|
solutionFromNext
|
||||||
) {
|
) {
|
||||||
const { required = [], template } = challenge;
|
const { required = [], template, removeComments } = challenge;
|
||||||
// we should avoid modifying challenge, as it gets reused:
|
// we should avoid modifying challenge, as it gets reused:
|
||||||
const files = cloneDeep(challenge.files);
|
const files = cloneDeep(challenge.files);
|
||||||
|
|
||||||
@ -528,16 +528,11 @@ async function createTestRunner(
|
|||||||
files[key].editableContents = solution[key].editableContents;
|
files[key].editableContents = solution[key].editableContents;
|
||||||
});
|
});
|
||||||
|
|
||||||
const { build, sources, loadEnzyme } = await buildChallenge(
|
const { build, sources, loadEnzyme } = await buildChallenge({
|
||||||
{
|
|
||||||
files,
|
files,
|
||||||
required,
|
required,
|
||||||
template
|
template
|
||||||
},
|
});
|
||||||
{
|
|
||||||
removeComments: challenge.removeComments
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const code = {
|
const code = {
|
||||||
contents: sources.index,
|
contents: sources.index,
|
||||||
@ -546,7 +541,7 @@ async function createTestRunner(
|
|||||||
|
|
||||||
const evaluator = await (buildChallenge === buildDOMChallenge
|
const evaluator = await (buildChallenge === buildDOMChallenge
|
||||||
? getContextEvaluator(build, sources, code, loadEnzyme)
|
? getContextEvaluator(build, sources, code, loadEnzyme)
|
||||||
: getWorkerEvaluator(build, sources, code));
|
: getWorkerEvaluator(build, sources, code, removeComments));
|
||||||
|
|
||||||
return async ({ text, testString }) => {
|
return async ({ text, testString }) => {
|
||||||
try {
|
try {
|
||||||
@ -587,12 +582,14 @@ async function getContextEvaluator(build, sources, code, loadEnzyme) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getWorkerEvaluator(build, sources, code) {
|
async function getWorkerEvaluator(build, sources, code, removeComments) {
|
||||||
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
|
const testWorker = createWorker(testEvaluator, { terminateWorker: true });
|
||||||
return {
|
return {
|
||||||
evaluate: async (testString, timeout) =>
|
evaluate: async (testString, timeout) =>
|
||||||
await testWorker.execute({ testString, build, code, sources }, timeout)
|
await testWorker.execute(
|
||||||
.done
|
{ testString, build, code, sources, removeComments },
|
||||||
|
timeout
|
||||||
|
).done
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user