UNPKG

25.9 kBJavaScriptView Raw
1"use strict";
2
3const {
4 addLeadingComment,
5 addDanglingComment,
6 addTrailingComment,
7 skipNewline,
8 hasNewline,
9 hasNewlineInRange,
10} = require("prettier").util;
11const {
12 concat,
13 join,
14 indent,
15 hardline,
16 cursor,
17 lineSuffix,
18 breakParent,
19} = require("prettier").doc.builders;
20const {
21 getNextNonSpaceNonCommentCharacterIndex,
22 isNextLineEmpty,
23 isPreviousLineEmpty,
24 isLookupNode,
25} = require("./util");
26
27/*
28Comment functions are meant to inspect various edge cases using given comment nodes,
29with information about where those comment nodes exist in the tree (ie enclosingNode,
30previousNode, followingNode), and then either call the built in functions to handle
31certain cases (ie addLeadingComment, addTrailingComment, addDanglingComment), or just
32let prettier core handle them. To signal that the plugin is taking over, the comment
33handler function should return true, otherwise returning false signals that prettier
34core should handle the comment
35
36args:
37 comment
38 text
39 options
40 ast
41 isLastComment
42*/
43
44function handleOwnLineComment(comment, text, options) {
45 const { precedingNode, enclosingNode, followingNode } = comment;
46 return (
47 handleLastFunctionArgComments(
48 text,
49 precedingNode,
50 enclosingNode,
51 followingNode,
52 comment,
53 options
54 ) ||
55 handleMemberExpressionComments(enclosingNode, followingNode, comment) ||
56 handleIfStatementComments(
57 text,
58 precedingNode,
59 enclosingNode,
60 followingNode,
61 comment,
62 options
63 ) ||
64 handleWhileComments(
65 text,
66 precedingNode,
67 enclosingNode,
68 followingNode,
69 comment,
70 options
71 ) ||
72 handleTryComments(enclosingNode, followingNode, comment) ||
73 handleClassComments(enclosingNode, followingNode, comment) ||
74 handleFunctionParameter(
75 text,
76 precedingNode,
77 enclosingNode,
78 followingNode,
79 comment
80 ) ||
81 handleFunction(text, enclosingNode, followingNode, comment, options) ||
82 handleForComments(enclosingNode, precedingNode, followingNode, comment) ||
83 handleInlineComments(
84 enclosingNode,
85 precedingNode,
86 followingNode,
87 comment
88 ) ||
89 handleDeclareComments(enclosingNode, precedingNode, followingNode, comment)
90 );
91}
92
93function handleEndOfLineComment(comment, text, options) {
94 const { precedingNode, enclosingNode, followingNode } = comment;
95 return (
96 handleArrayComments(
97 text,
98 precedingNode,
99 enclosingNode,
100 followingNode,
101 comment,
102 options
103 ) ||
104 handleReturnComments(
105 text,
106 precedingNode,
107 enclosingNode,
108 followingNode,
109 comment,
110 options
111 ) ||
112 handleLastFunctionArgComments(
113 text,
114 precedingNode,
115 enclosingNode,
116 followingNode,
117 comment,
118 options
119 ) ||
120 handleRetifComments(
121 enclosingNode,
122 precedingNode,
123 followingNode,
124 comment,
125 text,
126 options
127 ) ||
128 handleIfStatementComments(
129 text,
130 precedingNode,
131 enclosingNode,
132 followingNode,
133 comment,
134 options
135 ) ||
136 handleWhileComments(
137 text,
138 precedingNode,
139 enclosingNode,
140 followingNode,
141 comment,
142 options
143 ) ||
144 handleTryComments(enclosingNode, followingNode, comment) ||
145 handleClassComments(enclosingNode, followingNode, comment) ||
146 handleFunctionParameter(
147 text,
148 precedingNode,
149 enclosingNode,
150 followingNode,
151 comment
152 ) ||
153 handleFunction(text, enclosingNode, followingNode, comment, options) ||
154 handleEntryComments(enclosingNode, comment) ||
155 handleCallComments(precedingNode, enclosingNode, comment) ||
156 handleVariableComments(enclosingNode, followingNode, comment) ||
157 handleInlineComments(
158 enclosingNode,
159 precedingNode,
160 followingNode,
161 comment
162 ) ||
163 handleNamespaceComments(
164 enclosingNode,
165 precedingNode,
166 followingNode,
167 comment
168 ) ||
169 handleDeclareComments(
170 enclosingNode,
171 precedingNode,
172 followingNode,
173 comment
174 ) ||
175 handleGoto(enclosingNode, comment)
176 );
177}
178
179function handleRemainingComment(comment, text, options) {
180 const { precedingNode, enclosingNode, followingNode } = comment;
181 return (
182 handleIfStatementComments(
183 text,
184 precedingNode,
185 enclosingNode,
186 followingNode,
187 comment,
188 options
189 ) ||
190 handleWhileComments(
191 text,
192 precedingNode,
193 enclosingNode,
194 followingNode,
195 comment,
196 options
197 ) ||
198 handleCommentInEmptyParens(text, enclosingNode, comment, options) ||
199 handleClassComments(enclosingNode, followingNode, comment) ||
200 handleTraitUseComments(enclosingNode, followingNode, comment) ||
201 handleFunctionParameter(
202 text,
203 precedingNode,
204 enclosingNode,
205 followingNode,
206 comment
207 ) ||
208 handleFunction(text, enclosingNode, followingNode, comment, options) ||
209 handleGoto(enclosingNode, comment) ||
210 handleHalt(precedingNode, enclosingNode, followingNode, comment) ||
211 handleBreakAndContinueStatementComments(enclosingNode, comment) ||
212 handleInlineComments(
213 enclosingNode,
214 precedingNode,
215 followingNode,
216 comment
217 ) ||
218 handleNamespaceComments(
219 enclosingNode,
220 precedingNode,
221 followingNode,
222 comment
223 )
224 );
225}
226
227function addBlockStatementFirstComment(node, comment) {
228 const { children } = node;
229 if (children.length === 0) {
230 addDanglingComment(node, comment);
231 } else {
232 addLeadingComment(children[0], comment);
233 }
234}
235
236function addBlockOrNotComment(node, comment) {
237 if (node.kind === "block") {
238 addBlockStatementFirstComment(node, comment);
239 } else {
240 addLeadingComment(node, comment);
241 }
242}
243
244function handleArrayComments(
245 text,
246 precedingNode,
247 enclosingNode,
248 followingNode,
249 comment
250) {
251 if (
252 !precedingNode &&
253 !followingNode &&
254 enclosingNode &&
255 enclosingNode.kind === "array"
256 ) {
257 addTrailingComment(enclosingNode, comment);
258 return true;
259 }
260
261 return false;
262}
263
264function handleReturnComments(
265 text,
266 precedingNode,
267 enclosingNode,
268 followingNode,
269 comment
270) {
271 if (enclosingNode && enclosingNode.kind === "return" && !enclosingNode.expr) {
272 addTrailingComment(enclosingNode, comment);
273 return true;
274 }
275
276 return false;
277}
278
279function handleLastFunctionArgComments(
280 text,
281 precedingNode,
282 enclosingNode,
283 followingNode,
284 comment,
285 options
286) {
287 const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex(
288 text,
289 comment,
290 options
291 );
292 const nextCharacter = text.charAt(nextCharIndex);
293
294 // Real functions
295 if (
296 precedingNode &&
297 precedingNode.kind === "identifier" &&
298 enclosingNode &&
299 (enclosingNode.kind === "function" || enclosingNode.kind === "method") &&
300 nextCharacter === ")"
301 ) {
302 addTrailingComment(enclosingNode, comment);
303 return true;
304 }
305
306 if (
307 enclosingNode &&
308 (enclosingNode.kind === "function" || enclosingNode.kind === "method") &&
309 followingNode &&
310 followingNode.kind === "block"
311 ) {
312 addBlockStatementFirstComment(followingNode, comment);
313 return true;
314 }
315
316 return false;
317}
318
319function handleMemberExpressionComments(enclosingNode, followingNode, comment) {
320 if (
321 enclosingNode &&
322 isLookupNode(enclosingNode) &&
323 followingNode &&
324 ["identifier", "variable", "encapsed"].includes(followingNode.kind)
325 ) {
326 addLeadingComment(enclosingNode, comment);
327
328 return true;
329 }
330
331 return false;
332}
333
334function handleIfStatementComments(
335 text,
336 precedingNode,
337 enclosingNode,
338 followingNode,
339 comment,
340 options
341) {
342 if (!enclosingNode || enclosingNode.kind !== "if" || !followingNode) {
343 return false;
344 }
345
346 const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex(
347 text,
348 comment,
349 options
350 );
351 const nextCharacter = text.charAt(nextCharIndex);
352
353 if (nextCharacter === ")") {
354 addTrailingComment(precedingNode, comment);
355 return true;
356 }
357
358 // Comments before `else`/`else if` treat as a dangling comment
359 if (
360 precedingNode === enclosingNode.body &&
361 followingNode === enclosingNode.alternate
362 ) {
363 addDanglingComment(enclosingNode, comment);
364 return true;
365 }
366
367 if (followingNode.kind === "if") {
368 addBlockOrNotComment(followingNode.body, comment);
369 return true;
370 }
371
372 // For comments positioned after the condition parenthesis in an if statement
373 // before the consequent without brackets on, such as
374 // if (a) /* comment */ true,
375 // we look at the next character to see if the following node
376 // is the consequent for the if statement
377 if (enclosingNode.body === followingNode) {
378 addLeadingComment(followingNode, comment);
379 return true;
380 }
381
382 return false;
383}
384
385function handleRetifComments(
386 enclosingNode,
387 precedingNode,
388 followingNode,
389 comment,
390 text,
391 options
392) {
393 const isSameLineAsPrecedingNode =
394 precedingNode &&
395 !hasNewlineInRange(
396 text,
397 options.locEnd(precedingNode),
398 options.locStart(comment)
399 );
400
401 if (
402 (!precedingNode || !isSameLineAsPrecedingNode) &&
403 enclosingNode &&
404 enclosingNode.kind === "retif" &&
405 followingNode
406 ) {
407 addLeadingComment(followingNode, comment);
408 return true;
409 }
410 return false;
411}
412
413function handleForComments(
414 enclosingNode,
415 precedingNode,
416 followingNode,
417 comment
418) {
419 if (
420 !followingNode &&
421 enclosingNode &&
422 (enclosingNode.kind === "for" || enclosingNode.kind === "foreach")
423 ) {
424 // For a shortform for loop (where the body is just one node), add
425 // this as a leading comment to the body
426 if (enclosingNode.body && enclosingNode.body.kind !== "block") {
427 addLeadingComment(followingNode, comment);
428 } else {
429 addLeadingComment(enclosingNode, comment);
430 }
431 return true;
432 }
433
434 return false;
435}
436
437function handleTraitUseComments(enclosingNode, followingNode, comment) {
438 if (
439 enclosingNode &&
440 enclosingNode.kind === "traituse" &&
441 enclosingNode.adaptations &&
442 !enclosingNode.adaptations.length
443 ) {
444 addDanglingComment(enclosingNode, comment);
445 return true;
446 }
447 return false;
448}
449
450function handleClassComments(enclosingNode, followingNode, comment) {
451 if (
452 enclosingNode &&
453 ["class", "interface", "trait"].includes(enclosingNode.kind)
454 ) {
455 // for extends nodes that have leading comments, we can store them as
456 // dangling comments so we can handle them in the printer
457
458 if (followingNode && enclosingNode.extends) {
459 if (!Array.isArray(enclosingNode.extends)) {
460 if (followingNode === enclosingNode.extends) {
461 addDanglingComment(followingNode, comment);
462 return true;
463 }
464 } else {
465 if (
466 enclosingNode.extends.some((extendsNode) => {
467 if (followingNode && followingNode === extendsNode) {
468 addDanglingComment(followingNode, comment);
469 return true;
470 }
471 })
472 ) {
473 return true;
474 }
475 }
476 }
477
478 // check each implements node - if any of them have comments we can store
479 // them as dangling comments and handle them in the printer
480 if (followingNode && enclosingNode.implements) {
481 if (
482 enclosingNode.implements.some((implementsNode) => {
483 if (followingNode && followingNode === implementsNode) {
484 addDanglingComment(followingNode, comment);
485 return true;
486 }
487 })
488 ) {
489 return true;
490 }
491 }
492
493 // For an empty class where the body is only made up of comments, we
494 // need to attach this as a dangling comment on the class node itself
495 if (!(enclosingNode.body && enclosingNode.body.length > 0)) {
496 addDanglingComment(enclosingNode, comment);
497 return true;
498 }
499 }
500 return false;
501}
502
503function handleFunction(text, enclosingNode, followingNode, comment, options) {
504 if (
505 enclosingNode &&
506 (enclosingNode.kind === "function" || enclosingNode.kind === "method")
507 ) {
508 // we need to figure out if there are any comments that should be assigned
509 // to the function return type. To do this we check if the comment location
510 // is between the last argument end location and the return type start location.
511 let argumentsLocEnd = 0;
512 for (let i = 0; i < enclosingNode.arguments.length; i++) {
513 argumentsLocEnd =
514 options.locEnd(enclosingNode.arguments[i]) > argumentsLocEnd
515 ? options.locEnd(enclosingNode.arguments[i])
516 : argumentsLocEnd;
517 }
518 const commentIsBetweenArgumentsAndBody =
519 enclosingNode.body &&
520 options.locStart(comment) > argumentsLocEnd &&
521 options.locEnd(comment) < options.locStart(enclosingNode.body);
522 const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex(
523 text,
524 comment,
525 options
526 );
527 // we additionally need to check if this isn't a trailing argument comment,
528 // by checking the next character isn't ")"
529 if (
530 enclosingNode.type &&
531 commentIsBetweenArgumentsAndBody &&
532 text.charAt(nextCharIndex) !== ")"
533 ) {
534 if (options.locEnd(comment) < options.locStart(enclosingNode.type)) {
535 // we need to store this as a dangling comment in case the type is nullable
536 // ie function(): ?string {} - the "nullable" attribute is part of the
537 // function node, not the type.
538 addDanglingComment(enclosingNode.type, comment);
539 return true;
540 }
541 addTrailingComment(enclosingNode.type, comment);
542 return true;
543 }
544 }
545 return false;
546}
547
548function handleFunctionParameter(
549 text,
550 precedingNode,
551 enclosingNode,
552 followingNode,
553 comment
554) {
555 if (
556 !enclosingNode ||
557 !["function", "method", "parameter"].includes(enclosingNode.kind)
558 ) {
559 return false;
560 }
561 if (
562 precedingNode.kind === "typereference" &&
563 followingNode.kind === "identifier"
564 ) {
565 addTrailingComment(precedingNode, comment);
566 return true;
567 }
568 return false;
569}
570
571function handleBreakAndContinueStatementComments(enclosingNode, comment) {
572 if (
573 enclosingNode &&
574 (enclosingNode.kind === "continue" || enclosingNode.kind === "break") &&
575 !enclosingNode.label
576 ) {
577 addTrailingComment(enclosingNode, comment);
578 return true;
579 }
580 return false;
581}
582
583function handleGoto(enclosingNode, comment) {
584 if (enclosingNode && ["label", "goto"].includes(enclosingNode.kind)) {
585 addTrailingComment(enclosingNode, comment);
586 return true;
587 }
588 return false;
589}
590
591function handleHalt(precedingNode, enclosingNode, followingNode, comment) {
592 if (enclosingNode && enclosingNode.kind === "halt") {
593 addDanglingComment(enclosingNode, comment);
594 return true;
595 }
596
597 if (precedingNode && precedingNode.kind === "halt") {
598 addDanglingComment(precedingNode, comment);
599 return true;
600 }
601
602 return false;
603}
604
605function handleCommentInEmptyParens(text, enclosingNode, comment, options) {
606 const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex(
607 text,
608 comment,
609 options
610 );
611
612 if (text.charAt(nextCharIndex) !== ")") {
613 return false;
614 }
615
616 // Only add dangling comments to fix the case when no arguments are present,
617 // i.e. a function without any argument.
618 if (
619 enclosingNode &&
620 (enclosingNode.kind === "function" ||
621 enclosingNode.kind === "closure" ||
622 enclosingNode.kind === "method" ||
623 enclosingNode.kind === "call" ||
624 enclosingNode.kind === "new") &&
625 enclosingNode.arguments.length === 0
626 ) {
627 addDanglingComment(enclosingNode, comment);
628 return true;
629 }
630 return false;
631}
632
633function handleInlineComments(
634 enclosingNode,
635 precedingNode,
636 followingNode,
637 comment
638) {
639 if (followingNode && followingNode.kind === "inline") {
640 if (!followingNode.leadingComments) {
641 followingNode.leadingComments = [];
642 }
643
644 if (!followingNode.leadingComments.includes(comment)) {
645 followingNode.leadingComments.push(comment);
646 }
647
648 return true;
649 } else if (
650 !enclosingNode &&
651 !followingNode &&
652 precedingNode &&
653 precedingNode.kind === "inline"
654 ) {
655 addDanglingComment(precedingNode, comment);
656 return true;
657 }
658 return false;
659}
660
661function handleEntryComments(enclosingNode, comment) {
662 if (enclosingNode && enclosingNode.kind === "entry") {
663 addLeadingComment(enclosingNode, comment);
664 return true;
665 }
666 return false;
667}
668
669function handleVariableComments(enclosingNode, followingNode, comment) {
670 if (
671 enclosingNode &&
672 enclosingNode.kind === "assign" &&
673 followingNode &&
674 (followingNode.kind === "array" ||
675 followingNode.kind === "string" ||
676 followingNode.kind === "encapsed")
677 ) {
678 addLeadingComment(followingNode, comment);
679 return true;
680 }
681 return false;
682}
683
684function handleTryComments(enclosingNode, followingNode, comment) {
685 if (!enclosingNode || enclosingNode.kind !== "try" || !followingNode) {
686 return false;
687 }
688
689 if (followingNode.kind === "block") {
690 addBlockStatementFirstComment(followingNode, comment);
691 return true;
692 }
693
694 if (followingNode.kind === "try") {
695 addBlockOrNotComment(followingNode.always, comment);
696 return true;
697 }
698
699 if (followingNode.kind === "catch") {
700 addBlockOrNotComment(followingNode.body, comment);
701 return true;
702 }
703
704 return false;
705}
706
707function handleCallComments(precedingNode, enclosingNode, comment) {
708 if (
709 enclosingNode &&
710 enclosingNode.kind === "call" &&
711 precedingNode &&
712 enclosingNode.what === precedingNode &&
713 enclosingNode.arguments.length > 0
714 ) {
715 addLeadingComment(enclosingNode.arguments[0], comment);
716 return true;
717 }
718 return false;
719}
720
721function handleNamespaceComments(
722 enclosingNode,
723 precedingNode,
724 followingNode,
725 comment
726) {
727 if (
728 !followingNode &&
729 !precedingNode &&
730 enclosingNode &&
731 enclosingNode.kind === "namespace" &&
732 !enclosingNode.withBrackets
733 ) {
734 addTrailingComment(enclosingNode, comment);
735 return true;
736 } else if (
737 !precedingNode &&
738 enclosingNode &&
739 enclosingNode.kind === "namespace" &&
740 !enclosingNode.withBrackets
741 ) {
742 addDanglingComment(enclosingNode, comment);
743 return true;
744 }
745
746 return false;
747}
748
749function handleDeclareComments(
750 enclosingNode,
751 precedingNode,
752 followingNode,
753 comment
754) {
755 if (!enclosingNode || enclosingNode.kind !== "declare") {
756 return false;
757 }
758
759 if (precedingNode && precedingNode.kind === "noop") {
760 return false;
761 }
762
763 if (!followingNode || enclosingNode.directives[0] === followingNode) {
764 if (enclosingNode.mode === "none") {
765 addTrailingComment(enclosingNode, comment);
766 } else {
767 addDanglingComment(enclosingNode, comment);
768 }
769
770 return true;
771 }
772
773 if (followingNode && precedingNode) {
774 addLeadingComment(followingNode, comment);
775
776 return true;
777 }
778
779 return false;
780}
781
782function handleWhileComments(
783 text,
784 precedingNode,
785 enclosingNode,
786 followingNode,
787 comment,
788 options
789) {
790 if (!enclosingNode || enclosingNode.kind !== "while" || !followingNode) {
791 return false;
792 }
793 // We unfortunately have no way using the AST or location of nodes to know
794 // if the comment is positioned before the condition parenthesis:
795 // while (a /* comment */) {}
796 // The only workaround I found is to look at the next character to see if
797 // it is a ).
798
799 const nextCharIndex = getNextNonSpaceNonCommentCharacterIndex(
800 text,
801 comment,
802 options
803 );
804 const nextCharacter = text.charAt(nextCharIndex);
805
806 if (nextCharacter === ")") {
807 addTrailingComment(precedingNode, comment);
808 return true;
809 }
810
811 if (followingNode.kind === "block") {
812 addBlockStatementFirstComment(followingNode, comment);
813 return true;
814 }
815
816 return false;
817}
818
819// https://github.com/prettier/prettier/blob/master/src/main/comments.js#L335
820function printComment(commentPath, options) {
821 const comment = commentPath.getValue();
822 comment.printed = true;
823 return options.printer.printComment(commentPath, options);
824}
825
826// https://github.com/prettier/prettier/blob/master/src/main/comments.js#L440
827function printDanglingComments(path, options, sameIndent, filter) {
828 const parts = [];
829 const node = path.getValue();
830
831 if (!node || !node.comments) {
832 return "";
833 }
834
835 path.each((commentPath) => {
836 const comment = commentPath.getValue();
837 if (
838 comment &&
839 !comment.leading &&
840 !comment.trailing &&
841 (!filter || filter(comment))
842 ) {
843 parts.push(printComment(commentPath, options));
844 }
845 }, "comments");
846
847 if (parts.length === 0) {
848 return "";
849 }
850
851 if (sameIndent) {
852 return join(hardline, parts);
853 }
854 return indent(concat([hardline, join(hardline, parts)]));
855}
856
857function hasLeadingComment(node) {
858 return node.comments && node.comments.some((comment) => comment.leading);
859}
860
861function hasTrailingComment(node) {
862 return node.comments && node.comments.some((comment) => comment.trailing);
863}
864
865function hasLeadingOwnLineComment(text, node, options) {
866 return (
867 node.comments &&
868 node.comments.some(
869 (comment) => comment.leading && hasNewline(text, options.locEnd(comment))
870 )
871 );
872}
873
874function printComments(comments, options) {
875 const parts = [];
876 comments.forEach((comment, index, comments) => {
877 comment.printed = true;
878 const isLastComment = comments.length === index + 1;
879 parts.push(comment.value);
880 if (!isLastComment) {
881 parts.push(hardline);
882 }
883 if (
884 isNextLineEmpty(options.originalText, comment, options) &&
885 !isLastComment
886 ) {
887 parts.push(hardline);
888 }
889 });
890 return concat(parts);
891}
892
893function isBlockComment(comment) {
894 return comment.kind === "commentblock";
895}
896
897function getCommentChildNodes(node) {
898 if (typeof node !== "object") {
899 return [];
900 }
901
902 const getChildNodes = (node) =>
903 Object.keys(node)
904 .filter(
905 (n) =>
906 n !== "kind" &&
907 n !== "loc" &&
908 n !== "errors" &&
909 n !== "extra" &&
910 n !== "comments" &&
911 n !== "leadingComments" &&
912 n !== "enclosingNode" &&
913 n !== "precedingNode" &&
914 n !== "followingNode"
915 )
916 .map((n) => node[n]);
917
918 return getChildNodes(node);
919}
920
921function canAttachComment(node) {
922 return (
923 node.kind && node.kind !== "commentblock" && node.kind !== "commentline"
924 );
925}
926
927// Based on https://github.com/prettier/prettier/blob/master/src/main/comments.js
928// TODO remove after https://github.com/prettier/prettier/issues/5087
929function prependCursorPlaceholder(path, options, printed) {
930 if (path.getNode() === options.cursorNode && path.getValue()) {
931 return concat([cursor, printed, cursor]);
932 }
933
934 return printed;
935}
936
937function printLeadingComment(commentPath, print, options) {
938 const comment = commentPath.getValue();
939 const contents = printComment(commentPath, options);
940
941 if (!contents) {
942 return "";
943 }
944
945 const isBlock =
946 options.printer.isBlockComment && options.printer.isBlockComment(comment);
947
948 // Leading block comments should see if they need to stay on the
949 // same line or not.
950 if (isBlock) {
951 return concat([
952 contents,
953 hasNewline(options.originalText, options.locEnd(comment))
954 ? hardline
955 : " ",
956 ]);
957 }
958
959 return concat([contents, hardline]);
960}
961
962function printTrailingComment(commentPath, print, options) {
963 const comment = commentPath.getValue();
964 const contents = printComment(commentPath, options);
965 if (!contents) {
966 return "";
967 }
968 const isBlock =
969 options.printer.isBlockComment && options.printer.isBlockComment(comment);
970
971 if (
972 hasNewline(options.originalText, options.locStart(comment), {
973 backwards: true,
974 })
975 ) {
976 // This allows comments at the end of nested structures:
977 // {
978 // x: 1,
979 // y: 2
980 // // A comment
981 // }
982 // Those kinds of comments are almost always leading comments, but
983 // here it doesn't go "outside" the block and turns it into a
984 // trailing comment for `2`. We can simulate the above by checking
985 // if this a comment on its own line; normal trailing comments are
986 // always at the end of another expression.
987
988 const isLineBeforeEmpty = isPreviousLineEmpty(
989 options.originalText,
990 comment,
991 options
992 );
993
994 return lineSuffix(
995 concat([hardline, isLineBeforeEmpty ? hardline : "", contents])
996 );
997 } else if (isBlock) {
998 // Trailing block comments never need a newline
999 return concat([" ", contents]);
1000 }
1001
1002 return concat([
1003 lineSuffix(concat([" ", contents])),
1004 !isBlock ? breakParent : "",
1005 ]);
1006}
1007
1008function printAllComments(path, print, options, needsSemi) {
1009 const value = path.getValue();
1010 const printed = print(path);
1011 const comments = value && value.comments;
1012
1013 if (!comments || comments.length === 0) {
1014 return prependCursorPlaceholder(path, options, printed);
1015 }
1016
1017 const leadingParts = [];
1018 const trailingParts = [needsSemi ? ";" : "", printed];
1019
1020 path.each((commentPath) => {
1021 const comment = commentPath.getValue();
1022 const { leading, trailing } = comment;
1023
1024 if (leading) {
1025 const contents = printLeadingComment(commentPath, print, options);
1026 if (!contents) {
1027 return;
1028 }
1029 leadingParts.push(contents);
1030
1031 const text = options.originalText;
1032 if (hasNewline(text, skipNewline(text, options.locEnd(comment)))) {
1033 leadingParts.push(hardline);
1034 }
1035 } else if (trailing) {
1036 trailingParts.push(printTrailingComment(commentPath, print, options));
1037 }
1038 }, "comments");
1039
1040 return prependCursorPlaceholder(
1041 path,
1042 options,
1043 concat(leadingParts.concat(trailingParts))
1044 );
1045}
1046
1047module.exports = {
1048 handleOwnLineComment,
1049 handleEndOfLineComment,
1050 handleRemainingComment,
1051 getCommentChildNodes,
1052 canAttachComment,
1053 isBlockComment,
1054 printDanglingComments,
1055 hasLeadingComment,
1056 hasTrailingComment,
1057 hasLeadingOwnLineComment,
1058 printComments,
1059 printAllComments,
1060};