summary refs log tree commit diff stats
path: root/compiler/linter.nim
diff options
context:
space:
mode:
Diffstat (limited to 'compiler/linter.nim')
-rw-r--r--compiler/linter.nim186
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)