Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor(types): Set up type checking #427

Merged
merged 16 commits into from
Jul 30, 2023
Merged

refactor(types): Set up type checking #427

merged 16 commits into from
Jul 30, 2023

Conversation

jeremyckahn
Copy link
Owner

@jeremyckahn jeremyckahn commented Jul 24, 2023

What this PR does

This PR begins Farmhand's adoption of Typed JavaScript. Typed JavaScript strikes a nice balance between TypeScript's full type safety and JavaScript's simplicity. The type system is defined entirely by JSDoc-style comments that are already pervasive throughout the codebase.

This transition is detailed in the ADR that is included in this PR. Additionally, this PR contains the following changes:

  • Introduction of a tsconfig.json file
  • Refactoring of src/utils/index.js to be completely type-safe
    • This seemed like the most sensible starting point
  • Removal of JSDoc site generation and related tooling
    • It wasn't really useful and it was getting in the way of defining accurate types
  • Removal of some unused/unnecessary code

How this change can be validated

A broad, light smoke test should be sufficient. getPlotImage needed the most substantive refactoring, so it would be worth giving the Field some extra attention to make sure it looks correct.

Questions or concerns about this change

The adoption of Typed JavaScript will be a long road and may never be complete. I don't think this is a problem, as the code base and development experience benefits from each step towards complete type safety. The practical drawback with this change is that type errors can be visually annoying in development environments until they are corrected.

I'm personally motivated to update legacy code to be type safe as I go, but there is no expectation or requirement for anyone to do so as they work on the project. The type system is not integrated with the CI system for now, and that would not be a consideration until all type errors are fixed (if that ever happens). For now it's just another static analysis tool to aid in development that is roughly adjacent to ESLint and Prettier.

Additional information

This is how type errors present in Neovim and VSCode environments that are set up to support TypeScript:

Type errors in Vim

Type errors in VSCode

To reiterate, these errors do not cause build failures. They are solely static analysis hints shown during development.

@vercel
Copy link

vercel bot commented Jul 24, 2023

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Updated (UTC)
farmhand ✅ Ready (Inspect) Visit Preview Jul 24, 2023 2:42pm

"@typescript-eslint/eslint-plugin": "^2.23.0",
"@typescript-eslint/parser": "^2.23.0",
"bittorrent-tracker": "^9.19.0",
"browserslist": "^4.21.9",
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was yelling at me while running tests, so I updated the config. This shouldn't affect too much.

export const getPlotImage = (plotContent, x, y) => {
if (plotContent) {
if (getPlotContentType(plotContent) === itemType.CROP) {
export const getPlotImage = (plotContents, x, y) => {
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function needed the most refactoring, but it should be functionally the same as before.

Comment on lines +439 to +442
if (!seedItemId)
throw new Error(
`Crop item ID ${cropItemId} does not have a corresponding seed`
)
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This error should never be thrown in practice, but it was necessary for this function to be type safe. I expect we'll need to use this pattern more as we adopt Typed JavaScript.

Comment on lines +718 to +727
/**
* @param {(?farmhand.plotContent)[][]} field
* @param {function(?farmhand.plotContent): boolean} condition
* @returns {?farmhand.plotContent}
*/
(field, condition) => {
const [plot = null] =
field.find(row => {
return row.find(condition)
}) ?? []
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was actually functionally broken before, as it didn't return what I intended. It simply hadn't manifested in a noticeable way yet. Typed JavaScript exposed a preexisting bug! 😃

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It appears that this change may have broken Scarecrows. A fix is at #431.

Comment on lines -682 to -688
export const getCrops = memoize(
(field, filterCondition) =>
field.reduce((acc, row) => {
acc.push(...row.filter(filterCondition))
/**
* @param {(?farmhand.plotContent)[][]} field
* @param {function(?farmhand.plotContent): boolean} condition
* @returns {?farmhand.plotContent}
*/
(field, condition) => {
const [plot = null] =
field.find(row => {
return row.find(condition)
}) ?? []

return acc
}, []),
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This wasn't being used, so I removed it.

Comment on lines -971 to -1023
if (sanitizedState.field) {
// Update plot data
sanitizedState.field = sanitizedState.field.map(row =>
row.map(plot => {
if (plot === null) {
return null
}

const { isFertilized, ...rest } = plot

return {
...rest,

// Convert from isFertilized (boolean) to fertilizerType (enum)
fertilizerType:
rest.fertilizerType ||
(isFertilized ? fertilizerType.STANDARD : fertilizerType.NONE),
}
})
)
}

const { tools: unlockedTools } = getLevelEntitlements(
levelAchieved(farmProductsSold(sanitizedState.itemsSold))
)

for (const tool of Object.keys(unlockedTools)) {
sanitizedState.toolLevels = unlockTool(sanitizedState.toolLevels, tool)
}

if (
!sanitizedState.showHomeScreen &&
sanitizedState.stageFocus === stageFocusType.HOME
) {
sanitizedState.stageFocus = stageFocusType.SHOP
}

// TODO: Remove these cowInventory and cowForSale transformations after
// 3/15/2023
sanitizedState.cowInventory = sanitizedState.cowInventory.map(cow => ({
ownerId: id,
originalOwnerId: id,
timesTraded: 0,
...cow,
}))

sanitizedState.cowForSale = {
ownerId: '',
originalOwnerId: '',
timesTraded: 0,
...sanitizedState.cowForSale,
}

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This function was long overdue for a cleanup, so I removed support for deprecated save files as part of this PR.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is pretty standard tsconfig.json boilerplate. Its presence is what causes the editor to recognize type errors as issues to present during development.

@jeremyckahn jeremyckahn marked this pull request as ready for review July 24, 2023 14:56
@jeremyckahn
Copy link
Owner Author

We discussed on Discord that the code looks good. I've QA'ed this thoroughly myself and there don't appear to be any regressions, so I'm going to merge this now.

@jeremyckahn jeremyckahn merged commit ea69b10 into develop Jul 30, 2023
5 checks passed
@jeremyckahn jeremyckahn deleted the feature/types branch July 30, 2023 18:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant