@@ -700,23 +700,144 @@ func (e DiagnosticExpectation) Description() string {
700700 return desc
701701}
702702
703- // NoOutstandingDiagnostics asserts that the workspace has no outstanding
704- // diagnostic messages .
705- func NoOutstandingDiagnostics ( ) Expectation {
703+ // Diagnostics asserts that there is at least one diagnostic matching the given
704+ // filters .
705+ func Diagnostics ( filters ... DiagnosticFilter ) Expectation {
706706 check := func (s State ) Verdict {
707- for _ , diags := range s .diagnostics {
708- if len (diags .Diagnostics ) > 0 {
707+ diags := flattenDiagnostics (s )
708+ for _ , filter := range filters {
709+ var filtered []flatDiagnostic
710+ for _ , d := range diags {
711+ if filter .check (d .name , d .diag ) {
712+ filtered = append (filtered , d )
713+ }
714+ }
715+ if len (filtered ) == 0 {
716+ // TODO(rfindley): if/when expectations describe their own failure, we
717+ // can provide more useful information here as to which filter caused
718+ // the failure.
709719 return Unmet
710720 }
721+ diags = filtered
722+ }
723+ return Met
724+ }
725+ var descs []string
726+ for _ , filter := range filters {
727+ descs = append (descs , filter .desc )
728+ }
729+ return SimpleExpectation {
730+ check : check ,
731+ description : "any diagnostics " + strings .Join (descs , ", " ),
732+ }
733+ }
734+
735+ // NoMatchingDiagnostics asserts that there are no diagnostics matching the
736+ // given filters. Notably, if no filters are supplied this assertion checks
737+ // that there are no diagnostics at all, for any file.
738+ //
739+ // TODO(rfindley): replace NoDiagnostics with this, and rename.
740+ func NoMatchingDiagnostics (filters ... DiagnosticFilter ) Expectation {
741+ check := func (s State ) Verdict {
742+ diags := flattenDiagnostics (s )
743+ for _ , filter := range filters {
744+ var filtered []flatDiagnostic
745+ for _ , d := range diags {
746+ if filter .check (d .name , d .diag ) {
747+ filtered = append (filtered , d )
748+ }
749+ }
750+ diags = filtered
751+ }
752+ if len (diags ) > 0 {
753+ return Unmet
711754 }
712755 return Met
713756 }
757+ var descs []string
758+ for _ , filter := range filters {
759+ descs = append (descs , filter .desc )
760+ }
714761 return SimpleExpectation {
715762 check : check ,
716- description : "no outstanding diagnostics" ,
763+ description : "no diagnostics " + strings .Join (descs , ", " ),
764+ }
765+ }
766+
767+ type flatDiagnostic struct {
768+ name string
769+ diag protocol.Diagnostic
770+ }
771+
772+ func flattenDiagnostics (state State ) []flatDiagnostic {
773+ var result []flatDiagnostic
774+ for name , diags := range state .diagnostics {
775+ for _ , diag := range diags .Diagnostics {
776+ result = append (result , flatDiagnostic {name , diag })
777+ }
717778 }
779+ return result
718780}
719781
782+ // -- Diagnostic filters --
783+
784+ // A DiagnosticFilter filters the set of diagnostics, for assertion with
785+ // Diagnostics or NoMatchingDiagnostics.
786+ type DiagnosticFilter struct {
787+ desc string
788+ check func (name string , _ protocol.Diagnostic ) bool
789+ }
790+
791+ // ForFile filters to diagnostics matching the sandbox-relative file name.
792+ func ForFile (name string ) DiagnosticFilter {
793+ return DiagnosticFilter {
794+ desc : fmt .Sprintf ("for file %q" , name ),
795+ check : func (diagName string , _ protocol.Diagnostic ) bool {
796+ return diagName == name
797+ },
798+ }
799+ }
800+
801+ // FromSource filters to diagnostics matching the given diagnostics source.
802+ func FromSource (source string ) DiagnosticFilter {
803+ return DiagnosticFilter {
804+ desc : fmt .Sprintf ("with source %q" , source ),
805+ check : func (_ string , d protocol.Diagnostic ) bool {
806+ return d .Source == source
807+ },
808+ }
809+ }
810+
811+ // AtRegexp filters to diagnostics in the file with sandbox-relative path name,
812+ // at the first position matching the given regexp pattern.
813+ //
814+ // TODO(rfindley): pass in the editor to expectations, so that they may depend
815+ // on editor state and AtRegexp can be a function rather than a method.
816+ func (e * Env ) AtRegexp (name , pattern string ) DiagnosticFilter {
817+ pos := e .RegexpSearch (name , pattern )
818+ return DiagnosticFilter {
819+ desc : fmt .Sprintf ("at the first position matching %q in %q" , pattern , name ),
820+ check : func (diagName string , d protocol.Diagnostic ) bool {
821+ // TODO(rfindley): just use protocol.Position for Pos, rather than
822+ // duplicating.
823+ return diagName == name && d .Range .Start .Line == uint32 (pos .Line ) && d .Range .Start .Character == uint32 (pos .Column )
824+ },
825+ }
826+ }
827+
828+ // WithMessageContaining filters to diagnostics whose message contains the
829+ // given substring.
830+ func WithMessageContaining (substring string ) DiagnosticFilter {
831+ return DiagnosticFilter {
832+ desc : fmt .Sprintf ("with message containing %q" , substring ),
833+ check : func (_ string , d protocol.Diagnostic ) bool {
834+ return strings .Contains (d .Message , substring )
835+ },
836+ }
837+ }
838+
839+ // TODO(rfindley): eliminate all expectations below this point.
840+
720841// NoDiagnostics asserts that either no diagnostics are sent for the
721842// workspace-relative path name, or empty diagnostics are sent.
722843func NoDiagnostics (name string ) Expectation {
@@ -749,43 +870,17 @@ func (e *Env) DiagnosticAtRegexpWithMessage(name, re, msg string) DiagnosticExpe
749870 return DiagnosticExpectation {path : name , pos : & pos , re : re , present : true , message : msg }
750871}
751872
752- // DiagnosticAtRegexpFromSource expects a diagnostic at the first position
753- // matching re, from the given source.
754- func (e * Env ) DiagnosticAtRegexpFromSource (name , re , source string ) DiagnosticExpectation {
755- e .T .Helper ()
756- pos := e .RegexpSearch (name , re )
757- return DiagnosticExpectation {path : name , pos : & pos , re : re , present : true , source : source }
758- }
759-
760873// DiagnosticAt asserts that there is a diagnostic entry at the position
761874// specified by line and col, for the workdir-relative path name.
762875func DiagnosticAt (name string , line , col int ) DiagnosticExpectation {
763876 return DiagnosticExpectation {path : name , pos : & fake.Pos {Line : line , Column : col }, present : true }
764877}
765878
766- // NoDiagnosticAtRegexp expects that there is no diagnostic entry at the start
767- // position matching the regexp search string re in the buffer specified by
768- // name. Note that this currently ignores the end position.
769- // This should only be used in combination with OnceMet for a given condition,
770- // otherwise it may always succeed.
771- func (e * Env ) NoDiagnosticAtRegexp (name , re string ) DiagnosticExpectation {
772- e .T .Helper ()
773- pos := e .RegexpSearch (name , re )
774- return DiagnosticExpectation {path : name , pos : & pos , re : re , present : false }
775- }
776-
777- // NoDiagnosticWithMessage asserts that there is no diagnostic entry with the
778- // given message.
779- //
780- // This should only be used in combination with OnceMet for a given condition,
781- // otherwise it may always succeed.
782- func NoDiagnosticWithMessage (name , msg string ) DiagnosticExpectation {
783- return DiagnosticExpectation {path : name , message : msg , present : false }
784- }
785-
786879// GoSumDiagnostic asserts that a "go.sum is out of sync" diagnostic for the
787880// given module (as formatted in a go.mod file, e.g. "example.com v1.0.0") is
788881// present.
882+ //
883+ // TODO(rfindley): remove this.
789884func (e * Env ) GoSumDiagnostic (name , module string ) Expectation {
790885 e .T .Helper ()
791886 // In 1.16, go.sum diagnostics should appear on the relevant module. Earlier
0 commit comments