diff options
Diffstat (limited to 'compiler/linter.nim')
-rw-r--r-- | compiler/linter.nim | 186 |
1 files changed, 84 insertions, 102 deletions
diff --git a/compiler/linter.nim b/compiler/linter.nim index 7c9cdec83..a80c377e9 100644 --- a/compiler/linter.nim +++ b/compiler/linter.nim @@ -7,53 +7,24 @@ # distribution, for details about the copyright. # -## This module implements the code "prettifier". This is part of the toolchain -## to convert Nim code into a consistent style. +## This module implements the style checker. -import - strutils, os, intsets, strtabs +import std/strutils +from std/sugar import dup -import options, ast, astalgo, msgs, semdata, ropes, idents, - lineinfos +import options, ast, msgs, idents, lineinfos, wordrecg, astmsgs, semdata, packages +export packages const Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'} proc identLen*(line: string, start: int): int = + result = 0 while start+result < line.len and line[start+result] in Letters: inc result -when false: - import prettybase - -type - StyleCheck* {.pure.} = enum None, Warn, Auto - -var - gOverWrite* = true - gStyleCheck*: StyleCheck - gCheckExtern*, gOnlyMainfile*: bool - -proc overwriteFiles*(conf: ConfigRef) = - let doStrip = options.getConfigVar(conf, "pretty.strip").normalize == "on" - for i in 0 .. high(conf.m.fileInfos): - if conf.m.fileInfos[i].dirty and - (not gOnlyMainfile or FileIndex(i) == conf.projectMainIdx): - let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath - else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim") - try: - var f = open(newFile, fmWrite) - for line in conf.m.fileInfos[i].lines: - if doStrip: - f.write line.strip(leading = false, trailing = true) - else: - f.write line - f.write(conf.m.fileInfos[i], "\L") - f.close - except IOError: - rawMessage(conf, errGenerated, "cannot open file: " & newFile) - proc `=~`(s: string, a: openArray[string]): bool = + result = false for x in a: if s.startsWith(x): return true @@ -70,10 +41,10 @@ proc beautifyName(s: string, k: TSymKind): string = # Types should start with a capital unless builtins like 'int' etc.: if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string", "char", "byte", "bool", "openArray", "seq", "array", "void", - "pointer", "float", "csize", "cdouble", "cchar", "cschar", - "cshort", "cu", "nil", "expr", "stmt", "typedesc", "auto", "any", - "range", "openarray", "varargs", "set", "cfloat" - ]: + "pointer", "float", "csize", "csize_t", "cdouble", "cchar", "cschar", + "cshort", "cu", "nil", "typedesc", "auto", "any", + "range", "openarray", "varargs", "set", "cfloat", "ref", "ptr", + "untyped", "typed", "static", "sink", "lent", "type", "owned", "iterable"]: result.add s[i] else: result.add toUpperAscii(s[i]) @@ -88,7 +59,9 @@ proc beautifyName(s: string, k: TSymKind): string = inc i while i < s.len: if s[i] == '_': - if i > 0 and s[i-1] in {'A'..'Z'}: + if i+1 >= s.len: + discard "trailing underscores should be stripped off" + elif i > 0 and s[i-1] in {'A'..'Z'}: # don't skip '_' as it's essential for e.g. 'GC_disable' result.add('_') inc i @@ -102,75 +75,84 @@ proc beautifyName(s: string, k: TSymKind): string = result.add s[i] inc i -proc differ*(line: string, a, b: int, x: string): bool = - let y = line[a..b] - result = cmpIgnoreStyle(y, x) == 0 and y != x +proc differ*(line: string, a, b: int, x: string): string = + proc substrEq(s: string, pos, last: int, substr: string): bool = + result = true + for i in 0..<substr.len: + if pos+i > last or s[pos+i] != substr[i]: return false + + result = "" + if not substrEq(line, a, b, x): + let y = line[a..b] + if cmpIgnoreStyle(y, x) == 0: + result = y -proc replaceInFile(conf: ConfigRef; info: TLineInfo; newName: string) = - let line = conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1] +proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = + let beau = beautifyName(s.name.s, k) + if s.name.s != beau: + lintReport(conf, info, beau, s.name.s) + +template styleCheckDef*(ctx: PContext; info: TLineInfo; sym: PSym; k: TSymKind) = + ## Check symbol definitions adhere to NEP1 style rules. + if optStyleCheck in ctx.config.options and # ignore if styleChecks are off + {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # check only if hint/error is enabled + hintName in ctx.config.notes and # ignore if name checks are not requested + ctx.config.belongsToProjectPackage(sym) and # ignore foreign packages + optStyleUsages notin ctx.config.globalOptions and # ignore if requested to only check name usage + sym.kind != skResult and # ignore `result` + sym.kind != skTemp and # ignore temporary variables created by the compiler + sym.name.s[0] in Letters and # ignore operators TODO: what about unicode symbols??? + k notin {skType, skGenericParam} and # ignore types and generic params + (sym.typ == nil or sym.typ.kind != tyTypeDesc) and # ignore `typedesc` + {sfImportc, sfExportc} * sym.flags == {} and # ignore FFI + sfAnon notin sym.flags: # ignore if created by compiler + nep1CheckDefImpl(ctx.config, info, sym, k) + +template styleCheckDef*(ctx: PContext; info: TLineInfo; s: PSym) = + ## Check symbol definitions adhere to NEP1 style rules. + styleCheckDef(ctx, info, s, s.kind) + +template styleCheckDef*(ctx: PContext; s: PSym) = + ## Check symbol definitions adhere to NEP1 style rules. + styleCheckDef(ctx, s.info, s, s.kind) + +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 + if first+1 < line.len and 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 checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) = - let beau = beautifyName(s, k) - if s != beau: - if gStyleCheck == StyleCheck.Auto: - sym.name = getIdent(cache, beau) - replaceInFile(conf, info, beau) - else: - message(conf, info, hintName, beau) - -proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) = - # operators stay as they are: - 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 {sfImportc, sfExportc} * s.flags == {} or gCheckExtern: - checkStyle(conf, cache, info, s.name.s, k, s) - -proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = - # operators stay as they are: - if k in {skResult, skTemp} or s.name.s[0] notin Letters: return - if k in {skType, skGenericParam} and sfAnon in s.flags: return - let beau = beautifyName(s.name.s, k) - if s.name.s != beau: - message(conf, info, hintName, beau) - -template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) = - if optCheckNep1 in conf.globalOptions: - nep1CheckDefImpl(conf, info, s, k) - when defined(nimfix): - if gStyleCheck != StyleCheck.None: styleCheckDefImpl(conf, cache, info, s, k) - -template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym) = - styleCheckDef(conf, info, s, s.kind) -template styleCheckDef*(conf: ConfigRef; s: PSym) = - styleCheckDef(conf, s.info, s, s.kind) + result = differ(line, first, last, newName) proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) = - if info.fileIndex.int < 0: return - # we simply convert it to what it looks like in the definition - # for consistency - - # operators stay as they are: - if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters: - return - if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return let newName = s.name.s - - replaceInFile(conf, info, newName) - #if newName == "File": writeStackTrace() - -template styleCheckUse*(info: TLineInfo; s: PSym) = - when defined(nimfix): - if gStyleCheck != StyleCheck.None: styleCheckUseImpl(conf, info, s) + let badName = differs(conf, info, newName) + if badName.len > 0: + lintReport(conf, info, newName, badName, "".dup(addDeclaredLoc(conf, s))) + +template styleCheckUse*(ctx: PContext; info: TLineInfo; sym: PSym) = + ## Check symbol uses match their definition's style. + if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off + hintName in ctx.config.notes and # ignore if name checks are not requested + ctx.config.belongsToProjectPackage(sym) and # ignore foreign packages + sym.kind != skTemp and # ignore temporary variables created by the compiler + sym.name.s[0] in Letters and # ignore operators TODO: what about unicode symbols??? + sfAnon notin sym.flags: # ignore temporary variables created by the compiler + styleCheckUseImpl(ctx.config, info, sym) + +proc checkPragmaUseImpl(conf: ConfigRef; info: TLineInfo; w: TSpecialWord; pragmaName: string) = + let wanted = $w + if pragmaName != wanted: + lintReport(conf, info, wanted, pragmaName) + +template checkPragmaUse*(ctx: PContext; info: TLineInfo; w: TSpecialWord; pragmaName: string, sym: PSym) = + ## Check builtin pragma uses match their definition's style. + ## Note: This only applies to builtin pragmas, not user pragmas. + if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off + hintName in ctx.config.notes and # ignore if name checks are not requested + (sym != nil and ctx.config.belongsToProjectPackage(sym)): # ignore foreign packages + checkPragmaUseImpl(ctx.config, info, w, pragmaName) |