package flow_parser

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

Source file jsdoc.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
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
(*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *)

module Sedlexing = Flow_sedlexing

module Param = struct
  type optionality =
    | NotOptional
    | Optional
    | OptionalWithDefault of string
  [@@deriving show, eq]

  type info = {
    description: string option;
    optional: optionality;
  }
  [@@deriving show, eq]

  type path =
    | Name
    | Element of path
    | Member of path * string
  [@@deriving show, eq]

  type t = (path * info) list [@@deriving show, eq]
end

module Params = struct
  type t = (string * Param.t) list [@@deriving show, eq]
end

module Unrecognized_tags = struct
  type t = (string * string option) list [@@deriving show, eq]
end

type t = {
  description: string option;
  params: Params.t;
  deprecated: string option;
  unrecognized_tags: Unrecognized_tags.t;
}

(*************)
(* accessors *)
(*************)

let description { description; _ } = description

let params { params; _ } = params

let deprecated { deprecated; _ } = deprecated

let unrecognized_tags { unrecognized_tags; _ } = unrecognized_tags

(***********)
(* parsing *)
(***********)

module Parser = struct
  (* regexps copied from Flow_lexer since sedlex doesn't let us import them *)

  let whitespace =
    [%sedlex.regexp?
      ( 0x0009 | 0x000B | 0x000C | 0x0020 | 0x00A0 | 0xfeff | 0x1680
      | 0x2000 .. 0x200a
      | 0x202f | 0x205f | 0x3000 )]

  let line_terminator_sequence = [%sedlex.regexp? '\n' | '\r' | "\r\n" | 0x2028 | 0x2029]

  let identifier = [%sedlex.regexp? Plus (Compl (white_space | '[' | '.' | ']' | '=' | '{'))]

  (* Helpers *)

  let empty = { description = None; params = []; deprecated = None; unrecognized_tags = [] }

  let trimmed_string_of_buffer buffer = buffer |> Buffer.contents |> String.trim

  let description_of_desc_buf desc_buf =
    match trimmed_string_of_buffer desc_buf with
    | "" -> None
    | s -> Some s

  (* like Base.List.Assoc.add, but maintains ordering differently:
   * - if k is already in the list, keeps it in that position and updates the value
   * - if k isn't in the list, adds it to the end *)
  let rec add_assoc ~equal k v = function
    | [] -> [(k, v)]
    | (k', v') :: xs ->
      if equal k' k then
        (k, v) :: xs
      else
        (k', v') :: add_assoc ~equal k v xs

  let add_param jsdoc name path description optional =
    let old_param_infos =
      match Base.List.Assoc.find ~equal:String.equal jsdoc.params name with
      | None -> []
      | Some param_infos -> param_infos
    in
    let new_param_infos =
      add_assoc ~equal:Param.equal_path path { Param.description; optional } old_param_infos
    in
    { jsdoc with params = add_assoc ~equal:String.equal name new_param_infos jsdoc.params }

  let add_unrecognized_tag jsdoc name description =
    let { unrecognized_tags; _ } = jsdoc in
    { jsdoc with unrecognized_tags = unrecognized_tags @ [(name, description)] }

  (* Parsing functions *)

  (*
     `description`, `description_or_tag`, and `description_startline` are
     helpers for parsing descriptions: a description is a possibly-multiline
     string terminated by EOF or a new tag. The beginning of each line could
     contain whitespace and asterisks, which are stripped out when parsing.
  *)
  let rec description desc_buf lexbuf =
    match%sedlex lexbuf with
    | line_terminator_sequence ->
      Buffer.add_string desc_buf (Sedlexing.Utf8.lexeme lexbuf);
      description_startline desc_buf lexbuf
    | any ->
      Buffer.add_string desc_buf (Sedlexing.Utf8.lexeme lexbuf);
      description desc_buf lexbuf
    | _ (* eof *) -> description_of_desc_buf desc_buf

  and description_or_tag desc_buf lexbuf =
    match%sedlex lexbuf with
    | '@' -> description_of_desc_buf desc_buf
    | _ -> description desc_buf lexbuf

  and description_startline desc_buf lexbuf =
    match%sedlex lexbuf with
    | '*'
    | whitespace ->
      description_startline desc_buf lexbuf
    | _ -> description_or_tag desc_buf lexbuf

  let rec param_path ?(path = Param.Name) lexbuf =
    match%sedlex lexbuf with
    | "[]" -> param_path ~path:(Param.Element path) lexbuf
    | ('.', identifier) ->
      let member = Sedlexing.Utf8.sub_lexeme lexbuf 1 (Sedlexing.lexeme_length lexbuf - 1) in
      param_path ~path:(Param.Member (path, member)) lexbuf
    | _ -> path

  let rec skip_tag jsdoc lexbuf =
    match%sedlex lexbuf with
    | Plus (Compl '@') -> skip_tag jsdoc lexbuf
    | '@' -> tag jsdoc lexbuf
    | _ (* eof *) -> jsdoc

  and param_tag_description jsdoc name path optional lexbuf =
    let desc_buf = Buffer.create 127 in
    let description = description desc_buf lexbuf in
    let jsdoc = add_param jsdoc name path description optional in
    tag jsdoc lexbuf

  and param_tag_pre_description jsdoc name path optional lexbuf =
    match%sedlex lexbuf with
    | ' ' -> param_tag_pre_description jsdoc name path optional lexbuf
    | '-' -> param_tag_description jsdoc name path optional lexbuf
    | _ -> param_tag_description jsdoc name path optional lexbuf

  and param_tag_optional_default jsdoc name path def_buf lexbuf =
    match%sedlex lexbuf with
    | ']' ->
      let default = Buffer.contents def_buf in
      param_tag_pre_description jsdoc name path (Param.OptionalWithDefault default) lexbuf
    | Plus (Compl ']') ->
      Buffer.add_string def_buf (Sedlexing.Utf8.lexeme lexbuf);
      param_tag_optional_default jsdoc name path def_buf lexbuf
    | _ ->
      let default = Buffer.contents def_buf in
      param_tag_pre_description jsdoc name path (Param.OptionalWithDefault default) lexbuf

  and param_tag_optional jsdoc lexbuf =
    match%sedlex lexbuf with
    | identifier ->
      let name = Sedlexing.Utf8.lexeme lexbuf in
      let path = param_path lexbuf in
      (match%sedlex lexbuf with
      | ']' -> param_tag_pre_description jsdoc name path Param.Optional lexbuf
      | '=' ->
        let def_buf = Buffer.create 127 in
        param_tag_optional_default jsdoc name path def_buf lexbuf
      | _ -> param_tag_pre_description jsdoc name path Param.Optional lexbuf)
    | _ -> skip_tag jsdoc lexbuf

  (* ignore jsdoc type annotation *)
  and param_tag_type jsdoc lexbuf =
    match%sedlex lexbuf with
    | '}' -> param_tag jsdoc lexbuf
    | Plus (Compl '}') -> param_tag_type jsdoc lexbuf
    | _ (* eof *) -> jsdoc

  and param_tag jsdoc lexbuf =
    match%sedlex lexbuf with
    | ' ' -> param_tag jsdoc lexbuf
    | '{' -> param_tag_type jsdoc lexbuf
    | '[' -> param_tag_optional jsdoc lexbuf
    | identifier ->
      let name = Sedlexing.Utf8.lexeme lexbuf in
      let path = param_path lexbuf in
      param_tag_pre_description jsdoc name path Param.NotOptional lexbuf
    | _ -> skip_tag jsdoc lexbuf

  and description_tag jsdoc lexbuf =
    let desc_buf = Buffer.create 127 in
    let description = description desc_buf lexbuf in
    let jsdoc = { jsdoc with description } in
    tag jsdoc lexbuf

  and deprecated_tag jsdoc lexbuf =
    let deprecated_tag_buf = Buffer.create 127 in
    let deprecated = Some (Base.Option.value ~default:"" (description deprecated_tag_buf lexbuf)) in
    { jsdoc with deprecated }

  and unrecognized_tag jsdoc name lexbuf =
    let desc_buf = Buffer.create 127 in
    let description = description desc_buf lexbuf in
    let jsdoc = add_unrecognized_tag jsdoc name description in
    tag jsdoc lexbuf

  and tag jsdoc lexbuf =
    match%sedlex lexbuf with
    | "param"
    | "arg"
    | "argument" ->
      param_tag jsdoc lexbuf
    | "description"
    | "desc" ->
      description_tag jsdoc lexbuf
    | "deprecated" -> deprecated_tag jsdoc lexbuf
    | identifier ->
      let name = Sedlexing.Utf8.lexeme lexbuf in
      unrecognized_tag jsdoc name lexbuf
    | _ -> skip_tag jsdoc lexbuf

  let initial lexbuf =
    match%sedlex lexbuf with
    | ('*', Compl '*') ->
      Sedlexing.rollback lexbuf;
      let desc_buf = Buffer.create 127 in
      let description = description_startline desc_buf lexbuf in
      let jsdoc = { empty with description } in
      Some (tag jsdoc lexbuf)
    | _ -> None
end

let parse str =
  let lexbuf = Sedlexing.Utf8.from_string str in
  Parser.initial lexbuf

(* find and parse the last jsdoc-containing comment in the list if exists *)
let of_comments =
  let open Flow_ast in
  let of_comment = function
    | (_, Comment.{ kind = Block; text; _ }) -> parse text
    | (_, Comment.{ kind = Line; _ }) -> None
  in
  let rec of_comment_list = function
    | [] -> None
    | c :: cs ->
      (match of_comment_list cs with
      | Some _ as j -> j
      | None -> of_comment c)
  in
  let of_syntax Syntax.{ leading; _ } = of_comment_list leading in
  Base.Option.bind ~f:of_syntax