package slipshow

  1. Overview
  2. Docs
Legend:
Page
Library
Module
Module type
Parameter
Class
Class type
Source

Source file diagnosis.ml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
type loc = Cmarkit.Textloc.t

let loc_of_ploc loc (idx, idx') =
  let open Cmarkit.Textloc in
  let file = file loc in
  let first_line = first_line loc in
  let last_line = first_line in
  let first_byte = first_byte loc + idx in
  let last_byte = first_byte + idx' - idx - 1 in
  v ~file ~first_line ~last_line ~first_byte ~last_byte

type t =
  | DuplicateID of { id : string; occurrences : loc list }
  | MissingFile of { file : string; error_msg : string; locs : loc list }
  | WrongType of { loc_reason : loc; loc_block : loc; expected_type : string }
  | ParsingError of { action : string; msg : string; loc : loc }
  | ParsingWarnor of { warnor : Actions_arguments.W.warnor; loc : loc }
  | MissingID of { id : string; loc : loc }
  | UnknownAttribute of { attr : string; loc : loc }
  | General of {
      code : string;
      msg : string;
      labels : (string * loc) list;
      notes : string list;
    }

(* This is currently used to render issues on things that don't have location:
   mostly CLI input. CLI input have much less errors they can raise, so it's OK
   if (most) of them are not great messages. But I still keep all of those here
   since this function will have some things to be taken for LSP integration. *)
let pp ppf = function
  | DuplicateID id ->
      Format.fprintf ppf "ID '%s' has already been given at %a." id.id
        (Fmt.list Cmarkit.Textloc.pp_ocaml)
        id.occurrences
  | MissingFile s ->
      Format.fprintf ppf "Missing file: %s, considering it as an URL. (%s)"
        s.file s.error_msg
  | WrongType { loc_reason = _; loc_block = _; expected_type } ->
      Format.fprintf ppf "Wrong type: expected type '%s'" expected_type
  | ParsingError { action; msg; loc = _ } ->
      Format.fprintf ppf
        "Parsing of the arguments of actions '%s' failed with '%s'" action msg
  | ParsingWarnor
      { warnor = UnusedArgument { action_name; argument_name; _ }; loc = _ } ->
      Format.fprintf ppf "Action '%s' does not accept argument '%s'" action_name
        argument_name
  | ParsingWarnor { warnor = Parsing_failure { msg; loc = _ }; loc = _ } ->
      Format.fprintf ppf "Action argument parsing failure: %s" msg
  | MissingID { id; loc = _ } ->
      Format.fprintf ppf "Id '%s' could not be found" id
  | General { msg; labels = _; notes = _; code = _ } ->
      Format.fprintf ppf "%s" msg (* TODO: improve *)
  | UnknownAttribute { attr; loc = _ } ->
      Format.fprintf ppf
        "Attribute '%s' is neither a standard HTML attribute nor a slipshow \
         specific one"
        attr

let with_range source_map loc f =
  let open Grace in
  let range (loc : loc) =
    let source = source_map (Cmarkit.Textloc.file loc) in
    let start = Cmarkit.Textloc.first_byte loc in
    let stop = Cmarkit.Textloc.last_byte loc + 1 in
    Range.create ~source (Byte_index.of_int start) (Byte_index.of_int stop)
  in
  try
    let range = range loc in
    Some (f ~range)
  with _ -> None

let to_grace source_map error =
  let open Grace in
  let with_range = with_range source_map in
  match error with
  | DuplicateID { id; occurrences } ->
      let labels =
        List.filter_map
          (fun occ -> with_range occ @@ Diagnostic.Label.primaryf "")
          occurrences
      in
      Some
        (Diagnostic.createf ~labels Warning "ID %s is assigned multiple times"
           id)
  | MissingFile { file; error_msg; locs } ->
      let labels =
        List.filter_map
          (fun loc -> with_range loc @@ Diagnostic.Label.primaryf "")
          locs
      in
      Some
        (Diagnostic.createf ~labels Warning "file '%s' could not be read: %s"
           file error_msg)
  | WrongType { loc_reason; loc_block; expected_type } ->
      let labels =
        List.filter_map Fun.id
          [
            with_range loc_reason
            @@ Diagnostic.Label.primaryf "This expects the id of a %s"
                 expected_type;
            with_range loc_block
            @@ Diagnostic.Label.primaryf "This is not a %s" expected_type;
          ]
      in
      Some (Diagnostic.createf ~labels Warning "Wrong type")
  | ParsingError { action; msg; loc } ->
      let labels =
        List.filter_map Fun.id
          [ with_range loc @@ Diagnostic.Label.primaryf "%s" msg ]
      in
      Some
        (Diagnostic.createf ~labels Warning
           "Action %s arguments could not be parsed" action)
  | ParsingWarnor
      {
        warnor =
          UnusedArgument
            { action_name; argument_name; loc = parse_loc; possible_arguments };
        loc;
      } ->
      let loc = loc_of_ploc loc parse_loc in
      let labels =
        List.filter_map Fun.id
          [
            with_range loc
            @@ Diagnostic.Label.primaryf
                 "Action '%s' does not take argument '%s'" action_name
                 argument_name;
          ]
      in
      let notes =
        match possible_arguments with
        | [] ->
            [
              Diagnostic.Message.createf "'%s' accepts no arguments" action_name;
            ]
        | _ ->
            [
              Diagnostic.Message.createf "'%s' accepts arguments '%s'"
                action_name
                (String.concat "', '" possible_arguments);
            ]
      in
      Some (Diagnostic.createf ~labels ~notes Warning "Invalid action argument")
  | ParsingWarnor { warnor = Parsing_failure { msg; loc = parse_loc }; loc } ->
      let loc = loc_of_ploc loc parse_loc in
      let labels =
        List.filter_map Fun.id
          [ with_range loc @@ Diagnostic.Label.primaryf "%s" msg ]
      in
      Some (Diagnostic.createf ~labels Warning "Failed to parse")
  | MissingID { id; loc } ->
      let labels =
        List.filter_map Fun.id
          [
            with_range loc
            @@ Diagnostic.Label.primaryf
                 "This should be an ID present in the document";
          ]
      in
      Some
        (Diagnostic.createf ~labels Warning "No element with id '%s' was found"
           id)
  | General { msg; labels; notes; code = _ } ->
      let labels =
        List.filter_map
          (fun (msg, loc) ->
            with_range loc @@ Diagnostic.Label.primaryf "%s" msg)
          labels
      in
      let notes =
        List.map (fun msg -> Diagnostic.Message.createf "%s" msg) notes
      in
      Some (Diagnostic.createf ~labels ~notes Warning "%s" msg)
  | UnknownAttribute { attr; loc } ->
      let labels =
        List.filter_map Fun.id
          [ with_range loc @@ Diagnostic.Label.primaryf "" ]
      in
      Some
        (Diagnostic.createf ~labels Warning "Non standard attribute: '%s'" attr)

let errors_acc = ref []
let add x = errors_acc := x :: !errors_acc

let with_ f =
  let old_errors = !errors_acc in
  errors_acc := [];
  let clean_up () =
    let errors = !errors_acc in
    errors_acc := old_errors;
    errors
  in
  try
    let res = f () in
    (res, clean_up ())
  with exn ->
    let _ = clean_up () in
    raise exn

let to_code = function
  | DuplicateID _ -> "DupID"
  | MissingFile _ -> "FSError"
  | WrongType _ -> "WrongType"
  | ParsingError _ -> "ActionParsing"
  | ParsingWarnor _ -> "ActionParsing"
  | MissingID _ -> "IDNotFound"
  | UnknownAttribute _ -> "UnknownAttribute"
  | General { code; _ } -> code

let report_no_src fmt x =
  let msg = Format.asprintf "%a" pp x in
  let msg = Grace.Diagnostic.createf ~labels:[] ~code:x Warning "%s" msg in
  Format.fprintf fmt "%a@.@."
    (Grace_ansi_renderer.pp_diagnostic ?config:None ~code_to_string:to_code)
    msg