nim styleChecker: implemented all the missing features (bugfix)

This commit is contained in:
Araq 2019-07-10 00:27:23 +02:00
commit bd689849f2
7 changed files with 111 additions and 51 deletions

View file

@ -878,6 +878,7 @@ proc getSymbol(L: var TLexer, tok: var TToken) =
var h: Hash = 0 var h: Hash = 0
var pos = L.bufpos var pos = L.bufpos
tokenBegin(tok, pos) tokenBegin(tok, pos)
var suspicious = false
while true: while true:
var c = L.buf[pos] var c = L.buf[pos]
case c case c
@ -888,21 +889,26 @@ proc getSymbol(L: var TLexer, tok: var TToken) =
c = chr(ord(c) + (ord('a') - ord('A'))) # toLower() c = chr(ord(c) + (ord('a') - ord('A'))) # toLower()
h = h !& ord(c) h = h !& ord(c)
inc(pos) inc(pos)
suspicious = true
of '_': of '_':
if L.buf[pos+1] notin SymChars: if L.buf[pos+1] notin SymChars:
lexMessage(L, errGenerated, "invalid token: trailing underscore") lexMessage(L, errGenerated, "invalid token: trailing underscore")
break break
inc(pos) inc(pos)
suspicious = true
else: break else: break
tokenEnd(tok, pos-1) tokenEnd(tok, pos-1)
h = !$h h = !$h
tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h) tok.ident = L.cache.getIdent(addr(L.buf[L.bufpos]), pos - L.bufpos, h)
L.bufpos = pos
if (tok.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or if (tok.ident.id < ord(tokKeywordLow) - ord(tkSymbol)) or
(tok.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)): (tok.ident.id > ord(tokKeywordHigh) - ord(tkSymbol)):
tok.tokType = tkSymbol tok.tokType = tkSymbol
else: else:
tok.tokType = TTokType(tok.ident.id + ord(tkSymbol)) tok.tokType = TTokType(tok.ident.id + ord(tkSymbol))
if suspicious and {optStyleHint, optStyleError} * L.config.globalOptions != {}:
lintReport(L.config, getLineInfo(L), tok.ident.s.normalize, tok.ident.s)
L.bufpos = pos
proc endOperator(L: var TLexer, tok: var TToken, pos: int, proc endOperator(L: var TLexer, tok: var TToken, pos: int,
hash: Hash) {.inline.} = hash: Hash) {.inline.} =

View file

@ -108,7 +108,7 @@ const
hintPath: "added path: '$1'", hintPath: "added path: '$1'",
hintConditionAlwaysTrue: "condition is always true: '$1'", hintConditionAlwaysTrue: "condition is always true: '$1'",
hintConditionAlwaysFalse: "condition is always false: '$1'", hintConditionAlwaysFalse: "condition is always false: '$1'",
hintName: "name should be: '$1'", hintName: "$1",
hintPattern: "$1", hintPattern: "$1",
hintExecuting: "$1", hintExecuting: "$1",
hintLinking: "", hintLinking: "",

View file

