linter.nim 6.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. #
  2. #
  3. # The Nim Compiler
  4. # (c) Copyright 2015 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements the code "prettifier". This is part of the toolchain
  10. ## to convert Nim code into a consistent style.
  11. import
  12. strutils, os, intsets, strtabs
  13. import options, ast, astalgo, msgs, semdata, ropes, idents,
  14. lineinfos, pathutils
  15. const
  16. Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
  17. proc identLen*(line: string, start: int): int =
  18. while start+result < line.len and line[start+result] in Letters:
  19. inc result
  20. when false:
  21. import prettybase
  22. type
  23. StyleCheck* {.pure.} = enum None, Warn, Auto
  24. var
  25. gOverWrite* = true
  26. gStyleCheck*: StyleCheck
  27. gCheckExtern*, gOnlyMainfile*: bool
  28. proc overwriteFiles*(conf: ConfigRef) =
  29. let doStrip = options.getConfigVar(conf, "pretty.strip").normalize == "on"
  30. for i in 0 .. high(conf.m.fileInfos):
  31. if conf.m.fileInfos[i].dirty and
  32. (not gOnlyMainfile or FileIndex(i) == conf.projectMainIdx):
  33. let newFile = if gOverWrite: conf.m.fileInfos[i].fullpath
  34. else: conf.m.fileInfos[i].fullpath.changeFileExt(".pretty.nim")
  35. try:
  36. var f = open(newFile.string, fmWrite)
  37. for line in conf.m.fileInfos[i].lines:
  38. if doStrip:
  39. f.write line.strip(leading = false, trailing = true)
  40. else:
  41. f.write line
  42. f.write(conf.m.fileInfos[i], "\L")
  43. f.close
  44. except IOError:
  45. rawMessage(conf, errGenerated, "cannot open file: " & newFile.string)
  46. proc `=~`(s: string, a: openArray[string]): bool =
  47. for x in a:
  48. if s.startsWith(x): return true
  49. proc beautifyName(s: string, k: TSymKind): string =
  50. # minimal set of rules here for transition:
  51. # GC_ is allowed
  52. let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
  53. if allUpper and k in {skConst, skEnumField, skType}: return s
  54. result = newStringOfCap(s.len)
  55. var i = 0
  56. case k
  57. of skType, skGenericParam:
  58. # Types should start with a capital unless builtins like 'int' etc.:
  59. if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
  60. "char", "byte", "bool", "openArray", "seq", "array", "void",
  61. "pointer", "float", "csize", "cdouble", "cchar", "cschar",
  62. "cshort", "cu", "nil", "expr", "stmt", "typedesc", "auto", "any",
  63. "range", "openarray", "varargs", "set", "cfloat"
  64. ]:
  65. result.add s[i]
  66. else:
  67. result.add toUpperAscii(s[i])
  68. of skConst, skEnumField:
  69. # for 'const' we keep how it's spelt; either upper case or lower case:
  70. result.add s[0]
  71. else:
  72. # as a special rule, don't transform 'L' to 'l'
  73. if s.len == 1 and s[0] == 'L': result.add 'L'
  74. elif '_' in s: result.add(s[i])
  75. else: result.add toLowerAscii(s[0])
  76. inc i
  77. while i < s.len:
  78. if s[i] == '_':
  79. if i > 0 and s[i-1] in {'A'..'Z'}:
  80. # don't skip '_' as it's essential for e.g. 'GC_disable'
  81. result.add('_')
  82. inc i
  83. result.add s[i]
  84. else:
  85. inc i
  86. result.add toUpperAscii(s[i])
  87. elif allUpper:
  88. result.add toLowerAscii(s[i])
  89. else:
  90. result.add s[i]
  91. inc i
  92. proc differ*(line: string, a, b: int, x: string): bool =
  93. let y = line[a..b]
  94. result = cmpIgnoreStyle(y, x) == 0 and y != x
  95. proc replaceInFile(conf: ConfigRef; info: TLineInfo; newName: string) =
  96. let line = conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1]
  97. var first = min(info.col.int, line.len)
  98. if first < 0: return
  99. #inc first, skipIgnoreCase(line, "proc ", first)
  100. while first > 0 and line[first-1] in Letters: dec first
  101. if first < 0: return
  102. if line[first] == '`': inc first
  103. let last = first+identLen(line, first)-1
  104. if differ(line, first, last, newName):
  105. # last-first+1 != newName.len or
  106. var x = line.substr(0, first-1) & newName & line.substr(last+1)
  107. system.shallowCopy(conf.m.fileInfos[info.fileIndex.int].lines[info.line.int-1], x)
  108. conf.m.fileInfos[info.fileIndex.int].dirty = true
  109. proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
  110. let beau = beautifyName(s, k)
  111. if s != beau:
  112. if gStyleCheck == StyleCheck.Auto:
  113. sym.name = getIdent(cache, beau)
  114. replaceInFile(conf, info, beau)
  115. else:
  116. message(conf, info, hintName, beau)
  117. proc styleCheckDefImpl(conf: ConfigRef; cache: IdentCache; info: TLineInfo; s: PSym; k: TSymKind) =
  118. # operators stay as they are:
  119. if k in {skResult, skTemp} or s.name.s[0] notin Letters: return
  120. if k in {skType, skGenericParam} and sfAnon in s.flags: return
  121. if {sfImportc, sfExportc} * s.flags == {} or gCheckExtern:
  122. checkStyle(conf, cache, info, s.name.s, k, s)
  123. proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
  124. # operators stay as they are:
  125. if k in {skResult, skTemp} or s.name.s[0] notin Letters: return
  126. if k in {skType, skGenericParam} and sfAnon in s.flags: return
  127. let beau = beautifyName(s.name.s, k)
  128. if s.name.s != beau:
  129. message(conf, info, hintName, beau)
  130. template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
  131. if optCheckNep1 in conf.globalOptions:
  132. nep1CheckDefImpl(conf, info, s, k)
  133. when defined(nimfix):
  134. if gStyleCheck != StyleCheck.None: styleCheckDefImpl(conf, cache, info, s, k)
  135. template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym) =
  136. styleCheckDef(conf, info, s, s.kind)
  137. template styleCheckDef*(conf: ConfigRef; s: PSym) =
  138. styleCheckDef(conf, s.info, s, s.kind)
  139. proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
  140. if info.fileIndex.int < 0: return
  141. # we simply convert it to what it looks like in the definition
  142. # for consistency
  143. # operators stay as they are:
  144. if s.kind in {skResult, skTemp} or s.name.s[0] notin Letters:
  145. return
  146. if s.kind in {skType, skGenericParam} and sfAnon in s.flags: return
  147. let newName = s.name.s
  148. replaceInFile(conf, info, newName)
  149. #if newName == "File": writeStackTrace()
  150. template styleCheckUse*(info: TLineInfo; s: PSym) =
  151. when defined(nimfix):
  152. if gStyleCheck != StyleCheck.None: styleCheckUseImpl(conf, info, s)