From 4ff00922da46306f10bac2739dfea4177d5ee35f Mon Sep 17 00:00:00 2001 From: Oliver Eyton-Williams Date: Mon, 13 Feb 2023 16:13:50 +0100 Subject: [PATCH] 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) --- client/i18n/schema-validation.ts | 2 +- .../SolutionViewer/SolutionViewer.tsx | 2 +- .../src/components/helpers/full-width-row.tsx | 19 +++++++------ client/src/components/seo/index.tsx | 15 +++++++++- .../solution-display-widget/index.tsx | 2 +- .../Challenges/classic/mobile-layout.tsx | 8 +++--- .../Challenges/rechallenge/builders.ts | 2 +- .../src/templates/Challenges/utils/build.ts | 28 +++++++++++++------ .../src/templates/Challenges/utils/frame.ts | 13 ++++++--- .../src/templates/Challenges/utils/index.ts | 2 +- client/src/utils/ajax.ts | 2 ++ client/src/utils/challenge-request-helpers.ts | 20 +++++++++++-- client/src/utils/report-error.ts | 4 +-- client/src/utils/solution-display-type.ts | 10 ++++++- client/src/utils/superblock-map-titles.ts | 2 +- config/donation-settings.ts | 18 ++++++++++-- config/i18n.ts | 2 +- config/superblock-order.ts | 12 ++++++-- .../api/routes/block-route.ts | 5 +++- .../api/routes/index-route.ts | 2 +- .../challenge-editor/api/routes/save-route.ts | 2 +- .../challenge-editor/api/routes/step-route.ts | 2 +- .../api/routes/super-block-route.ts | 5 +++- .../api/routes/tools-route.ts | 5 +++- .../challenge-editor/api/utils/get-blocks.ts | 7 ++++- .../api/utils/get-step-contents.ts | 2 +- tools/challenge-editor/api/utils/get-steps.ts | 8 +++++- tools/challenge-editor/api/utils/save-step.ts | 2 +- tools/challenge-helper-scripts/commands.ts | 6 ++-- tools/challenge-helper-scripts/utils.ts | 8 +++--- tools/scripts/build/schema/trending-schema.ts | 2 +- .../src/color-system/color-system.stories.tsx | 2 +- .../src/color-system/color-system.tsx | 2 +- .../form-control/form-control-feedback.tsx | 2 +- .../src/form-control/form-control-static.tsx | 2 +- .../utils/gen-component-script.ts | 2 +- .../utils/gen-component-template.ts | 14 +++++----- utils/get-lines.ts | 2 +- utils/index.ts | 8 ++++-- utils/validate.ts | 27 ++++++++++++++---- 40 files changed, 196 insertions(+), 84 deletions(-) diff --git a/client/i18n/schema-validation.ts b/client/i18n/schema-validation.ts index 02c0e86a1b6..d9ccd807d23 100644 --- a/client/i18n/schema-validation.ts +++ b/client/i18n/schema-validation.ts @@ -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') ) diff --git a/client/src/components/SolutionViewer/SolutionViewer.tsx b/client/src/components/SolutionViewer/SolutionViewer.tsx index 255d9bf607d..841146b438b 100644 --- a/client/src/components/SolutionViewer/SolutionViewer.tsx +++ b/client/src/components/SolutionViewer/SolutionViewer.tsx @@ -9,7 +9,7 @@ type Props = { }; type Solution = Pick; -function SolutionViewer({ challengeFiles, solution }: Props) { +function SolutionViewer({ challengeFiles, solution }: Props): JSX.Element { const isLegacy = !challengeFiles || !challengeFiles.length; const solutions = isLegacy ? [ diff --git a/client/src/components/helpers/full-width-row.tsx b/client/src/components/helpers/full-width-row.tsx index e91da137a59..707b6621b62 100644 --- a/client/src/components/helpers/full-width-row.tsx +++ b/client/src/components/helpers/full-width-row.tsx @@ -6,15 +6,16 @@ interface FullWidthRowProps { className?: string; } -const FullWidthRow = ({ children, className }: FullWidthRowProps) => { - return ( - - - {children} - - - ); -}; +const FullWidthRow = ({ + children, + className +}: FullWidthRowProps): JSX.Element => ( + + + {children} + + +); FullWidthRow.displayName = 'FullWidthRow'; diff --git a/client/src/components/seo/index.tsx b/client/src/components/seo/index.tsx index 9abb02284cf..25b40ca884a 100644 --- a/client/src/components/seo/index.tsx +++ b/client/src/components/seo/index.tsx @@ -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 { diff --git a/client/src/components/solution-display-widget/index.tsx b/client/src/components/solution-display-widget/index.tsx index 50694807034..b720163ae25 100644 --- a/client/src/components/solution-display-widget/index.tsx +++ b/client/src/components/solution-display-widget/index.tsx @@ -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'); diff --git a/client/src/templates/Challenges/classic/mobile-layout.tsx b/client/src/templates/Challenges/classic/mobile-layout.tsx index c79b86f9871..04965a795e1 100644 --- a/client/src/templates/Challenges/classic/mobile-layout.tsx +++ b/client/src/templates/Challenges/classic/mobile-layout.tsx @@ -39,17 +39,17 @@ class MobileLayout extends Component { 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, diff --git a/client/src/templates/Challenges/rechallenge/builders.ts b/client/src/templates/Challenges/rechallenge/builders.ts index 9b0fd91f86e..f174c79acb9 100644 --- a/client/src/templates/Challenges/rechallenge/builders.ts +++ b/client/src/templates/Challenges/rechallenge/builders.ts @@ -10,7 +10,7 @@ export function concatHtml({ required = [], template, contents -}: ConcatHTMLOptions) { +}: ConcatHTMLOptions): string { const embedSource = template ? _template(template) : ({ source }: { source: ConcatHTMLOptions['contents'] }) => source; diff --git a/client/src/templates/Challenges/utils/build.ts b/client/src/templates/Challenges/utils/build.ts index 81d04296ab2..6951043d988 100644 --- a/client/src/templates/Challenges/utils/build.ts +++ b/client/src/templates/Challenges/utils/build.ts @@ -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 | 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 | 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'; } diff --git a/client/src/templates/Challenges/utils/frame.ts b/client/src/templates/Challenges/utils/frame.ts index 906df13cc46..4e1a29b8074 100644 --- a/client/src/templates/Challenges/utils/frame.ts +++ b/client/src/templates/Challenges/utils/frame.ts @@ -132,11 +132,15 @@ const createHeader = (id = mainPreviewId) => ` `; +type TestResult = + | { pass: boolean } + | { err: { message: string; stack?: string } }; + export const runTestInTestFrame = async function ( document: Document, test: string, timeout: number -) { +): Promise { 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, diff --git a/client/src/templates/Challenges/utils/index.ts b/client/src/templates/Challenges/utils/index.ts index 81f0fb5146b..f4e3a5aa511 100644 --- a/client/src/templates/Challenges/utils/index.ts +++ b/client/src/templates/Challenges/utils/index.ts @@ -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', diff --git a/client/src/utils/ajax.ts b/client/src/utils/ajax.ts index fbf3b19459a..b284b6924d8 100644 --- a/client/src/utils/ajax.ts +++ b/client/src/utils/ajax.ts @@ -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( fileContainer: ({ files: (File & { key: string })[] } & Rest)[] = [] ) { diff --git a/client/src/utils/challenge-request-helpers.ts b/client/src/utils/challenge-request-helpers.ts index 3451f372437..d4017894beb 100644 --- a/client/src/utils/challenge-request-helpers.ts +++ b/client/src/utils/challenge-request-helpers.ts @@ -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; } diff --git a/client/src/utils/report-error.ts b/client/src/utils/report-error.ts index d58bc024ba5..4d4bc6cdc43 100644 --- a/client/src/utils/report-error.ts +++ b/client/src/utils/report-error.ts @@ -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); } diff --git a/client/src/utils/solution-display-type.ts b/client/src/utils/solution-display-type.ts index 8c1be9198b3..101553f5286 100644 --- a/client/src/utils/solution-display-type.ts +++ b/client/src/utils/solution-display-type.ts @@ -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' diff --git a/client/src/utils/superblock-map-titles.ts b/client/src/utils/superblock-map-titles.ts index 1615bc2f5e8..dae9af0dcba 100644 --- a/client/src/utils/superblock-map-titles.ts +++ b/client/src/utils/superblock-map-titles.ts @@ -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) diff --git a/config/donation-settings.ts b/config/donation-settings.ts index 4196a53f475..0ade9659fc6 100644 --- a/config/donation-settings.ts +++ b/config/donation-settings.ts @@ -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 }; } diff --git a/config/i18n.ts b/config/i18n.ts index 88a0e24713b..786c9a54261 100644 --- a/config/i18n.ts +++ b/config/i18n.ts @@ -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`); } diff --git a/config/superblock-order.ts b/config/superblock-order.ts index a7ba0346a0a..8857c06a228 100644 --- a/config/superblock-order.ts +++ b/config/superblock-order.ts @@ -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 => { diff --git a/tools/challenge-editor/api/routes/block-route.ts b/tools/challenge-editor/api/routes/block-route.ts index 0f50ff77e76..c12955fd289 100644 --- a/tools/challenge-editor/api/routes/block-route.ts +++ b/tools/challenge-editor/api/routes/block-route.ts @@ -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 => { const { superblock, block } = req.params; const steps = await getSteps(superblock, block); diff --git a/tools/challenge-editor/api/routes/index-route.ts b/tools/challenge-editor/api/routes/index-route.ts index 2abe9098ffc..6721d6bebe2 100644 --- a/tools/challenge-editor/api/routes/index-route.ts +++ b/tools/challenge-editor/api/routes/index-route.ts @@ -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); }; diff --git a/tools/challenge-editor/api/routes/save-route.ts b/tools/challenge-editor/api/routes/save-route.ts index f41ae15c217..beceeb8f357 100644 --- a/tools/challenge-editor/api/routes/save-route.ts +++ b/tools/challenge-editor/api/routes/save-route.ts @@ -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 => { const { superblock, block, step } = req.params; const content = (req.body as { content: string }).content; diff --git a/tools/challenge-editor/api/routes/step-route.ts b/tools/challenge-editor/api/routes/step-route.ts index 3bef974858c..e04d2b60280 100644 --- a/tools/challenge-editor/api/routes/step-route.ts +++ b/tools/challenge-editor/api/routes/step-route.ts @@ -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 => { const { superblock, block, step } = req.params; const stepContents = await getStepContent(superblock, block, step); diff --git a/tools/challenge-editor/api/routes/super-block-route.ts b/tools/challenge-editor/api/routes/super-block-route.ts index e20ee2d1f8c..ad50c4bb529 100644 --- a/tools/challenge-editor/api/routes/super-block-route.ts +++ b/tools/challenge-editor/api/routes/super-block-route.ts @@ -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 => { const sup = req.params.superblock; const blocks = await getBlocks(sup); diff --git a/tools/challenge-editor/api/routes/tools-route.ts b/tools/challenge-editor/api/routes/tools-route.ts index 255fc5ee568..86b87ec706a 100644 --- a/tools/challenge-editor/api/routes/tools-route.ts +++ b/tools/challenge-editor/api/routes/tools-route.ts @@ -25,7 +25,10 @@ const toolsSwitch: ToolsSwitch = { } }; -export const toolsRoute = async (req: Request, res: Response) => { +export const toolsRoute = async ( + req: Request, + res: Response +): Promise => { const { superblock, block, command } = req.params; const { num } = req.body as Record; const directory = join( diff --git a/tools/challenge-editor/api/utils/get-blocks.ts b/tools/challenge-editor/api/utils/get-blocks.ts index d359efe9af7..71d76dd2342 100644 --- a/tools/challenge-editor/api/utils/get-blocks.ts +++ b/tools/challenge-editor/api/utils/get-blocks.ts @@ -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 => { const filePath = join(CHALLENGE_DIR, sup); const files = await readdir(filePath); diff --git a/tools/challenge-editor/api/utils/get-step-contents.ts b/tools/challenge-editor/api/utils/get-step-contents.ts index f60c943d872..82cc1dd3b5d 100644 --- a/tools/challenge-editor/api/utils/get-step-contents.ts +++ b/tools/challenge-editor/api/utils/get-step-contents.ts @@ -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'); diff --git a/tools/challenge-editor/api/utils/get-steps.ts b/tools/challenge-editor/api/utils/get-steps.ts index 867cfd37f1e..e2f658bf7a2 100644 --- a/tools/challenge-editor/api/utils/get-steps.ts +++ b/tools/challenge-editor/api/utils/get-steps.ts @@ -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 => { const filePath = join(CHALLENGE_DIR, sup, block); const metaPath = join(META_DIR, block, 'meta.json'); diff --git a/tools/challenge-editor/api/utils/save-step.ts b/tools/challenge-editor/api/utils/save-step.ts index e4d3133ad79..5dfb5d3f27a 100644 --- a/tools/challenge-editor/api/utils/save-step.ts +++ b/tools/challenge-editor/api/utils/save-step.ts @@ -7,7 +7,7 @@ export const saveStep = async ( block: string, step: string, content: string -) => { +): Promise => { try { const filePath = join(CHALLENGE_DIR, sup, block, step); diff --git a/tools/challenge-helper-scripts/commands.ts b/tools/challenge-helper-scripts/commands.ts index 401311a1b23..27d54670130 100644 --- a/tools/challenge-helper-scripts/commands.ts +++ b/tools/challenge-helper-scripts/commands.ts @@ -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`; } diff --git a/tools/challenge-helper-scripts/utils.ts b/tools/challenge-helper-scripts/utils.ts index 4fa7c64b833..879db8a1d4a 100644 --- a/tools/challenge-helper-scripts/utils.ts +++ b/tools/challenge-helper-scripts/utils.ts @@ -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[] = []; diff --git a/tools/scripts/build/schema/trending-schema.ts b/tools/scripts/build/schema/trending-schema.ts index 935fd918e6f..c742eaf5bf4 100644 --- a/tools/scripts/build/schema/trending-schema.ts +++ b/tools/scripts/build/schema/trending-schema.ts @@ -65,6 +65,6 @@ const schema = Joi.object().keys({ export const trendingSchemaValidator = ( trendingObj: Record -) => { +): Joi.ValidationResult => { return schema.validate(trendingObj); }; diff --git a/tools/ui-components/src/color-system/color-system.stories.tsx b/tools/ui-components/src/color-system/color-system.stories.tsx index 59a4f879fcb..2ccbf3cf724 100644 --- a/tools/ui-components/src/color-system/color-system.stories.tsx +++ b/tools/ui-components/src/color-system/color-system.stories.tsx @@ -6,6 +6,6 @@ const story = { component: AllPalettes }; -export const ColorSystem = () => ; +export const ColorSystem = (): JSX.Element => ; export default story; diff --git a/tools/ui-components/src/color-system/color-system.tsx b/tools/ui-components/src/color-system/color-system.tsx index ee9d97d3a5d..e4da89bd48d 100644 --- a/tools/ui-components/src/color-system/color-system.tsx +++ b/tools/ui-components/src/color-system/color-system.tsx @@ -53,7 +53,7 @@ const Palette = ({ colors }: PaletteProps) => { ); }; -export const AllPalettes = () => { +export const AllPalettes = (): JSX.Element => { return ( <> diff --git a/tools/ui-components/src/form-control/form-control-feedback.tsx b/tools/ui-components/src/form-control/form-control-feedback.tsx index 707a79c9e33..bd2a07529d7 100644 --- a/tools/ui-components/src/form-control/form-control-feedback.tsx +++ b/tools/ui-components/src/form-control/form-control-feedback.tsx @@ -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'; diff --git a/tools/ui-components/src/form-control/form-control-static.tsx b/tools/ui-components/src/form-control/form-control-static.tsx index c85811930d1..3a6ba924e3d 100644 --- a/tools/ui-components/src/form-control/form-control-static.tsx +++ b/tools/ui-components/src/form-control/form-control-static.tsx @@ -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(' '); diff --git a/tools/ui-components/utils/gen-component-script.ts b/tools/ui-components/utils/gen-component-script.ts index 18250a19470..d7b218b22c8 100644 --- a/tools/ui-components/utils/gen-component-script.ts +++ b/tools/ui-components/utils/gen-component-script.ts @@ -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.'); } diff --git a/tools/ui-components/utils/gen-component-template.ts b/tools/ui-components/utils/gen-component-template.ts index 70f268903e9..8634e42f879 100644 --- a/tools/ui-components/utils/gen-component-template.ts +++ b/tools/ui-components/utils/gen-component-template.ts @@ -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
Hello, I am a ${name} component
; }; `; // 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'; `; diff --git a/utils/get-lines.ts b/utils/get-lines.ts index a13112dfee6..012079f17fb 100644 --- a/utils/get-lines.ts +++ b/utils/get-lines.ts @@ -1,4 +1,4 @@ -export function getLines(contents: string, range?: number[]) { +export function getLines(contents: string, range?: number[]): string { if (!range) { return ''; } diff --git a/utils/index.ts b/utils/index.ts index 3a4b3b31633..60e4d572167 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -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 => idToPath.keys(); +export const getPathFromID = (id: string): string | undefined => + idToPath.get(id); +export const getTitleFromId = (id: string): string | undefined => + idToTitle.get(id); diff --git a/utils/validate.ts b/utils/validate.ts index 9f5ebf3e6af..12a1fa76019 100644 --- a/utils/validate.ts +++ b/utils/validate.ts @@ -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;