linter.nim 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142
  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 style checker.
  10. import strutils
  11. import options, ast, msgs, idents, lineinfos, wordrecg
  12. const
  13. Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
  14. proc identLen*(line: string, start: int): int =
  15. while start+result < line.len and line[start+result] in Letters:
  16. inc result
  17. proc `=~`(s: string, a: openArray[string]): bool =
  18. for x in a:
  19. if s.startsWith(x): return true
  20. proc beautifyName(s: string, k: TSymKind): string =
  21. # minimal set of rules here for transition:
  22. # GC_ is allowed
  23. let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
  24. if allUpper and k in {skConst, skEnumField, skType}: return s
  25. result = newStringOfCap(s.len)
  26. var i = 0
  27. case k
  28. of skType, skGenericParam:
  29. # Types should start with a capital unless builtins like 'int' etc.:
  30. if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
  31. "char", "byte", "bool", "openArray", "seq", "array", "void",
  32. "pointer", "float", "csize", "csize_t", "cdouble", "cchar", "cschar",
  33. "cshort", "cu", "nil", "typedesc", "auto", "any",
  34. "range", "openarray", "varargs", "set", "cfloat", "ref", "ptr",
  35. "untyped", "typed", "static", "sink", "lent", "type", "owned"]:
  36. result.add s[i]
  37. else:
  38. result.add toUpperAscii(s[i])
  39. of skConst, skEnumField:
  40. # for 'const' we keep how it's spelt; either upper case or lower case:
  41. result.add s[0]
  42. else:
  43. # as a special rule, don't transform 'L' to 'l'
  44. if s.len == 1 and s[0] == 'L': result.add 'L'
  45. elif '_' in s: result.add(s[i])
  46. else: result.add toLowerAscii(s[0])
  47. inc i
  48. while i < s.len:
  49. if s[i] == '_':
  50. if i > 0 and s[i-1] in {'A'..'Z'}:
  51. # don't skip '_' as it's essential for e.g. 'GC_disable'
  52. result.add('_')
  53. inc i
  54. result.add s[i]
  55. else:
  56. inc i
  57. result.add toUpperAscii(s[i])
  58. elif allUpper:
  59. result.add toLowerAscii(s[i])
  60. else:
  61. result.add s[i]
  62. inc i
  63. proc differ*(line: string, a, b: int, x: string): string =
  64. proc substrEq(s: string, pos, last: int, substr: string): bool =
  65. var i = 0
  66. while i < substr.len and pos+i <= last and s[pos+i] == substr[i]:
  67. inc i
  68. return i == substr.len
  69. let last = min(b, line.len)
  70. result = ""
  71. if not substrEq(line, a, b, x):
  72. let y = line[a..b]
  73. if cmpIgnoreStyle(y, x) == 0:
  74. result = y
  75. proc checkStyle(conf: ConfigRef; cache: IdentCache; info: TLineInfo, s: string, k: TSymKind; sym: PSym) =
  76. let beau = beautifyName(s, k)
  77. if s != beau:
  78. lintReport(conf, info, beau, s)
  79. proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
  80. # operators stay as they are:
  81. if k in {skResult, skTemp} or s.name.s[0] notin Letters: return
  82. if k in {skType, skGenericParam} and sfAnon in s.flags: return
  83. if s.typ != nil and s.typ.kind == tyTypeDesc: return
  84. if {sfImportc, sfExportc} * s.flags != {}: return
  85. if optStyleCheck notin s.options: return
  86. let beau = beautifyName(s.name.s, k)
  87. if s.name.s != beau:
  88. lintReport(conf, info, beau, s.name.s)
  89. template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
  90. if {optStyleHint, optStyleError} * conf.globalOptions != {}:
  91. nep1CheckDefImpl(conf, info, s, k)
  92. template styleCheckDef*(conf: ConfigRef; info: TLineInfo; s: PSym) =
  93. styleCheckDef(conf, info, s, s.kind)
  94. template styleCheckDef*(conf: ConfigRef; s: PSym) =
  95. styleCheckDef(conf, s.info, s, s.kind)
  96. proc differs(conf: ConfigRef; info: TLineInfo; newName: string): string =
  97. let line = sourceLine(conf, info)
  98. var first = min(info.col.int, line.len)
  99. if first < 0: return
  100. #inc first, skipIgnoreCase(line, "proc ", first)
  101. while first > 0 and line[first-1] in Letters: dec first
  102. if first < 0: return
  103. if first+1 < line.len and line[first] == '`': inc first
  104. let last = first+identLen(line, first)-1
  105. result = differ(line, first, last, newName)
  106. proc styleCheckUse*(conf: ConfigRef; info: TLineInfo; s: PSym) =
  107. if info.fileIndex.int < 0: return
  108. # we simply convert it to what it looks like in the definition
  109. # for consistency
  110. # operators stay as they are:
  111. if s.kind == skTemp or s.name.s[0] notin Letters or sfAnon in s.flags:
  112. return
  113. let newName = s.name.s
  114. let oldName = differs(conf, info, newName)
  115. if oldName.len > 0:
  116. lintReport(conf, info, newName, oldName)
  117. proc checkPragmaUse*(conf: ConfigRef; info: TLineInfo; w: TSpecialWord; pragmaName: string) =
  118. let wanted = canonPragmaSpelling(w)
  119. if pragmaName != wanted:
  120. lintReport(conf, info, wanted, pragmaName)