linter.nim 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  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 std/strutils
  11. from std/sugar import dup
  12. import options, ast, msgs, idents, lineinfos, wordrecg, astmsgs, semdata, packages, modulegraphs
  13. export packages
  14. const
  15. Letters* = {'a'..'z', 'A'..'Z', '0'..'9', '\x80'..'\xFF', '_'}
  16. proc identLen*(line: string, start: int): int =
  17. result = 0
  18. while start+result < line.len and line[start+result] in Letters:
  19. inc result
  20. proc `=~`(s: string, a: openArray[string]): bool =
  21. result = false
  22. for x in a:
  23. if s.startsWith(x): return true
  24. proc beautifyName(s: string, k: TSymKind): string =
  25. # minimal set of rules here for transition:
  26. # GC_ is allowed
  27. let allUpper = allCharsInSet(s, {'A'..'Z', '0'..'9', '_'})
  28. if allUpper and k in {skConst, skEnumField, skType}: return s
  29. result = newStringOfCap(s.len)
  30. var i = 0
  31. case k
  32. of skType, skGenericParam:
  33. # Types should start with a capital unless builtins like 'int' etc.:
  34. if s =~ ["int", "uint", "cint", "cuint", "clong", "cstring", "string",
  35. "char", "byte", "bool", "openArray", "seq", "array", "void",
  36. "pointer", "float", "csize", "csize_t", "cdouble", "cchar", "cschar",
  37. "cshort", "cu", "nil", "typedesc", "auto", "any",
  38. "range", "openarray", "varargs", "set", "cfloat", "ref", "ptr",
  39. "untyped", "typed", "static", "sink", "lent", "type", "owned", "iterable"]:
  40. result.add s[i]
  41. else:
  42. result.add toUpperAscii(s[i])
  43. of skConst, skEnumField:
  44. # for 'const' we keep how it's spelt; either upper case or lower case:
  45. result.add s[0]
  46. else:
  47. # as a special rule, don't transform 'L' to 'l'
  48. if s.len == 1 and s[0] == 'L': result.add 'L'
  49. elif '_' in s: result.add(s[i])
  50. else: result.add toLowerAscii(s[0])
  51. inc i
  52. while i < s.len:
  53. if s[i] == '_':
  54. if i+1 >= s.len:
  55. discard "trailing underscores should be stripped off"
  56. elif i > 0 and s[i-1] in {'A'..'Z'}:
  57. # don't skip '_' as it's essential for e.g. 'GC_disable'
  58. result.add('_')
  59. inc i
  60. result.add s[i]
  61. else:
  62. inc i
  63. result.add toUpperAscii(s[i])
  64. elif allUpper:
  65. result.add toLowerAscii(s[i])
  66. else:
  67. result.add s[i]
  68. inc i
  69. proc differ*(line: string, a, b: int, x: string): string =
  70. proc substrEq(s: string, pos, last: int, substr: string): bool =
  71. result = true
  72. for i in 0..<substr.len:
  73. if pos+i > last or s[pos+i] != substr[i]: return false
  74. result = ""
  75. if not substrEq(line, a, b, x):
  76. let y = line[a..b]
  77. if cmpIgnoreStyle(y, x) == 0:
  78. result = y
  79. proc nep1CheckDefImpl(conf: ConfigRef; info: TLineInfo; s: PSym; k: TSymKind) =
  80. let beau = beautifyName(s.name.s, k)
  81. if s.name.s != beau:
  82. lintReport(conf, info, beau, s.name.s)
  83. template styleCheckDef*(ctx: PContext; info: TLineInfo; sym: PSym; k: TSymKind) =
  84. ## Check symbol definitions adhere to NEP1 style rules.
  85. if optStyleCheck in ctx.config.options and # ignore if styleChecks are off
  86. {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # check only if hint/error is enabled
  87. hintName in ctx.config.notes and # ignore if name checks are not requested
  88. ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
  89. optStyleUsages notin ctx.config.globalOptions and # ignore if requested to only check name usage
  90. sym.kind != skResult and # ignore `result`
  91. sym.kind != skTemp and # ignore temporary variables created by the compiler
  92. sym.name.s[0] in Letters and # ignore operators TODO: what about unicode symbols???
  93. k notin {skType, skGenericParam} and # ignore types and generic params
  94. (sym.typ == nil or sym.typ.kind != tyTypeDesc) and # ignore `typedesc`
  95. {sfImportc, sfExportc} * sym.flags == {} and # ignore FFI
  96. sfAnon notin sym.flags: # ignore if created by compiler
  97. nep1CheckDefImpl(ctx.config, info, sym, k)
  98. template styleCheckDef*(ctx: PContext; info: TLineInfo; s: PSym) =
  99. ## Check symbol definitions adhere to NEP1 style rules.
  100. styleCheckDef(ctx, info, s, s.kind)
  101. template styleCheckDef*(ctx: PContext; s: PSym) =
  102. ## Check symbol definitions adhere to NEP1 style rules.
  103. styleCheckDef(ctx, s.info, s, s.kind)
  104. proc differs(conf: ConfigRef; info: TLineInfo; newName: string): string =
  105. let line = sourceLine(conf, info)
  106. var first = min(info.col.int, line.len)
  107. if first < 0: return
  108. #inc first, skipIgnoreCase(line, "proc ", first)
  109. while first > 0 and line[first-1] in Letters: dec first
  110. if first < 0: return
  111. if first+1 < line.len and line[first] == '`': inc first
  112. let last = first+identLen(line, first)-1
  113. result = differ(line, first, last, newName)
  114. proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
  115. let newName = s.name.s
  116. let badName = differs(conf, info, newName)
  117. if badName.len > 0:
  118. lintReport(conf, info, newName, badName, "".dup(addDeclaredLoc(conf, s)))
  119. template styleCheckUse*(ctx: PContext; info: TLineInfo; sym: PSym) =
  120. ## Check symbol uses match their definition's style.
  121. if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
  122. hintName in ctx.config.notes and # ignore if name checks are not requested
  123. ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)) and # ignore foreign packages
  124. sym.kind != skTemp and # ignore temporary variables created by the compiler
  125. sym.name.s[0] in Letters and # ignore operators TODO: what about unicode symbols???
  126. sfAnon notin sym.flags: # ignore temporary variables created by the compiler
  127. styleCheckUseImpl(ctx.config, info, sym)
  128. proc checkPragmaUseImpl(conf: ConfigRef; info: TLineInfo; w: TSpecialWord; pragmaName: string) =
  129. let wanted = $w
  130. if pragmaName != wanted:
  131. lintReport(conf, info, wanted, pragmaName)
  132. template checkPragmaUse*(ctx: PContext; info: TLineInfo; w: TSpecialWord; pragmaName: string, sym: PSym) =
  133. ## Check builtin pragma uses match their definition's style.
  134. ## Note: This only applies to builtin pragmas, not user pragmas.
  135. if {optStyleHint, optStyleError} * ctx.config.globalOptions != {} and # ignore if styleChecks are off
  136. hintName in ctx.config.notes and # ignore if name checks are not requested
  137. ctx.config.belongsToProjectPackageMaybeNil(getModule(ctx.graph, info.fileIndex)): # ignore foreign packages
  138. checkPragmaUseImpl(ctx.config, info, w, pragmaName)