@ -25,17 +25,12 @@ proc identLen*(line: string, start: int): int =
type type
StyleCheck* {.pure.} = enum None, Warn, Auto StyleCheck* {.pure.} = enum None, Warn, Auto
var
gOverWrite* = true
gStyleCheck*: StyleCheck
gCheckExtern*, gOnlyMainfile*: bool
proc overwriteFiles*(conf: ConfigRef) = proc overwriteFiles*(conf: ConfigRef) =
let doStrip = options.getConfigVar(conf, "pretty.strip").normalize == "on" let doStrip = options.getConfigVar(conf, "pretty.strip").normalize == "on"
for i in 0 .. high(conf.m.fileInfos): for i in 0 .. high(conf.m.fileInfos):
if conf.m.fileInfos[i].dirty and if conf.m.fileInfos[i].dirty and
(not gOnlyMainfile or FileIndex(i) == conf.projectMainIdx): (FileIndex(i) == conf.projectMainIdx):
let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath let newFile = if false: conf.m.fileInfos[i].fullpath
else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim") else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim")
try: try:
var f = open(newFile.string, fmWrite) var f = open(newFile.string, fmWrite)
@ -69,7 +64,7 @@ proc beautifyName(s: string, k: TSymKind): string =
"pointer", "float", "csize", "cdouble", "cchar", "cschar", "pointer", "float", "csize", "cdouble", "cchar", "cschar",
"cshort", "cu", "nil", "typedesc", "auto", "any", "cshort", "cu", "nil", "typedesc", "auto", "any",
"range", "openarray", "varargs", "set", "cfloat", "ref", "ptr", "range", "openarray", "varargs", "set", "cfloat", "ref", "ptr",
"untyped", "typed", "static", "sink", "lent", "type"]: "untyped", "typed", "static", "sink", "lent", "type", "owned"]:
result.add s[i] result.add s[i]
else: else:
result.add toUpperAscii(s[i]) result.add toUpperAscii(s[i])
@ -98,46 +93,23 @@ proc beautifyName(s: string, k: TSymKind): string =
result.add s[i] result.add s[i]
inc i inc i
proc differ*(line: string, a, b: int, x: string): bool = proc differ*(line: string, a, b: int, x: string): string =
let y = line[a..b] let y = line[a..b]
result = cmpIgnoreStyle(y, x) == 0 and y != x if cmpIgnoreStyle(y, x) == 0 and y != x:
result = y
proc replaceInFile(conf: ConfigRef; info: TLineInfo; newName: string) =
let line = conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1]
var first = min(info.col.int, line.len)
if first < 0: return
#inc first, skipIgnoreCase(line, "proc ", first)
while first > 0 and line[first-1] in Letters: dec first
if first < 0: return
if line[first] == '`': inc first
let last = first+identLen(line, first)-1
if differ(line, first, last, newName):
# last-first+1 != newName.len or
var x = line.substr(0, first-1) & newName & line.substr(last+1)
system.shallowCopy(conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1], x)
conf.m.fileInfos[info.fileIndex.int].dirty = true
proc lintReport(conf: ConfigRef; info: TLineInfo, beau: string) =
if optStyleError in conf.globalOptions:
localError(conf, info, "name should be: '$1'" % beau)
else: else:
message(conf, info, hintName, beau) result = ""
proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) = proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
let beau = beautifyName(s, k) let beau = beautifyName(s, k)
if s != beau: if s != beau:
if gStyleCheck == StyleCheck.Auto: lintReport(conf, info, beau, s)
sym.name = getIdent(cache, beau)
replaceInFile(conf, info, beau)
else:
lintReport(conf, info, beau)
proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) = proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) =
# operators stay as they are: # operators stay as they are:
if k in {skResult, skTemp} or s.name.s[0] notin Letters: return if k in {skResult, skTemp} or s.name.s[0] notin Letters: return
if k in {skType, skGenericParam} and sfAnon in s.flags: return if k in {skType, skGenericParam} and sfAnon in s.flags: return
if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern: if {sfImportc, sfExportc} * s.flags == {}:
checkStyle(conf, cache, info, s.name.s, k, s) checkStyle(conf, cache, info, s.name.s, k, s)
proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
@ -148,7 +120,7 @@ proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
if {sfImportc, sfExportc} * s.flags != {}: return if {sfImportc, sfExportc} * s.flags != {}: return
let beau = beautifyName(s.name.s, k) let beau = beautifyName(s.name.s, k)
if s.name.s != beau: if s.name.s != beau:
lintReport(conf, info, beau) lintReport(conf, info, beau, s.name.s)
template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
if {optStyleHint, optStyleError} * conf.globalOptions != {}: if {optStyleHint, optStyleError} * conf.globalOptions != {}:
@ -161,19 +133,57 @@ template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym) =
template styleCheckDef*(conf: ConfigRef; s: PSym) = template styleCheckDef*(conf: ConfigRef; s: PSym) =
styleCheckDef(conf, s.info, s, s.kind) styleCheckDef(conf, s.info, s, s.kind)
proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) = proc differs(conf: ConfigRef; info: TLineInfo; newName: string): string =
let line = sourceLine(conf, info)
var first = min(info.col.int, line.len)
if first < 0: return
#inc first, skipIgnoreCase(line, "proc ", first)
while first > 0 and line[first-1] in Letters: dec first
if first < 0: return
if line[first] == '`': inc first
let last = first+identLen(line, first)-1
result = differ(line, first, last, newName)
proc styleCheckUse*(conf: ConfigRef; info: TLineInfo; s: PSym) =
if info.fileIndex.int < 0: return if info.fileIndex.int < 0: return
# we simply convert it to what it looks like in the definition # we simply convert it to what it looks like in the definition
# for consistency # for consistency
# operators stay as they are: # operators stay as they are:
if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters: if s.kind == skTemp or s.name.s[0] notin Letters or sfAnon in s.flags:
return return
if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
let newName = s.name.s
replaceInFile(conf, info, newName) let newName = s.name.s
#if newName == "File": writeStackTrace() let oldName = differs(conf, info, newName)
if oldName.len > 0:
lintReport(conf, info, newName, oldName)
proc checkPragmaUse*(conf: ConfigRef; info: TLineInfo; pragmaName: string) =
const inMixedCase = [
"noSideEffect", "importCompilerProc", "incompleteStruct", "requiresInit",
"sideEffect", "compilerProc", "lineDir", "stackTrace", "lineTrace",
"rangeChecks", "boundChecks",
"overflowChecks", "nilChecks",
"floatChecks", "nanChecks", "infChecks", "moveChecks",
"nonReloadable", "executeOnReload",
"deadCodeElim",
"compileTime", "noInit", "fieldChecks",
"linearScanEnd",
"computedGoto", "injectStmt",
"asmNoStackframe", "implicitStatic", "codegenDecl", "liftLocals"
]
let name = pragmaName.normalize
for x in inMixedCase:
if x.normalize == name:
if pragmaName != x:
lintReport(conf, info, x, pragmaName)
return
let wanted = pragmaName.toLowerAscii.replace("_", "")
if pragmaName != wanted:
lintReport(conf, info, wanted, pragmaName)
template styleCheckUse*(info: TLineInfo; s: PSym) = template styleCheckUse*(info: TLineInfo; s: PSym) =
when defined(nimfix): when defined(nimfix):

