jsdoc/
lib.rs

1use nom::{
2    bytes::complete::{tag, take_while},
3    error::ErrorKind,
4    IResult, InputIter, Slice,
5};
6use swc_common::{Span, Spanned};
7
8pub use self::input::Input;
9use crate::ast::*;
10
11pub mod ast;
12mod input;
13
14pub fn parse(i: Input) -> IResult<Input, JsDoc> {
15    let i = skip(i);
16
17    let mut tags = Vec::new();
18    let lo = i.span().lo;
19    let (description, mut i) = take_while(|c| c != '@')(i)?;
20    let description = trim(description).into();
21
22    i = skip(i);
23
24    while i.starts_with('@') {
25        let (input, tag) = parse_tag_item(i)?;
26        i = input;
27        tags.push(tag);
28        i = skip(i);
29    }
30
31    let hi = i.span().hi;
32    Ok((
33        i,
34        JsDoc {
35            span: Span::new(lo, hi),
36            tags,
37            description,
38        },
39    ))
40}
41
42pub fn parse_tag_item(i: Input) -> IResult<Input, TagItem> {
43    let i = skip(i);
44    let (_, i) = tag("@")(i)?;
45
46    let (mut i, tag_name) = parse_word(i)?;
47    i = skip_ws(i);
48
49    let span = tag_name.span();
50    let tag = match &*tag_name.value {
51        "abstract" | "virtual" => Tag::Abstract(AbstractTag { span }),
52
53        "access" => {
54            let (input, access) = parse_one_of(i, &["private", "protected", "package", "public"])?;
55            i = input;
56            Tag::Access(AccessTag { span, access })
57        }
58
59        "alias" => {
60            let (input, name_path) = parse_name_path(i)?;
61            i = input;
62            Tag::Alias(AliasTag { span, name_path })
63        }
64
65        "async" => Tag::Async(AsyncTag { span }),
66
67        "augments" | "extends" => {
68            let (input, name_path) = parse_name_path(i)?;
69            i = input;
70            Tag::Augments(AugmentsTag {
71                span,
72                class: name_path,
73            })
74        }
75
76        "author" => {
77            let (input, author) = parse_line(i)?;
78            i = input;
79            Tag::Author(AuthorTag { span, author })
80        }
81
82        "borrows" => {
83            let (input, from) = parse_name_path(i)?;
84            let (_, input) = tag("as")(input)?;
85            let (input, to) = parse_name_path(input)?;
86            i = input;
87            Tag::Borrows(BorrowsTag { span, from, to })
88        }
89
90        "callback" => {
91            let (input, name_path) = parse_name_path(i)?;
92            i = input;
93            Tag::Callback(CallbackTag { span, name_path })
94        }
95
96        "class" | "constructor" => {
97            // TODO: name is must if ty is some
98            let (input, ty) = parse_opt_str(i)?;
99            let (input, name) = parse_opt_str(input)?;
100            i = input;
101
102            Tag::Class(ClassTag { span, ty, name })
103        }
104
105        "classdesc" => {
106            let (input, desc) = parse_line(i)?;
107            i = input;
108
109            Tag::ClassDesc(JSDocClassDescTag { span, desc })
110        }
111
112        "constant" | "const" => {
113            // TODO: name is must if ty is some
114            let (input, ty) = parse_opt_str(i)?;
115            let (input, name) = parse_opt_str(input)?;
116            i = input;
117            Tag::Const(ConstTag { span, ty, name })
118        }
119
120        "constructs" => {
121            let (input, name) = parse_line(i)?;
122            i = input;
123            Tag::Constructs(ConstructsTag { span, name })
124        }
125
126        "copyright" => {
127            let (input, text) = parse_line(i)?;
128            i = input;
129            Tag::Copyright(CopyrightTag { span, text })
130        }
131
132        "default" | "defaultvalue" => {
133            let (input, value) = parse_line(i)?;
134            i = input;
135            Tag::Default(DefaultTag { span, value })
136        }
137
138        "deprecated" => {
139            let (input, text) = parse_line(i)?;
140            i = input;
141            Tag::Deprecated(DeprecatedTag { span, text })
142        }
143
144        "description" | "desc" => {
145            let (input, text) = parse_line(i)?;
146            i = input;
147            Tag::Description(DescriptionTag { span, text })
148        }
149
150        "enum" => {
151            let (input, ty) = parse_type(i)?;
152            i = input;
153            Tag::Enum(EnumTag { span, ty })
154        }
155
156        "event" => {
157            // TODO: implement this
158            let (input, ty) = parse_line(i)?;
159            i = input;
160            Tag::Unknown(UnknownTag { span, extras: ty })
161        }
162
163        "example" => {
164            let (text, input) = take_while(|c| c != '@')(i)?;
165            i = input;
166            Tag::Example(ExampleTag {
167                span,
168                text: text.into(),
169            })
170        }
171
172        "exports" => {
173            let (input, text) = parse_line(i)?;
174            i = input;
175            Tag::Exports(ExportsTag {
176                span,
177                module_name: text,
178            })
179        }
180
181        "external" | "host" => {
182            let (input, name) = parse_line(i)?;
183            i = input;
184            Tag::External(ExternalTag { span, name })
185        }
186
187        "file" | "fileoverview" | "overview" => {
188            let (input, text) = parse_line(i)?;
189            i = input;
190            Tag::File(FileTag { span, text })
191        }
192
193        "fires" | "emits" => {
194            // TODO: implement this
195            let (input, ty) = parse_line(i)?;
196            i = input;
197            Tag::Unknown(UnknownTag { span, extras: ty })
198        }
199
200        "function" | "func" | "method" => {
201            let (input, name) = parse_opt_str(i)?;
202            i = input;
203            Tag::Function(FunctionTag { span, name })
204        }
205
206        "generator" => Tag::Generator(GeneratorTag { span }),
207
208        "hideconstructor" => Tag::HideConstructor(HideConstructorTag { span }),
209
210        "ignore" => Tag::Ignore(IgnoreTag { span }),
211
212        "implements" => {
213            let (input, class) = parse_type(i)?;
214            i = input;
215            Tag::Implements(ImplementsTag { span, class })
216        }
217
218        "inheritdoc" => Tag::InheritDoc(InheritDocTag { span }),
219
220        "inner" => Tag::Inner(InnerTag { span }),
221
222        "instance" => Tag::Instance(InstanceTag { span }),
223
224        "interface" => {
225            let (input, name) = parse_opt_str(i)?;
226            i = input;
227            Tag::Interface(InterfaceTag { span, name })
228        }
229
230        "kind" => {
231            let (input, name) = parse_one_of(
232                i,
233                &[
234                    "class",
235                    "constant",
236                    "event",
237                    "external",
238                    "file",
239                    "function",
240                    "member",
241                    "mixin",
242                    "module",
243                    "namespace",
244                    "typedef",
245                ],
246            )?;
247            i = input;
248            Tag::Kind(KindTag { span, name })
249        }
250
251        "lends" => {
252            let (input, name) = parse_name_path(i)?;
253            i = input;
254            Tag::Lends(LendsTag { span, name })
255        }
256
257        "license" => {
258            let (input, identifier) = parse_line(i)?;
259            i = input;
260            Tag::License(LicenseTag { span, identifier })
261        }
262
263        "listens" => {
264            let (input, event_name) = parse_line(i)?;
265            i = input;
266            Tag::Listens(ListensTag { span, event_name })
267        }
268
269        "member" | "var" => {
270            let (input, ty) = parse_word(i)?;
271            let (input, name) = parse_word(input)?;
272            i = input;
273            Tag::Member(MemberTag { span, ty, name })
274        }
275
276        "memberof" | "memberof!" => {
277            let (input, parent_name_path) = parse_name_path(i)?;
278            i = input;
279            Tag::MemberOf(MemberOfTag {
280                span,
281                parent_name_path,
282            })
283        }
284
285        "mixes" => {
286            let (input, name_path) = parse_name_path(i)?;
287            i = input;
288            Tag::Mixes(MixesTag { span, name_path })
289        }
290
291        "mixin" => {
292            let (input, name) = parse_line(i)?;
293            i = input;
294            Tag::Mixin(MixinTag { span, name })
295        }
296
297        "module" => {
298            let (input, ty) = parse_word(i)?;
299            let (input, name) = parse_line(input)?;
300            i = input;
301            Tag::Module(ModuleTag { span, ty, name })
302        }
303
304        "name" => {
305            let (input, name_path) = parse_name_path(i)?;
306            i = input;
307            Tag::Name(NameTag { span, name_path })
308        }
309
310        "namespace" => {
311            let (input, ty) = parse_opt_type(i)?;
312            let (input, name) = parse_opt_str(input)?;
313            i = input;
314            Tag::Namespace(NamespaceTag { span, ty, name })
315        }
316
317        "override" => Tag::Override(OverrideTag { span }),
318
319        "package" => {
320            let (input, ty) = parse_opt_type(i)?;
321            i = input;
322            Tag::Package(PackageTag { span, ty })
323        }
324
325        "param" | "arg" | "argument" => {
326            let (input, ty) = parse_opt_type(i)?;
327            let (input, name) = parse_opt_word(input)?;
328            let (input, desc) = parse_line(input)?;
329            i = input;
330            Tag::Parameter(ParameterTag {
331                span,
332                ty,
333                name,
334                desc,
335            })
336        }
337
338        "private" => {
339            let (input, ty) = parse_opt_type(i)?;
340            i = input;
341            Tag::Private(PrivateTag { span, ty })
342        }
343
344        "property" | "prop" => {
345            let (input, ty) = parse_opt_type(i)?;
346            let (input, name_path) = parse_name_path(input)?;
347            let (input, desc) = parse_line(input)?;
348            i = input;
349            Tag::Property(PropertyTag {
350                span,
351                ty,
352                name_path,
353                desc,
354            })
355        }
356
357        "protected" => {
358            let (input, ty) = parse_opt_type(i)?;
359            i = input;
360            Tag::Protected(ProtectedTag { span, ty })
361        }
362
363        "public" => Tag::Public(PublicTag { span }),
364
365        "readonly" => Tag::Readonly(ReadonlyTag { span }),
366
367        "requires" => {
368            let (input, name_path) = parse_name_path(i)?;
369            i = input;
370            Tag::Requires(RequiresTag { span, name_path })
371        }
372
373        "returns" | "return" => {
374            let (input, ty) = parse_opt_type(i)?;
375            let (input, description) = parse_line(input)?;
376            i = input;
377            Tag::Return(ReturnTag {
378                span,
379                ty,
380                description,
381            })
382        }
383
384        "see" => {
385            let (input, text) = parse_line(i)?;
386            i = input;
387            Tag::See(SeeTag { span, text })
388        }
389
390        "since" => {
391            let (input, version) = parse_line(i)?;
392            i = input;
393            Tag::Since(SinceTag { span, version })
394        }
395
396        "static" => Tag::Static(StaticTag { span }),
397
398        "summary" => {
399            let (input, text) = parse_line(i)?;
400            i = input;
401            Tag::Summary(SummaryTag { span, text })
402        }
403
404        "this" => {
405            let (input, name_path) = parse_name_path(i)?;
406            i = input;
407            Tag::This(ThisTag { span, name_path })
408        }
409
410        "throws" => {
411            let (input, text) = parse_line(i)?;
412            i = input;
413            Tag::Throw(ThrowTag { span, text })
414        }
415
416        "todo" => {
417            let (input, text) = parse_line(i)?;
418            i = input;
419            Tag::Todo(TodoTag { span, text })
420        }
421
422        "tutorial" => {
423            let (input, text) = parse_line(i)?;
424            i = input;
425            Tag::Tutorial(TutorialTag { span, text })
426        }
427
428        "type" => {
429            let (input, name) = parse_line(i)?;
430            i = input;
431            Tag::Type(TypeTag { span, name })
432        }
433
434        "typedef" => {
435            let (input, ty) = parse_opt_type(i)?;
436            let (input, name_path) = parse_name_path(input)?;
437            i = input;
438            Tag::TypeDef(TypeDefTag {
439                span,
440                ty,
441                name_path,
442            })
443        }
444
445        "variation" => {
446            let (input, number) = parse_line(i)?;
447            i = input;
448            Tag::Variation(VariationTag { span, number })
449        }
450
451        "version" => {
452            let (input, value) = parse_line(i)?;
453            i = input;
454            Tag::Version(VersionTag { span, value })
455        }
456
457        "yields" | "yield" => {
458            let (input, value) = parse_opt_type(i)?;
459            let (input, description) = parse_line(input)?;
460            i = input;
461            Tag::Yield(YieldTag {
462                span,
463                value,
464                description,
465            })
466        }
467
468        _ => {
469            let (input, extras) = parse_str(i)?;
470            i = input;
471            Tag::Unknown(UnknownTag { span, extras })
472        }
473    };
474
475    Ok((
476        i,
477        TagItem {
478            span,
479            tag_name,
480            tag,
481        },
482    ))
483}
484
485fn parse_opt_str(i: Input) -> IResult<Input, Option<Text>> {
486    let (i, res) = parse_line(i)?;
487
488    if res.value.is_empty() {
489        Ok((i, None))
490    } else {
491        Ok((i, Some(res)))
492    }
493}
494
495fn parse_str(i: Input) -> IResult<Input, Text> {
496    parse_line(i)
497}
498
499fn parse_type(i: Input) -> IResult<Input, Text> {
500    parse_line(i)
501}
502
503// ----- ----- Done ----- -----
504
505fn trim(i: Input) -> Input {
506    let prev_len = i.len();
507    let new_str = i.trim_start();
508
509    let start = prev_len - new_str.len();
510
511    let end = i.trim_end().len();
512
513    if start >= end {
514        return i;
515    }
516
517    i.slice(start..end)
518}
519
520fn parse_opt_type(i: Input) -> IResult<Input, Option<Text>> {
521    let i = skip_ws(i);
522    if i.starts_with('{') {
523        if let Some(pos) = i.find('}') {
524            let ret = i.slice(..pos + 1);
525            let i = i.slice(pos + 1..);
526            return Ok((i, Some(ret.into())));
527        }
528    }
529
530    parse_opt_word(i)
531}
532
533fn parse_one_of<'i>(i: Input<'i>, list: &[&str]) -> IResult<Input<'i>, Text> {
534    for &item in list {
535        if i.starts_with(item) {
536            let res = tag::<&str, Input<'_>, (_, ErrorKind)>(item)(i);
537            match res {
538                Ok(v) => return Ok((v.1, v.0.into())),
539                Err(..) => continue,
540            }
541        }
542    }
543
544    Err(nom::Err::Error(nom::error::Error::new(i, ErrorKind::Tag)))
545}
546
547fn parse_name_path(mut i: Input) -> IResult<Input, NamePath> {
548    let lo = i.span().lo;
549    let mut components = Vec::new();
550
551    loop {
552        let (input, component) = parse_word(i)?;
553        components.push(component);
554        i = input;
555
556        let (_, input) = match tag(".")(i) {
557            Ok(v) => v,
558            Err(err) => {
559                if components.is_empty() {
560                    return Err(err);
561                }
562
563                return Ok((
564                    i,
565                    NamePath {
566                        span: Span::new(lo, i.span().hi),
567                        components,
568                    },
569                ));
570            }
571        };
572        i = input;
573    }
574}
575
576fn parse_opt_word(i: Input) -> IResult<Input, Option<Text>> {
577    let (i, v) = parse_word(i)?;
578    if v.value.is_empty() {
579        return Ok((i, None));
580    }
581
582    Ok((i, Some(v)))
583}
584
585fn parse_word(i: Input) -> IResult<Input, Text> {
586    let res = i.iter_indices().find(|(_, c)| {
587        !(('a' <= *c && *c <= 'z') || ('A' <= *c && *c <= 'Z' || *c == '<' || *c == '>'))
588    });
589
590    if let Some((idx, _)) = res {
591        let rest = i.slice(idx + 1..);
592        let ret = i.slice(..idx);
593
594        Ok((rest, ret.into()))
595    } else {
596        // Everything was alphabet
597        Ok((Input::empty(), i.into()))
598    }
599}
600
601fn parse_line(i: Input) -> IResult<Input, Text> {
602    let i = skip_ws(i);
603    let res = i.iter_indices().find(|(_, c)| *c == '\n' || *c == '\r');
604
605    if let Some((idx, _)) = res {
606        let rest = i.slice(idx + 1..);
607        let ret = i.slice(..idx);
608
609        Ok((rest, ret.into()))
610    } else {
611        Ok((i, Input::empty().into()))
612    }
613}
614
615/// Skips whitespace
616///
617/// This function does not handle newline nor *.
618fn skip_ws(i: Input) -> Input {
619    let mut index = 0;
620
621    for (idx, c) in i.char_indices() {
622        if c == ' ' {
623            index = idx + 1;
624            continue;
625        }
626
627        break;
628    }
629
630    i.slice(index..)
631}
632
633/// Skips whitespace and * at line start
634fn skip(i: Input) -> Input {
635    //
636
637    let mut at_line_start = true;
638    let mut index = 0;
639
640    for (idx, c) in i.char_indices() {
641        if at_line_start && c == '*' {
642            at_line_start = false;
643            index = idx + 1;
644            continue;
645        }
646
647        if c == '\r' || c == '\n' {
648            at_line_start = true;
649            index = idx + 1;
650            continue;
651        }
652
653        if c == ' ' {
654            index = idx + 1;
655            continue;
656        }
657
658        break;
659    }
660
661    i.slice(index..)
662}
663
664#[cfg(test)]
665mod tests {
666    use swc_common::BytePos;
667
668    use super::*;
669
670    fn input(s: &str) -> Input {
671        Input::new(BytePos(0), BytePos(s.len() as _), s)
672    }
673
674    #[test]
675    fn issue_1058() {
676        let (i, ret) = parse(input(
677            r#"
678            *
679            * @param list - LinkedList<T>
680            * @example <caption>Linkedlists.compareWith</caption>
681            * import { LinkedList } from './js_test/linkedlist.ts'
682            * const testArr = [1, 2, 3, 4, 5, 6, 78, 9, 0, 65];
683            * const firstList = new LinkedList<number>();
684            * const secondList = new LinkedList<number>();
685            * for (let data of testArr) {
686            *   firstList.insertNode(data);
687            *   secondList.insertNode(data);
688            * }
689            * const result = firstList.compareWith(secondList);
690            * assert(result);
691            * @returns boolean
692            "#,
693        ))
694        .unwrap();
695
696        assert_eq!(&*skip(i), "");
697        assert_eq!(ret.tags.len(), 3);
698    }
699
700    #[test]
701    fn access_tag() {
702        let (i, ret) = parse(input(
703            " Give x another name.
704        @alias myObject
705        @namespace
706     ",
707        ))
708        .unwrap();
709
710        assert_eq!(&*ret.description.value, "Give x another name.");
711        assert_eq!(ret.tags.len(), 2);
712        assert_eq!(&*skip(i), "");
713    }
714
715    #[test]
716    fn abstract_tag() {
717        let (i, ret) = parse_tag_item(input(
718            "
719        * @abstract ",
720        ))
721        .unwrap();
722
723        match ret.tag {
724            Tag::Abstract(..) => {}
725            _ => panic!("Invalid tag: {:?}", ret.tag),
726        }
727
728        assert_eq!(&*skip(i), "");
729    }
730
731    #[test]
732    fn yield_tag_1() {
733        let (i, ret) = parse_tag_item(input(
734            "
735        *
736        * @yields {number} The next number in the Fibonacci sequence.
737       ",
738        ))
739        .unwrap();
740
741        match ret.tag {
742            Tag::Yield(tag) => {
743                assert_eq!(tag.value.as_ref().map(|v| &*v.value), Some("{number}"));
744                assert_eq!(
745                    &*tag.description.value,
746                    "The next number in the Fibonacci sequence."
747                );
748            }
749            _ => panic!("Invalid tag: {:?}", ret.tag),
750        }
751
752        assert_eq!(&*skip(i), "");
753    }
754
755    #[test]
756    fn trim_1() {
757        assert_eq!(&*trim(input(" foo ")), "foo");
758        assert_eq!(&*trim(input("foo ")), "foo");
759        assert_eq!(&*trim(input(" foo")), "foo");
760    }
761
762    #[test]
763    fn skip_1() {
764        let i = skip(input(
765            "
766        *
767        * @yields ",
768        ));
769
770        assert_eq!(&*i, "@yields ");
771    }
772
773    #[test]
774    fn one_of_1() {
775        let (rest, ret) = parse_one_of(input("foo bar\nbaz"), &["fo", "bar"]).unwrap();
776
777        assert_eq!(&*ret.value, "fo");
778        assert_eq!(&*rest, "o bar\nbaz");
779    }
780
781    #[test]
782    fn line_1() {
783        let (rest, ret) = parse_line(input("foo bar\nbaz")).unwrap();
784
785        assert_eq!(&*ret.value, "foo bar");
786        assert_eq!(&*rest, "baz");
787    }
788
789    #[test]
790    fn word_1() {
791        let (rest, ret) = parse_word(input("foo bar\nbaz")).unwrap();
792
793        assert_eq!(&*ret.value, "foo");
794        assert_eq!(&*rest, "bar\nbaz");
795    }
796}