summary refs log tree commit diff stats
path: root/compiler/linter.nim
blob: a80c377e95100f12f7d46e3e7d364064c5a790a5 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
#
#
#           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 style checker.

import std/strutils
from std/sugar import dup

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

proc `=~`(s: string, a: openArray[string]): bool =
  result = false
  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", "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])
  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+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
        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): 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 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 first+1 < line.len and line[first] == '`': inc first

  let last = first+identLen(line, first)-1
  result = differ(line, first, last, newName)

proc styleCheckUseImpl(conf: ConfigRef; info: TLineInfo; s: PSym) =
  let newName = s.name.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)