diff --git a/docs/docs/contributors/How Can I Contribute.md b/docs/docs/contributors/How Can I Contribute.md index 20b9636f9b..b46ff62300 100644 --- a/docs/docs/contributors/How Can I Contribute.md +++ b/docs/docs/contributors/How Can I Contribute.md @@ -192,4 +192,4 @@ This update to the tree made it very straightforward to fix the original bug in Another example of code that could benefit from a better representation is [extern](https://github.com/fsprojects/fantomas/issues?q=is%3Aissue+is%3Aopen+extern). - \ No newline at end of file + \ No newline at end of file diff --git a/docs/docs/contributors/Print AST with Context.md b/docs/docs/contributors/Print AST with Context.md index e7331d74c3..655541cf97 100644 --- a/docs/docs/contributors/Print AST with Context.md +++ b/docs/docs/contributors/Print AST with Context.md @@ -68,4 +68,23 @@ Instead we can use various helper functions that take the `Context` as parameter Please take a moment to debug the unit tests in [CodePrinterHelperFunctionsTests.fs](https://github.com/fsprojects/fantomas/blob/master/src/Fantomas.Core.Tests/CodePrinterHelperFunctionsTests.fs). This will give you a better understanding of how we capture events in `CodePrinter`. +### Debugging CodePrinter + +One thing that is a bit harder to grasp initially, is what is happening when you put a breakpoint in `CodePrinter.fs`. +In `CodePrinter.fs` we compose a format function that takes a `Context` and returns a `Context`. +We do this by traversing the syntax tree, and when you put a breakpoint in `genTypeDefn` for example: + +![Breakpoint in genTypeDefn](../../images/debugging-code-printer-1.png) + +we are still in the process of composing the format function. +**The Context has not been going through our format function yet!** + +If we want to debug when the `Context` is traveling through the format function, we can easily, temporarily, insert an additional function to inspect the `Context` content: + +![Breakpoint in Context -> Context](../../images/debugging-code-printer-2.png) + +The `dumpAndContinue` helper function can be used to inspect the `Context`. +Please remove all usages when submitting a PR 😸. + + diff --git a/docs/docs/contributors/The Missing Comment.md b/docs/docs/contributors/The Missing Comment.md new file mode 100644 index 0000000000..82864f8acf --- /dev/null +++ b/docs/docs/contributors/The Missing Comment.md @@ -0,0 +1,124 @@ +--- +category: Contributors +categoryindex: 2 +index: 10 +--- +# The Missing Comment + +Code comments can literally exist between every single F# token. I'm looking at you block comment `(* ... *)`. +As explained in [Detecting trivia](./Prepare%20Context.html#Detecting-trivia), we need to do quite some processing to restore code comments. +In this guide, we would like to give you seven tips to restore a missing comment! + +## Breathe + +We understand it very well that losing a code comment after formatting can be extremely frustrating. +There is no easy fix that will solve all the missing comments overnight. Each case is very individual and can be complex to solve. +May these steps help towards fixing your problem! + +## Isolate the problem + +Before we can commence our murder mystery, it is best to narrow down our problem space. + +Example ([#2490](https://github.com/fsprojects/fantomas/issues/2490)): + +```fsharp +let (* this comment disappears after formatting *) a = [] + +type A = + { + X : int + ... + } + +while true do () +``` + +Using the [online tool](https://fsprojects.github.io/fantomas-tools/#/fantomas/preview), we can remove any code that isn't relevant. +The `type` and `while` code can be trimmed and the problem still exists. + +## Check the syntax tree + +Every code comment should be present on the root level of the syntax tree. +[ParsedImplFileInputTrivia](../../reference/fsharp-compiler-syntaxtrivia-parsedimplfileinputtrivia.html) or [ParsedSigFileInputTrivia](../../reference/fsharp-compiler-syntaxtrivia-parsedsigfileinputtrivia.html) should contain the comment. + +```fsharp +ImplFile + (ParsedImplFileInput + ("tmp.fsx", true, QualifiedNameOfFile Tmp$fsx, [], [], + [ ... ], (false, false), + { ConditionalDirectives = [] + CodeComments = [BlockComment tmp.fsx (1,4--1,50)] })) +``` + +If the comment is not there this means the F# lexer and parser didn't pick up the comment. In the unlikely event this happened, this should be fixed first over at [dotnet/fsharp](https://github.com/dotnet/fsharp). + +## Was the comment detected as trivia? + +Fantomas grabs the comments straight from the syntax tree, and transforms them as `Trivia`. +This is a fairly straightforward process, and you can easily visually inspect this using the [online tool](https://fsprojects.github.io/fantomas-tools/#/trivia). + +![Trivia in online tool](../../images/online-tool-trivia-1.png) + +If your comment does not show up there, it means there is a problem between getting the information from the syntax tree and constructing the `Trivia` in `Trivia.fs`. + +## Was the trivia assigned to a trivia node? + +The `Trivia` needs to be assigned to a `TriviaNode` to be stored in the `Context`. +Using the center tab in the tool, we can see the tree structure of all available `TriviaNodes`. + +![Root node in online tool](../../images/online-tool-trivia-2.png) + +Choosing the best suitable node can be quite tricky, there are different strategies for different `TriviaTypes`. +In this example `SynBindingKind_Normal` and `SynPat_Named` are good candidates as they appear right after the comment. +Once the `Trivia` is assigned to a `TriviaNode`, a `TriviaInstruction` will be created. This is the link between them and is what will be stored in the `Context`. + +The left-most tab in the tool shows us the result of the assigment: + +![Trivia Instructions in online tool](../../images/online-tool-trivia-3.png) + +In this example, at the time of writing, there are no `TriviaInstructions`. This means the assignment in `Trivia.fs` failed and we would need to investigate why. + +## Was the trivia assigned to the best possible node? + +Sometimes the selected `TriviaNode` isn't really the best possible candidate. +In [#640](https://github.com/fsprojects/fantomas/issues/640), the `Directive` trivia are assigned to `SynBindingKind_Normal`. + +![Wrong node assignment in online tool](../../images/online-tool-trivia-4.png) + +Notice that `SynBindingKind_Normal` starts at line `6`, while the `Trivia` is wrapped around the `inline` keyword on line `4`. +It would be ideal if we could use the `inline` keyword as a `TriviaNode`, but looking at the tree, it doesn't appear to be present. +Why is that? Well, the syntax tree does, at the time of writing, not provide a `range` for the keyword. +This would be a great addition as to [SyntaxTreeTrivia](../../reference/fsharp-compiler-syntaxtrivia.html), and can be done by submitting a pull request to [dotnet/fsharp](https://github.com/dotnet/fsharp). + +## Printing the TriviaInstruction + +The last piece of the puzzle is printing the actual `TriviaInstruction` in `CodePrinter`. +If everything up to this point went well, and the comment is still missing after formatting, it means it was not printed. + +Every `TriviaInstruction` has a type (`FsAstType`) and a `range`. The `range` is taken from an actual AST node, so when we encounter this `range` in `CodePrinter`, we need to act upon it. +We typically do this by calling `genTriviaFor`, passing down the `type`, the `range` and a function `f` that should be composed in between. + +Example: + +```fsharp +genTriviaFor Ident_ (* : FsAstType *) ident.idRange (* range : *) genIdent (* : Context -> Context *) +``` + +If we look at the definition of `genTriviaFor`: + +```fsharp +and genTriviaFor (mainNodeName: FsAstType) (range: Range) f ctx = + (enterNodeFor mainNodeName range +> f +> leaveNodeFor mainNodeName range) ctx +``` + +We can derive that the composition will be as follows: + +```fsharp +enterNodeFor Ident_ ident.idRange ++> genIdent ++> leaveNodeFor Ident_ ident.idRange +``` + +`enterNodeFor` and `leaveNodeFor` will print the `TriviaInstruction` it finds for the `range` and `type` inputs in the `Context`. + + \ No newline at end of file diff --git a/docs/images/debugging-code-printer-1.png b/docs/images/debugging-code-printer-1.png new file mode 100644 index 0000000000..cfcd1b0662 Binary files /dev/null and b/docs/images/debugging-code-printer-1.png differ diff --git a/docs/images/debugging-code-printer-2.png b/docs/images/debugging-code-printer-2.png new file mode 100644 index 0000000000..9e8be16019 Binary files /dev/null and b/docs/images/debugging-code-printer-2.png differ diff --git a/docs/images/online-tool-trivia-1.png b/docs/images/online-tool-trivia-1.png new file mode 100644 index 0000000000..60e3dcb887 Binary files /dev/null and b/docs/images/online-tool-trivia-1.png differ diff --git a/docs/images/online-tool-trivia-2.png b/docs/images/online-tool-trivia-2.png new file mode 100644 index 0000000000..ebd65f80b7 Binary files /dev/null and b/docs/images/online-tool-trivia-2.png differ diff --git a/docs/images/online-tool-trivia-3.png b/docs/images/online-tool-trivia-3.png new file mode 100644 index 0000000000..a3471b48da Binary files /dev/null and b/docs/images/online-tool-trivia-3.png differ diff --git a/docs/images/online-tool-trivia-4.png b/docs/images/online-tool-trivia-4.png new file mode 100644 index 0000000000..7affbf9f93 Binary files /dev/null and b/docs/images/online-tool-trivia-4.png differ