microsoft/qdk

Public

mirrored from https://github.com/microsoft/qdkAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
iadavis/pipeline-issue-debugging

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

source/compiler/qsc_doc_gen/src/display.rs

813lines · modecode

1// Copyright (c) Microsoft Corporation.
2// Licensed under the MIT License.
3
4use qsc_ast::ast::{self, Idents, TypeParameter as AstTypeParameter};
5use qsc_frontend::resolve;
6use qsc_hir::{
7 hir,
8 ty::{self, TypeParameter as HirTypeParameter},
9};
10use regex_lite::Regex;
11use std::{
12 fmt::{Display, Formatter, Result},
13 rc::Rc,
14};
15
16/// Trait describing a struct capable of resolving various ids found in the AST and HIR.
17pub trait Lookup {
18 /// Looks up the type of a node in user code
19 fn get_ty(&self, expr_id: ast::NodeId) -> Option<&ty::Ty>;
20
21 /// Looks up the resolution of a node in user code
22 fn get_res(&self, id: ast::NodeId) -> Option<&resolve::Res>;
23
24 /// Returns the hir `Item` node referred to by `item_id`,
25 /// along with the `Package` and `PackageId` for the package
26 /// that it was found in.
27 fn resolve_item_relative_to_user_package(
28 &self,
29 item_id: &hir::ItemId,
30 ) -> (&hir::Item, &hir::Package, hir::ItemId);
31
32 /// Returns the hir `Item` node referred to by `res`.
33 /// `Res`s can resolve to external packages, and the references
34 /// are relative, so here we also need the
35 /// local `PackageId` that the `res` itself came from.
36 fn resolve_item_res(&self, res: &hir::Res) -> (&hir::Item, hir::ItemId);
37
38 /// Returns the hir `Item` node referred to by `item_id`.
39 /// `ItemId`s can refer to external packages, and the references
40 /// are relative, so here we also need the local `PackageId`
41 /// that the `ItemId` originates from.
42 fn resolve_item(&self, item_id: &hir::ItemId) -> (&hir::Item, &hir::Package, hir::ItemId);
43}
44
45pub struct CodeDisplay<'a> {
46 pub compilation: &'a dyn Lookup,
47}
48
49#[allow(clippy::unused_self)]
50impl<'a> CodeDisplay<'a> {
51 #[must_use]
52 pub fn hir_callable_decl(&self, decl: &'a hir::CallableDecl) -> impl Display + '_ {
53 HirCallableDecl { decl }
54 }
55
56 #[must_use]
57 pub fn ast_callable_decl(&self, decl: &'a ast::CallableDecl) -> impl Display + '_ {
58 AstCallableDecl {
59 lookup: self.compilation,
60 decl,
61 }
62 }
63
64 #[must_use]
65 pub fn name_ty_id(&self, name: &'a str, ty_id: ast::NodeId) -> impl Display + '_ {
66 NameTyId {
67 lookup: self.compilation,
68 name,
69 ty_id,
70 }
71 }
72
73 #[must_use]
74 pub fn ident_ty(&self, ident: &'a ast::Ident, ty: &'a ast::Ty) -> impl Display + '_ {
75 IdentTy { ident, ty }
76 }
77
78 #[must_use]
79 pub fn ident_ty_def(&self, ident: &'a ast::Ident, def: &'a ast::TyDef) -> impl Display + 'a {
80 IdentTyDef { ident, def }
81 }
82
83 #[must_use]
84 pub fn struct_decl(&self, decl: &'a ast::StructDecl) -> impl Display + 'a {
85 StructDecl { decl }
86 }
87
88 #[must_use]
89 pub fn hir_udt_field(&self, field: &'a ty::UdtField) -> impl Display + '_ {
90 HirUdtField { field }
91 }
92
93 #[must_use]
94 pub fn hir_udt(&self, udt: &'a ty::Udt) -> impl Display + '_ {
95 HirUdt::new(udt)
96 }
97
98 #[must_use]
99 pub fn hir_pat(&self, pat: &'a hir::Pat) -> impl Display + '_ {
100 HirPat { pat }
101 }
102
103 #[must_use]
104 pub fn get_param_offset(&self, decl: &hir::CallableDecl) -> u32 {
105 HirCallableDecl { decl }.get_param_offset()
106 }
107
108 // The rest of the display implementations are not made public b/c they're not used,
109 // but there's no reason they couldn't be
110}
111
112// Display impls for each syntax/hir element we may encounter
113
114struct IdentTy<'a> {
115 ident: &'a ast::Ident,
116 ty: &'a ast::Ty,
117}
118
119impl Display for IdentTy<'_> {
120 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
121 write!(f, "{} : {}", self.ident.name, AstTy { ty: self.ty },)
122 }
123}
124
125struct NameTyId<'a> {
126 lookup: &'a dyn Lookup,
127 name: &'a str,
128 ty_id: ast::NodeId,
129}
130
131impl Display for NameTyId<'_> {
132 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
133 write!(
134 f,
135 "{} : {}",
136 self.name,
137 TyId {
138 lookup: self.lookup,
139 ty_id: self.ty_id,
140 },
141 )
142 }
143}
144
145struct HirCallableDecl<'a> {
146 decl: &'a hir::CallableDecl,
147}
148
149impl HirCallableDecl<'_> {
150 fn get_param_offset(&self) -> u32 {
151 let offset = match self.decl.kind {
152 hir::CallableKind::Function => "function".len(),
153 hir::CallableKind::Operation => "operation".len(),
154 } + 1 // this is for the space between keyword and name
155 + self.decl.name.name.len()
156 + display_type_params(&self.decl.generics).len();
157
158 u32::try_from(offset)
159 .expect("failed to cast usize to u32 while calculating parameter offset")
160 }
161}
162
163impl Display for HirCallableDecl<'_> {
164 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
165 let kind = match self.decl.kind {
166 hir::CallableKind::Function => "function",
167 hir::CallableKind::Operation => "operation",
168 };
169
170 write!(f, "{} {}", kind, self.decl.name.name)?;
171 let type_params = display_type_params(&self.decl.generics);
172 write!(f, "{type_params}")?;
173 let input = HirPat {
174 pat: &self.decl.input,
175 };
176 if matches!(self.decl.input.kind, hir::PatKind::Tuple(_)) {
177 write!(f, "{input}")?;
178 } else {
179 write!(f, "({input})")?;
180 }
181 write!(
182 f,
183 " : {}{}",
184 self.decl.output.display(),
185 FunctorSetValue {
186 functors: self.decl.functors,
187 },
188 )
189 }
190}
191
192struct AstCallableDecl<'a> {
193 lookup: &'a dyn Lookup,
194 decl: &'a ast::CallableDecl,
195}
196
197impl Display for AstCallableDecl<'_> {
198 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
199 let kind = match self.decl.kind {
200 ast::CallableKind::Function => "function",
201 ast::CallableKind::Operation => "operation",
202 };
203
204 let functors = ast_callable_functors(self.decl);
205 let functors = FunctorSetValue { functors };
206
207 write!(f, "{} {}", kind, self.decl.name.name)?;
208 if !self.decl.generics.is_empty() {
209 let type_params = self
210 .decl
211 .generics
212 .iter()
213 .map(
214 |AstTypeParameter {
215 ty, constraints, ..
216 }| {
217 format!(
218 "{}{}",
219 ty.name,
220 if constraints.0.is_empty() {
221 Default::default()
222 } else {
223 format!(
224 ": {}",
225 constraints
226 .0
227 .iter()
228 .map(|bound| {
229 let constraint_parameters = bound
230 .parameters
231 .iter()
232 .map(|x| format!("{}", AstTy { ty: &x.ty }))
233 .collect::<Vec<_>>()
234 .join(", ");
235 format!(
236 "{}{}",
237 bound.name.name,
238 if constraint_parameters.is_empty() {
239 Default::default()
240 } else {
241 format!("[{constraint_parameters}]")
242 }
243 )
244 })
245 .collect::<Vec<_>>()
246 .join(" + ")
247 )
248 }
249 )
250 },
251 )
252 .collect::<Vec<_>>()
253 .join(", ");
254 write!(f, "<{type_params}>")?;
255 }
256 let input = AstPat {
257 pat: &self.decl.input,
258 lookup: self.lookup,
259 };
260 if matches!(*self.decl.input.kind, ast::PatKind::Tuple(_)) {
261 write!(f, "{input}")?;
262 } else {
263 write!(f, "({input})")?;
264 }
265 write!(
266 f,
267 " : {}{}",
268 AstTy {
269 ty: &self.decl.output
270 },
271 functors,
272 )
273 }
274}
275
276struct HirPat<'a> {
277 pat: &'a hir::Pat,
278}
279
280impl Display for HirPat<'_> {
281 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
282 match &self.pat.kind {
283 hir::PatKind::Bind(name) => write!(f, "{} : {}", name.name, self.pat.ty.display()),
284 hir::PatKind::Discard => write!(f, "_ : {}", self.pat.ty.display()),
285 hir::PatKind::Tuple(items) => {
286 let mut elements = items.iter();
287 if let Some(elem) = elements.next() {
288 write!(f, "({}", HirPat { pat: elem })?;
289 for elem in elements {
290 write!(f, ", {}", HirPat { pat: elem })?;
291 }
292 write!(f, ")")
293 } else {
294 write!(f, "()")
295 }
296 }
297 hir::PatKind::Err => write!(f, "?"),
298 }
299 }
300}
301
302struct AstPat<'a> {
303 lookup: &'a dyn Lookup,
304 pat: &'a ast::Pat,
305}
306
307impl Display for AstPat<'_> {
308 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
309 match &*self.pat.kind {
310 ast::PatKind::Bind(ident, anno) => match anno {
311 Some(ty) => write!(f, "{}", IdentTy { ident, ty }),
312 None => write!(
313 f,
314 "{}",
315 NameTyId {
316 lookup: self.lookup,
317 name: &ident.name,
318 ty_id: self.pat.id
319 }
320 ),
321 },
322 ast::PatKind::Discard(anno) => match anno {
323 Some(ty) => write!(f, "{}", AstTy { ty }),
324 None => write!(
325 f,
326 "_ : {}",
327 TyId {
328 lookup: self.lookup,
329 ty_id: self.pat.id,
330 }
331 ),
332 },
333 ast::PatKind::Elided => write!(f, "..."),
334 ast::PatKind::Paren(item) => write!(
335 f,
336 "{}",
337 AstPat {
338 lookup: self.lookup,
339 pat: item,
340 }
341 ),
342 ast::PatKind::Tuple(items) => {
343 let mut elements = items.iter();
344 if let Some(elem) = elements.next() {
345 write!(
346 f,
347 "({}",
348 AstPat {
349 lookup: self.lookup,
350 pat: elem,
351 }
352 )?;
353 for elem in elements {
354 write!(
355 f,
356 ", {}",
357 AstPat {
358 lookup: self.lookup,
359 pat: elem,
360 }
361 )?;
362 }
363 write!(f, ")")
364 } else {
365 write!(f, "()")
366 }
367 }
368 ast::PatKind::Err => write!(f, "?"),
369 }
370 }
371}
372
373struct IdentTyDef<'a> {
374 ident: &'a ast::Ident,
375 def: &'a ast::TyDef,
376}
377
378impl Display for IdentTyDef<'_> {
379 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
380 if let Some(fields) = as_struct(self.def) {
381 write!(f, "struct {} ", self.ident.name)?;
382 fmt_brace_seq(f, &fields, |item| IdentTy {
383 ident: &item.name,
384 ty: &item.ty,
385 })
386 } else {
387 write!(
388 f,
389 "newtype {} = {}",
390 self.ident.name,
391 TyDef { def: self.def }
392 )
393 }
394 }
395}
396
397struct StructDecl<'a> {
398 decl: &'a ast::StructDecl,
399}
400
401impl Display for StructDecl<'_> {
402 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
403 write!(f, "struct {} ", self.decl.name.name)?;
404 fmt_brace_seq(f, &self.decl.fields, |item| IdentTy {
405 ident: &item.name,
406 ty: &item.ty,
407 })
408 }
409}
410
411struct HirUdt<'a> {
412 udt: &'a ty::Udt,
413 is_struct: bool,
414}
415
416impl<'a> HirUdt<'a> {
417 fn new(udt: &'a ty::Udt) -> Self {
418 HirUdt {
419 udt,
420 is_struct: udt.is_struct(),
421 }
422 }
423}
424
425impl Display for HirUdt<'_> {
426 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
427 if self.is_struct {
428 match &self.udt.definition.kind {
429 ty::UdtDefKind::Tuple(fields) => {
430 write!(f, "struct {} ", self.udt.name)?;
431 fmt_brace_seq(f, fields, UdtDef::new)?;
432 }
433 ty::UdtDefKind::Field(_) => {}
434 }
435 Ok(())
436 } else {
437 let udt_def = UdtDef::new(&self.udt.definition);
438 write!(f, "newtype {} = {}", self.udt.name, udt_def)
439 }
440 }
441}
442
443struct UdtDef<'a> {
444 name: Option<Rc<str>>,
445 kind: UdtDefKind<'a>,
446}
447
448enum UdtDefKind<'a> {
449 SingleTy(&'a ty::Ty),
450 TupleTy(Vec<UdtDef<'a>>),
451}
452
453impl<'a> UdtDef<'a> {
454 pub fn new(def: &'a ty::UdtDef) -> Self {
455 match &def.kind {
456 ty::UdtDefKind::Field(field) => UdtDef {
457 name: field.name.clone(),
458 kind: UdtDefKind::SingleTy(&field.ty),
459 },
460 ty::UdtDefKind::Tuple(defs) => UdtDef {
461 name: None,
462 kind: UdtDefKind::TupleTy(defs.iter().map(UdtDef::new).collect()),
463 },
464 }
465 }
466}
467
468impl Display for UdtDef<'_> {
469 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
470 if let Some(name) = &self.name {
471 write!(f, "{name} : ")?;
472 }
473
474 match &self.kind {
475 UdtDefKind::SingleTy(ty) => {
476 write!(f, "{}", ty.display())
477 }
478 UdtDefKind::TupleTy(defs) => fmt_tuple(f, defs, |def| def),
479 }
480 }
481}
482
483struct HirUdtField<'a> {
484 field: &'a ty::UdtField,
485}
486
487impl Display for HirUdtField<'_> {
488 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
489 if let Some(doc) = &self.field.doc {
490 for line in doc.lines() {
491 writeln!(f, "/// {line}")?;
492 }
493 }
494 if let Some(name) = &self.field.name {
495 write!(f, "{name} : ")?;
496 }
497 write!(f, "{}", self.field.ty.display())
498 }
499}
500
501struct FunctorSetValue {
502 functors: ty::FunctorSetValue,
503}
504
505impl Display for FunctorSetValue {
506 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
507 if let ty::FunctorSetValue::Empty = self.functors {
508 Ok(())
509 } else {
510 write!(f, " is {}", self.functors)
511 }
512 }
513}
514
515struct TyId<'a> {
516 lookup: &'a dyn Lookup,
517 ty_id: ast::NodeId,
518}
519
520impl Display for TyId<'_> {
521 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
522 if let Some(ty) = self.lookup.get_ty(self.ty_id) {
523 write!(f, "{}", ty.display())
524 } else {
525 write!(f, "?")
526 }
527 }
528}
529
530struct AstTy<'a> {
531 ty: &'a ast::Ty,
532}
533
534impl Display for AstTy<'_> {
535 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
536 match self.ty.kind.as_ref() {
537 ast::TyKind::Array(ty) => write!(f, "{}[]", AstTy { ty }),
538 ast::TyKind::Arrow(kind, input, output, functors) => {
539 let arrow = match kind {
540 ast::CallableKind::Function => "->",
541 ast::CallableKind::Operation => "=>",
542 };
543 write!(
544 f,
545 "({} {} {}{})",
546 AstTy { ty: input },
547 arrow,
548 AstTy { ty: output },
549 FunctorExpr { functors }
550 )
551 }
552 ast::TyKind::Hole => write!(f, "_"),
553 ast::TyKind::Paren(ty) => write!(f, "{}", AstTy { ty }),
554 ast::TyKind::Path(path) => write!(f, "{}", AstPathKind { path }),
555 ast::TyKind::Param(AstTypeParameter { ty, .. }) => write!(f, "{}", ty.name),
556 ast::TyKind::Tuple(tys) => fmt_tuple(f, tys, |ty| AstTy { ty }),
557 ast::TyKind::Err => write!(f, "?"),
558 }
559 }
560}
561
562struct FunctorExpr<'a> {
563 functors: &'a Option<Box<ast::FunctorExpr>>,
564}
565
566impl Display for FunctorExpr<'_> {
567 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
568 match self.functors {
569 Some(functors) => {
570 let functors = eval_functor_expr(functors);
571 write!(f, "{}", FunctorSetValue { functors })
572 }
573 None => Ok(()),
574 }
575 }
576}
577
578struct AstPathKind<'a> {
579 path: &'a ast::PathKind,
580}
581
582impl Display for AstPathKind<'_> {
583 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
584 if let ast::PathKind::Ok(path) = self.path {
585 write!(f, "{}", path.full_name())
586 } else {
587 write!(f, "?")
588 }
589 }
590}
591
592struct TyDef<'a> {
593 def: &'a ast::TyDef,
594}
595
596impl Display for TyDef<'_> {
597 fn fmt(&self, f: &mut Formatter<'_>) -> Result {
598 match self.def.kind.as_ref() {
599 ast::TyDefKind::Field(name, ty, _) => match name {
600 Some(name) => write!(f, "{} : {}", name.name, AstTy { ty }),
601 None => write!(f, "{}", AstTy { ty }),
602 },
603 ast::TyDefKind::Paren(def) => write!(f, "{}", TyDef { def }),
604 ast::TyDefKind::Tuple(tys) => fmt_tuple(f, tys, |def| TyDef { def }),
605 ast::TyDefKind::Err => write!(f, "?"),
606 }
607 }
608}
609
610fn fmt_tuple<'a, I, O>(
611 formatter: &mut Formatter,
612 items: &'a [I],
613 map: impl Fn(&'a I) -> O,
614) -> Result
615where
616 O: Display,
617{
618 let mut elements = items.iter();
619 if let Some(elem) = elements.next() {
620 write!(formatter, "({}", map(elem))?;
621 if elements.len() == 0 {
622 write!(formatter, ",)")?;
623 } else {
624 for elem in elements {
625 write!(formatter, ", {}", map(elem))?;
626 }
627 write!(formatter, ")")?;
628 }
629 } else {
630 write!(formatter, "Unit")?;
631 }
632 Ok(())
633}
634
635fn fmt_brace_seq<'a, I, O>(
636 formatter: &mut Formatter<'_>,
637 items: &'a [I],
638 map: impl Fn(&'a I) -> O,
639) -> Result
640where
641 O: Display,
642{
643 write!(formatter, "{{ ")?;
644 if let Some((last, most)) = items.split_last() {
645 for item in most {
646 write!(formatter, "{}, ", map(item))?;
647 }
648 write!(formatter, "{} ", map(last))?;
649 }
650 write!(formatter, "}}")
651}
652
653fn display_type_params(generics: &[HirTypeParameter]) -> String {
654 let type_params = generics
655 .iter()
656 .filter_map(|generic| match generic {
657 HirTypeParameter::Ty { name, bounds } => Some(format!(
658 "{}{}",
659 name,
660 if bounds.is_empty() {
661 Default::default()
662 } else {
663 format!(": {bounds}")
664 }
665 )),
666 HirTypeParameter::Functor(_) => None,
667 })
668 .collect::<Vec<_>>()
669 .join(", ");
670 if type_params.is_empty() {
671 type_params
672 } else {
673 format!("<{type_params}>")
674 }
675}
676
677//
678// helpers that don't manipulate any strings
679//
680
681fn ast_callable_functors(callable: &ast::CallableDecl) -> ty::FunctorSetValue {
682 let mut functors = callable
683 .functors
684 .as_ref()
685 .map_or(ty::FunctorSetValue::Empty, |f| {
686 eval_functor_expr(f.as_ref())
687 });
688
689 if let ast::CallableBody::Specs(specs) = callable.body.as_ref() {
690 for spec in specs {
691 let spec_functors = match spec.spec {
692 ast::Spec::Body => ty::FunctorSetValue::Empty,
693 ast::Spec::Adj => ty::FunctorSetValue::Adj,
694 ast::Spec::Ctl => ty::FunctorSetValue::Ctl,
695 ast::Spec::CtlAdj => ty::FunctorSetValue::CtlAdj,
696 };
697 functors = functors.union(&spec_functors);
698 }
699 }
700
701 functors
702}
703
704fn eval_functor_expr(expr: &ast::FunctorExpr) -> ty::FunctorSetValue {
705 match expr.kind.as_ref() {
706 ast::FunctorExprKind::BinOp(op, lhs, rhs) => {
707 let lhs_functors = eval_functor_expr(lhs);
708 let rhs_functors = eval_functor_expr(rhs);
709 match op {
710 ast::SetOp::Union => lhs_functors.union(&rhs_functors),
711 ast::SetOp::Intersect => lhs_functors.intersect(&rhs_functors),
712 }
713 }
714 ast::FunctorExprKind::Lit(ast::Functor::Adj) => ty::FunctorSetValue::Adj,
715 ast::FunctorExprKind::Lit(ast::Functor::Ctl) => ty::FunctorSetValue::Ctl,
716 ast::FunctorExprKind::Paren(inner) => eval_functor_expr(inner),
717 }
718}
719
720fn as_struct(ty_def: &ast::TyDef) -> Option<Vec<ast::FieldDef>> {
721 match ty_def.kind.as_ref() {
722 ast::TyDefKind::Paren(inner) => as_struct(inner),
723 ast::TyDefKind::Tuple(fields) => {
724 let mut converted_fields = Vec::new();
725 for field in fields {
726 let field = remove_parens(field);
727 match field.kind.as_ref() {
728 ast::TyDefKind::Field(Some(name), field_ty, doc) => {
729 converted_fields.push(ast::FieldDef {
730 id: field.id,
731 span: field.span,
732 name: name.clone(),
733 doc: doc.as_ref().map(Rc::clone),
734 ty: field_ty.clone(),
735 });
736 }
737 _ => return None,
738 }
739 }
740 Some(converted_fields)
741 }
742 ast::TyDefKind::Err | ast::TyDefKind::Field(..) => None,
743 }
744}
745
746fn remove_parens(ty_def: &ast::TyDef) -> &ast::TyDef {
747 match ty_def.kind.as_ref() {
748 ast::TyDefKind::Paren(inner) => remove_parens(inner.as_ref()),
749 _ => ty_def,
750 }
751}
752
753//
754// parsing functions for working with doc comments
755//
756
757/// Takes a doc string from Q# and increases all of the markdown header levels by one level.
758/// i.e. `# Summary` becomes `## Summary`
759#[must_use]
760pub fn increase_header_level(doc: &str) -> String {
761 let re = Regex::new(r"(?mi)^(#+)( [\s\S]+?)$").expect("Invalid regex");
762 re.replace_all(doc, "$1#$2").to_string()
763}
764
765/// Takes a doc string from Q# and returns the contents of the `# Summary` section. If no
766/// such section can be found, returns the original doc string.
767#[must_use]
768pub fn parse_doc_for_summary(doc: &str) -> String {
769 let re = Regex::new(r"(?mi)(?:^# Summary$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex");
770 match re.captures(doc) {
771 Some(captures) => {
772 let capture = captures
773 .get(1)
774 .expect("Didn't find the capture for the given regex");
775 capture.as_str()
776 }
777 None => doc,
778 }
779 .trim()
780 .to_string()
781}
782
783/// Takes a doc string from a Q# callable and the name of a parameter of
784/// that callable. Returns the description of that parameter found in the
785/// doc string. If no description is found, returns the empty string.
786#[must_use]
787pub fn parse_doc_for_param(doc: &str, param: &str) -> String {
788 let re = Regex::new(r"(?mi)(?:^# Input$)([\s\S]*?)(?:(^# .*)|\z)").expect("Invalid regex");
789 let input = match re.captures(doc) {
790 Some(captures) => {
791 let capture = captures
792 .get(1)
793 .expect("Didn't find the capture for the given regex");
794 capture.as_str()
795 }
796 None => return String::new(),
797 }
798 .trim();
799
800 let re = Regex::new(format!(r"(?mi)(?:^## {param}$)([\s\S]*?)(?:(^(#|##) .*)|\z)").as_str())
801 .expect("Invalid regex");
802 match re.captures(input) {
803 Some(captures) => {
804 let capture = captures
805 .get(1)
806 .expect("Didn't find the capture for the given regex");
807 capture.as_str()
808 }
809 None => return String::new(),
810 }
811 .trim()
812 .to_string()
813}
814