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

lint: refactor logic of finding missing files #168

Merged
merged 1 commit into from
Feb 3, 2021

Conversation

ee7
Copy link
Member

@ee7 ee7 commented Feb 2, 2021

Also remove an unneeded import and fix trivial style issues.

Also remove an unneeded import and fix trivial style issues.
@ee7 ee7 force-pushed the lint-refactor-logic-missing-files branch from 69a7b56 to e838a20 Compare February 2, 2021 11:16
@ee7
Copy link
Member Author

ee7 commented Feb 2, 2021

@ErikSchierboom can you review again? I tweaked it a bit to:

  • Prefer proc over template
  • Try to improve the function name
  • Try to Improve the comments

for file in files:
let path = subdir / file
if not fileExists(path):
writeError("Missing file", path)
Copy link
Member

Choose a reason for hiding this comment

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

Should this be:

Suggested change
writeError("Missing file", path)
writeError("Missing file", path)
result = false

Copy link
Member Author

@ee7 ee7 Feb 2, 2021

Choose a reason for hiding this comment

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

No, because the current writeError is:

template writeError(description: string, details: string) =
stdout.styledWriteLine(fgRed, description & ":")
stdout.writeLine(details)
stdout.write "\n"
result = false

Due to how templates work, writeError is already setting the result of the subdirsContain proc to false.

Try running configlet lint with this PR using some track dir that has missing files. You should see that we detect them, but the only place that can set result to false is inside the writeError template.

I might refactor this pattern later though. I haven't settled on the most expressive/maintainable way to write the linting code, but this is one alternative to writing something like:

proc isValidConfigFile(...): bool =
  result = true
  if not isValidFoo(...):
    result = false
  if not isValidBar(...):
    result = false

where we write result = false everywhere. Another alternative is to pass result as a var parameter to each validator function:

proc checkValidFoo(..., res: var bool) =
  if not myCond:
    res = false

proc isValidConfigFile(...): bool =
  result = true
  checkValidFoo(..., result)
  checkValidBar(..., result)

We can't write this

proc isValidConfigFile(...): bool =
  isValidFoo(...) and isValidBar(...) and isValidBaz(...)

because we don't want to short-circuit - we should show later linting errors even if earlier ones fail.

See the use of writeError in #169 - in particular, how the result of isValidConceptExerciseConfig can become false. I don't claim this is the best approach, though.

We could probably improve the writeError name - any ideas?

Copy link
Member

Choose a reason for hiding this comment

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

Ah, now I understand. I think the original pattern is fine, but maybe with some rename as suggested. But what to use as the name... 🤔 The only thing I could come up with is handleError, but I don't know.

let path = dir / conceptExerciseFile
if not fileExists(path):
writeError("Missing file", path)
result = subdirsContain(conceptDir, conceptExerciseFiles)
Copy link
Member

Choose a reason for hiding this comment

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

A general question: when should one assign to result instead of doing return?

Copy link
Member Author

Choose a reason for hiding this comment

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

That's a good question. It's partly a matter of personal taste.

The official style guide contains:

The return statement should ideally be used when its control-flow properties are required. Use a procedure's implicit result variable whenever possible. This improves readability.

That's a common style, which optimizes for making immediate return obvious. However, Nim style is still evolving: some people argue that we should use return more often, and use result = only when building up a return value over multiple lines.

We also have the option of omitting result = or return entirely here, writing just

subdirsContain(conceptDir, conceptExerciseFiles)

The parent proc then returns the result of that call (the compiler enforces that it returns a bool as declared in the proc signature).

That style can improve readability/robustness in some places, but here I find it clearest to assign to result - we're doing the call because we're interested in its return value, rather than to perform some other action.

Let me know if I can clarify further.

You might like to read https://peterme.net/tips-and-tricks-with-implicit-return-in-nim.html - probably one of the best discussions on this topic.

Copy link
Member

Choose a reason for hiding this comment

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

That does help! Thanks

@ee7 ee7 merged commit 357f313 into exercism:main Feb 3, 2021
@ee7 ee7 deleted the lint-refactor-logic-missing-files branch February 3, 2021 12:15
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