View file

@ -588,3 +588,10 @@ proc listHints*(conf: ConfigRef) =
if hint in conf.notes: "x" else: " ", if hint in conf.notes: "x" else: " ",
lineinfos.HintsToStr[ord(hint) - ord(hintMin)] lineinfos.HintsToStr[ord(hint) - ord(hintMin)]
]) ])
proc lintReport*(conf: ConfigRef; info: TLineInfo, beau, got: string) =
let m = "'$2' should be: '$1'" % [beau, got]
if optStyleError in conf.globalOptions:
localError(conf, info, m)
else:
message(conf, info, hintName, m)

View file

@ -12,7 +12,7 @@
import import
os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer, os, platform, condsyms, ast, astalgo, idents, semdata, msgs, renderer,
wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees, wordrecg, ropes, options, strutils, extccomp, math, magicsys, trees,
types, lookups, lineinfos, pathutils types, lookups, lineinfos, pathutils, linter
const const
FirstCallConv* = wNimcall FirstCallConv* = wNimcall
@ -610,9 +610,9 @@ proc pragmaLine(c: PContext, n: PNode) =
proc processPragma(c: PContext, n: PNode, i: int) = proc processPragma(c: PContext, n: PNode, i: int) =
let it = n[i] let it = n[i]
if it.kind notin nkPragmaCallKinds and it.len == 2: invalidPragma(c, n) if it.kind notin nkPragmaCallKinds and it.safeLen == 2: invalidPragma(c, n)
elif it[0].kind != nkIdent: invalidPragma(c, n) elif it.safeLen != 2 or it[0].kind != nkIdent or it[1].kind != nkIdent:
elif it[1].kind != nkIdent: invalidPragma(c, n) invalidPragma(c, n)
var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options) var userPragma = newSym(skTemplate, it[1].ident, nil, it.info, c.config.options)
userPragma.ast = newNode(nkPragma, n.info, n.sons[i+1..^1]) userPragma.ast = newNode(nkPragma, n.info, n.sons[i+1..^1])
@ -762,6 +762,9 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
let ident = considerQuotedIdent(c, key) let ident = considerQuotedIdent(c, key)
var userPragma = strTableGet(c.userPragmas, ident) var userPragma = strTableGet(c.userPragmas, ident)
if userPragma != nil: if userPragma != nil:
if {optStyleHint, optStyleError} * c.config.globalOptions != {}:
styleCheckUse(c.config, key.info, userPragma)
# number of pragmas increase/decrease with user pragma expansion # number of pragmas increase/decrease with user pragma expansion
inc c.instCounter inc c.instCounter
if c.instCounter > 100: if c.instCounter > 100:
@ -774,6 +777,8 @@ proc singlePragma(c: PContext, sym: PSym, n: PNode, i: var int,
else: else:
let k = whichKeyword(ident) let k = whichKeyword(ident)
if k in validPragmas: if k in validPragmas:
if {optStyleHint, optStyleError} * c.config.globalOptions != {}:
checkPragmaUse(c.config, key.info, ident.s)
case k case k
of wExportc: of wExportc:
makeExternExport(c, sym, getOptionalStr(c, it, "$1"), it.info) makeExternExport(c, sym, getOptionalStr(c, it, "$1"), it.info)

View file

@ -32,7 +32,7 @@
# included from sigmatch.nim # included from sigmatch.nim
import algorithm, prefixmatches, lineinfos, pathutils, parseutils import algorithm, prefixmatches, lineinfos, pathutils, parseutils, linter
from wordrecg import wDeprecated, wError, wAddr, wYield, specialWords from wordrecg import wDeprecated, wError, wAddr, wYield, specialWords
when defined(nimsuggest): when defined(nimsuggest):
@ -539,6 +539,8 @@ proc markUsed(conf: ConfigRef; info: TLineInfo; s: PSym; usageSym: var PSym) =
if sfError in s.flags: userError(conf, info, s) if sfError in s.flags: userError(conf, info, s)
when defined(nimsuggest): when defined(nimsuggest):
suggestSym(conf, info, s, usageSym, false) suggestSym(conf, info, s, usageSym, false)
if {optStyleHint, optStyleError} * conf.globalOptions != {}:
styleCheckUse(conf, info, s)
proc safeSemExpr*(c: PContext, n: PNode): PNode = proc safeSemExpr*(c: PContext, n: PNode): PNode =
# use only for idetools support! # use only for idetools support!

30
tests/tools/tlinter.nim Normal file
View file

@ -0,0 +1,30 @@
discard """
cmd: '''nim c --styleCheck:hint $file'''
nimout: '''
tlinter.nim(21, 14) Hint: 'nosideeffect' should be: 'noSideEffect' [Name]
tlinter.nim(21, 28) Hint: 'myown' should be: 'myOwn' [Name]
tlinter.nim(21, 35) Hint: 'inLine' should be: 'inline' [Name]
tlinter.nim(25, 1) Hint: 'tyPE' should be: 'type' [Name]
tlinter.nim(23, 1) Hint: 'foO' should be: 'foo' [Name]
tlinter.nim(27, 14) Hint: 'Foo_bar' should be: 'FooBar' [Name]
tlinter.nim(29, 6) Hint: 'someVAR' should be: 'someVar' [Name]
'''
action: "compile"
"""
{.pragma: myOwn.}
proc foo() {.nosideeffect, myown, inLine.} = debugEcho "hi"
foO()
tyPE FooBar = string
var someVar: Foo_bar = "a"
echo someVAR