1 # rule put: a call to `Map.put(`: among the significant tokens, a `Map.put` 2 # token with a `(` token right after it. Base's Map.put is an internal helper 3 # of Map.set: on a leaf it keeps the old key and replaces the value without 4 # comparing keys, so putting a new key silently overwrites another entry 5 # (bend-http c91bdf8: "b" into a map of "a" replaced "a"). Use Map.set. A 6 # file that defines Map.put itself (Base's source), a `def` keyword with a 7 # `Map.put` token right after it, is exempt. 8 import Base 9 import ../../src.bend as Src 10 import ../../lazy/lazy.bend as Lazy 11 import ../../finding.bend as F 12 import ../../syntax/lex.bend as Lex 13 import ../tokens.bend as T 14 15 # is the first token `Map.put`? 16 def defines.named(toks: List<&2, Lex.Tok>) -> Bool: 17 match toks: 18 case Con{Lex.Tok{k, +t, l, c}, rest}: 19 String.eq(t, "Map.put") 20 case Nil{}: 21 False{} 22 23 # a keyword? a token's kind is read here, its text in the tests beside it 24 def defines.key(kk: Lex.TokKind) -> Bool: 25 match kk: 26 case Lex.TKey{}: 27 True{} 28 case other: 29 False{} 30 31 # a dotted name? 32 def calls.dotted(kk: Lex.TokKind) -> Bool: 33 match kk: 34 case Lex.TDotted{}: 35 True{} 36 case other: 37 False{} 38 39 # does the file define Map.put: a `def` keyword right before a `Map.put`? 40 def defines(toks: List<&2, Lex.Tok>) -> Bool: 41 match toks: 42 case Con{Lex.Tok{k, +d, l, c}, +rest}: 43 Lazy.or_else(Bool.and(defines.key(k), Bool.and(String.eq(d, "def"), defines.named(rest))), _u => defines(rest)) 44 case Nil{}: 45 False{} 46 47 # a dotted name then an open bracket: a finding when they are `Map.put` and 48 # `(`, before what the rest reports 49 def calls.one( 50 +tt: String, 51 +oo: String, 52 +ll: U32, 53 +cc: U32, 54 +path: String, 55 +more: List<&2, F.Finding> 56 ) -> List<&2, F.Finding>: 57 Bool.pick(List<&2, F.Finding>, Bool.and(String.eq(tt, "Map.put"), String.eq(oo, "(")), 58 F.Finding{path, ll, cc, 7, "put", "Map.put does not compare keys, so a new key can overwrite an existing entry; use Map.set."} 59 <> more, 60 more) 61 62 # every `Map.put(` among the significant tokens 63 def calls(toks: List<&2, Lex.Tok>, +path: String) -> List<&2, F.Finding>: 64 match toks: 65 case Con{Lex.Tok{k, +t, +l, +c}, +rest}: 66 match rest: 67 case Con{Lex.Tok{k2, +o, l2, c2}, r2}: 68 Lazy.either(List<&2, F.Finding>, Bool.and(calls.dotted(k), Lex.is_open(k2)), _u => calls.one(t, o, l, c, path, 69 calls(r2, path)), _v => calls(rest, path)) 70 case Nil{}: 71 Nil{} 72 case Nil{}: 73 Nil{} 74 75 def check.on(+toks: List<&2, Lex.Tok>, path: String) -> List<&2, F.Finding>: 76 Lazy.stop(List<&2, F.Finding>, defines(toks), [], _u => calls(toks, path)) 77 78 # the rule 79 def check(ss: Src.Src) -> List<&2, F.Finding>: 80 Src.Src{path, text, toks, tree, bound, items} = ss 81 check.on(T.sig(toks), path)