mirror of
https://github.com/freeCodeCamp/freeCodeCamp.git
synced 2026-03-28 05:00:53 -04:00
refactor: fix hidden eslint errors (#49365)
* refactor: explicit types for validate * refactor: explicit return types for ui-components * refactor: use exec instead of match * refactor: add lots more boundary types * refactor: more eslint warnings * refactor: more explicit exports * refactor: more explicit types * refactor: even more explicit types * fix: relax type contrainsts for superblock-order * refactor: final boundaries * refactor: avoid using 'object' type * fix: use named import for captureException This uses TypeScript (which works) instead of import/namespace (which doesn't) to check if captureException exists in sentry/gatsby (it does)
This commit is contained in:
committed by
GitHub
parent
218fe6605b
commit
4ff00922da
@@ -215,7 +215,7 @@ const schemaValidation = (
|
||||
if (
|
||||
fileName === 'motivation' &&
|
||||
!(fileJson.motivationalQuotes as MotivationalQuotes).every(
|
||||
(object: object) =>
|
||||
object =>
|
||||
Object.prototype.hasOwnProperty.call(object, 'quote') &&
|
||||
Object.prototype.hasOwnProperty.call(object, 'author')
|
||||
)
|
||||
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
};
|
||||
type Solution = Pick<ChallengeFile, 'ext' | 'contents' | 'fileKey'>;
|
||||
|
||||
function SolutionViewer({ challengeFiles, solution }: Props) {
|
||||
function SolutionViewer({ challengeFiles, solution }: Props): JSX.Element {
|
||||
const isLegacy = !challengeFiles || !challengeFiles.length;
|
||||
const solutions = isLegacy
|
||||
? [
|
||||
|
||||
@@ -6,15 +6,16 @@ interface FullWidthRowProps {
|
||||
className?: string;
|
||||
}
|
||||
|
||||
const FullWidthRow = ({ children, className }: FullWidthRowProps) => {
|
||||
return (
|
||||
<Row className={className}>
|
||||
<Col sm={8} smOffset={2} xs={12}>
|
||||
{children}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
};
|
||||
const FullWidthRow = ({
|
||||
children,
|
||||
className
|
||||
}: FullWidthRowProps): JSX.Element => (
|
||||
<Row className={className}>
|
||||
<Col sm={8} smOffset={2} xs={12}>
|
||||
{children}
|
||||
</Col>
|
||||
</Row>
|
||||
);
|
||||
|
||||
FullWidthRow.displayName = 'FullWidthRow';
|
||||
|
||||
|
||||
@@ -17,10 +17,23 @@ interface SiteData {
|
||||
};
|
||||
}
|
||||
|
||||
interface Item {
|
||||
'@type': 'Course';
|
||||
url: string;
|
||||
name: string;
|
||||
description?: string;
|
||||
provider: {
|
||||
'@type': 'Organization';
|
||||
name: string;
|
||||
sameAs: string;
|
||||
nonprofitStatus: string;
|
||||
};
|
||||
}
|
||||
|
||||
interface ListItem {
|
||||
'@type': 'ListItem';
|
||||
position: number;
|
||||
item: object;
|
||||
item: Item;
|
||||
}
|
||||
|
||||
interface StructuredData {
|
||||
|
||||
@@ -22,7 +22,7 @@ export function SolutionDisplayWidget({
|
||||
showUserCode,
|
||||
showProjectPreview,
|
||||
displayContext
|
||||
}: Props) {
|
||||
}: Props): JSX.Element | null {
|
||||
const { id, solution, githubLink } = completedChallenge;
|
||||
const { t } = useTranslation();
|
||||
const viewText = t('buttons.view');
|
||||
|
||||
@@ -39,17 +39,17 @@ class MobileLayout extends Component<MobileLayoutProps, MobileLayoutState> {
|
||||
currentTab: this.props.hasEditableBoundaries ? Tab.Editor : Tab.Instructions
|
||||
};
|
||||
|
||||
switchTab = (tab: Tab) => {
|
||||
switchTab = (tab: Tab): void => {
|
||||
this.setState({
|
||||
currentTab: tab
|
||||
});
|
||||
};
|
||||
|
||||
handleKeyDown = () => this.props.updateUsingKeyboardInTablist(true);
|
||||
handleKeyDown = (): void => this.props.updateUsingKeyboardInTablist(true);
|
||||
|
||||
handleClick = () => this.props.updateUsingKeyboardInTablist(false);
|
||||
handleClick = (): void => this.props.updateUsingKeyboardInTablist(false);
|
||||
|
||||
render() {
|
||||
render(): JSX.Element {
|
||||
const { currentTab } = this.state;
|
||||
const {
|
||||
hasEditableBoundaries,
|
||||
|
||||
@@ -10,7 +10,7 @@ export function concatHtml({
|
||||
required = [],
|
||||
template,
|
||||
contents
|
||||
}: ConcatHTMLOptions) {
|
||||
}: ConcatHTMLOptions): string {
|
||||
const embedSource = template
|
||||
? _template(template)
|
||||
: ({ source }: { source: ConcatHTMLOptions['contents'] }) => source;
|
||||
|
||||
@@ -107,11 +107,13 @@ const buildFunctions = {
|
||||
[challengeTypes.multifileCertProject]: buildDOMChallenge
|
||||
};
|
||||
|
||||
export function canBuildChallenge(challengeData: BuildChallengeData) {
|
||||
export function canBuildChallenge(challengeData: BuildChallengeData): boolean {
|
||||
const { challengeType } = challengeData;
|
||||
return Object.prototype.hasOwnProperty.call(buildFunctions, challengeType);
|
||||
}
|
||||
|
||||
// TODO: Figure out and (hopefully) simplify the return type.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export async function buildChallenge(
|
||||
challengeData: BuildChallengeData,
|
||||
options: BuildOptions
|
||||
@@ -131,6 +133,8 @@ const testRunners = {
|
||||
[challengeTypes.pythonProject]: getDOMTestRunner,
|
||||
[challengeTypes.multifileCertProject]: getDOMTestRunner
|
||||
};
|
||||
// TODO: Figure out and (hopefully) simplify the return type.
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function getTestRunner(
|
||||
buildData: BuildChallengeData,
|
||||
runnerConfig: TestRunnerConfig,
|
||||
@@ -185,10 +189,16 @@ async function getDOMTestRunner(
|
||||
runTestInTestFrame(document, testString, testTimeout);
|
||||
}
|
||||
|
||||
type BuildResult = {
|
||||
challengeType: number;
|
||||
build: string;
|
||||
sources: Source | undefined;
|
||||
};
|
||||
|
||||
export function buildDOMChallenge(
|
||||
{ challengeFiles, required = [], template = '' }: BuildChallengeData,
|
||||
{ usesTestRunner } = { usesTestRunner: false }
|
||||
) {
|
||||
): Promise<BuildResult> | undefined {
|
||||
const finalRequires = [...required];
|
||||
if (usesTestRunner) finalRequires.push(...frameRunner);
|
||||
|
||||
@@ -225,7 +235,7 @@ export function buildDOMChallenge(
|
||||
export function buildJSChallenge(
|
||||
{ challengeFiles }: { challengeFiles: ChallengeFiles },
|
||||
options: BuildOptions
|
||||
) {
|
||||
): Promise<BuildResult> | undefined {
|
||||
const pipeLine = composeFunctions(...getTransformers(options));
|
||||
|
||||
const finalFiles = challengeFiles?.map(pipeLine);
|
||||
@@ -262,7 +272,7 @@ export function updatePreview(
|
||||
buildData: BuildChallengeData,
|
||||
document: Document,
|
||||
proxyLogger: ProxyLogger
|
||||
) {
|
||||
): void {
|
||||
if (
|
||||
buildData.challengeType === challengeTypes.html ||
|
||||
buildData.challengeType === challengeTypes.multifileCertProject
|
||||
@@ -293,7 +303,7 @@ function getDocumentTitle(buildData: BuildChallengeData) {
|
||||
export function updateProjectPreview(
|
||||
buildData: BuildChallengeData,
|
||||
document: Document
|
||||
) {
|
||||
): void {
|
||||
if (
|
||||
buildData.challengeType === challengeTypes.html ||
|
||||
buildData.challengeType === challengeTypes.multifileCertProject
|
||||
@@ -309,7 +319,7 @@ export function updateProjectPreview(
|
||||
}
|
||||
}
|
||||
|
||||
export function challengeHasPreview({ challengeType }: ChallengeMeta) {
|
||||
export function challengeHasPreview({ challengeType }: ChallengeMeta): boolean {
|
||||
return (
|
||||
challengeType === challengeTypes.html ||
|
||||
challengeType === challengeTypes.modern ||
|
||||
@@ -317,13 +327,15 @@ export function challengeHasPreview({ challengeType }: ChallengeMeta) {
|
||||
);
|
||||
}
|
||||
|
||||
export function isJavaScriptChallenge({ challengeType }: ChallengeMeta) {
|
||||
export function isJavaScriptChallenge({
|
||||
challengeType
|
||||
}: ChallengeMeta): boolean {
|
||||
return (
|
||||
challengeType === challengeTypes.js ||
|
||||
challengeType === challengeTypes.jsProject
|
||||
);
|
||||
}
|
||||
|
||||
export function isLoopProtected(challengeMeta: ChallengeMeta) {
|
||||
export function isLoopProtected(challengeMeta: ChallengeMeta): boolean {
|
||||
return challengeMeta.superBlock !== 'coding-interview-prep';
|
||||
}
|
||||
|
||||
@@ -132,11 +132,15 @@ const createHeader = (id = mainPreviewId) => `
|
||||
</script>
|
||||
`;
|
||||
|
||||
type TestResult =
|
||||
| { pass: boolean }
|
||||
| { err: { message: string; stack?: string } };
|
||||
|
||||
export const runTestInTestFrame = async function (
|
||||
document: Document,
|
||||
test: string,
|
||||
timeout: number
|
||||
) {
|
||||
): Promise<TestResult | undefined> {
|
||||
const { contentDocument: frame } = document.getElementById(
|
||||
testId
|
||||
) as HTMLIFrameElement;
|
||||
@@ -309,7 +313,7 @@ export const createMainPreviewFramer = (
|
||||
document: Document,
|
||||
proxyLogger: ProxyLogger,
|
||||
frameTitle: string
|
||||
) =>
|
||||
): ((args: Context) => void) =>
|
||||
createFramer(
|
||||
document,
|
||||
mainPreviewId,
|
||||
@@ -322,7 +326,7 @@ export const createMainPreviewFramer = (
|
||||
export const createProjectPreviewFramer = (
|
||||
document: Document,
|
||||
frameTitle: string
|
||||
) =>
|
||||
): ((args: Context) => void) =>
|
||||
createFramer(
|
||||
document,
|
||||
projectPreviewId,
|
||||
@@ -336,7 +340,8 @@ export const createTestFramer = (
|
||||
document: Document,
|
||||
proxyLogger: ProxyLogger,
|
||||
frameReady: () => void
|
||||
) => createFramer(document, testId, initTestFrame, proxyLogger, frameReady);
|
||||
): ((args: Context) => void) =>
|
||||
createFramer(document, testId, initTestFrame, proxyLogger, frameReady);
|
||||
|
||||
const createFramer = (
|
||||
document: Document,
|
||||
|
||||
@@ -35,7 +35,7 @@ export function transformEditorLink(url: string): string {
|
||||
|
||||
export function enhancePrismAccessibility(
|
||||
prismEnv: Prism.hooks.ElementHighlightedEnvironment
|
||||
) {
|
||||
): void {
|
||||
const langs: { [key: string]: string } = {
|
||||
js: 'JavaScript',
|
||||
javascript: 'JavaScript',
|
||||
|
||||
@@ -136,6 +136,8 @@ function parseApiResponseToClientUser(data: ApiUser): UserResponse {
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: this at least needs a few aliases so it's human readable
|
||||
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
||||
export function mapFilesToChallengeFiles<File, Rest>(
|
||||
fileContainer: ({ files: (File & { key: string })[] } & Rest)[] = []
|
||||
) {
|
||||
|
||||
@@ -13,11 +13,25 @@ interface StandardizeRequestBodyArgs {
|
||||
challengeType: number;
|
||||
}
|
||||
|
||||
interface File {
|
||||
contents: string;
|
||||
ext: string;
|
||||
history: string[];
|
||||
key: string;
|
||||
name: string;
|
||||
}
|
||||
|
||||
interface Body {
|
||||
id: string;
|
||||
files?: File[];
|
||||
challengeType: number;
|
||||
}
|
||||
|
||||
export function standardizeRequestBody({
|
||||
id,
|
||||
challengeFiles = [],
|
||||
challengeType
|
||||
}: StandardizeRequestBodyArgs) {
|
||||
}: StandardizeRequestBodyArgs): Body {
|
||||
return {
|
||||
id,
|
||||
files: challengeFiles?.map(({ fileKey, contents, ext, name, history }) => {
|
||||
@@ -33,12 +47,12 @@ export function standardizeRequestBody({
|
||||
};
|
||||
}
|
||||
|
||||
export function getStringSizeInBytes(str = '') {
|
||||
export function getStringSizeInBytes(str = ''): number {
|
||||
const stringSizeInBytes = new Blob([JSON.stringify(str)]).size;
|
||||
|
||||
return stringSizeInBytes;
|
||||
}
|
||||
|
||||
export function bodySizeFits(bodySizeInBytes: number) {
|
||||
export function bodySizeFits(bodySizeInBytes: number): boolean {
|
||||
return bodySizeInBytes <= MAX_BODY_SIZE;
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import * as Sentry from '@sentry/gatsby';
|
||||
import { captureException } from '@sentry/gatsby';
|
||||
import envData from '../../../config/env.json';
|
||||
|
||||
const { sentryClientDSN } = envData;
|
||||
@@ -11,5 +11,5 @@ export function reportClientSideError(
|
||||
): string | void {
|
||||
return sentryClientDSN === null
|
||||
? console.error(`Client: ${message}`, e)
|
||||
: Sentry.captureException(e);
|
||||
: captureException(e);
|
||||
}
|
||||
|
||||
@@ -2,12 +2,20 @@ import type { CompletedChallenge } from '../redux/prop-types';
|
||||
import { challengeTypes } from '../../utils/challenge-types';
|
||||
import { maybeUrlRE } from '.';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/naming-convention
|
||||
type DisplayType =
|
||||
| 'none'
|
||||
| 'showMultifileProjectSolution'
|
||||
| 'showUserCode'
|
||||
| 'showProjectAndGithubLinks'
|
||||
| 'showProjectLink';
|
||||
|
||||
export const getSolutionDisplayType = ({
|
||||
solution,
|
||||
githubLink,
|
||||
challengeFiles,
|
||||
challengeType
|
||||
}: CompletedChallenge) => {
|
||||
}: CompletedChallenge): DisplayType => {
|
||||
if (challengeFiles?.length)
|
||||
return challengeType === challengeTypes.multifileCertProject
|
||||
? 'showMultifileProjectSolution'
|
||||
|
||||
@@ -14,7 +14,7 @@ const superBlocksWithoutLastWord = [
|
||||
SuperBlocks.TheOdinProject
|
||||
];
|
||||
|
||||
export function getSuperBlockTitleForMap(superBlock: SuperBlocks) {
|
||||
export function getSuperBlockTitleForMap(superBlock: SuperBlocks): string {
|
||||
const i18nSuperBlock = i18next.t(`intro:${superBlock}.title`);
|
||||
|
||||
return superBlocksWithoutLastWord.includes(superBlock)
|
||||
|
||||
@@ -80,8 +80,22 @@ export const paypalConfigTypes = {
|
||||
}
|
||||
};
|
||||
|
||||
type DonationAmount = 500 | 1000 | 2000 | 3000 | 4000 | 5000;
|
||||
|
||||
interface OneTimeConfig {
|
||||
amount: DonationAmount;
|
||||
duration: 'one-time';
|
||||
planId: null;
|
||||
}
|
||||
|
||||
interface SubscriptionConfig {
|
||||
amount: DonationAmount;
|
||||
duration: 'month';
|
||||
planId: string;
|
||||
}
|
||||
|
||||
export const paypalConfigurator = (
|
||||
donationAmount: 500 | 1000 | 2000 | 3000 | 4000 | 5000,
|
||||
donationAmount: DonationAmount,
|
||||
donationDuration: 'one-time' | 'month',
|
||||
paypalConfig: {
|
||||
month: {
|
||||
@@ -93,7 +107,7 @@ export const paypalConfigurator = (
|
||||
5000: { planId: string };
|
||||
};
|
||||
}
|
||||
) => {
|
||||
): OneTimeConfig | SubscriptionConfig => {
|
||||
if (donationDuration === 'one-time') {
|
||||
return { amount: donationAmount, duration: donationDuration, planId: null };
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ export const rtlLangs = ['arabic'];
|
||||
// locale is sourced from a JSON file, so we use getLangCode to
|
||||
// find the associated enum values
|
||||
|
||||
export function getLangCode(locale: PropertyKey) {
|
||||
export function getLangCode(locale: PropertyKey): string {
|
||||
if (isPropertyOf(LangCodes, locale)) return LangCodes[locale];
|
||||
throw new Error(`${String(locale)} is not a valid locale`);
|
||||
}
|
||||
|
||||
@@ -576,11 +576,17 @@ function shouldShowSuperblocks({
|
||||
return true;
|
||||
}
|
||||
|
||||
type Config = {
|
||||
language: string;
|
||||
showNewCurriculum?: string;
|
||||
showUpcomingChanges?: string;
|
||||
};
|
||||
|
||||
export function getLearnSuperBlocks({
|
||||
language = 'english',
|
||||
showNewCurriculum = 'false',
|
||||
showUpcomingChanges = 'false'
|
||||
}) {
|
||||
}: Config): SuperBlocks[] {
|
||||
const learnSuperBlocks: SuperBlocks[] = [];
|
||||
|
||||
Object.values(TranslationStates).forEach(translationState => {
|
||||
@@ -608,7 +614,7 @@ export function getAuditedSuperBlocks({
|
||||
language = 'english',
|
||||
showNewCurriculum = 'false',
|
||||
showUpcomingChanges = 'false'
|
||||
}) {
|
||||
}: Config): SuperBlocks[] {
|
||||
const auditedSuperBlocks: SuperBlocks[] = [];
|
||||
|
||||
Object.values(SuperBlockStates).forEach(superBlockState => {
|
||||
@@ -634,7 +640,7 @@ export function getNotAuditedSuperBlocks({
|
||||
language = 'english',
|
||||
showNewCurriculum = 'false',
|
||||
showUpcomingChanges = 'false'
|
||||
}) {
|
||||
}: Config): SuperBlocks[] {
|
||||
const notAuditedSuperBlocks: SuperBlocks[] = [];
|
||||
|
||||
Object.values(SuperBlockStates).forEach(superBlockState => {
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { getSteps } from '../utils/get-steps';
|
||||
|
||||
export const blockRoute = async (req: Request, res: Response) => {
|
||||
export const blockRoute = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
const { superblock, block } = req.params;
|
||||
|
||||
const steps = await getSteps(superblock, block);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { superBlockList } from '../configs/super-block-list';
|
||||
|
||||
export const indexRoute = (req: Request, res: Response) => {
|
||||
export const indexRoute = (req: Request, res: Response): void => {
|
||||
res.json(superBlockList);
|
||||
};
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { saveStep } from '../utils/save-step';
|
||||
|
||||
export const saveRoute = async (req: Request, res: Response) => {
|
||||
export const saveRoute = async (req: Request, res: Response): Promise<void> => {
|
||||
const { superblock, block, step } = req.params;
|
||||
const content = (req.body as { content: string }).content;
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { getStepContent } from '../utils/get-step-contents';
|
||||
|
||||
export const stepRoute = async (req: Request, res: Response) => {
|
||||
export const stepRoute = async (req: Request, res: Response): Promise<void> => {
|
||||
const { superblock, block, step } = req.params;
|
||||
|
||||
const stepContents = await getStepContent(superblock, block, step);
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { getBlocks } from '../utils/get-blocks';
|
||||
|
||||
export const superblockRoute = async (req: Request, res: Response) => {
|
||||
export const superblockRoute = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
const sup = req.params.superblock;
|
||||
|
||||
const blocks = await getBlocks(sup);
|
||||
|
||||
@@ -25,7 +25,10 @@ const toolsSwitch: ToolsSwitch = {
|
||||
}
|
||||
};
|
||||
|
||||
export const toolsRoute = async (req: Request, res: Response) => {
|
||||
export const toolsRoute = async (
|
||||
req: Request,
|
||||
res: Response
|
||||
): Promise<void> => {
|
||||
const { superblock, block, command } = req.params;
|
||||
const { num } = req.body as Record<string, number>;
|
||||
const directory = join(
|
||||
|
||||
@@ -4,7 +4,12 @@ import { CHALLENGE_DIR, META_DIR } from '../configs/paths';
|
||||
|
||||
import { PartialMeta } from '../interfaces/partial-meta';
|
||||
|
||||
export const getBlocks = async (sup: string) => {
|
||||
type Block = {
|
||||
name: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export const getBlocks = async (sup: string): Promise<Block[]> => {
|
||||
const filePath = join(CHALLENGE_DIR, sup);
|
||||
|
||||
const files = await readdir(filePath);
|
||||
|
||||
@@ -7,7 +7,7 @@ export const getStepContent = async (
|
||||
sup: string,
|
||||
block: string,
|
||||
step: string
|
||||
) => {
|
||||
): Promise<{ name: string; fileData: string }> => {
|
||||
const filePath = join(CHALLENGE_DIR, sup, block, step);
|
||||
|
||||
const fileData = await readFile(filePath, 'utf8');
|
||||
|
||||
@@ -10,7 +10,13 @@ const getFileOrder = (id: string, meta: PartialMeta) => {
|
||||
return meta.challengeOrder.findIndex(([f]) => f === id);
|
||||
};
|
||||
|
||||
export const getSteps = async (sup: string, block: string) => {
|
||||
type Step = {
|
||||
name: string;
|
||||
id: string;
|
||||
path: string;
|
||||
};
|
||||
|
||||
export const getSteps = async (sup: string, block: string): Promise<Step[]> => {
|
||||
const filePath = join(CHALLENGE_DIR, sup, block);
|
||||
|
||||
const metaPath = join(META_DIR, block, 'meta.json');
|
||||
|
||||
@@ -7,7 +7,7 @@ export const saveStep = async (
|
||||
block: string,
|
||||
step: string,
|
||||
content: string
|
||||
) => {
|
||||
): Promise<boolean> => {
|
||||
try {
|
||||
const filePath = join(CHALLENGE_DIR, sup, block, step);
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import {
|
||||
updateStepTitles
|
||||
} from './utils';
|
||||
|
||||
function deleteStep(stepNum: number) {
|
||||
function deleteStep(stepNum: number): void {
|
||||
if (stepNum < 1) {
|
||||
throw 'Step not deleted. Step num must be a number greater than 0.';
|
||||
}
|
||||
@@ -28,7 +28,7 @@ function deleteStep(stepNum: number) {
|
||||
console.log(`Sucessfully deleted step #${stepNum}`);
|
||||
}
|
||||
|
||||
function insertStep(stepNum: number) {
|
||||
function insertStep(stepNum: number): void {
|
||||
if (stepNum < 1) {
|
||||
throw 'Step not inserted. New step number must be greater than 0.';
|
||||
}
|
||||
@@ -56,7 +56,7 @@ function insertStep(stepNum: number) {
|
||||
console.log(`Sucessfully inserted new step #${stepNum}`);
|
||||
}
|
||||
|
||||
function createEmptySteps(num: number) {
|
||||
function createEmptySteps(num: number): void {
|
||||
if (num < 1 || num > 1000) {
|
||||
throw `No steps created. arg 'num' must be between 1 and 1000 inclusive`;
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ const createStepFile = ({
|
||||
stepNum,
|
||||
projectPath = getProjectPath(),
|
||||
challengeSeeds = {}
|
||||
}: Options) => {
|
||||
}: Options): ObjectID => {
|
||||
const challengeId = new ObjectID();
|
||||
|
||||
const template = getStepTemplate({
|
||||
@@ -36,7 +36,7 @@ interface InsertOptions {
|
||||
stepId: ObjectID;
|
||||
}
|
||||
|
||||
function insertStepIntoMeta({ stepNum, stepId }: InsertOptions) {
|
||||
function insertStepIntoMeta({ stepNum, stepId }: InsertOptions): void {
|
||||
const existingMeta = getMetaData();
|
||||
const oldOrder = [...existingMeta.challengeOrder];
|
||||
oldOrder.splice(stepNum - 1, 0, [stepId.toString()]);
|
||||
@@ -49,7 +49,7 @@ function insertStepIntoMeta({ stepNum, stepId }: InsertOptions) {
|
||||
updateMetaData({ ...existingMeta, challengeOrder });
|
||||
}
|
||||
|
||||
function deleteStepFromMeta({ stepNum }: { stepNum: number }) {
|
||||
function deleteStepFromMeta({ stepNum }: { stepNum: number }): void {
|
||||
const existingMeta = getMetaData();
|
||||
const oldOrder = [...existingMeta.challengeOrder];
|
||||
oldOrder.splice(stepNum - 1, 1);
|
||||
@@ -62,7 +62,7 @@ function deleteStepFromMeta({ stepNum }: { stepNum: number }) {
|
||||
updateMetaData({ ...existingMeta, challengeOrder });
|
||||
}
|
||||
|
||||
const updateStepTitles = () => {
|
||||
const updateStepTitles = (): void => {
|
||||
const meta = getMetaData();
|
||||
|
||||
const fileNames: string[] = [];
|
||||
|
||||
@@ -65,6 +65,6 @@ const schema = Joi.object().keys({
|
||||
|
||||
export const trendingSchemaValidator = (
|
||||
trendingObj: Record<string, string>
|
||||
) => {
|
||||
): Joi.ValidationResult => {
|
||||
return schema.validate(trendingObj);
|
||||
};
|
||||
|
||||
@@ -6,6 +6,6 @@ const story = {
|
||||
component: AllPalettes
|
||||
};
|
||||
|
||||
export const ColorSystem = () => <AllPalettes />;
|
||||
export const ColorSystem = (): JSX.Element => <AllPalettes />;
|
||||
|
||||
export default story;
|
||||
|
||||
@@ -53,7 +53,7 @@ const Palette = ({ colors }: PaletteProps) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const AllPalettes = () => {
|
||||
export const AllPalettes = (): JSX.Element => {
|
||||
return (
|
||||
<>
|
||||
<Palette colors={getPaletteByColorName('gray')} />
|
||||
|
||||
@@ -6,7 +6,7 @@ export const FormControlFeedback = ({
|
||||
children,
|
||||
className,
|
||||
testId
|
||||
}: FormControlVariationProps) => {
|
||||
}: FormControlVariationProps): JSX.Element => {
|
||||
const defaultClasses =
|
||||
'absolute top-0 right-0 z-2 block w-8 h-8 leading-8 ' +
|
||||
'text-center pointer-events-none text-green-700';
|
||||
|
||||
@@ -6,7 +6,7 @@ export const FormControlStatic = ({
|
||||
className,
|
||||
children,
|
||||
testId
|
||||
}: FormControlVariationProps) => {
|
||||
}: FormControlVariationProps): JSX.Element => {
|
||||
const defaultClasses = 'py-1.5 mb-0 min-h-43-px text-foreground-secondary';
|
||||
|
||||
const classes = [defaultClasses, className].join(' ');
|
||||
|
||||
@@ -8,7 +8,7 @@ if (!name) {
|
||||
throw new Error('You must include a component name.');
|
||||
}
|
||||
|
||||
if (!name.match(/^[A-Z]/)) {
|
||||
if (!/^[A-Z]/.exec(name)) {
|
||||
throw new Error('Component name must be in PascalCase.');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,23 +1,23 @@
|
||||
// component.tsx
|
||||
export const component = (name: string) => `
|
||||
export const component = (name: string): string => `
|
||||
import React from 'react';
|
||||
|
||||
|
||||
import { ${name}Props } from './types';
|
||||
|
||||
|
||||
export const ${name} = ({}: ${name}Props) => {
|
||||
return <div>Hello, I am a ${name} component</div>;
|
||||
};
|
||||
`;
|
||||
|
||||
// types.ts
|
||||
export const type = (name: string) => `
|
||||
export const type = (name: string): string => `
|
||||
export interface ${name}Props {
|
||||
className?: string
|
||||
}
|
||||
`;
|
||||
|
||||
// component.test.tsx
|
||||
export const test = (name: string) => `
|
||||
export const test = (name: string): string => `
|
||||
import React from 'react';
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
@@ -30,7 +30,7 @@ describe('<${name} />', () => {
|
||||
`;
|
||||
|
||||
// component.stories.tsx
|
||||
export const story = (name: string) => `
|
||||
export const story = (name: string): string => `
|
||||
import React from 'react';
|
||||
import { Story } from '@storybook/react';
|
||||
import { ${name}, ${name}Props } from '.';
|
||||
@@ -53,7 +53,7 @@ export default story;
|
||||
`;
|
||||
|
||||
// index.ts
|
||||
export const barrel = (name: string, kebabCasedName: string) => `
|
||||
export const barrel = (name: string, kebabCasedName: string): string => `
|
||||
export { ${name} } from './${kebabCasedName}';
|
||||
export type { ${name}Props } from './types';
|
||||
`;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
export function getLines(contents: string, range?: number[]) {
|
||||
export function getLines(contents: string, range?: number[]): string {
|
||||
if (!range) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@@ -44,6 +44,8 @@ for (const [id, title] of idToTitle) {
|
||||
}
|
||||
}
|
||||
|
||||
export const getCertIds = () => idToPath.keys();
|
||||
export const getPathFromID = (id: string) => idToPath.get(id);
|
||||
export const getTitleFromId = (id: string) => idToTitle.get(id);
|
||||
export const getCertIds = (): IterableIterator<string> => idToPath.keys();
|
||||
export const getPathFromID = (id: string): string | undefined =>
|
||||
idToPath.get(id);
|
||||
export const getTitleFromId = (id: string): string | undefined =>
|
||||
idToTitle.get(id);
|
||||
|
||||
@@ -1,21 +1,36 @@
|
||||
export const invalidCharError = {
|
||||
type Valid = {
|
||||
valid: true;
|
||||
error: null;
|
||||
};
|
||||
|
||||
type Invalid = {
|
||||
valid: false;
|
||||
error: string;
|
||||
};
|
||||
|
||||
type Validated = Valid | Invalid;
|
||||
|
||||
export const invalidCharError: Invalid = {
|
||||
valid: false,
|
||||
error: 'contains invalid characters'
|
||||
};
|
||||
export const validationSuccess = { valid: true, error: null };
|
||||
export const usernameTooShort = { valid: false, error: 'is too short' };
|
||||
export const usernameIsHttpStatusCode = {
|
||||
export const validationSuccess: Valid = { valid: true, error: null };
|
||||
export const usernameTooShort: Invalid = {
|
||||
valid: false,
|
||||
error: 'is too short'
|
||||
};
|
||||
export const usernameIsHttpStatusCode: Invalid = {
|
||||
valid: false,
|
||||
error: 'is a reserved error code'
|
||||
};
|
||||
|
||||
const validCharsRE = /^[a-zA-Z0-9\-_+]*$/;
|
||||
export const isHttpStatusCode = (str: string) => {
|
||||
export const isHttpStatusCode = (str: string): boolean => {
|
||||
const output = parseInt(str, 10);
|
||||
return !isNaN(output) && output >= 100 && output <= 599;
|
||||
};
|
||||
|
||||
export const isValidUsername = (str: string) => {
|
||||
export const isValidUsername = (str: string): Validated => {
|
||||
if (!validCharsRE.test(str)) return invalidCharError;
|
||||
if (str.length < 3) return usernameTooShort;
|
||||
if (isHttpStatusCode(str)) return usernameIsHttpStatusCode;
|
||||
|
||||
Reference in New Issue
Block a user