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

Add front-end linter and formatter #11

Merged
merged 9 commits into from
Mar 4, 2023
Merged

Add front-end linter and formatter #11

merged 9 commits into from
Mar 4, 2023

Conversation

ghassanachi
Copy link
Contributor

@ghassanachi ghassanachi commented Feb 2, 2023

Step 1 of #10.

  1. Run a linter on client and a check for that in CI

Lint and Format

npm commands: npm run lint && npm run format

I used the configuration from npm create svelte@latest as a reference for Svelte linting and formatting best practices, since I am not familiar with it. To help organize Tailwind classes and catch conflicting classes, I added the prettier-plugin-tailwindcss package. However, I had to disable auto-loading of the prettier plugin due to a limitation. Also, I had to change tailwind.config.cjs to a CommonJS module, because it was not being loaded properly otherwise.

I fixed a few small linting and formatting errors and warnings to pass the linter. However, it might be difficult to track these changes with all the other formatting changes introduced in this PR. Here is a quick list of the fixes:

  1. Remove the unused resp variable in Question.svelte
  2. Remove the unused reject variable in store.js
  3. Remove reject1, resolve, and reject in a few anonymous functions in store.js, since they were not being used
  4. Remove the border property in a few places in the Svelte components, as the border-2 property was overriding

CI

I added a Github Actions that runs the linter and formatter for any PR into the main branch. Up to you if you want to add a branch protection rule for this action.

I considered adding Husky to run a lint and format pre-commit hook locally, but decided against it to keep things simple. If someone wants this functionality, they can create a local git-hook.

Notes

The npm commands were copied as is from the npm create svelte@latest app and target any files that they know how to format, including JSON files, configuration files, and README code blocks. I left them as is, since I don't see any harm in it. If you want to reduce the number of changes in this PR, I can restore those files and set the scripts to only target js and svelte files in our source directory.

Copy link
Owner

@jonhoo jonhoo left a comment

Choose a reason for hiding this comment

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

Thanks for taking this on! It generally looks good, just some small minor notes (questions really).

client/src/Question.svelte Show resolved Hide resolved
client/src/store.js Show resolved Hide resolved
@@ -157,14 +158,14 @@ export async function questionData(qid, qs) {
continue;
}
// clear next batch to go
resolve(true);
resolve && resolve(true);
Copy link
Owner

Choose a reason for hiding this comment

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

Huh, why is the resolve && bit needed here?

Copy link
Contributor Author

@ghassanachi ghassanachi Feb 6, 2023

Choose a reason for hiding this comment

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

Eslint can't tell that resolve will always be assigned (since it is assigned inside the Promise(() => {}) function) so it complains that resolve might be undefined here.

The && is just an old habit I picked up from previous JS codebases I worked on. This code is roughly equivalent to:

// `!!` coerces resolve into a boolean, which is what the && is doing implicitly above
// !!undefined => 0
// !!(() => {})  => true  -- this works with any function
if (!!resolve) {  resolve(true) }

Since we are using ecma2020 features, I could also change it to resolve?.(true) by using the optional chaining operator.

I'm don't think there is an equivalent to the ! typescript operator (not null/undefined) in javascript, so if we want to make the linter happy without disabling it we'll need some of check (&&/if) or optional chaining.

If we want the code to be more explicit and not rely on quirks like coercion or newer features like ?, then I would probably go with if (resolve !== undefined) { resolve(true) }

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I went ahead and changed it to the explicit if (resolve !== undefined) { resolve(true) } check in 06a283d. Let me know if you would prefer the optional chaining option, or something else entirely.

Copy link
Owner

Choose a reason for hiding this comment

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

My thinking here was more that if this is ever undefined, I'd want us to error, rather than just silently not call resolve the way we would given a construct like this. Tracking down failures to resolve are super painful :p

Copy link
Contributor Author

@ghassanachi ghassanachi Feb 14, 2023

Choose a reason for hiding this comment

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

I was sure I replied to this over the weekend, but I must have forgotten to click on comment and lost the previous message 😢.

You raised a good point, I was initially just trying to bypass this limitation of the type system (since the callback to promises is synchronous so it's not possible for resolve to ever be undefined with the current code. But I hadn't considered that future code changes could lead to frustrating troubleshooting.

The executor is called synchronously (as soon as the Promise is constructed) with the resolveFunc and rejectFunc functions as arguments. (https://developer.mozilla.org/enUS/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise)

I'll revert the code back to what it was before.

More Robust Fix Suggestion For a more permanent fix though, the following idea came to mind:
/**
 * SAFETY: Since the Promise constructor (executor) is ran synchronously on Promise creation
 * both `resolve` and `reject` will not be `undefined` when returned from this function.
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/Promise
  * @returns {Promise & { resolve: (value: unkown) => void, reject: (reason?: any) => void}
 */
function deferrablePromise() {
  let resolve;
  let reject;
  const promise = new Promise((rs, rj) => {
    resolve = thisResolve;
    reject = thisReject;
  });
  return Object.assign(promise, { resolve, reject });
}
/* ... */
fetch_done = deferrablePromise(); // Create a promise that holds it's own resolve function
/* ... */ 
fetch_done.resolve(true)

The deferrablePromise would isolate the undefined issue to a single code point, and this deferrablePromise could also be used for batch, fetching and a few other places to simplify the code a little more.

Copy link
Owner

Choose a reason for hiding this comment

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

That seems reasonable to me!

@jonhoo jonhoo merged commit 7460252 into jonhoo:main Mar 4, 2023
jonhoo pushed a commit that referenced this pull request Sep 9, 2023
Put 1.70 in there (for instance if you want to pin against OnceLock stabilizing) and it will actually test 1.7 as it appears github auto converts this to a float?

Putting in quotes seems to do the right thing here
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.

2 participants