1use fml_syntax::ast::{
9 self, AstNode, ConceptMap as ConceptMapNode, Expr, GroupTypeMode, ListMode, ModelMode,
10 ParameterMode, TransformValue,
11};
12use fml_syntax::{
13 Diagnostic, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, parse,
14 quote_fhirpath_string,
15};
16use serde_json::{Map, Value, json};
17
18use crate::model::{
19 Constant, Dependent, Extension, Group, Input, Parameter, ParameterValue, Rule, Source,
20 Structure, StructureMap, Target,
21};
22
23pub const SOURCE_ELEMENT_EXTENSION: &str =
25 "http://hl7.org/fhir/6.0/StructureDefinition/extension-StructureMap.group.rule.source.element";
26pub const TARGET_ELEMENT_EXTENSION: &str =
28 "http://hl7.org/fhir/6.0/StructureDefinition/extension-StructureMap.group.rule.target.element";
29pub const REFERENCES_CONTAINED_EXTENSION: &str =
31 "http://hl7.org/fhir/StructureDefinition/referencesContained";
32pub const AUTO_VARIABLE: &str = "vvv";
34pub const BATCH_UNNAMED_PREFIX: &str = "unnamed";
36
37#[derive(Debug, Clone)]
39pub struct Compiled {
40 pub map: StructureMap,
41 pub diagnostics: Vec<Diagnostic>,
43}
44
45impl Compiled {
46 pub fn has_errors(&self) -> bool {
47 self.diagnostics
48 .iter()
49 .any(|d| d.severity == fml_syntax::Severity::Error)
50 }
51}
52
53pub fn compile(text: &str) -> Compiled {
55 let parse = parse(text);
56 let mut compiled = compile_tree(&parse.tree());
57 let mut diagnostics = parse.diagnostics().to_vec();
58 diagnostics.append(&mut compiled.diagnostics);
59 compiled.diagnostics = diagnostics;
60 compiled
61}
62
63pub fn compile_tree(file: &ast::SourceFile) -> Compiled {
65 let mut compiler = Compiler {
66 diagnostics: Vec::new(),
67 };
68 let map = compiler.file(file);
69 Compiled {
70 map,
71 diagnostics: compiler.diagnostics,
72 }
73}
74
75struct Compiler {
76 diagnostics: Vec<Diagnostic>,
77}
78
79impl Compiler {
80 fn error(&mut self, node: &SyntaxNode, message: impl Into<String>) {
81 self.diagnostics
82 .push(Diagnostic::error(trimmed_range(node), message));
83 }
84
85 fn warning(&mut self, node: &SyntaxNode, message: impl Into<String>) {
86 self.diagnostics
87 .push(Diagnostic::warning(trimmed_range(node), message));
88 }
89
90 fn file(&mut self, file: &ast::SourceFile) -> StructureMap {
91 let mut map = StructureMap::new();
92 if let Some(declaration) = file.map_declaration() {
93 map.url = declaration.url().and_then(|u| u.value());
94 map.name = declaration.name().and_then(|n| n.value());
95 map.description = trailing_comments(declaration.syntax());
96 map.status = Some("draft".to_owned());
97 }
98 for metadata in file.metadata() {
99 self.metadata(&mut map, &metadata);
100 }
101 if map.id.is_none()
102 && let Some(name) = &map.name
103 {
104 let id = make_id(name);
105 if !id.is_empty() {
106 map.id = Some(id);
107 }
108 }
109 if map.status.is_none() {
110 map.status = Some("draft".to_owned());
111 }
112 if map.description.is_none() {
113 map.description = map.title.clone();
114 }
115 for concept_map in file.concept_maps() {
116 let contained = self.concept_map(&concept_map, map.status.as_deref());
117 map.contained.push(contained);
118 }
119 for uses in file.uses() {
120 let mode = uses.mode().unwrap_or(ModelMode::Source);
121 map.structure.push(Structure {
122 url: uses.url().and_then(|u| u.value()).unwrap_or_default(),
123 mode: mode.as_str().to_owned(),
124 alias: uses.alias(),
125 documentation: leading_comments(uses.syntax()),
126 other: Map::new(),
127 });
128 }
129 for import in file.imports() {
130 if let Some(url) = import.url().and_then(|u| u.value()) {
131 map.import.push(url);
132 }
133 }
134 for constant in file.constants() {
135 map.constants.push(Constant {
136 name: constant.name().unwrap_or_default(),
137 value: constant
138 .expression()
139 .map(|e| canonical(&e))
140 .unwrap_or_default(),
141 other: Map::new(),
142 });
143 }
144 for group in file.groups() {
145 let group = self.group(&group);
146 map.group.push(group);
147 }
148 map
149 }
150
151 fn metadata(&mut self, map: &mut StructureMap, metadata: &ast::Metadata) {
152 let Some(key) = metadata.key().map(|k| k.text()) else {
153 return;
154 };
155 let value = metadata.value().and_then(|v| v.value());
156 let field = match key.as_str() {
157 "url" => &mut map.url,
158 "name" => &mut map.name,
159 "title" => &mut map.title,
160 "version" => &mut map.version,
161 "description" => &mut map.description,
162 "status" => &mut map.status,
163 "date" => &mut map.date,
164 "publisher" => &mut map.publisher,
165 "purpose" => &mut map.purpose,
166 "copyright" => &mut map.copyright,
167 "experimental" => {
168 map.experimental = value.map(|v| v == "true");
169 return;
170 }
171 _ => {
172 self.warning(
173 metadata.syntax(),
174 format!("metadata `{key}` is not supported and is ignored"),
175 );
176 return;
177 }
178 };
179 *field = value;
180 }
181
182 fn concept_map(&mut self, node: &ConceptMapNode, status: Option<&str>) -> Value {
184 let id = node.url().and_then(|u| u.value()).unwrap_or_default();
185 let prefixes: Vec<(String, String)> = node
186 .prefixes()
187 .filter_map(|p| Some((p.name()?, p.url()?.value()?)))
188 .collect();
189 let resolve = |compiler: &mut Compiler, prefix: Option<String>, at: &SyntaxNode| {
190 let prefix = prefix.unwrap_or_default();
191 match prefixes.iter().find(|(name, _)| *name == prefix) {
192 Some((_, url)) => Some(url.clone()),
193 None => {
194 compiler.error(at, format!("unknown concept map prefix `{prefix}`"));
195 None
196 }
197 }
198 };
199 let mut groups: Vec<ConceptMapGroup> = Vec::new();
200 for unmapped in node.unmapped() {
201 let Some(source) = resolve(self, unmapped.prefix(), unmapped.syntax()) else {
202 continue;
203 };
204 let index = concept_map_group(&mut groups, &source, None);
205 match unmapped.mode().as_deref() {
206 Some("provided") => groups[index].unmapped = Some("use-source-code"),
207 other => self.error(
208 unmapped.syntax(),
209 format!(
210 "unsupported unmapped mode `{}`; only `provided` is defined",
211 other.unwrap_or_default()
212 ),
213 ),
214 }
215 }
216 for entry in node.entries() {
217 let (Some(source_code), Some(target_code)) = (entry.source(), entry.target()) else {
218 continue;
219 };
220 let source = resolve(self, source_code.prefix(), source_code.syntax());
221 let target = resolve(self, target_code.prefix(), target_code.syntax());
222 let (Some(source), Some(target)) = (source, target) else {
223 continue;
224 };
225 let relation = entry.relation().unwrap_or_default();
226 let relationship = match relation.as_str() {
227 "-" => "related-to",
228 "==" => "equivalent",
229 "!=" => "not-related-to",
230 "<=" => "source-is-narrower-than-target",
231 ">=" => "source-is-broader-than-target",
232 other => {
233 self.error(
234 entry.syntax(),
235 format!("unknown concept map relationship `{other}`"),
236 );
237 continue;
238 }
239 };
240 let index = concept_map_group(&mut groups, &source, Some(&target));
241 groups[index].elements.push(json!({
242 "code": source_code.code().unwrap_or_default(),
243 "target": [{
244 "code": target_code.code().unwrap_or_default(),
245 "relationship": relationship,
246 }],
247 }));
248 }
249 let mut map = Map::new();
250 map.insert("resourceType".into(), "ConceptMap".into());
251 map.insert("id".into(), id.into());
252 if let Some(status) = status {
253 map.insert("status".into(), status.into());
254 }
255 let groups: Vec<Value> = groups
256 .into_iter()
257 .map(|g| {
258 let mut group = Map::new();
259 group.insert("source".into(), g.source.into());
260 if let Some(target) = g.target {
261 group.insert("target".into(), target.into());
262 }
263 if !g.elements.is_empty() {
264 group.insert("element".into(), g.elements.into());
265 }
266 if let Some(mode) = g.unmapped {
267 group.insert("unmapped".into(), json!({ "mode": mode }));
268 }
269 Value::Object(group)
270 })
271 .collect();
272 if !groups.is_empty() {
273 map.insert("group".into(), groups.into());
274 }
275 Value::Object(map)
276 }
277
278 fn group(&mut self, node: &ast::Group) -> Group {
279 let mut group = Group {
280 name: node.name().unwrap_or_default(),
281 extends: node.extends(),
282 type_mode: node.type_mode().map(|mode| {
283 match mode {
284 GroupTypeMode::Types => "types",
285 GroupTypeMode::TypeAndTypes => "type-and-types",
286 }
287 .to_owned()
288 }),
289 documentation: leading_comments(node.syntax()),
290 ..Group::default()
291 };
292 for parameter in node.parameters() {
293 group.input.push(Input {
294 name: parameter.name().unwrap_or_default(),
295 type_name: parameter.type_annotation().and_then(|t| t.type_name()),
296 mode: match parameter.mode() {
297 Some(ParameterMode::Target) => "target",
298 _ => "source",
299 }
300 .to_owned(),
301 documentation: None,
302 other: Map::new(),
303 });
304 }
305 if let Some(body) = node.body() {
306 group.rule = self.rules(&body);
307 }
308 group
309 }
310
311 fn rules(&mut self, block: &ast::RuleBlock) -> Vec<Rule> {
312 let mut rules = Vec::new();
313 for node in block.rules() {
314 self.rule(&node, &mut rules);
315 }
316 rules
317 }
318
319 fn rule(&mut self, node: &ast::Rule, out: &mut Vec<Rule>) {
321 let mut rule = Rule {
322 documentation: leading_comments(node.syntax()),
323 ..Rule::default()
324 };
325 rule.source = node.sources().map(|s| self.source(&s)).collect();
326 rule.target = node.targets().map(|t| self.target(&t)).collect();
327
328 let fields = node.identity_fields();
329 if !fields.is_empty() {
330 return self.batch_identity(node, rule, &fields, out);
331 }
332
333 if let Some(dependent) = node.dependent() {
334 if let Some(block) = dependent.rules() {
335 rule.rule = self.rules(&block);
336 }
337 for invocation in dependent.invocations() {
338 rule.dependent.push(Dependent {
339 name: invocation.name().unwrap_or_default(),
340 parameter: invocation
341 .arguments()
342 .map(|argument| self.parameter(&argument, false).into())
343 .collect(),
344 other: Map::new(),
345 });
346 }
347 }
348 if is_simple_copy(&rule) {
349 rule.source[0].variable = Some(AUTO_VARIABLE.to_owned());
350 rule.target[0].variable = Some(AUTO_VARIABLE.to_owned());
351 rule.target[0].transform = Some("create".to_owned());
352 }
353 rule.name = match node.name() {
354 Some(name) => Some(name.replace('-', "")),
355 None => self.generated_name(node, &rule),
356 };
357 out.push(rule);
358 }
359
360 fn generated_name(&mut self, node: &ast::Rule, rule: &Rule) -> Option<String> {
363 if rule.source.len() != 1 {
364 self.error(node.syntax(), "a rule with several sources needs a name");
365 return None;
366 }
367 let source = &rule.source[0];
368 let path = source_path(source);
369 if path.is_empty() {
370 self.warning(
371 node.syntax(),
372 "a rule whose source has no element needs a name",
373 );
374 return None;
375 }
376 Some(match &source.type_name {
377 Some(type_name) => path + &capitalize(type_name),
378 None => path,
379 })
380 }
381
382 fn batch_identity(
384 &mut self,
385 node: &ast::Rule,
386 mut first: Rule,
387 fields: &[String],
388 out: &mut Vec<Rule>,
389 ) {
390 let prefix = node
391 .name()
392 .map(|n| n.replace('-', ""))
393 .unwrap_or_else(|| BATCH_UNNAMED_PREFIX.to_owned());
394 let source_context = first
395 .source
396 .first()
397 .map(|s| s.context.clone())
398 .unwrap_or_default();
399 let target_context = first.target.first().and_then(|t| t.context.clone());
400 if first.target.is_empty() {
401 first.target.push(Target::default());
402 }
403 let copy = |rule: &mut Rule, field: &str| {
404 let source = &mut rule.source[0];
405 source.element = Some(field.to_owned());
406 source.variable = Some(AUTO_VARIABLE.to_owned());
407 let target = &mut rule.target[0];
408 target.element = Some(field.to_owned());
409 target.variable = Some(AUTO_VARIABLE.to_owned());
410 target.transform = Some("create".to_owned());
411 rule.name = Some(make_id(&format!("{prefix}{field}")));
412 };
413 copy(&mut first, &fields[0]);
414 out.push(first);
415 for field in &fields[1..] {
416 let mut rule = Rule {
417 source: vec![Source {
418 context: source_context.clone(),
419 ..Source::default()
420 }],
421 target: vec![Target {
422 context: target_context.clone(),
423 ..Target::default()
424 }],
425 ..Rule::default()
426 };
427 copy(&mut rule, field);
428 out.push(rule);
429 }
430 }
431
432 fn source(&mut self, node: &ast::Source) -> Source {
433 let mut segments = node.path().map(|p| p.segments()).unwrap_or_default();
434 let mut source = Source::default();
435 if !segments.is_empty() {
436 source.context = segments.remove(0);
437 }
438 let mut segments = segments.into_iter();
439 source.element = segments.next();
440 source.extension = segments
441 .map(|s| Extension::string(SOURCE_ELEMENT_EXTENSION, s))
442 .collect();
443 source.type_name = node.type_annotation().and_then(|t| t.type_name());
444 if let Some((min, max)) = node.cardinality() {
445 source.min = min.parse().ok();
446 source.max = Some(max);
447 }
448 if let Some(default) = node.default_value()
449 && let Some(expression) = default.expression()
450 {
451 source.default_value = Some(match expression.syntax().kind() {
453 SyntaxKind::Literal
454 if expression
455 .syntax()
456 .first_token()
457 .is_some_and(|t| t.kind() == SyntaxKind::DoubleQuotedString) =>
458 {
459 quote_fhirpath_string(&ast::unescape(
460 expression.syntax().first_token().unwrap().text(),
461 ))
462 }
463 _ => canonical(&expression),
464 });
465 }
466 source.list_mode = node
467 .list_mode()
468 .map(|mode| list_mode_code(&mode).to_owned());
469 source.variable = node.alias();
470 source.condition = node.where_clause().map(|e| canonical(&e));
471 source.check = node.check_clause().map(|e| canonical(&e));
472 source.log_message = node.log_clause().map(|e| canonical(&e));
473 source
474 }
475
476 fn target(&mut self, node: &ast::Target) -> Target {
477 let mut target = Target::default();
478 if let Some(path) = node.path() {
479 let mut segments = path.segments().into_iter();
480 target.context = segments.next();
481 target.element = segments.next();
482 target.extension = segments
483 .map(|s| Extension::string(TARGET_ELEMENT_EXTENSION, s))
484 .collect();
485 }
486 match node.transform() {
487 Some(TransformValue::Literal(literal)) => {
488 target.transform = Some("copy".to_owned());
489 target.parameter.push(literal_parameter(&literal).into());
490 }
491 Some(TransformValue::Name(name)) => {
492 target.transform = Some("copy".to_owned());
493 target
494 .parameter
495 .push(ParameterValue::Id(name.text()).into());
496 }
497 Some(TransformValue::Expression(expression)) => {
498 target.transform = Some("evaluate".to_owned());
499 target
500 .parameter
501 .push(ParameterValue::String(canonical(&expression)).into());
502 }
503 Some(TransformValue::Invocation(invocation)) => {
504 let name = invocation.name().unwrap_or_default();
505 if !TRANSFORMS.contains(&name.as_str()) {
506 self.error(invocation.syntax(), format!("unknown transform `{name}`"));
507 }
508 let evaluate = name == "evaluate";
509 target.parameter = invocation
510 .arguments()
511 .enumerate()
512 .map(|(index, argument)| {
513 Parameter::from(self.parameter(&argument, evaluate && index == 1))
514 })
515 .collect();
516 if name == "translate"
519 && let Some(parameter) = target.parameter.get_mut(1)
520 && let ParameterValue::String(map) = ¶meter.value
521 && map.starts_with('#')
522 {
523 parameter.value_element = Some(json!({
524 "extension": [{
525 "url": REFERENCES_CONTAINED_EXTENSION,
526 "valueReference": { "reference": map },
527 }],
528 }));
529 }
530 target.transform = Some(name);
531 }
532 None => {}
533 }
534 target.variable = node.alias();
535 if let Some(mode) = node.list_mode() {
536 if let ListMode::Share(id) = &mode {
537 target.list_rule_id = Some(id.clone());
538 }
539 target.list_mode.push(list_mode_code(&mode).to_owned());
540 }
541 target
542 }
543
544 fn parameter(&mut self, argument: &Expr, expression: bool) -> ParameterValue {
547 if expression {
548 return ParameterValue::String(canonical(argument));
549 }
550 let node = argument.syntax();
551 match node.kind() {
552 SyntaxKind::NameRef => {
553 let token = node.first_token().expect("a name has a token");
554 ParameterValue::Id(ast::name_text(&token))
555 }
556 SyntaxKind::Literal => match ast::Literal::cast(node.clone()) {
557 Some(literal) => literal_parameter(&literal),
558 None => ParameterValue::String(canonical(argument)),
559 },
560 SyntaxKind::PrefixExpr
561 if node
562 .first_token()
563 .is_some_and(|t| t.kind() == SyntaxKind::Minus) =>
564 {
565 let text: String = canonical(argument).split_whitespace().collect();
566 if let Ok(value) = text.parse::<i64>() {
567 ParameterValue::Integer(value)
568 } else if let Ok(value) = text.parse::<serde_json::Number>() {
569 ParameterValue::Decimal(value)
570 } else {
571 self.complex_parameter(argument)
572 }
573 }
574 _ => self.complex_parameter(argument),
575 }
576 }
577
578 fn complex_parameter(&mut self, argument: &Expr) -> ParameterValue {
579 self.error(
580 argument.syntax(),
581 "a transform argument must be a variable or a constant; use `evaluate` for expressions",
582 );
583 ParameterValue::String(canonical(argument))
584 }
585}
586
587const TRANSFORMS: &[&str] = &[
589 "create",
590 "copy",
591 "truncate",
592 "escape",
593 "cast",
594 "append",
595 "translate",
596 "reference",
597 "dateOp",
598 "uuid",
599 "pointer",
600 "evaluate",
601 "cc",
602 "c",
603 "qty",
604 "id",
605 "cp",
606 "toDateTime",
607 "unixToDateTime",
608 "toDate",
609 "unixToDate",
610 "toTime",
611 "unixToTime",
612];
613
614struct ConceptMapGroup {
615 source: String,
616 target: Option<String>,
617 elements: Vec<Value>,
618 unmapped: Option<&'static str>,
619}
620
621fn concept_map_group(
624 groups: &mut Vec<ConceptMapGroup>,
625 source: &str,
626 target: Option<&str>,
627) -> usize {
628 for (index, group) in groups.iter_mut().enumerate() {
629 if group.source == source
630 && (group.target.is_none() || target.is_none() || group.target.as_deref() == target)
631 {
632 if group.target.is_none()
633 && let Some(target) = target
634 {
635 group.target = Some(target.to_owned());
636 }
637 return index;
638 }
639 }
640 groups.push(ConceptMapGroup {
641 source: source.to_owned(),
642 target: target.map(str::to_owned),
643 elements: Vec::new(),
644 unmapped: None,
645 });
646 groups.len() - 1
647}
648
649fn is_simple_copy(rule: &Rule) -> bool {
652 let [source] = rule.source.as_slice() else {
653 return false;
654 };
655 let [target] = rule.target.as_slice() else {
656 return false;
657 };
658 !source.context.is_empty()
659 && source.element.is_some()
660 && source.variable.is_none()
661 && target.context.is_some()
662 && target.element.is_some()
663 && target.variable.is_none()
664 && target.parameter.is_empty()
665 && rule.dependent.is_empty()
666 && rule.rule.is_empty()
667}
668
669pub fn source_path(source: &Source) -> String {
671 source
672 .element
673 .iter()
674 .cloned()
675 .chain(
676 source
677 .extension
678 .iter()
679 .filter(|e| e.url == SOURCE_ELEMENT_EXTENSION)
680 .filter_map(|e| e.value_string.clone()),
681 )
682 .collect::<Vec<_>>()
683 .join(".")
684}
685
686fn canonical(expression: &Expr) -> String {
688 crate::canonical::canonical(expression)
689}
690
691fn literal_parameter(literal: &ast::Literal) -> ParameterValue {
692 let value = literal.value().unwrap_or_default();
693 match literal.kind() {
694 Some(SyntaxKind::Integer | SyntaxKind::LongNumber) => value
695 .trim_end_matches('L')
696 .parse()
697 .map(ParameterValue::Integer)
698 .unwrap_or(ParameterValue::String(value)),
699 Some(SyntaxKind::Decimal) => value
700 .parse()
701 .map(ParameterValue::Decimal)
702 .unwrap_or(ParameterValue::String(value)),
703 Some(SyntaxKind::Date) => ParameterValue::Date(value.trim_start_matches('@').to_owned()),
704 Some(SyntaxKind::DateTime) => {
705 ParameterValue::DateTime(value.trim_start_matches('@').to_owned())
706 }
707 Some(SyntaxKind::Time) => ParameterValue::Time(value.trim_start_matches("@T").to_owned()),
708 Some(SyntaxKind::Ident) if value == "true" || value == "false" => {
709 ParameterValue::Boolean(value == "true")
710 }
711 _ => ParameterValue::String(value),
712 }
713}
714
715fn list_mode_code(mode: &ListMode) -> &'static str {
716 match mode {
717 ListMode::First => "first",
718 ListMode::NotFirst => "not_first",
719 ListMode::Last => "last",
720 ListMode::NotLast => "not_last",
721 ListMode::OnlyOne => "only_one",
722 ListMode::Single => "single",
723 ListMode::Collate => "collate",
724 ListMode::Share(_) => "share",
725 }
726}
727
728pub fn make_id(name: &str) -> String {
730 name.chars()
731 .filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '.')
732 .collect()
733}
734
735fn capitalize(text: &str) -> String {
736 let mut chars = text.chars();
737 match chars.next() {
738 Some(first) => first.to_uppercase().chain(chars).collect(),
739 None => String::new(),
740 }
741}
742
743fn leading_comments(node: &SyntaxNode) -> Option<String> {
747 let mut comments = Vec::new();
748 let mut previous: Option<SyntaxElement> = None;
749 for element in node.siblings_with_tokens(Direction::Prev).skip(1) {
750 match element {
751 NodeOrToken::Token(token) if token.kind() == SyntaxKind::Whitespace => {}
752 NodeOrToken::Token(token) if token.kind().is_comment() => comments.push(token),
753 element => {
754 previous = Some(element);
755 break;
756 }
757 }
758 }
759 comments.reverse();
760 if previous
762 .as_ref()
763 .is_some_and(|p| p.kind() == SyntaxKind::MapDeclaration)
764 {
765 return None;
766 }
767 let owns_trailing = previous
768 .as_ref()
769 .is_some_and(|p| matches!(p.kind(), SyntaxKind::Rule | SyntaxKind::UsesDeclaration));
770 if owns_trailing
771 && comments
772 .first()
773 .is_some_and(|first| !after_line_break(first))
774 {
775 comments.remove(0);
776 }
777 if comments.is_empty() {
778 return None;
779 }
780 Some(
781 comments
782 .iter()
783 .map(|token| comment_text(token.text()))
784 .collect::<Vec<_>>()
785 .join("\r\n"),
786 )
787}
788
789fn after_line_break(token: &fml_syntax::SyntaxToken) -> bool {
791 token.prev_sibling_or_token().is_some_and(|e| match e {
792 NodeOrToken::Token(t) => {
793 t.kind() == SyntaxKind::Whitespace && t.text().contains(['\n', '\r'])
794 }
795 NodeOrToken::Node(_) => false,
796 })
797}
798
799fn trailing_comments(node: &SyntaxNode) -> Option<String> {
801 let comments: Vec<String> = node
802 .siblings_with_tokens(Direction::Next)
803 .skip(1)
804 .take_while(|e| e.kind().is_trivia())
805 .filter_map(|e| e.into_token())
806 .filter(|t| t.kind().is_comment())
807 .map(|t| comment_text(t.text()))
808 .collect();
809 (!comments.is_empty()).then(|| comments.join("\r\n"))
810}
811
812fn comment_text(text: &str) -> String {
813 let inner = if let Some(line) = text.strip_prefix("//") {
814 line
815 } else {
816 text.strip_prefix("/*")
817 .map(|t| t.strip_suffix("*/").unwrap_or(t))
818 .unwrap_or(text)
819 };
820 inner.trim().to_owned()
821}
822
823fn trimmed_range(node: &SyntaxNode) -> TextRange {
825 let mut tokens = node
826 .descendants_with_tokens()
827 .filter_map(NodeOrToken::into_token)
828 .filter(|t| !t.kind().is_trivia());
829 match tokens.next() {
830 Some(first) => {
831 let end = tokens
832 .last()
833 .map_or(first.text_range().end(), |t| t.text_range().end());
834 TextRange::new(first.text_range().start(), end)
835 }
836 None => node.text_range(),
837 }
838}