feat(rechallenge): Retool challenge framework (#13666)
* feat(rechallenge): Retool challenge framework * fix(code-storage): should use setContent not updateContent * fix(rechallenge): fix context issue and temporal zone of death * fix(rechallenge): Fix frame sources for user code * fix(polyvinyl): Set should ignore source and transform should keep track of source * fix(rechallenge): Missing return statement causing issues
This commit is contained in:
committed by
Quincy Larson
parent
da52116860
commit
ee8ac7b453
@@ -1,99 +1,124 @@
|
||||
import { helpers, Observable } from 'rx';
|
||||
import { Observable } from 'rx';
|
||||
import cond from 'lodash/cond';
|
||||
import identity from 'lodash/identity';
|
||||
import stubTrue from 'lodash/stubTrue';
|
||||
import conforms from 'lodash/conforms';
|
||||
|
||||
const throwForJsHtml = {
|
||||
ext: /js|html/,
|
||||
throwers: [
|
||||
{
|
||||
name: 'multiline-comment',
|
||||
description: 'Detect if a JS multi-line comment is left open',
|
||||
thrower: function checkForComments({ contents }) {
|
||||
const openingComments = contents.match(/\/\*/gi);
|
||||
const closingComments = contents.match(/\*\//gi);
|
||||
if (
|
||||
openingComments &&
|
||||
(!closingComments || openingComments.length > closingComments.length)
|
||||
) {
|
||||
throw new Error('SyntaxError: Unfinished multi-line comment');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'nested-jQuery',
|
||||
description: 'Nested dollar sign calls breaks browsers',
|
||||
detectUnsafeJQ: /\$\s*?\(\s*?\$\s*?\)/gi,
|
||||
thrower: function checkForNestedJquery({ contents }) {
|
||||
if (contents.match(this.detectUnsafeJQ)) {
|
||||
throw new Error('Unsafe $($)');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'unfinished-function',
|
||||
description: 'lonely function keywords breaks browsers',
|
||||
detectFunctionCall: /function\s*?\(|function\s+\w+\s*?\(/gi,
|
||||
thrower: function checkForUnfinishedFunction({ contents }) {
|
||||
if (
|
||||
contents.match(/function/g) &&
|
||||
!contents.match(this.detectFunctionCall)
|
||||
) {
|
||||
throw new Error(
|
||||
'SyntaxError: Unsafe or unfinished function declaration'
|
||||
);
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'unsafe console call',
|
||||
description: 'console call stops tests scripts from running',
|
||||
detectUnsafeConsoleCall: /if\s\(null\)\sconsole\.log\(1\);/gi,
|
||||
thrower: function checkForUnsafeConsole({ contents }) {
|
||||
if (contents.match(this.detectUnsafeConsoleCall)) {
|
||||
throw new Error('Invalid if (null) console.log(1); detected');
|
||||
}
|
||||
}
|
||||
}, {
|
||||
name: 'glitch in code',
|
||||
description: 'Code with the URL glitch.com or glitch.me' +
|
||||
'should not be allowed to run',
|
||||
detectGlitchInCode: /glitch\.(com|me)/gi,
|
||||
thrower: function checkForGlitch({ contents }) {
|
||||
if (contents.match(this.detectGlitchInCode)) {
|
||||
throw new Error('Glitch.com or Glitch.me should not be in the code');
|
||||
}
|
||||
import castToObservable from '../../common/app/utils/cast-to-observable.js';
|
||||
|
||||
const HTML$JSReg = /html|js/;
|
||||
|
||||
const testHTMLJS = conforms({ ext: (ext) => HTML$JSReg.test(ext) });
|
||||
// const testJS = matchesProperty('ext', 'js');
|
||||
const passToNext = [ stubTrue, identity ];
|
||||
|
||||
// Detect if a JS multi-line comment is left open
|
||||
const throwIfOpenComments = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function _checkForComments({ contents }) {
|
||||
const openingComments = contents.match(/\/\*/gi);
|
||||
const closingComments = contents.match(/\*\//gi);
|
||||
if (
|
||||
openingComments &&
|
||||
(!closingComments || openingComments.length > closingComments.length)
|
||||
) {
|
||||
throw new SyntaxError('Unfinished multi-line comment');
|
||||
}
|
||||
}
|
||||
]
|
||||
};
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
export default function throwers() {
|
||||
const source = this;
|
||||
return source.map(file$ => file$.flatMap(file => {
|
||||
if (!throwForJsHtml.ext.test(file.ext)) {
|
||||
|
||||
// Nested dollar sign calls breaks browsers
|
||||
const nestedJQCallReg = /\$\s*?\(\s*?\$\s*?\)/gi;
|
||||
const throwIfNestedJquery = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (nestedJQCallReg.test(contents)) {
|
||||
throw new SyntaxError('Nested jQuery calls breaks browsers');
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
const functionReg = /function/g;
|
||||
const functionCallReg = /function\s*?\(|function\s+\w+\s*?\(/gi;
|
||||
// lonely function keywords breaks browsers
|
||||
const ThrowIfUnfinishedFunction = cond([
|
||||
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (
|
||||
functionReg.test(contents) &&
|
||||
!functionCallReg.test(contents)
|
||||
) {
|
||||
throw new SyntaxError(
|
||||
'Unsafe or unfinished function declaration'
|
||||
);
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
|
||||
// console call stops tests scripts from running
|
||||
const unsafeConsoleCallReg = /if\s\(null\)\sconsole\.log\(1\);/gi;
|
||||
const throwIfUnsafeConsoleCall = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (unsafeConsoleCallReg.test(contents)) {
|
||||
throw new SyntaxError(
|
||||
'`if (null) console.log(1)` detected. This will break tests'
|
||||
);
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
// Code with the URL hyperdev.com should not be allowed to run,
|
||||
const goMixReg = /glitch\.(com|me)/gi;
|
||||
const throwIfGomixDetected = cond([
|
||||
[
|
||||
testHTMLJS,
|
||||
function({ contents }) {
|
||||
if (goMixReg.test(contents)) {
|
||||
throw new Error('Glitch.com or Glitch.me should not be in the code');
|
||||
}
|
||||
}
|
||||
],
|
||||
passToNext
|
||||
]);
|
||||
|
||||
const validators = [
|
||||
throwIfOpenComments,
|
||||
throwIfGomixDetected,
|
||||
throwIfNestedJquery,
|
||||
ThrowIfUnfinishedFunction,
|
||||
throwIfUnsafeConsoleCall
|
||||
];
|
||||
|
||||
export default function validate(file) {
|
||||
return validators.reduce((obs, validator) => obs.flatMap(file => {
|
||||
try {
|
||||
return castToObservable(validator(file));
|
||||
} catch (err) {
|
||||
return Observable.throw(err);
|
||||
}
|
||||
}), Observable.of(file))
|
||||
// if no error has occured map to the original file
|
||||
.map(() => file)
|
||||
// if err add it to the file
|
||||
// and return file
|
||||
.catch(err => {
|
||||
file.error = err;
|
||||
return Observable.just(file);
|
||||
}
|
||||
return Observable.from(throwForJsHtml.throwers)
|
||||
.flatMap(context => {
|
||||
try {
|
||||
let finalObs;
|
||||
const maybeObservableOrPromise = context.thrower(file);
|
||||
if (helpers.isPromise(maybeObservableOrPromise)) {
|
||||
finalObs = Observable.fromPromise(maybeObservableOrPromise);
|
||||
} else if (Observable.isObservable(maybeObservableOrPromise)) {
|
||||
finalObs = maybeObservableOrPromise;
|
||||
} else {
|
||||
finalObs = Observable.just(maybeObservableOrPromise);
|
||||
}
|
||||
return finalObs;
|
||||
} catch (err) {
|
||||
return Observable.throw(err);
|
||||
}
|
||||
})
|
||||
// if none of the throwers throw, wait for last one
|
||||
.last({ defaultValue: null })
|
||||
// then map to the original file
|
||||
.map(file)
|
||||
// if err add it to the file
|
||||
// and return file
|
||||
.catch(err => {
|
||||
file.error = err;
|
||||
return Observable.just(file);
|
||||
});
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user