diff --git a/src/libraries/Microsoft.PowerFx.Core/Syntax/TexlPretty.cs b/src/libraries/Microsoft.PowerFx.Core/Syntax/TexlPretty.cs index 8953027a54..81e9f176a3 100644 --- a/src/libraries/Microsoft.PowerFx.Core/Syntax/TexlPretty.cs +++ b/src/libraries/Microsoft.PowerFx.Core/Syntax/TexlPretty.cs @@ -518,6 +518,9 @@ public static string FormatUserDefinitions(ParseUserDefinitionResult result, str foreach (var info in result.UserDefinitionSourceInfos) { var declaration = info.Declaration.Trim(); + + // If a // line comment is the last thing in the declaration, Trim() ate the newline that terminated it — emit a line break before '=' so the operator doesn't land inside the comment. + var preAssignSeparator = EndsInsideLineComment(declaration) ? "\n " : " "; var name = info.Name; var before = info.Before; var after = info.After; @@ -526,7 +529,7 @@ public static string FormatUserDefinitions(ParseUserDefinitionResult result, str case UserDefinitionType.NamedFormula: var nf = result.NamedFormulas.First(nf => nf.Ident == name); - definitions.Add(declaration + $" {(nf.ColonEqual ? TexlLexer.PunctuatorColonEqual : TexlLexer.PunctuatorEqual)} " + string.Concat(visitor.CommentsOf(before).With(nf.Formula.ParseTree.Accept(visitor, new Context(0)).With(visitor.CommentsOf(after))))); + definitions.Add(declaration + $"{preAssignSeparator}{(nf.ColonEqual ? TexlLexer.PunctuatorColonEqual : TexlLexer.PunctuatorEqual)} " + string.Concat(visitor.CommentsOf(before).With(nf.Formula.ParseTree.Accept(visitor, new Context(0)).With(visitor.CommentsOf(after))))); break; case UserDefinitionType.UDF: var udf = result.UDFs.First(udf => udf.Ident == name); @@ -535,12 +538,12 @@ public static string FormatUserDefinitions(ParseUserDefinitionResult result, str $"\n{{\n\t{string.Concat(visitor.CommentsOf(before).With(udf.Body.Accept(visitor, new Context(1)).With(visitor.CommentsOf(after)))).Trim()}\n}}" : string.Concat(visitor.CommentsOf(before).With(udf.Body.Accept(visitor, new Context(0)).With(visitor.CommentsOf(after)))); - definitions.Add(declaration + $" {TexlLexer.PunctuatorEqual} " + udfBody); + definitions.Add(declaration + $"{preAssignSeparator}{TexlLexer.PunctuatorEqual} " + udfBody); break; case UserDefinitionType.DefinedType: var type = result.DefinedTypes.First(type => type.Ident == name); - definitions.Add(declaration + $" {TexlLexer.PunctuatorColonEqual} " + string.Concat(visitor.CommentsOf(before).With(type.Type.Accept(visitor, new Context(0))).With(visitor.CommentsOf(after)))); + definitions.Add(declaration + $"{preAssignSeparator}{TexlLexer.PunctuatorColonEqual} " + string.Concat(visitor.CommentsOf(before).With(type.Type.Accept(visitor, new Context(0))).With(visitor.CommentsOf(after)))); break; default: continue; @@ -566,6 +569,52 @@ public static string FormatUserDefinitions(ParseUserDefinitionResult result, str return output; } + private static bool EndsInsideLineComment(string text) + { + var inLineComment = false; + var inBlockComment = false; + for (var i = 0; i < text.Length; i++) + { + var c = text[i]; + if (inLineComment) + { + if (c == '\n') + { + inLineComment = false; + } + + continue; + } + + if (inBlockComment) + { + if (c == '*' && i + 1 < text.Length && text[i + 1] == '/') + { + inBlockComment = false; + i++; + } + + continue; + } + + if (c == '/' && i + 1 < text.Length) + { + if (text[i + 1] == '/') + { + inLineComment = true; + i++; + } + else if (text[i + 1] == '*') + { + inBlockComment = true; + i++; + } + } + } + + return inLineComment; + } + private LazyList CommentsOf(SourceList list) { if (list == null) diff --git a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs index 34ecb50a4b..a60d7ae3ee 100644 --- a/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs +++ b/src/tests/Microsoft.PowerFx.Core.Tests.Shared/FormatterTests.cs @@ -325,6 +325,16 @@ public void TestPrettyPrint(string script, string expected) "// Math\nAdd(x:Number,y:Number):Number= /*c1*/ x+y;\n// Trig\nCosine(x:Number):Number=Cos(x);\n", "// Math\nAdd(x:Number,y:Number):Number = /*c1*/x + y;\n// Trig\nCosine(x:Number):Number = Cos(x);")] + // Trailing // line comment on the header line must not be merged with the '=' body (issue #2998). + [InlineData( + "MyNF // My Named Formula\n = Pi()/2;", + "MyNF // My Named Formula\n = Pi() / 2;")] + + // Mixing /*..*/ block and // line comments in a UDF declaration must also preserve the line break. + [InlineData( + "Add(x:Number,y:Number):Number /* rt */ // trailing\n = x+y;", + "Add(x:Number,y:Number):Number /* rt */ // trailing\n = x + y;")] + // Issue #2997: trailing comment on the last user definition must be preserved. [InlineData("MyNF=Pi()/2; // Half PI", "MyNF = Pi() / 2; // Half PI")] [InlineData("MyNF=Pi()/2; /* Half PI */", "MyNF = Pi() / 2; /* Half PI */")]