-
-
Notifications
You must be signed in to change notification settings - Fork 14
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 validator templates to procs #204
Conversation
While the template-based validator functions lead to some really nice looking code, working with them proved to be slightly harder. To keep the barrier to new contributors as low as possible, the templates have been converted to regular procs, which are easier to understand by people new to Nim.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With this PR, I've purposefully tried to make the code as obvious as possible. This will allow us to more easily add linting rules. I'll leave it to you (@ee7) to check if we can refactor this to something prettier without losing any readability. But I think this PR gets us to a state where we can at least make good progress on adding new linting rules.
if not checkString(data, "github_username", path): | ||
result = false | ||
if not checkString(data, "exercism_username", path, isRequired = false): | ||
result = false |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
According to the documentation, the and
operator does short-cut evaluation. Otherwise I might have written it like this:
if not checkString(data, "github_username", path): | |
result = false | |
if not checkString(data, "exercism_username", path, isRequired = false): | |
result = false | |
result = | |
checkString(data, "github_username", path) and | |
checkString(data, "exercism_username", path, isRequired = false) |
Another option I've considered was to pass the result
value as an argument to the checkString
function, but the current, verbose code does have the advantage of being very easy to understand.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the and
short circuit the first operation, we could do:
result = true
[...]
result = checkString(data, "github_username", path) and result
result = checkString(data, "exercism_username", path) and result
It's more concise and maybe prettier, but possibly less obvious for someone less seasoned.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That is an option. I'll wait for @ee7 to weigh in too
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another option I've considered was to pass the
result
value as an argument to thecheckString
function
Yes, my first preference would probably be:
result = true
result.foo(data, "github_username", path)
result.foo(data, "exercism_username", path, isRequired = false)
if we can find a name for foo
that conveys "set result
to false if"
Because this seems less obvious:
result = true
data.checkString("github_username", path, result)
data.checkString("exercism_username", path, result, isRequired = false)
My second preference would probably be as @valentin-p suggested.
The and
operator does work like that:
proc echoAndReturnTrue: bool =
echo "t"
return true
proc echoAndReturnFalse: bool =
echo "f"
return false
proc foo: bool =
result = true
result = echoAndReturnTrue() and result
result = echoAndReturnFalse() and result
result = echoAndReturnTrue() and result
result = echoAndReturnFalse() and result
proc bar: bool =
result = true
result = result and echoAndReturnTrue()
result = result and echoAndReturnFalse()
result = result and echoAndReturnTrue()
result = result and echoAndReturnFalse()
echo foo()
echo ""
echo bar()
t
f
t
f
false
t
f
false
result = false | ||
if not checkFiles(data, "files", path): | ||
result = false | ||
if not checkArrayOfStrings(data, "", "forked_from", path, isRequired = false): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I still don't like the ""
argument being required. I'd like to refactor in the future to not having to require that. But it's fine for now.
Updated some As of bb0bed1:
src/helpers.nim
19-
20:proc writeError*(description: string, details: string) =
21- stdout.styledWriteLine(fgRed, description & ":")
src/lint/concept_exercises.nim
49- except:
50: writeError("JSON parsing error", getCurrentExceptionMsg())
51- result = false
src/lint/lint.nim
16- if not fileExists(path):
17: writeError("Missing file", path)
18- result = false
src/lint/track_config.nim
50- if not tags.contains(s):
51: writeError("Not a valid tag: " & $data, path)
52- result = false
53- else:
54: writeError("Tag is not a string: " & $data, path)
55- result = false
--
80- except:
81: writeError("JSON parsing error", getCurrentExceptionMsg())
82- return false
--
85- else:
86: writeError("Missing file", configJsonPath)
87- result = false
src/lint/validators.nim
10- if data.kind != JObject:
11: writeError("Not an object: " & q(context), path)
12- result = false
--
18- if data[key].kind != JObject:
19: writeError("Not an object: " & q(key), path)
20- result = false
21- elif isRequired:
22: writeError("Missing key: " & q(key), path)
23- result = false
--
30- if s.len == 0:
31: writeError("String is zero-length: " & q(key), path)
32- result = false
33- elif s.strip().len == 0:
34: writeError("String is whitespace-only: " & q(key), path)
35- result = false
36- else:
37: writeError("Not a string: " & q(key) & ": " & $data[key], path)
38- result = false
39- elif isRequired:
40: writeError("Missing key: " & q(key), path)
41- result = false
--
55- if isRequired:
56: writeError("Array is empty: " & format(context, key), path)
57- result = false
--
62- if s.len == 0:
63: writeError("Array contains zero-length string: " & format(context, key), path)
64- result = false
65- elif s.strip().len == 0:
66: writeError("Array contains whitespace-only string: " & q(key), path)
67- result = false
68- else:
69: writeError("Array contains non-string: " & format(context, key) & ": " & $item, path)
70- result = false
71- else:
72: writeError("Not an array: " & format(context, key), path)
73- result = false
74- elif isRequired:
75: writeError("Missing key: " & format(context, key), path)
76- result = false
--
85- if isRequired:
86: writeError("Array is empty: " & q(key), path)
87- result = false
--
92- else:
93: writeError("Not an array: " & q(key), path)
94- result = false
95- elif isRequired:
96: writeError("Missing key: " & q(key), path)
97- result = false
--
102- if data[key].kind != JBool:
103: writeError("Not a bool: " & q(key) & ": " & $data[key], path)
104- result = false
105- elif isRequired:
106: writeError("Missing key: " & q(key), path)
107- result = false
--
112- if data[key].kind != JInt:
113: writeError("Not an integer: " & q(key) & ": " & $data[key], path)
114- result = false
115- elif isRequired:
116: writeError("Missing key: " & q(key), path)
117- result = false |
I don't love the repetition of We could make proc setFalse*(b: var bool, description: string, details: string) =
b = false
stdout.styledWriteLine(fgRed, description & ":")
stdout.writeLine(details)
stdout.write "\n" I've pushed a branch that implements that, one commit ahead of this PR. See bb0bed1...1f619dd What do you think? |
I like the pattern. The only thing I don't like is the name, as we are not just setting it to false but also writing an error. I don't really know of any better names, but I feel like we should at least mention that it is doing something with error handling.
If we can find a better name, I'm happy to use your version. |
Agreed. I'm fine with If linting errors will be written to
|
Yeah, agreed. I'm also fine with |
Updated the branch, see bb0bed1...abc07a8. I went with Bikeshedding:
I also added a commit that makes some style changes for lines we touched. Feel free to push those commits to this PR if you approve of them. I've verified that the output of Tracks that exit with 1click to expand05ab1e
ada
arm64-assembly
babashka
ballerina
ceylon
cfml
clojure
clojurescript
coffeescript
coq
cpp
crystal
d
dart
emacs-lisp
erlang
factor
forth
fortran
gleam
gnu-apl
go
groovy
haskell
idris
io
j
java
julia
kotlin
lfe
mips
nix
objective-c
ocaml
perl5
pharo-smalltalk
plsql
pony
powershell
prolog
purescript
racket
raku
reasonml
research_experiment_1
ruby
scala
scheme
shen
sml
solidity
swift
system-verilog
typescript
vbnet
vimscript
x86-64-assembly
zig
Tracks that exit with 0click to expand
|
Partly from running `nimpretty`.
@ee7 I like |
Thanks! |
We should have removed this line in 1bb5c11 (exercism#204).
While the template-based validator functions lead to some really nice looking code, working with them proved to be slightly harder. To keep the barrier to new contributors as low as possible, the templates have been converted to regular procs, which are easier to understand by people new to Nim.