add compile time check to detect duplicate abbr and duplicate name

This commit is contained in:
jangko 2021-09-02 15:12:38 +07:00 committed by zah
parent ab4ba1cbfd
commit 0cd09d75c8
9 changed files with 204 additions and 0 deletions

View File

@ -659,6 +659,31 @@ proc generateFieldSetters(RecordType: NimNode): NimNode =
result.add settersArray
debugMacroResult "Field Setters"
proc checkDuplicate(cmd: CmdInfo, opt: OptInfo, fieldName: NimNode) =
for x in cmd.opts:
if opt.name == x.name:
error "duplicate name detected: " & opt.name, fieldName
if opt.abbr.len > 0 and opt.abbr == x.abbr:
error "duplicate abbr detected: " & opt.abbr, fieldName
proc validPath(path: var seq[CmdInfo], parent, node: CmdInfo): bool =
for x in parent.opts:
if x.kind != Discriminator: continue
for y in x.subCmds:
if y == node:
path.add y
return true
if validPath(path, y, node):
path.add y
return true
false
proc findPath(parent, node: CmdInfo): seq[CmdInfo] =
# find valid path from parent to node
result = newSeq[CmdInfo]()
doAssert validPath(result, parent, node)
result.add parent
proc cmdInfoFromType(T: NimNode): CmdInfo =
result = CmdInfo()
@ -741,9 +766,14 @@ proc cmdInfoFromType(T: NimNode): CmdInfo =
if branchEnumVal.kind == nnkDotExpr:
branchEnumVal = branchEnumVal[1]
var cmd = findCmd(discriminator.subCmds, $branchEnumVal)
# we respect subcommand hierarchy when looking for duplicate
let path = findPath(result, cmd)
for n in path:
checkDuplicate(n, opt, field.name)
cmd.opts.add opt
else:
checkDuplicate(result, opt, field.name)
result.opts.add opt
macro configurationRtti(RecordType: type): untyped =

View File

@ -1,3 +1,4 @@
import os
mode = ScriptMode.Verbose
packageName = "confutils"
@ -13,3 +14,18 @@ requires "nim >= 1.0.0",
task test, "Run all tests":
exec "nim c -r --threads:off -d:release tests/test_all"
exec "nim c -r --threads:on -d:release tests/test_all"
exec "nim c --threads:off -d:release tests/test_duplicates"
exec "nim c --threads:on -d:release tests/test_duplicates"
#Also iterate over every test in tests/fail, and verify they fail to compile.
echo "\r\nTest Fail to Compile:"
for path in listFiles(thisDir() / "tests" / "fail"):
if path.split(".")[^1] != "nim":
continue
if gorgeEx("nim c " & path).exitCode != 0:
echo " [OK] ", path.split(DirSep)[^1]
else:
echo " [FAILED] ", path.split(DirSep)[^1]
exec "exit 1"

View File

@ -0,0 +1,10 @@
import
../../confutils,
../../confutils/defs
type
TestConf* = object
dataDir* {.abbr: "d" }: OutDir
importDir* {.abbr: "d" }: OutDir
let c = TestConf.load()

View File

@ -0,0 +1,19 @@
import
../../confutils,
../../confutils/defs
type
Command = enum
noCommand
TestConf* = object
dataDir* {.abbr: "d" }: OutDir
case cmd* {.
command
defaultValue: noCommand }: Command
of noCommand:
importDir* {.abbr: "d" }: OutDir
let c = TestConf.load()

View File

@ -0,0 +1,20 @@
import
../../confutils,
../../confutils/defs
type
Command = enum
noCommand
TestConf* = object
dataDir* {.abbr: "d" }: OutDir
case cmd* {.
command
defaultValue: noCommand }: Command
of noCommand:
importDir* {.abbr: "i" }: OutDir
importKey* {.abbr: "i" }: OutDir
let c = TestConf.load()

View File

@ -0,0 +1,10 @@
import
../../confutils,
../../confutils/defs
type
TestConf* = object
dataDir* {.name: "data-dir" }: OutDir
importDir* {.name: "data-dir" }: OutDir
let c = TestConf.load()

View File

@ -0,0 +1,19 @@
import
../../confutils,
../../confutils/defs
type
Command = enum
noCommand
TestConf* = object
dataDir* {.name: "data-dir" }: OutDir
case cmd* {.
command
defaultValue: noCommand }: Command
of noCommand:
importDir* {.name: "data-dir" }: OutDir
let c = TestConf.load()

View File

@ -0,0 +1,20 @@
import
../../confutils,
../../confutils/defs
type
Command = enum
noCommand
TestConf* = object
dataDir* {.name: "data-dir" }: OutDir
case cmd* {.
command
defaultValue: noCommand }: Command
of noCommand:
importDir* {.name: "import-dir" }: OutDir
importKey* {.name: "import-dir" }: OutDir
let c = TestConf.load()

60
tests/test_duplicates.nim Normal file
View File

@ -0,0 +1,60 @@
import
../confutils,
../confutils/defs
# duplicate name and abbr from different subcommand
# at the same level is allowed
# but hierarchical duplicate is not allowed
type
Command = enum
noCommand
subCommand
BranchCmd = enum
branchA
branchB
TestConf* = object
dataDir* {.abbr: "d" }: OutDir
case cmd* {.
command
defaultValue: noCommand }: Command
of noCommand:
importDir* {.
abbr: "i"
name: "import"
}: OutDir
outputDir* {.
abbr: "o"
name: "output"
}: OutDir
of subCommand:
importKey* {.
abbr: "i"
name: "import"
}: OutDir
case subcmd* {.
command
defaultValue: branchA }: BranchCmd
of branchA:
outputFolder* {.
abbr: "o"
name: "output"
}: OutDir
of branchB:
importFolder* {.
abbr: "f"
name: "import-folder"
}: OutDir
let c = TestConf.load()
discard c