@@ -700,23 +700,144 @@ func (e DiagnosticExpectation) Description() string {
700
700
return desc
701
701
}
702
702
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 {
706
706
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.
709
719
return Unmet
710
720
}
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
711
754
}
712
755
return Met
713
756
}
757
+ var descs []string
758
+ for _ , filter := range filters {
759
+ descs = append (descs , filter .desc )
760
+ }
714
761
return SimpleExpectation {
715
762
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
+ }
717
778
}
779
+ return result
718
780
}
719
781
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
+
720
841
// NoDiagnostics asserts that either no diagnostics are sent for the
721
842
// workspace-relative path name, or empty diagnostics are sent.
722
843
func NoDiagnostics (name string ) Expectation {
@@ -749,43 +870,17 @@ func (e *Env) DiagnosticAtRegexpWithMessage(name, re, msg string) DiagnosticExpe
749
870
return DiagnosticExpectation {path : name , pos : & pos , re : re , present : true , message : msg }
750
871
}
751
872
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
-
760
873
// DiagnosticAt asserts that there is a diagnostic entry at the position
761
874
// specified by line and col, for the workdir-relative path name.
762
875
func DiagnosticAt (name string , line , col int ) DiagnosticExpectation {
763
876
return DiagnosticExpectation {path : name , pos : & fake.Pos {Line : line , Column : col }, present : true }
764
877
}
765
878
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
-
786
879
// GoSumDiagnostic asserts that a "go.sum is out of sync" diagnostic for the
787
880
// given module (as formatted in a go.mod file, e.g. "example.com v1.0.0") is
788
881
// present.
882
+ //
883
+ // TODO(rfindley): remove this.
789
884
func (e * Env ) GoSumDiagnostic (name , module string ) Expectation {
790
885
e .T .Helper ()
791
886
// In 1.16, go.sum diagnostics should appear on the relevant module. Earlier
0 commit comments