mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-05-28 18:26:54 +00:00
refactor(client): fixed/hid type errors + extended isPoly (#58527)
This commit is contained in:
committed by
GitHub
parent
25c964abc0
commit
d2effdaa41
@@ -5,7 +5,6 @@ import type {
|
|||||||
import { mergeChallengeFiles } from './saved-challenges';
|
import { mergeChallengeFiles } from './saved-challenges';
|
||||||
|
|
||||||
const jsChallenge = {
|
const jsChallenge = {
|
||||||
id: '1',
|
|
||||||
contents: 'js contents',
|
contents: 'js contents',
|
||||||
fileKey: 'jsFileKey',
|
fileKey: 'jsFileKey',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -13,11 +12,11 @@ const jsChallenge = {
|
|||||||
head: 'head',
|
head: 'head',
|
||||||
tail: 'tail',
|
tail: 'tail',
|
||||||
history: [],
|
history: [],
|
||||||
seed: 'original js contents'
|
seed: 'original js contents',
|
||||||
|
path: 'index.js'
|
||||||
};
|
};
|
||||||
|
|
||||||
const cssChallenge = {
|
const cssChallenge = {
|
||||||
id: '2',
|
|
||||||
contents: 'css contents',
|
contents: 'css contents',
|
||||||
fileKey: 'cssFileKey',
|
fileKey: 'cssFileKey',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -25,11 +24,11 @@ const cssChallenge = {
|
|||||||
head: 'head',
|
head: 'head',
|
||||||
tail: 'tail',
|
tail: 'tail',
|
||||||
history: [],
|
history: [],
|
||||||
seed: 'original css contents'
|
seed: 'original css contents',
|
||||||
|
path: 'styles.css'
|
||||||
};
|
};
|
||||||
|
|
||||||
const htmlChallenge = {
|
const htmlChallenge = {
|
||||||
id: '3',
|
|
||||||
contents: 'html contents',
|
contents: 'html contents',
|
||||||
fileKey: 'htmlFileKey',
|
fileKey: 'htmlFileKey',
|
||||||
name: 'name',
|
name: 'name',
|
||||||
@@ -37,7 +36,8 @@ const htmlChallenge = {
|
|||||||
head: 'head',
|
head: 'head',
|
||||||
tail: 'tail',
|
tail: 'tail',
|
||||||
history: [],
|
history: [],
|
||||||
seed: 'original html contents'
|
seed: 'original html contents',
|
||||||
|
path: 'index.html'
|
||||||
};
|
};
|
||||||
|
|
||||||
const savedJsChallenge: SavedChallengeFile = {
|
const savedJsChallenge: SavedChallengeFile = {
|
||||||
|
|||||||
@@ -51,7 +51,9 @@ const jsWorkerExecutor = new WorkerExecutor(jsTestEvaluator, {
|
|||||||
terminateWorker: true
|
terminateWorker: true
|
||||||
});
|
});
|
||||||
|
|
||||||
type ApplyFunctionProps = (file: ChallengeFile) => Promise<ChallengeFile>;
|
type ApplyFunctionProps = (
|
||||||
|
file: ChallengeFile
|
||||||
|
) => Promise<ChallengeFile> | ChallengeFile;
|
||||||
|
|
||||||
const applyFunction =
|
const applyFunction =
|
||||||
(fn: ApplyFunctionProps) => async (file: ChallengeFile) => {
|
(fn: ApplyFunctionProps) => async (file: ChallengeFile) => {
|
||||||
@@ -80,7 +82,7 @@ function buildSourceMap(challengeFiles: ChallengeFile[]): Source | undefined {
|
|||||||
(sources, challengeFile) => {
|
(sources, challengeFile) => {
|
||||||
sources.index += challengeFile.source || '';
|
sources.index += challengeFile.source || '';
|
||||||
sources.contents = sources.index;
|
sources.contents = sources.index;
|
||||||
sources.original[challengeFile.history[0]] = challengeFile.source;
|
sources.original[challengeFile.history[0]] = challengeFile.source ?? null;
|
||||||
sources.editableContents += challengeFile.editableContents || '';
|
sources.editableContents += challengeFile.editableContents || '';
|
||||||
return sources;
|
return sources;
|
||||||
},
|
},
|
||||||
@@ -235,11 +237,11 @@ export async function buildDOMChallenge(
|
|||||||
);
|
);
|
||||||
const isMultifile = challengeFiles.length > 1;
|
const isMultifile = challengeFiles.length > 1;
|
||||||
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
// I'm reasonably sure this is fine, but we need to migrate transformers to
|
||||||
const transformers =
|
// TypeScript to be sure.
|
||||||
isMultifile && hasJsx
|
const transformers: ApplyFunctionProps[] = (isMultifile && hasJsx
|
||||||
? getMultifileJSXTransformers(options)
|
? getMultifileJSXTransformers(options)
|
||||||
: getTransformers(options);
|
: getTransformers(options)) as unknown as ApplyFunctionProps[];
|
||||||
|
|
||||||
const pipeLine = composeFunctions(...transformers);
|
const pipeLine = composeFunctions(...transformers);
|
||||||
const usesTestRunner = options?.usesTestRunner ?? false;
|
const usesTestRunner = options?.usesTestRunner ?? false;
|
||||||
@@ -274,7 +276,9 @@ export async function buildJSChallenge(
|
|||||||
options: BuildOptions
|
options: BuildOptions
|
||||||
): Promise<BuildResult> {
|
): Promise<BuildResult> {
|
||||||
if (!challengeFiles) throw Error('No challenge files provided');
|
if (!challengeFiles) throw Error('No challenge files provided');
|
||||||
const pipeLine = composeFunctions(...getTransformers(options));
|
const pipeLine = composeFunctions(
|
||||||
|
...(getTransformers(options) as unknown as ApplyFunctionProps[])
|
||||||
|
);
|
||||||
|
|
||||||
const finalFiles = await Promise.all(challengeFiles?.map(pipeLine));
|
const finalFiles = await Promise.all(challengeFiles?.map(pipeLine));
|
||||||
const error = finalFiles.find(({ error }) => error)?.error;
|
const error = finalFiles.find(({ error }) => error)?.error;
|
||||||
@@ -311,7 +315,9 @@ export async function buildPythonChallenge({
|
|||||||
challengeFiles
|
challengeFiles
|
||||||
}: BuildChallengeData): Promise<BuildResult> {
|
}: BuildChallengeData): Promise<BuildResult> {
|
||||||
if (!challengeFiles) throw new Error('No challenge files provided');
|
if (!challengeFiles) throw new Error('No challenge files provided');
|
||||||
const pipeLine = composeFunctions(...getPythonTransformers());
|
const pipeLine = composeFunctions(
|
||||||
|
...(getPythonTransformers() as unknown as ApplyFunctionProps[])
|
||||||
|
);
|
||||||
const finalFiles = await Promise.all(challengeFiles.map(pipeLine));
|
const finalFiles = await Promise.all(challengeFiles.map(pipeLine));
|
||||||
const error = finalFiles.find(({ error }) => error)?.error;
|
const error = finalFiles.find(({ error }) => error)?.error;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ import { ChallengeFile } from "../../src/redux/prop-types";
|
|||||||
|
|
||||||
export const challengeFiles: ChallengeFile[] = [
|
export const challengeFiles: ChallengeFile[] = [
|
||||||
{
|
{
|
||||||
id: '0',
|
|
||||||
contents: 'some ts',
|
contents: 'some ts',
|
||||||
error: null,
|
error: null,
|
||||||
ext: 'ts',
|
ext: 'ts',
|
||||||
@@ -14,9 +13,9 @@ export const challengeFiles: ChallengeFile[] = [
|
|||||||
tail: '',
|
tail: '',
|
||||||
editableRegionBoundaries: [],
|
editableRegionBoundaries: [],
|
||||||
usesMultifileEditor: true,
|
usesMultifileEditor: true,
|
||||||
|
path: 'index.ts',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '1',
|
|
||||||
contents: 'some css',
|
contents: 'some css',
|
||||||
error: null,
|
error: null,
|
||||||
ext: 'css',
|
ext: 'css',
|
||||||
@@ -28,9 +27,9 @@ export const challengeFiles: ChallengeFile[] = [
|
|||||||
tail: '',
|
tail: '',
|
||||||
editableRegionBoundaries: [],
|
editableRegionBoundaries: [],
|
||||||
usesMultifileEditor: true,
|
usesMultifileEditor: true,
|
||||||
|
path: 'styles.css',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '2',
|
|
||||||
contents: 'some html',
|
contents: 'some html',
|
||||||
error: null,
|
error: null,
|
||||||
ext: 'html',
|
ext: 'html',
|
||||||
@@ -42,9 +41,9 @@ export const challengeFiles: ChallengeFile[] = [
|
|||||||
tail: '',
|
tail: '',
|
||||||
editableRegionBoundaries: [],
|
editableRegionBoundaries: [],
|
||||||
usesMultifileEditor: true,
|
usesMultifileEditor: true,
|
||||||
|
path: 'index.html',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '3',
|
|
||||||
contents: 'some js',
|
contents: 'some js',
|
||||||
error: null,
|
error: null,
|
||||||
ext: 'js',
|
ext: 'js',
|
||||||
@@ -56,9 +55,9 @@ export const challengeFiles: ChallengeFile[] = [
|
|||||||
tail: '',
|
tail: '',
|
||||||
editableRegionBoundaries: [],
|
editableRegionBoundaries: [],
|
||||||
usesMultifileEditor: true,
|
usesMultifileEditor: true,
|
||||||
|
path: 'script.js',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: '4',
|
|
||||||
contents: 'some jsx',
|
contents: 'some jsx',
|
||||||
error: null,
|
error: null,
|
||||||
ext: 'jsx',
|
ext: 'jsx',
|
||||||
@@ -70,5 +69,6 @@ export const challengeFiles: ChallengeFile[] = [
|
|||||||
tail: '',
|
tail: '',
|
||||||
editableRegionBoundaries: [],
|
editableRegionBoundaries: [],
|
||||||
usesMultifileEditor: true,
|
usesMultifileEditor: true,
|
||||||
|
path: 'index.jsx',
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
+38
-21
@@ -1,12 +1,17 @@
|
|||||||
// originally based off of https://github.com/gulpjs/vinyl
|
// originally based off of https://github.com/gulpjs/vinyl
|
||||||
import invariant from 'invariant';
|
import invariant from 'invariant';
|
||||||
|
|
||||||
export type Ext = 'js' | 'html' | 'css' | 'jsx' | 'ts';
|
const exts = ['js', 'html', 'css', 'jsx', 'ts'] as const;
|
||||||
|
export type Ext = (typeof exts)[number];
|
||||||
|
|
||||||
export type ChallengeFile = {
|
export type IncompleteChallengeFile = {
|
||||||
fileKey: string;
|
fileKey: string;
|
||||||
ext: Ext;
|
ext: Ext;
|
||||||
name: string;
|
name: string;
|
||||||
|
contents: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ChallengeFile = IncompleteChallengeFile & {
|
||||||
editableRegionBoundaries?: number[];
|
editableRegionBoundaries?: number[];
|
||||||
editableContents?: string;
|
editableContents?: string;
|
||||||
usesMultifileEditor?: boolean;
|
usesMultifileEditor?: boolean;
|
||||||
@@ -14,9 +19,8 @@ export type ChallengeFile = {
|
|||||||
head: string;
|
head: string;
|
||||||
tail: string;
|
tail: string;
|
||||||
seed: string;
|
seed: string;
|
||||||
contents: string;
|
|
||||||
source?: string | null;
|
source?: string | null;
|
||||||
id: string;
|
path: string;
|
||||||
history: string[];
|
history: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -65,25 +69,39 @@ export function createPoly<Rest>({
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isPoly(poly: unknown): poly is ChallengeFile {
|
export function isPoly(poly: unknown): poly is ChallengeFile {
|
||||||
return (
|
function hasProperties(poly: unknown): poly is Record<string, unknown> {
|
||||||
!!poly &&
|
return (
|
||||||
typeof poly === 'object' &&
|
!!poly &&
|
||||||
'contents' in poly &&
|
typeof poly === 'object' &&
|
||||||
|
'contents' in poly &&
|
||||||
|
'name' in poly &&
|
||||||
|
'ext' in poly &&
|
||||||
|
'fileKey' in poly &&
|
||||||
|
'head' in poly &&
|
||||||
|
'tail' in poly &&
|
||||||
|
'seed' in poly &&
|
||||||
|
'history' in poly
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const hasCorrectTypes = (poly: Record<string, unknown>): boolean =>
|
||||||
typeof poly.contents === 'string' &&
|
typeof poly.contents === 'string' &&
|
||||||
'name' in poly &&
|
|
||||||
typeof poly.name === 'string' &&
|
typeof poly.name === 'string' &&
|
||||||
'ext' in poly &&
|
exts.includes(poly.ext as Ext) &&
|
||||||
typeof poly.ext === 'string' &&
|
typeof poly.fileKey === 'string' &&
|
||||||
'history' in poly &&
|
typeof poly.head === 'string' &&
|
||||||
Array.isArray(poly.history)
|
typeof poly.tail === 'string' &&
|
||||||
);
|
typeof poly.seed === 'string' &&
|
||||||
|
Array.isArray(poly.history);
|
||||||
|
|
||||||
|
return hasProperties(poly) && hasCorrectTypes(poly);
|
||||||
}
|
}
|
||||||
|
|
||||||
function checkPoly(poly: ChallengeFile) {
|
function checkPoly(poly: ChallengeFile) {
|
||||||
invariant(
|
invariant(
|
||||||
isPoly(poly),
|
isPoly(poly),
|
||||||
'function should receive a PolyVinyl, but got %s',
|
'function should receive a PolyVinyl, but got %s',
|
||||||
poly
|
JSON.stringify(poly)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -102,15 +120,14 @@ export function setContent(
|
|||||||
|
|
||||||
// This is currently only used to add back properties that are not stored in the
|
// This is currently only used to add back properties that are not stored in the
|
||||||
// database.
|
// database.
|
||||||
export function regeneratePathAndHistory(poly: ChallengeFile) {
|
export function regeneratePathAndHistory(file: IncompleteChallengeFile) {
|
||||||
const newPath = poly.name + '.' + poly.ext;
|
const newPath = file.name + '.' + file.ext;
|
||||||
const newPoly = {
|
const newFile = {
|
||||||
...poly,
|
...file,
|
||||||
path: newPath,
|
path: newPath,
|
||||||
history: [newPath]
|
history: [newPath]
|
||||||
};
|
};
|
||||||
checkPoly(newPoly);
|
return newFile;
|
||||||
return newPoly;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async function clearHeadTail(polyP: Promise<ChallengeFile>) {
|
async function clearHeadTail(polyP: Promise<ChallengeFile>) {
|
||||||
|
|||||||
Reference in New Issue
Block a user