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.nim176
1 files changed, 176 insertions, 0 deletions
diff --git a/compiler/linter.nim b/compiler/linter.nim
new file mode 100644
index 000000000..7c9cdec83
--- /dev/null
+++ b/compiler/linter.nim
@@ -0,0 +1,176 @@
+#
+#
+#           The Nim Compiler
+#        (c) Copyright 2015 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    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.
+
+import
+  strutils, os, intsets, strtabs
+
+import options, ast, astalgo, msgs, semdata, ropes, idents,
+  lineinfos
+
+const
+  Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
+
+proc identLen*(line: string, start: int): int =
+  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 =
+  for x in a:
+    if s.startsWith(x): return true
+
+proc beautifyName(s: string, k: TSymKind): string =
+  # minimal set of rules here for transition:
+  # GC_ is allowed
+
+  let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
+  if allUpper and k in {skConst, skEnumField, skType}: return s
+  result = newStringOfCap(s.len)
+  var i = 0
+  case k
+  of skType, skGenericParam:
+    # 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"
+             ]:
+      result.add s[i]
+    else:
+      result.add toUpperAscii(s[i])
+  of skConst, skEnumField:
+    # for 'const' we keep how it's spelt; either upper case or lower case:
+    result.add s[0]
+  else:
+    # as a special rule, don't transform 'L' to 'l'
+    if s.len == 1 and s[0] == 'L': result.add 'L'
+    elif '_' in s: result.add(s[i])
+    else: result.add toLowerAscii(s[0])
+  inc i
+  while i < s.len:
+    if s[i] == '_':
+      if 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
+        result.add s[i]
+      else:
+        inc i
+        result.add toUpperAscii(s[i])
+    elif allUpper:
+      result.add toLowerAscii(s[i])
+    else:
+      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 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 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)
+
+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)