~/bend-docscommunity

src/noqa.bend source

src/noqa.bend on the hub · documented module

# src/noqa: `# noqa: CODE` comments, and the filter they make, after ruff.# A comment token (the lexer's, so never a string's contents) whose text is# `#`, any spaces, then `noqa` is a mark. A `:` right after `noqa` opens# its codes: runs of ASCII letters and digits, split by commas with spaces# around them allowed, the list ending at anything else, so free text may# follow (`# noqa: L001 IO entry point`). `noqa` at the comment's end, or# followed by a space, names no code: a bare mark. Codes are case-sensitive# and are src/codes.bend's (`L001`, `U002`). A finding is silenced when a# mark of its own file, on its own line, names its code.## No rule reads this: the planner (lint/plan.bend) and the language server# run `keep` over the graded findings once every rule has run, the project# rules included, and then `noqa` (rules/style/noqa.bend) over what the# marks silenced. Pure (GPU-safe).import Baseimport ./lazy/lazy.bend as Lazyimport ./syntax/lex.bend as Leximport ./finding.bend as Fimport ./codes.bend as Codesimport ./src.bend as Src# one noqa comment: its line and column (0-based) and width, and the codes# it names, none for a bare onetype Mark is Data:  Mark{line: U32, col: U32, len: U32, codes: List<&2, String>}# a file's noqa commentstype Marks is Data:  Marks{path: String, marks: List<&2, Mark>}# ---------------------------------------------------------------------------# reading a comment# what a char is to the code list: a space, a comma, a letter or digit, or# anything elsetype Cls is Data:  CSpace{}  CComma{}  CCode{}  COther{}# a char's classdef cls(+cc: Char) -> Cls:  +code = Bool.or(Char.is_alpha(cc), Char.is_digit(cc))  +tail = Bool.pick(Cls, code, CCode{}, COther{})  Bool.pick(Cls, Char.is_eq(cc, ' '), CSpace{}, Bool.pick(Cls, Char.is_eq(cc, ','), CComma{}, tail))# where the code list reader stands: before a code (spaces allowed), inside# one, after one (spaces, then a comma or the list's end), or past the listtype Phase is Data:  Before{}  Inside{}  After{}  Past{}# the reader: where it stands, the code it is reading (its chars, the last# first), and the codes read (the last first)type Rd is Data:  Rd{phase: Phase, buf: List<&2, Char>, acc: List<&2, String>}# the chars read, the last first, as a codedef word(buf: List<&2, Char>) -> String:  String.from_list(List.reverse(&2, Char, buf))# before a code: a space waits, a letter or digit starts one, anything else# ends the listdef step.before(kk: Cls, +cc: Char, +acc: List<&2, String>) -> Rd:  match kk:    case CSpace{}:      Rd{Before{}, [], acc}    case CComma{}:      Rd{Past{}, [], acc}    case CCode{}:      Rd{Inside{}, [cc], acc}    case COther{}:      Rd{Past{}, [], acc}# inside a code: a letter or digit extends it; a space, a comma or anything# else ends it, and a comma expects the nextdef step.inside(kk: Cls, +cc: Char, +buf: List<&2, Char>, +acc: List<&2, String>) -> Rd:  match kk:    case CSpace{}:      Rd{After{}, [], word(buf) <> acc}    case CComma{}:      Rd{Before{}, [], word(buf) <> acc}    case CCode{}:      Rd{Inside{}, cc <> buf, acc}    case COther{}:      Rd{Past{}, [], word(buf) <> acc}# after a code: a space waits, a comma expects the next, anything else ends# the listdef step.after(kk: Cls, +acc: List<&2, String>) -> Rd:  match kk:    case CSpace{}:      Rd{After{}, [], acc}    case CComma{}:      Rd{Before{}, [], acc}    case CCode{}:      Rd{Past{}, [], acc}    case COther{}:      Rd{Past{}, [], acc}# the reader after one more chardef step.at(ph: Phase, +cc: Char, +buf: List<&2, Char>, +acc: List<&2, String>) -> Rd:  match ph:    case Before{}:      step.before(cls(cc), cc, acc)    case Inside{}:      step.inside(cls(cc), cc, buf, acc)    case After{}:      step.after(cls(cc), acc)    case Past{}:      Rd{Past{}, buf, acc}# the reader after one more chardef step(+cc: Char, rd: Rd) -> Rd:  Rd{ph, buf, acc} = rd  step.at(ph, cc, buf, acc)# the reader after the textdef run(tt: String, rd: Rd) -> Rd:  match tt:    case SNil{}:      rd    case SCon{cc, rest}:      run(rest, step(cc, rd))# the codes read, in order, the one still being read includeddef finish.at(ph: Phase, +buf: List<&2, Char>, +acc: List<&2, String>) -> List<&2, String>:  match ph:    case Before{}:      List.reverse(&2, String, acc)    case Inside{}:      List.reverse(&2, String, word(buf) <> acc)    case After{}:      List.reverse(&2, String, acc)    case Past{}:      List.reverse(&2, String, acc)# the codes read, in orderdef finish(rd: Rd) -> List<&2, String>:  Rd{ph, buf, acc} = rd  finish.at(ph, buf, acc)# the codes a text after `noqa:` namesdef codes(tt: String) -> List<&2, String>:  finish(run(tt, Rd{Before{}, [], []}))# the text after its leading spacesdef lead(tt: String) -> String:  match tt:    case SCon{' ', rest}:      lead(rest)    case other:      other# a comment's text past `noqa`: a mark naming the codes after a `:`, a bare# one at the end or before a space, and none for anything else (`noqas`)def mark.rest(+line: U32, +col: U32, +len: U32, tt: String) -> List<&2, Mark>:  match tt:    case SNil{}:      [Mark{line, col, len, []}]    case SCon{':', rest}:      [Mark{line, col, len, codes(rest)}]    case SCon{' ', _rest}:      [Mark{line, col, len, []}]    case other:      []# a comment's text past `#` and its spaces: a mark when it opens with `noqa`def mark.word(+line: U32, +col: U32, +len: U32, tt: String) -> List<&2, Mark>:  match tt:    case SCon{'n', SCon{'o', SCon{'q', SCon{'a', rest}}}}:      mark.rest(line, col, len, rest)    case other:      []# a comment's text: a mark when it is `#`, any spaces, then `noqa`def mark.text(+line: U32, +col: U32, +len: U32, tt: String) -> List<&2, Mark>:  match tt:    case SCon{'#', rest}:      mark.word(line, col, len, lead(rest))    case other:      []# a comment token's mark, when it is one; any other token has nonedef mark.of(tk: Lex.Tok) -> List<&2, Mark>:  match tk:    case Lex.Tok{Lex.TComment{}, +t, l, c}:      mark.text(l, c, U32.from_nat(String.length(t)), t)    case other:      []# the marks among the tokens, in orderdef marks(toks: List<&2, Lex.Tok>) -> List<&2, Mark>:  match toks:    case Nil{}:      Nil{}    case Con{t, rest}:      List.append(&2, Mark, mark.of(t), marks(rest))# a file's marks, from its tokensdef file(path: String, toks: List<&2, Lex.Tok>) -> Marks:  Marks{path, marks(toks)}# each source's marks, in orderdef of_srcs(ss: List<&2, Src.Src>) -> List<&2, Marks>:  match ss:    case Nil{}:      Nil{}    case Con{Src.Src{path, _text, toks, _tree, _bound, _items}, rest}:      file(path, toks) <> of_srcs(rest)# ---------------------------------------------------------------------------# the filter# the codes include this onedef named(cs: List<&2, String>, +code: String) -> Bool:  match cs:    case Nil{}:      False{}    case Con{c, rest}:      Lazy.or_else(String.eq(c, code), _u => named(rest, code))# a mark on the line names the codedef on_line(mks: List<&2, Mark>, +line: U32, +code: String) -> Bool:  match mks:    case Nil{}:      False{}    case Con{Mark{l, _col, _len, cs}, rest}:      Lazy.or_else(Lazy.and_then(U32.is_eq(l, line), _u => named(cs, code)), _v => on_line(rest, line, code))# a mark of the file at the path, on the line, names the codedef silenced(ms: List<&2, Marks>, +path: String, +line: U32, +code: String) -> Bool:  match ms:    case Nil{}:      False{}    case Con{Marks{p, mks}, rest}:      Lazy.or_else(Lazy.and_then(String.eq(p, path), _u => on_line(mks, line, code)),        _v => silenced(rest, path, line, code))# the graded findings no mark silences, in orderdef keep(+ms: List<&2, Marks>, gs: List<&2, F.Graded>) -> List<&2, F.Graded>:  match gs:    case Nil{}:      Nil{}    case Con{F.Graded{ll, F.Finding{+path, +line, col, len, +rule, msg}}, rest}:      +more = keep(ms, rest)      Bool.pick(List<&2, F.Graded>, silenced(ms, path, line, Codes.code(rule)), more,        F.Graded{ll, F.Finding{path, line, col, len, rule, msg}} <> more)