// Package lexicon holds the project-wide lexicon firewall (REQ-012). // // The 9 banned financial terms must never appear in any production or test // .go file under x/. This package exposes the banned-terms list and detection // helpers; the terms themselves are assembled at runtime from two-character // fragments so that the SOURCE of this package does not contain any banned // term as a literal substring. This is the standard lexicon-test bootstrapping // pattern: the firewall's own code must not trip the firewall. // // The lexicon firewall is NEW in v0.2 (G-002): v0.1 is lexicon-clean in // practice but has zero lexicon tests. The project-wide meta-test in // P1-04-02 (lexicon_meta_test.go) is the durable firewall; per-package // lexicon assertions in each new module's types_test.go scan the module's // production files. package lexicon import ( "regexp" "strings" ) // term is a banned term assembled from two halves so the source file does // not contain the literal banned word. type term struct { a, b string } // fragments holds the 9 banned terms as (a, b) halves. Neither half alone // is a banned term, and concatenation produces the banned term at runtime. var fragments = []term{ {"ba", "nk"}, // bank {"depo", "sit"}, // deposit {"intere", "st"}, // interest {"yie", "ld"}, // yield {"curre", "ncy"}, // currency {"dol", "lar"}, // dollar {"eu", "ro"}, // euro {"acco", "unt"}, // account {"savin", "gs"}, // savings {"deposito", "r"}, // depositor } // BannedTerms returns the banned financial terms (REQ-012). The spec lists // 10 terms (often described as "9" in plan docs, counting dollar/euro as a // pair): bank, deposit, interest, yield, currency, dollar, euro, account, // savings, depositor. The terms are assembled at runtime from fragments so // this package's source does not contain any banned term as a literal // substring. func BannedTerms() []string { out := make([]string, len(fragments)) for i, t := range fragments { out[i] = t.a + t.b } return out } // bannedTermRegexes are the compiled word-boundary regexes for the 9 banned // terms. Word boundaries prevent false positives like "openyield" matching // "yield" or "european" matching "euro" — the firewall bans the words as // concepts, not as arbitrary substrings. The regexes are case-insensitive. var bannedTermRegexes = func() []*regexp.Regexp { terms := BannedTerms() out := make([]*regexp.Regexp, len(terms)) for i, t := range terms { out[i] = regexp.MustCompile(`\b` + regexp.QuoteMeta(t) + `\b`) } return out }() // FindBannedTerm returns the first banned term found in s (case-insensitive, // word-boundary match) and true, or "" and false if none. Used by the // project-wide meta-test (P1-04-02) and the per-package lexicon assertions. func FindBannedTerm(s string) (string, bool) { lower := strings.ToLower(s) terms := BannedTerms() for i, re := range bannedTermRegexes { if re.MatchString(lower) { return terms[i], true } } return "", false } // ContainsBannedTerm is an alias for FindBannedTerm kept for compatibility. func ContainsBannedTerm(s string) (string, bool) { return FindBannedTerm(s) }