fix: nimcheck improvements for generics, JS operators, Python 3.10+ syntax
- Add tolerantMode for generic Nim code with < > brackets - Fix JS ?. and ?? operator tokenization - Add Python 3.10 match/case keywords, underscore prefix support - Fix f-string parsing operator precedence bug - Fix duplicate foundClosing assignments in JS/PHP tokenizers - Treat unknown flavors as info instead of error - Add comprehensive test coverage for all fixes
This commit is contained in:
parent
9f50fd7e37
commit
f3ad8e64c8
@ -85,18 +85,18 @@ proc validateSource*(
|
|||||||
opts.flavor = detectedFlavor
|
opts.flavor = detectedFlavor
|
||||||
|
|
||||||
if detectedFlavor == lfUnknown:
|
if detectedFlavor == lfUnknown:
|
||||||
# Cannot determine language
|
|
||||||
result = newValidationResult()
|
result = newValidationResult()
|
||||||
result.valid = false
|
result.valid = true
|
||||||
result.flavor = lfUnknown
|
result.flavor = lfUnknown
|
||||||
result.detectionMethod = fdmUnknown
|
result.detectionMethod = fdmUnknown
|
||||||
|
result.infos += 1
|
||||||
result.errors.add(newValidationError(
|
result.errors.add(newValidationError(
|
||||||
esError,
|
esInfo,
|
||||||
"Could not detect language flavor from source content",
|
"Unrecognized file type - auto-passed as valid",
|
||||||
ErrInvalidSyntax,
|
InfoUnrecognizedFlavor,
|
||||||
newSourcePosition(1, 1, 0),
|
newSourcePosition(1, 1, 0),
|
||||||
newSourceRange(newSourcePosition(1, 1, 0), newSourcePosition(1, 1, 0)),
|
newSourceRange(newSourcePosition(1, 1, 0), newSourcePosition(1, 1, 0)),
|
||||||
hint = "Specify the flavor explicitly, or add a shebang/extension"
|
hint = "File type not recognized by nimcheck. Consider adding a validator for this language."
|
||||||
))
|
))
|
||||||
result.durationMs = 0.0
|
result.durationMs = 0.0
|
||||||
return result
|
return result
|
||||||
|
|||||||
@ -258,10 +258,13 @@ proc closeBracket*(
|
|||||||
# Bracket validation
|
# Bracket validation
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
proc validateBrackets*(self: TokenizerBase): seq[ValidationError] =
|
proc validateBrackets*(self: TokenizerBase, tolerantAngleBrackets: bool = false): seq[ValidationError] =
|
||||||
## Check the bracket stack for unclosed brackets at end of file.
|
## Check the bracket stack for unclosed brackets at end of file.
|
||||||
|
## When tolerantAngleBrackets is true, unmatched angle brackets produce warnings instead of errors.
|
||||||
result = @[]
|
result = @[]
|
||||||
for (ch, pos) in self.bracketStack:
|
for (ch, pos) in self.bracketStack:
|
||||||
|
let isAngle = ch == '<' or ch == '>'
|
||||||
|
let severity = if isAngle and tolerantAngleBrackets: esWarning else: esError
|
||||||
let pairStr = case ch:
|
let pairStr = case ch:
|
||||||
of '(': ")"
|
of '(': ")"
|
||||||
of '[': "]"
|
of '[': "]"
|
||||||
@ -270,7 +273,7 @@ proc validateBrackets*(self: TokenizerBase): seq[ValidationError] =
|
|||||||
else: $ch
|
else: $ch
|
||||||
let hintStr = &"Add a closing '{pairStr}' before this point"
|
let hintStr = &"Add a closing '{pairStr}' before this point"
|
||||||
let err = newValidationError(
|
let err = newValidationError(
|
||||||
esError,
|
severity,
|
||||||
&"Unclosed '{ch}' - expected matching '{pairStr}'",
|
&"Unclosed '{ch}' - expected matching '{pairStr}'",
|
||||||
ErrUnclosedBracket,
|
ErrUnclosedBracket,
|
||||||
pos,
|
pos,
|
||||||
|
|||||||
@ -223,6 +223,7 @@ type
|
|||||||
flavor*: LanguageFlavor # lfUnknown = auto-detect
|
flavor*: LanguageFlavor # lfUnknown = auto-detect
|
||||||
debugMode*: bool
|
debugMode*: bool
|
||||||
strictMode*: bool # Warnings become errors
|
strictMode*: bool # Warnings become errors
|
||||||
|
tolerantMode*: bool # Downgrade non-critical errors to warnings
|
||||||
maxErrors*: int # Stop after N errors (0 = unlimited)
|
maxErrors*: int # Stop after N errors (0 = unlimited)
|
||||||
includeContext*: bool # Include surrounding source in errors
|
includeContext*: bool # Include surrounding source in errors
|
||||||
includeHints*: bool # Include fix suggestions
|
includeHints*: bool # Include fix suggestions
|
||||||
@ -384,6 +385,7 @@ proc newValidationOptions*(
|
|||||||
flavor: LanguageFlavor = lfUnknown,
|
flavor: LanguageFlavor = lfUnknown,
|
||||||
debugMode: bool = false,
|
debugMode: bool = false,
|
||||||
strictMode: bool = false,
|
strictMode: bool = false,
|
||||||
|
tolerantMode: bool = false,
|
||||||
maxErrors: int = 0,
|
maxErrors: int = 0,
|
||||||
includeContext: bool = true,
|
includeContext: bool = true,
|
||||||
includeHints: bool = true
|
includeHints: bool = true
|
||||||
@ -393,6 +395,7 @@ proc newValidationOptions*(
|
|||||||
flavor: flavor,
|
flavor: flavor,
|
||||||
debugMode: debugMode,
|
debugMode: debugMode,
|
||||||
strictMode: strictMode,
|
strictMode: strictMode,
|
||||||
|
tolerantMode: tolerantMode,
|
||||||
maxErrors: maxErrors,
|
maxErrors: maxErrors,
|
||||||
includeContext: includeContext,
|
includeContext: includeContext,
|
||||||
includeHints: includeHints
|
includeHints: includeHints
|
||||||
@ -463,6 +466,7 @@ const
|
|||||||
# Info/Warning codes (I0xxx / W0xxx)
|
# Info/Warning codes (I0xxx / W0xxx)
|
||||||
InfoLongLine* = "I0001"
|
InfoLongLine* = "I0001"
|
||||||
InfoTrailingWhitespace* = "I0002"
|
InfoTrailingWhitespace* = "I0002"
|
||||||
|
InfoUnrecognizedFlavor* = "I0003"
|
||||||
WarnUnusedVariable* = "W0001"
|
WarnUnusedVariable* = "W0001"
|
||||||
WarnInconsistentNaming* = "W0002"
|
WarnInconsistentNaming* = "W0002"
|
||||||
WarnMissingDoc* = "W0003"
|
WarnMissingDoc* = "W0003"
|
||||||
|
|||||||
@ -45,8 +45,9 @@ method createTokenizer*(self: ValidatorBase): TokenizerBase {.base, gcsafe.} =
|
|||||||
|
|
||||||
method analyzeTokens*(self: ValidatorBase) {.base, gcsafe.} =
|
method analyzeTokens*(self: ValidatorBase) {.base, gcsafe.} =
|
||||||
## Analyze tokenized output for structural issues (brackets, etc).
|
## Analyze tokenized output for structural issues (brackets, etc).
|
||||||
## Default: check bracket balance.
|
## Default: check bracket balance with tolerant mode support.
|
||||||
let bracketErrors = self.tokenizer.validateBrackets()
|
let tolerantAngleBrackets = self.options.tolerantMode
|
||||||
|
let bracketErrors = self.tokenizer.validateBrackets(tolerantAngleBrackets)
|
||||||
for err in bracketErrors:
|
for err in bracketErrors:
|
||||||
self.result.errors.add(err)
|
self.result.errors.add(err)
|
||||||
|
|
||||||
|
|||||||
@ -45,7 +45,7 @@ method analyzeTokens*(self: NimValidator) =
|
|||||||
if self.options.debugMode:
|
if self.options.debugMode:
|
||||||
debugEnter("NIM_ANALYZE", "Analyzing Nim structure")
|
debugEnter("NIM_ANALYZE", "Analyzing Nim structure")
|
||||||
|
|
||||||
# Get parent's bracket validation
|
# Get parent's bracket validation (tolerant mode will downgrade any stray angle bracket warnings)
|
||||||
procCall ValidatorBase(self).analyzeTokens()
|
procCall ValidatorBase(self).analyzeTokens()
|
||||||
|
|
||||||
# Nim-specific checks
|
# Nim-specific checks
|
||||||
@ -59,12 +59,10 @@ method analyzeTokens*(self: NimValidator) =
|
|||||||
of tkComment, tkDocComment:
|
of tkComment, tkDocComment:
|
||||||
self.result.moduleInfo.comments += 1
|
self.result.moduleInfo.comments += 1
|
||||||
of tkString, tkMultilineString, tkRawString:
|
of tkString, tkMultilineString, tkRawString:
|
||||||
# Check for unclosed strings (already handled in tokenizer)
|
|
||||||
discard
|
discard
|
||||||
of tkKeyword:
|
of tkKeyword:
|
||||||
# Track imports
|
# Track imports
|
||||||
if tok.value in ["import", "include"]:
|
if tok.value in ["import", "include"]:
|
||||||
# Look ahead for imported module name
|
|
||||||
var j = i + 1
|
var j = i + 1
|
||||||
while j < tokens.len and tokens[j].kind in {tkWhitespace, tkNewline}:
|
while j < tokens.len and tokens[j].kind in {tkWhitespace, tkNewline}:
|
||||||
j += 1
|
j += 1
|
||||||
@ -72,7 +70,6 @@ method analyzeTokens*(self: NimValidator) =
|
|||||||
var importInfo = ImportInfo(module: tokens[j].value, position: tokens[j].position)
|
var importInfo = ImportInfo(module: tokens[j].value, position: tokens[j].position)
|
||||||
if tok.value == "from":
|
if tok.value == "from":
|
||||||
importInfo.module = tokens[j].value
|
importInfo.module = tokens[j].value
|
||||||
# Find 'import' keyword after 'from'
|
|
||||||
var k = j + 1
|
var k = j + 1
|
||||||
while k < tokens.len and tokens[k].kind in {tkWhitespace, tkNewline, tkPunctuation}:
|
while k < tokens.len and tokens[k].kind in {tkWhitespace, tkNewline, tkPunctuation}:
|
||||||
k += 1
|
k += 1
|
||||||
@ -83,16 +80,29 @@ method analyzeTokens*(self: NimValidator) =
|
|||||||
if k < tokens.len and tokens[k].kind == tkIdentifier:
|
if k < tokens.len and tokens[k].kind == tkIdentifier:
|
||||||
importInfo.symbols.add(tokens[k].value)
|
importInfo.symbols.add(tokens[k].value)
|
||||||
self.result.moduleInfo.imports.add(importInfo)
|
self.result.moduleInfo.imports.add(importInfo)
|
||||||
of tkIdentifier:
|
elif tok.value in ["proc", "func", "method", "template", "macro", "iterator", "converter"]:
|
||||||
# Check for proc/func/method/template/macro/keyword name
|
# Skip past generic parameters [T] before function name
|
||||||
if i > 0 and tokens[i-1].kind == tkKeyword and
|
var j = i + 1
|
||||||
tokens[i-1].value in ["proc", "func", "method", "template", "macro", "iterator", "converter"]:
|
while j < tokens.len and tokens[j].kind in {tkWhitespace, tkNewline}:
|
||||||
var funcInfo = FunctionInfo(
|
j += 1
|
||||||
name: tok.value,
|
# Skip brackets around generics (e.g., proc foo[T](x: T))
|
||||||
kind: tokens[i-1].value,
|
if j < tokens.len and tokens[j].kind == kOpenBracket:
|
||||||
position: tok.position
|
var bracketDepth = 1
|
||||||
)
|
j += 1
|
||||||
self.result.moduleInfo.functions.add(funcInfo)
|
while j < tokens.len and bracketDepth > 0:
|
||||||
|
if tokens[j].kind == kOpenBracket: bracketDepth += 1
|
||||||
|
elif tokens[j].kind == kCloseBracket: bracketDepth -= 1
|
||||||
|
j += 1
|
||||||
|
while j < tokens.len and tokens[j].kind in {tkWhitespace, tkNewline}:
|
||||||
|
j += 1
|
||||||
|
if j < tokens.len and tokens[j].kind == tkIdentifier:
|
||||||
|
var funcInfo = FunctionInfo(
|
||||||
|
name: tokens[j].value,
|
||||||
|
kind: tok.value,
|
||||||
|
position: tok.position,
|
||||||
|
isAsync: tok.value == "macro" or tok.value == "template"
|
||||||
|
)
|
||||||
|
self.result.moduleInfo.functions.add(funcInfo)
|
||||||
# Check for type definitions
|
# Check for type definitions
|
||||||
elif i > 0 and tokens[i-1].kind == tkKeyword and tokens[i-1].value == "type":
|
elif i > 0 and tokens[i-1].kind == tkKeyword and tokens[i-1].value == "type":
|
||||||
var classInfo = ClassInfo(
|
var classInfo = ClassInfo(
|
||||||
|
|||||||
@ -119,7 +119,6 @@ method tokenize*(self: JavaScriptTokenizer) =
|
|||||||
regex.add(self.advance())
|
regex.add(self.advance())
|
||||||
self.emitToken(tkSpecial, regex, regStart)
|
self.emitToken(tkSpecial, regex, regStart)
|
||||||
foundClosing = true
|
foundClosing = true
|
||||||
foundClosing = true
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
regex.add(rc)
|
regex.add(rc)
|
||||||
@ -145,12 +144,10 @@ method tokenize*(self: JavaScriptTokenizer) =
|
|||||||
content.add(quote)
|
content.add(quote)
|
||||||
self.emitToken(tkString, content, strStart)
|
self.emitToken(tkString, content, strStart)
|
||||||
foundClosing = true
|
foundClosing = true
|
||||||
foundClosing = true
|
|
||||||
break
|
break
|
||||||
elif sc == '\n':
|
elif sc == '\n':
|
||||||
self.recordError(esError, "Newline in string", ErrUnclosedString, strStart)
|
self.recordError(esError, "Newline in string", ErrUnclosedString, strStart)
|
||||||
foundClosing = true
|
foundClosing = true
|
||||||
foundClosing = true
|
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
content.add(sc)
|
content.add(sc)
|
||||||
@ -192,13 +189,17 @@ method tokenize*(self: JavaScriptTokenizer) =
|
|||||||
var op = ""
|
var op = ""
|
||||||
while self.hasMore() and self.peek() in {'+', '-', '*', '/', '%', '^', '~', '|', '&', '<', '>', '=', '!', '?', '.'}:
|
while self.hasMore() and self.peek() in {'+', '-', '*', '/', '%', '^', '~', '|', '&', '<', '>', '=', '!', '?', '.'}:
|
||||||
let nc = self.peek()
|
let nc = self.peek()
|
||||||
# Handle . operator specially (not in multi-char)
|
if nc == '.' and op.len > 0:
|
||||||
if nc == '.' and op.len > 0: break
|
if op == "?":
|
||||||
|
op.add(self.advance())
|
||||||
|
break
|
||||||
op.add(self.advance())
|
op.add(self.advance())
|
||||||
if op == "=" or op == "=>":
|
if op == "=" or op == "=>":
|
||||||
self.emitToken(tkAssignment, op, opStart)
|
self.emitToken(tkAssignment, op, opStart)
|
||||||
elif op == "..." or op == "..":
|
elif op == "..." or op == "..":
|
||||||
self.emitToken(tkOperator, op, opStart)
|
self.emitToken(tkOperator, op, opStart)
|
||||||
|
elif op == "??":
|
||||||
|
self.emitToken(tkOperator, op, opStart)
|
||||||
else:
|
else:
|
||||||
self.emitToken(tkOperator, op, opStart)
|
self.emitToken(tkOperator, op, opStart)
|
||||||
|
|
||||||
|
|||||||
@ -316,9 +316,9 @@ method tokenize*(self: NimTokenizer) =
|
|||||||
elif op == ":=" or op == "+=" or op == "-=" or op == "*=" or op == "/=" or op == "%=":
|
elif op == ":=" or op == "+=" or op == "-=" or op == "*=" or op == "/=" or op == "%=":
|
||||||
self.emitToken(tkAssignment, op, opStart)
|
self.emitToken(tkAssignment, op, opStart)
|
||||||
elif op == "<":
|
elif op == "<":
|
||||||
self.emitBracketToken(kAngleOpen, op, opStart)
|
self.emitToken(tkOperator, op, opStart)
|
||||||
elif op == ">":
|
elif op == ">":
|
||||||
self.emitBracketToken(kAngleClose, op, opStart)
|
self.emitToken(tkOperator, op, opStart)
|
||||||
else:
|
else:
|
||||||
self.emitToken(tkOperator, op, opStart)
|
self.emitToken(tkOperator, op, opStart)
|
||||||
|
|
||||||
|
|||||||
@ -134,7 +134,6 @@ method tokenize*(self: PHPTokenizer) =
|
|||||||
content.add(quote)
|
content.add(quote)
|
||||||
self.emitToken(tkString, content, strStart)
|
self.emitToken(tkString, content, strStart)
|
||||||
foundClosing = true
|
foundClosing = true
|
||||||
foundClosing = true
|
|
||||||
break
|
break
|
||||||
elif sc == '\n':
|
elif sc == '\n':
|
||||||
self.recordError(esError, "Unclosed string at end of line", ErrUnclosedString, strStart)
|
self.recordError(esError, "Unclosed string at end of line", ErrUnclosedString, strStart)
|
||||||
|
|||||||
@ -24,9 +24,9 @@ type
|
|||||||
const
|
const
|
||||||
pythonKeywords* = @[
|
pythonKeywords* = @[
|
||||||
"False", "None", "True", "and", "as", "assert", "async", "await",
|
"False", "None", "True", "and", "as", "assert", "async", "await",
|
||||||
"break", "class", "continue", "def", "del", "elif", "else", "except",
|
"break", "case", "class", "continue", "def", "del", "elif", "else", "except",
|
||||||
"finally", "for", "from", "global", "if", "import", "in", "is",
|
"finally", "for", "from", "global", "if", "import", "in", "is",
|
||||||
"lambda", "nonlocal", "not", "or", "pass", "raise", "return",
|
"lambda", "match", "nonlocal", "not", "or", "pass", "raise", "return",
|
||||||
"try", "while", "with", "yield"
|
"try", "while", "with", "yield"
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -158,7 +158,7 @@ method tokenize*(self: PythonTokenizer) =
|
|||||||
# f-strings
|
# f-strings
|
||||||
elif c == 'f' or c == 'F':
|
elif c == 'f' or c == 'F':
|
||||||
let peekStr = self.peekString(2)
|
let peekStr = self.peekString(2)
|
||||||
if peekStr.len >= 2 and peekStr[1] == '"' or peekStr[1] == '\'':
|
if peekStr.len >= 2 and (peekStr[1] == '"' or peekStr[1] == '\''):
|
||||||
let fStart = self.currentPos()
|
let fStart = self.currentPos()
|
||||||
discard self.advance() # f
|
discard self.advance() # f
|
||||||
let quote = self.advance() # " or '
|
let quote = self.advance() # " or '
|
||||||
@ -257,7 +257,7 @@ method tokenize*(self: PythonTokenizer) =
|
|||||||
self.emitToken(tkNumber, num, numStart)
|
self.emitToken(tkNumber, num, numStart)
|
||||||
|
|
||||||
# Identifiers and keywords
|
# Identifiers and keywords
|
||||||
elif isAlpha(c):
|
elif isAlpha(c) or c == '_':
|
||||||
let idStart = self.currentPos()
|
let idStart = self.currentPos()
|
||||||
var ident = ""
|
var ident = ""
|
||||||
while self.hasMore() and (isAlphaNum(self.peek()) or self.peek() == '_'):
|
while self.hasMore() and (isAlphaNum(self.peek()) or self.peek() == '_'):
|
||||||
|
|||||||
3
tests/fixtures/bash/valid.sh
vendored
3
tests/fixtures/bash/valid.sh
vendored
@ -1,2 +1,5 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
echo "Hello, world!"
|
echo "Hello, world!"
|
||||||
|
if [[ "$a" = "$b" ]]; then
|
||||||
|
echo "a equals b"
|
||||||
|
fi
|
||||||
|
|||||||
2
tests/fixtures/javascript/valid.js
vendored
2
tests/fixtures/javascript/valid.js
vendored
@ -1,3 +1,5 @@
|
|||||||
function greet(name) {
|
function greet(name) {
|
||||||
return `Hello, ${name}!`;
|
return `Hello, ${name}!`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const x = obj?.prop ?? 'default';
|
||||||
14
tests/fixtures/nim/with_generics.nim
vendored
Normal file
14
tests/fixtures/nim/with_generics.nim
vendored
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
## Nim code with generics - should validate cleanly (no false "Unclosed '<'")
|
||||||
|
proc jarray[T](node: T): T =
|
||||||
|
result = node
|
||||||
|
|
||||||
|
proc pair[A, B](a: A, b: B): (A, B) =
|
||||||
|
(a, b)
|
||||||
|
|
||||||
|
proc findIf[T](items: seq[T], pred: proc(x: T): bool): Option[T] =
|
||||||
|
for item in items:
|
||||||
|
if pred(item):
|
||||||
|
return some(item)
|
||||||
|
|
||||||
|
proc compare[T](a, b: T): bool =
|
||||||
|
a < b and b > a
|
||||||
9
tests/fixtures/python/valid.py
vendored
9
tests/fixtures/python/valid.py
vendored
@ -1,2 +1,11 @@
|
|||||||
def hello(name: str) -> str:
|
def hello(name: str) -> str:
|
||||||
return f"Hello, {name}!"
|
return f"Hello, {name}!"
|
||||||
|
|
||||||
|
def process(value):
|
||||||
|
match value:
|
||||||
|
case 1:
|
||||||
|
return 'one'
|
||||||
|
case _:
|
||||||
|
return 'other'
|
||||||
|
|
||||||
|
_private_var = 42
|
||||||
@ -144,3 +144,18 @@ suite "JavaScript Exhaustive Tests":
|
|||||||
let src = "const big = 9007199254740991n;\n"
|
let src = "const big = 9007199254740991n;\n"
|
||||||
let result = validateSource(src, flavor = lfJavaScript)
|
let result = validateSource(src, flavor = lfJavaScript)
|
||||||
check result.errors.len >= 0
|
check result.errors.len >= 0
|
||||||
|
|
||||||
|
test "nullish coalescing operator ?? working":
|
||||||
|
let src = "const x = a ?? b ?? c;\n"
|
||||||
|
let result = validateSource(src, flavor = lfJavaScript)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "optional chaining ?. operator working":
|
||||||
|
let src = "const x = obj?.prop?.nested;\n"
|
||||||
|
let result = validateSource(src, flavor = lfJavaScript)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "combined ?. and ?? operators":
|
||||||
|
let src = "const x = obj?.prop ?? 'default';\n"
|
||||||
|
let result = validateSource(src, flavor = lfJavaScript)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|||||||
@ -164,3 +164,38 @@ suite "Nim Exhaustive Tests":
|
|||||||
let src = "proc test() =\n ##[ this is a doc comment\n that never ends\n discard\n"
|
let src = "proc test() =\n ##[ this is a doc comment\n that never ends\n discard\n"
|
||||||
let result = validateSource(src, flavor = lfNim)
|
let result = validateSource(src, flavor = lfNim)
|
||||||
check result.errors.len >= 0 # May not detect ##[ unclosed
|
check result.errors.len >= 0 # May not detect ##[ unclosed
|
||||||
|
|
||||||
|
test "generic proc with type parameter":
|
||||||
|
let src = "proc jarray[T](node: T): T =\n result = node\n"
|
||||||
|
let result = validateSource(src, flavor = lfNim)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "generic proc with multiple type params and comparisons":
|
||||||
|
let src = "proc compare[T](a, b: T): bool =\n a < b and b > a\n"
|
||||||
|
let result = validateSource(src, flavor = lfNim)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "generic proc with Option[T]":
|
||||||
|
let src = "proc findIf[T](items: seq[T], pred: proc(x: T): bool): Option[T] =\n for item in items:\n if pred(item):\n return some(item)\n"
|
||||||
|
let result = validateSource(src, flavor = lfNim)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "template with generics":
|
||||||
|
let src = "template wrap[T](x: T): T = x\n"
|
||||||
|
let result = validateSource(src, flavor = lfNim)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "chained comparisons with < >":
|
||||||
|
let src = "let x = 1 < 2 and 3 > 2 and 4 >= 1 and 5 <= 10\n"
|
||||||
|
let result = validateSource(src, flavor = lfNim)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "valid .nim file with generics":
|
||||||
|
let result = validateFile("tests/fixtures/nim/with_generics.nim")
|
||||||
|
check result.errors.len == 0
|
||||||
|
|
||||||
|
test "tolerant mode with false angle bracket":
|
||||||
|
let opts = newValidationOptions(tolerantMode = true)
|
||||||
|
let src = "proc jarray[T](node: T): T =\n result = node\n"
|
||||||
|
let result = validateSource(src, flavor = lfNim, options = opts)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|||||||
@ -149,3 +149,8 @@ suite "Python Exhaustive Tests":
|
|||||||
let src = "s = \"line1\\\nline2\\\nline3\"\n"
|
let src = "s = \"line1\\\nline2\\\nline3\"\n"
|
||||||
let result = validateSource(src, flavor = lfPython)
|
let result = validateSource(src, flavor = lfPython)
|
||||||
check result.errors.len >= 0
|
check result.errors.len >= 0
|
||||||
|
|
||||||
|
test "underscore private convention":
|
||||||
|
let src = "_private = 42\n"
|
||||||
|
let result = validateSource(src, flavor = lfPython)
|
||||||
|
check result.errors.len == 0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user