From 37c5a78da30c8c57c74f6d7c3c0a1e418eb4ef5b Mon Sep 17 00:00:00 2001 From: Praveen Kuttappan Date: Fri, 6 Sep 2024 15:40:31 -0400 Subject: [PATCH 1/6] Add a dotnet tool to create API review text from input APIview json token file --- src/dotnet/APIView/APIView.sln | 6 +++ src/dotnet/APIView/APIView/Model/CodeFile.cs | 16 +++++- .../APIView/APIView/Model/V2/ReviewLine.cs | 9 ++-- .../APIViewJsonValidator.csproj | 19 +++++++ .../APIView/APIViewJsonValidator/Program.cs | 51 +++++++++++++++++++ 5 files changed, 96 insertions(+), 5 deletions(-) create mode 100644 src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj create mode 100644 src/dotnet/APIView/APIViewJsonValidator/Program.cs diff --git a/src/dotnet/APIView/APIView.sln b/src/dotnet/APIView/APIView.sln index 1aa0a367db1..30986905281 100644 --- a/src/dotnet/APIView/APIView.sln +++ b/src/dotnet/APIView/APIView.sln @@ -23,6 +23,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpAPIParserTests", "..\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestReferenceWithInternalsVisibleTo", "..\Azure.ClientSdk.Analyzers\TestReferenceWithInternalsVisibleTo\TestReferenceWithInternalsVisibleTo.csproj", "{0FE36A2D-EB25-4119-A7DA-2605BB2516C2}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APIViewJsonValidator", "APIViewJsonValidator\APIViewJsonValidator.csproj", "{424B9F9A-1518-40C3-B7D3-88B53A038A23}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -69,6 +71,10 @@ Global {0FE36A2D-EB25-4119-A7DA-2605BB2516C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FE36A2D-EB25-4119-A7DA-2605BB2516C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FE36A2D-EB25-4119-A7DA-2605BB2516C2}.Release|Any CPU.Build.0 = Release|Any CPU + {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Debug|Any CPU.Build.0 = Debug|Any CPU + {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Release|Any CPU.ActiveCfg = Release|Any CPU + {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/dotnet/APIView/APIView/Model/CodeFile.cs b/src/dotnet/APIView/APIView/Model/CodeFile.cs index f5b7fe1c666..2a182d8b632 100644 --- a/src/dotnet/APIView/APIView/Model/CodeFile.cs +++ b/src/dotnet/APIView/APIView/Model/CodeFile.cs @@ -153,9 +153,21 @@ public string GetApiText() StringBuilder sb = new(); foreach (var line in ReviewLines) { - line.AppendApiTextToBuilder(sb, 0, true); + line.AppendApiTextToBuilder(sb, 0, true, GetIndentationForLanguage(Language)); } return sb.ToString(); - } + } + + public static int GetIndentationForLanguage(string language) + { + switch (language) + { + case "C++": + case "C": + return 2; + default: + return 4; + } + } } } diff --git a/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs b/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs index 2779cd4b0c8..7f1485080af 100644 --- a/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs +++ b/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs @@ -61,7 +61,7 @@ public void AddToken(ReviewToken token) Tokens.Add(token); } - public void AppendApiTextToBuilder(StringBuilder sb, int indent = 0, bool skipDocs = true) + public void AppendApiTextToBuilder(StringBuilder sb, int indent = 0, bool skipDocs = true, int lineIndentSpaces = 4) { if (skipDocs && Tokens.Count > 0 && Tokens[0].IsDocumentation == true) { @@ -77,7 +77,10 @@ public void AppendApiTextToBuilder(StringBuilder sb, int indent = 0, bool skipDo //Add spaces for indentation for (int i = 0; i < indent; i++) { - sb.Append(" "); + for(int j = 0; j < lineIndentSpaces; j++) + { + sb.Append(" "); + } } //Process all tokens sb.Append(ToString(true)); @@ -85,7 +88,7 @@ public void AppendApiTextToBuilder(StringBuilder sb, int indent = 0, bool skipDo sb.Append(Environment.NewLine); foreach (var child in Children) { - child.AppendApiTextToBuilder(sb, indent + 1, skipDocs); + child.AppendApiTextToBuilder(sb, indent + 1, skipDocs, lineIndentSpaces); } } diff --git a/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj b/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj new file mode 100644 index 00000000000..6d2667d94dc --- /dev/null +++ b/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + True + + + + + + + + + + + diff --git a/src/dotnet/APIView/APIViewJsonValidator/Program.cs b/src/dotnet/APIView/APIViewJsonValidator/Program.cs new file mode 100644 index 00000000000..d1b376ddab3 --- /dev/null +++ b/src/dotnet/APIView/APIViewJsonValidator/Program.cs @@ -0,0 +1,51 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +using System; +using System.CommandLine.Invocation; +using System.CommandLine; +using System.IO.Compression; +using System.Text; +using System.Text.RegularExpressions; +using System.Xml.Linq; +using APIView; +using ApiView; + +public static class Program +{ + public static int Main(string[] args) + { + var jsonFilePath = new Option("--path", "Path to the input json file").ExistingOnly(); + jsonFilePath.IsRequired = true; + + var rootCommand = new RootCommand("Generate API review output from token JSON file to verify the input json file") + { + jsonFilePath + }; + + rootCommand.SetHandler(async (FileInfo jsonFilePath) => + { + try + { + var parentDirectory = jsonFilePath.Directory; + var outputFilePath = Path.Combine(parentDirectory?.FullName, jsonFilePath.Name.Replace(".json", ".txt")); + using (var stream = jsonFilePath.OpenRead()) + { + await GenerateReviewTextFromJson(stream, outputFilePath); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error reading input json file : {ex.Message}"); + throw; + } + }, jsonFilePath); + return rootCommand.InvokeAsync(args).Result; + } + + private static async Task GenerateReviewTextFromJson(Stream stream, string outputFilePath) + { + var codeFile = await CodeFile.DeserializeAsync(stream, false, true); + string apiOutput = codeFile.GetApiText(); + await File.WriteAllTextAsync(outputFilePath, apiOutput); + } +} From 9a894beb60b953830cfd77a7fb0ecec1c4f91780 Mon Sep 17 00:00:00 2001 From: Praveen Kuttappan Date: Tue, 17 Sep 2024 14:08:29 -0400 Subject: [PATCH 2/6] Add utility to convert API view token to new tree token model --- src/dotnet/APIView/APIView.sln | 2 +- src/dotnet/APIView/APIView/Model/CodeFile.cs | 178 +++++++++++++++++- .../APIView/APIView/Model/V2/ReviewLine.cs | 4 + ...dator.csproj => APIViewJsonUtility.csproj} | 5 +- .../APIView/APIViewJsonValidator/Program.cs | 67 ++++++- 5 files changed, 245 insertions(+), 11 deletions(-) rename src/dotnet/APIView/APIViewJsonValidator/{APIViewJsonValidator.csproj => APIViewJsonUtility.csproj} (78%) diff --git a/src/dotnet/APIView/APIView.sln b/src/dotnet/APIView/APIView.sln index 30986905281..f4d2a54df53 100644 --- a/src/dotnet/APIView/APIView.sln +++ b/src/dotnet/APIView/APIView.sln @@ -23,7 +23,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpAPIParserTests", "..\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestReferenceWithInternalsVisibleTo", "..\Azure.ClientSdk.Analyzers\TestReferenceWithInternalsVisibleTo\TestReferenceWithInternalsVisibleTo.csproj", "{0FE36A2D-EB25-4119-A7DA-2605BB2516C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APIViewJsonValidator", "APIViewJsonValidator\APIViewJsonValidator.csproj", "{424B9F9A-1518-40C3-B7D3-88B53A038A23}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APIViewJsonUtility", "APIViewJsonValidator\APIViewJsonUtility.csproj", "{424B9F9A-1518-40C3-B7D3-88B53A038A23}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/src/dotnet/APIView/APIView/Model/CodeFile.cs b/src/dotnet/APIView/APIView/Model/CodeFile.cs index 2a182d8b632..e4a88c2b087 100644 --- a/src/dotnet/APIView/APIView/Model/CodeFile.cs +++ b/src/dotnet/APIView/APIView/Model/CodeFile.cs @@ -11,7 +11,6 @@ using System.Text; using System.Text.Json; using System.Text.Json.Serialization; -using System.Text.RegularExpressions; using System.Threading.Tasks; namespace ApiView @@ -169,5 +168,182 @@ public static int GetIndentationForLanguage(string language) return 4; } } + + public void ConvertToTreeTokenModel() + { + Dictionary navigationItems = new Dictionary(); + ReviewLine reviewLine = new ReviewLine(); + ReviewLine previousLine = null; + bool isDocumentation = false; + bool isHidden = false; + bool skipDiff = false; + bool isDeprecated = false; + bool skipIndent = false; + //Process all navigation items in old model to generate a map + GetNavigationMap(navigationItems, Navigation); + + List currentLineTokens = new List(); + foreach(var oldToken in Tokens) + { + ReviewToken token = null; + switch(oldToken.Kind) + { + case CodeFileTokenKind.DocumentRangeStart: + isDocumentation = true; break; + case CodeFileTokenKind.DocumentRangeEnd: + isDocumentation = false; break; + case CodeFileTokenKind.DeprecatedRangeStart: + isDeprecated = true; break; + case CodeFileTokenKind.DeprecatedRangeEnd: + isDeprecated = false; break; + case CodeFileTokenKind.SkipDiffRangeStart: + skipDiff = true; break; + case CodeFileTokenKind.SkipDiffRangeEnd: + skipDiff = false; break; + case CodeFileTokenKind.HiddenApiRangeStart: + isHidden = true; break; + case CodeFileTokenKind.HiddenApiRangeEnd: + isHidden = false; break; + case CodeFileTokenKind.Keyword: + token = ReviewToken.CreateKeywordToken(oldToken.Value, false); + break; + case CodeFileTokenKind.Comment: + token = ReviewToken.CreateCommentToken(oldToken.Value, false); + break; + case CodeFileTokenKind.Text: + token = ReviewToken.CreateTextToken(oldToken.Value, oldToken.NavigateToId, false); + break; + case CodeFileTokenKind.Punctuation: + token = ReviewToken.CreatePunctuationToken(oldToken.Value, false); + break; + case CodeFileTokenKind.TypeName: + token = ReviewToken.CreateTypeNameToken(oldToken.Value, false); + //Set Navigation display name + if (!string.IsNullOrEmpty(oldToken.DefinitionId) && navigationItems.ContainsKey(oldToken.DefinitionId)) + token.NavigationDisplayName = navigationItems[oldToken.DefinitionId]; + break; + case CodeFileTokenKind.MemberName: + token = ReviewToken.CreateMemberNameToken(oldToken.Value, false); + break; + case CodeFileTokenKind.StringLiteral: + token = ReviewToken.CreateStringLiteralToken(oldToken.Value, false); + break; + case CodeFileTokenKind.Literal: + token = ReviewToken.CreateLiteralToken(oldToken.Value, false); + break; + case CodeFileTokenKind.ExternalLinkStart: + token = ReviewToken.CreateStringLiteralToken(oldToken.Value, false); + break; + case CodeFileTokenKind.Whitespace: + if (currentLineTokens.Count > 0) { + currentLineTokens.Last().HasSuffixSpace = true; + } + else if (!skipIndent) { + reviewLine.Indent += oldToken.Value.Length; + } + break; + case CodeFileTokenKind.Newline: + var parent = previousLine; + skipIndent = false; + if (currentLineTokens.Count > 0) + { + while (parent != null && parent.Indent >= reviewLine.Indent) + parent = parent.parentLine; + } + else + { + //If current line is empty line then add it as an empty line under previous line's parent + parent = previousLine?.parentLine; + } + + if (parent == null) + { + this.ReviewLines.Add(reviewLine); + } + else + { + parent.Children.Add(reviewLine); + reviewLine.parentLine = parent; + } + + if (currentLineTokens.Count == 0) + { + //Empty line. So just add previous line as related line + reviewLine.RelatedToLine = previousLine?.LineId; + } + else + { + reviewLine.Tokens = currentLineTokens; + previousLine = reviewLine; + } + + reviewLine = new ReviewLine(); + // If previous line ends with "," then next line will be sub line to show split content in multiple lines. + // Set next line's indent same as current line + // This is required to convert C++ tokens correctly + if (previousLine != null && previousLine.Tokens.LastOrDefault()?.Value == "," && Language == "C++") + { + reviewLine.Indent = previousLine.Indent; + skipIndent = true; + } + currentLineTokens = new List(); + break; + case CodeFileTokenKind.LineIdMarker: + if (string.IsNullOrEmpty(reviewLine.LineId)) + reviewLine.LineId = oldToken.Value; + break; + default: + Console.WriteLine($"Unsupported token kind to convert to new model, Kind: {oldToken.Kind}, value: {oldToken.Value}, Line Id: {oldToken.DefinitionId}"); + break; + } + + if (token != null) + { + currentLineTokens.Add(token); + + if (oldToken.Equals("}") || oldToken.Equals("};")) + reviewLine.IsContextEndLine = true; + if (isHidden) + reviewLine.IsHidden = true; + if (oldToken.DefinitionId != null) + reviewLine.LineId = oldToken.DefinitionId; + if (oldToken.CrossLanguageDefinitionId != null) + reviewLine.CrossLanguageId = oldToken.CrossLanguageDefinitionId; + if (isDeprecated) + token.IsDeprecated = true; + if (skipDiff) + token.SkipDiff = true; + if (isDocumentation) + token.IsDocumentation = true; + } + } + + //Process last line + if (currentLineTokens.Count > 0) + { + reviewLine.Tokens = currentLineTokens; + var parent = previousLine; + while (parent != null && parent.Indent >= reviewLine.Indent) + parent = parent.parentLine; + + if (parent == null) + this.ReviewLines.Add(reviewLine); + else + parent.Children.Add(reviewLine); + } + } + + private static void GetNavigationMap(Dictionary navigationItems, NavigationItem[] items) + { + if (items == null) + return; + + foreach (var item in items) + { + var key = string.IsNullOrEmpty(item.NavigationId) ? item.Text : item.NavigationId; + navigationItems.Add(key, item.Text); + GetNavigationMap(navigationItems, item.ChildItems); + } + } } } diff --git a/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs b/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs index 7f1485080af..9b0c758615a 100644 --- a/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs +++ b/src/dotnet/APIView/APIView/Model/V2/ReviewLine.cs @@ -56,6 +56,10 @@ public class ReviewLine [JsonIgnore] public bool Processed { get; set; } = false; + [JsonIgnore] + public int Indent { get; set; } + [JsonIgnore] + public ReviewLine parentLine { get; set; } public void AddToken(ReviewToken token) { Tokens.Add(token); diff --git a/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj b/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonUtility.csproj similarity index 78% rename from src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj rename to src/dotnet/APIView/APIViewJsonValidator/APIViewJsonUtility.csproj index 6d2667d94dc..7d37d10fb3b 100644 --- a/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonValidator.csproj +++ b/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonUtility.csproj @@ -1,11 +1,12 @@ - + Exe + true net8.0 enable enable - True + APIViewJsonTool diff --git a/src/dotnet/APIView/APIViewJsonValidator/Program.cs b/src/dotnet/APIView/APIViewJsonValidator/Program.cs index d1b376ddab3..cd6a0f7c1ee 100644 --- a/src/dotnet/APIView/APIViewJsonValidator/Program.cs +++ b/src/dotnet/APIView/APIViewJsonValidator/Program.cs @@ -16,29 +16,57 @@ public static int Main(string[] args) { var jsonFilePath = new Option("--path", "Path to the input json file").ExistingOnly(); jsonFilePath.IsRequired = true; + var outputDir = new Option("--outputDir", "Path to the output Directory(Optional)."); + var dumpOption = new Option("--dumpApiText", "Dump the API text to a txt file."); + var convertOption = new Option("--convertToTree", "Convert old APIView token model to new tree token model."); var rootCommand = new RootCommand("Generate API review output from token JSON file to verify the input json file") { - jsonFilePath + jsonFilePath, + outputDir, + dumpOption, + convertOption }; - rootCommand.SetHandler(async (FileInfo jsonFilePath) => + rootCommand.SetHandler(static async (jsonFilePath, outputDir, dumpOption, convertOption) => { + if(!dumpOption && !convertOption) + { + Console.Error.WriteLine("Please specify either --dumpApiText or --convertToTree option."); + return; + } + try { - var parentDirectory = jsonFilePath.Directory; - var outputFilePath = Path.Combine(parentDirectory?.FullName, jsonFilePath.Name.Replace(".json", ".txt")); - using (var stream = jsonFilePath.OpenRead()) + var outputFileDirectory = outputDir?.FullName ?? jsonFilePath.Directory.FullName; + if (dumpOption) + { + + var outputFilePath = Path.Combine(outputFileDirectory, jsonFilePath.Name.Replace(".json", ".txt")); + Console.WriteLine($"Dumping API text to {outputFilePath}"); + using (var stream = jsonFilePath.OpenRead()) + { + await GenerateReviewTextFromJson(stream, outputFilePath); + } + } + + if (convertOption) { - await GenerateReviewTextFromJson(stream, outputFilePath); + var outputFilePath = Path.Combine(outputFileDirectory, jsonFilePath.Name.Replace(".json", "_new.json")); + Console.WriteLine($"Converting old Json API view tokens to new Json model API tokens. New json file: {outputFilePath}"); + using (var stream = jsonFilePath.OpenRead()) + { + await ConvertToTreeModel(stream, outputFilePath); + } } + } catch (Exception ex) { Console.Error.WriteLine($"Error reading input json file : {ex.Message}"); throw; } - }, jsonFilePath); + }, jsonFilePath, outputDir, dumpOption, convertOption); return rootCommand.InvokeAsync(args).Result; } @@ -48,4 +76,29 @@ private static async Task GenerateReviewTextFromJson(Stream stream, string outpu string apiOutput = codeFile.GetApiText(); await File.WriteAllTextAsync(outputFilePath, apiOutput); } + + private static async Task ConvertToTreeModel(Stream stream, string outputFilePath) + { + CodeFile codeFile = null; + try + { + codeFile = await CodeFile.DeserializeAsync(stream, false, false); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Input json is probably not using old token model. Error reading input json file. : {ex.Message}"); + throw; + } + + if(codeFile != null) + { + codeFile.ConvertToTreeTokenModel(); + Console.WriteLine("Converted APIView token model to new schema"); + codeFile.Navigation = null; + codeFile.Tokens = null; + using var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write); + await codeFile.SerializeAsync(fileStream); + Console.WriteLine($"New APIView json token file generated at {outputFilePath}"); + } + } } From 55c8ab81a4afbd70b7f6235f6545e8952a6bcf8a Mon Sep 17 00:00:00 2001 From: Praveen Kuttappan Date: Wed, 18 Sep 2024 16:14:33 -0400 Subject: [PATCH 3/6] Support existing navigation panel tokens for backward compatibility for converted token files --- src/dotnet/APIView/APIView/Model/CodeFile.cs | 16 ++- .../APIView/APIViewJsonValidator/Program.cs | 3 +- .../APIViewWeb/Helpers/CodeFileHelpers.cs | 103 ++++++++++++------ .../APIViewWeb/LeanModels/CodePanelModels.cs | 3 + .../src/app/_models/codePanelModels.ts | 1 + .../app/_workers/apitree-builder.worker.ts | 13 ++- 6 files changed, 97 insertions(+), 42 deletions(-) diff --git a/src/dotnet/APIView/APIView/Model/CodeFile.cs b/src/dotnet/APIView/APIView/Model/CodeFile.cs index e4a88c2b087..3a9414544b4 100644 --- a/src/dotnet/APIView/APIView/Model/CodeFile.cs +++ b/src/dotnet/APIView/APIView/Model/CodeFile.cs @@ -147,12 +147,12 @@ public async Task SerializeAsync(Stream stream) /// Generates a complete text representation of API surface to help generating the content. /// One use case of this function will be to support download request of entire API review surface. /// - public string GetApiText() + public string GetApiText(bool skipDocs = true) { StringBuilder sb = new(); foreach (var line in ReviewLines) { - line.AppendApiTextToBuilder(sb, 0, true, GetIndentationForLanguage(Language)); + line.AppendApiTextToBuilder(sb, 0, skipDocs, GetIndentationForLanguage(Language)); } return sb.ToString(); } @@ -179,6 +179,7 @@ public void ConvertToTreeTokenModel() bool skipDiff = false; bool isDeprecated = false; bool skipIndent = false; + string className = ""; //Process all navigation items in old model to generate a map GetNavigationMap(navigationItems, Navigation); @@ -205,7 +206,10 @@ public void ConvertToTreeTokenModel() case CodeFileTokenKind.HiddenApiRangeEnd: isHidden = false; break; case CodeFileTokenKind.Keyword: - token = ReviewToken.CreateKeywordToken(oldToken.Value, false); + token = ReviewToken.CreateKeywordToken(oldToken.Value, false); + var keywordValue = oldToken.Value.ToLower(); + if (keywordValue == "class" || keywordValue == "enum" || keywordValue == "struct" || keywordValue == "interface" || keywordValue == "type" || keywordValue == "namespace") + className = keywordValue; break; case CodeFileTokenKind.Comment: token = ReviewToken.CreateCommentToken(oldToken.Value, false); @@ -218,9 +222,9 @@ public void ConvertToTreeTokenModel() break; case CodeFileTokenKind.TypeName: token = ReviewToken.CreateTypeNameToken(oldToken.Value, false); - //Set Navigation display name - if (!string.IsNullOrEmpty(oldToken.DefinitionId) && navigationItems.ContainsKey(oldToken.DefinitionId)) - token.NavigationDisplayName = navigationItems[oldToken.DefinitionId]; + if (currentLineTokens.Any(t => t.Kind == TokenKind.Keyword && t.Value.ToLower() == className)) + token.RenderClasses.Add(className); + className = ""; break; case CodeFileTokenKind.MemberName: token = ReviewToken.CreateMemberNameToken(oldToken.Value, false); diff --git a/src/dotnet/APIView/APIViewJsonValidator/Program.cs b/src/dotnet/APIView/APIViewJsonValidator/Program.cs index cd6a0f7c1ee..909b02fc128 100644 --- a/src/dotnet/APIView/APIViewJsonValidator/Program.cs +++ b/src/dotnet/APIView/APIViewJsonValidator/Program.cs @@ -73,7 +73,7 @@ public static int Main(string[] args) private static async Task GenerateReviewTextFromJson(Stream stream, string outputFilePath) { var codeFile = await CodeFile.DeserializeAsync(stream, false, true); - string apiOutput = codeFile.GetApiText(); + string apiOutput = codeFile.GetApiText(false); await File.WriteAllTextAsync(outputFilePath, apiOutput); } @@ -94,7 +94,6 @@ private static async Task ConvertToTreeModel(Stream stream, string outputFilePat { codeFile.ConvertToTreeTokenModel(); Console.WriteLine("Converted APIView token model to new schema"); - codeFile.Navigation = null; codeFile.Tokens = null; using var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write); await codeFile.SerializeAsync(fileStream); diff --git a/src/dotnet/APIView/APIViewWeb/Helpers/CodeFileHelpers.cs b/src/dotnet/APIView/APIViewWeb/Helpers/CodeFileHelpers.cs index 3c90c2bdabc..cf4c84508af 100644 --- a/src/dotnet/APIView/APIViewWeb/Helpers/CodeFileHelpers.cs +++ b/src/dotnet/APIView/APIViewWeb/Helpers/CodeFileHelpers.cs @@ -7,6 +7,7 @@ using APIViewWeb.LeanModels; using System; using System.Collections.Generic; +using System.Drawing; using System.Linq; using System.Threading.Tasks; @@ -35,7 +36,7 @@ public static async Task GenerateCodePanelDataAsync(CodePanelRawD CollectDocumentationLines(diffLines, codePanelData.DiffDocumentationMap, 1, "root", true); // Check if diff is required for active revision and diff revision to avoid unnecessary diff calculation bool hasSameApis = AreCodeFilesSame(codePanelRawData.activeRevisionCodeFile, codePanelRawData.diffRevisionCodeFile); - if(!hasSameApis) + if (!hasSameApis) { reviewLines = FindDiff(reviewLines, codePanelRawData.diffRevisionCodeFile.ReviewLines); // Remap nodeIdHashed for documentation to diff adjusted nodeIdHashed so that documentation is correctly listed on review. @@ -52,20 +53,23 @@ public static async Task GenerateCodePanelDataAsync(CodePanelRawD int idx = 0; string nodeHashId = ""; - Dictionary relatedLineMap = new Dictionary(); + Dictionary relatedLineMap = new Dictionary(); foreach (var reviewLine in reviewLines) { if (reviewLine.IsDocumentation) continue; nodeHashId = await BuildAPITree(codePanelData: codePanelData, codePanelRawData: codePanelRawData, reviewLine: reviewLines[idx], parentNodeIdHashed: rootNodeId, nodePositionAtLevel: idx, prevNodeHashId: nodeHashId, relatedLineMap: relatedLineMap); - idx++; + idx++; } //Set related line's node ID hashed in tree metadata - foreach(var key in relatedLineMap.Keys) + foreach (var key in relatedLineMap.Keys) { codePanelData.SetLineAsRelated(key, relatedLineMap[key]); } + + // Create navigation tree using information is code file for backward compatibility when existing code file object is converted to new model. + CreateNavigationTree(codePanelData, codePanelRawData.activeRevisionCodeFile); return codePanelData; } @@ -98,13 +102,13 @@ private static async Task BuildAPITree(CodePanelData codePanelData, Code // Process all child lines int idx = 0; - string childNodeHashId = ""; + string childNodeHashId = ""; foreach (var childLine in reviewLine.Children) { if (childLine.IsDocumentation) continue; childNodeHashId = await BuildAPITree(codePanelData: codePanelData, codePanelRawData: codePanelRawData, reviewLine: childLine, - parentNodeIdHashed: nodeIdHashed, nodePositionAtLevel: idx, prevNodeHashId: childNodeHashId, relatedLineMap: relatedLineMap, indent: indent + 1); + parentNodeIdHashed: nodeIdHashed, nodePositionAtLevel: idx, prevNodeHashId: childNodeHashId, relatedLineMap: relatedLineMap, indent: indent + 1); idx++; }; @@ -117,7 +121,7 @@ private static async Task BuildAPITree(CodePanelData codePanelData, Code var classes = codePanelData.NodeMetaDataObj[prevNodeHashId].CodeLinesObj.LastOrDefault()?.RowClassesObj; if (classes != null) { - classes = classes.Where(c=> c == "added" || c == "removed").ToHashSet(); + classes = classes.Where(c => c == "added" || c == "removed").ToHashSet(); codePanelData.NodeMetaDataObj[nodeIdHashed].CodeLinesObj.LastOrDefault()?.RowClassesObj.UnionWith(classes); } } @@ -188,7 +192,7 @@ private static void BuildNodeTokens(CodePanelData codePanelData, CodePanelRawDat bool skipDocDiff = diffDocLines.Count == 0 || activeDocLines.Count == 0; foreach (var docRow in docLines) { - if(!skipDocDiff && !docsIntersect.Contains(docRow)) + if (!skipDocDiff && !docsIntersect.Contains(docRow)) { if (activeDocs.Contains(docRow)) { @@ -199,14 +203,14 @@ private static void BuildNodeTokens(CodePanelData codePanelData, CodePanelRawDat { docRow.DiffKind = DiffKind.Removed; docRow.RowClassesObj.Add("removed"); - } + } } docRow.NodeId = codePanelRow.NodeId; docRow.NodeIdHashed = codePanelRow.NodeIdHashed; InsertCodePanelRowData(codePanelData: codePanelData, rowData: docRow, nodeIdHashed: nodeIdHashed); } } - + // Get comment for current row var commentsForRow = CollectUserCommentsForRow(codePanelRawData, reviewLine.LineId, nodeIdHashed, codePanelRow); //Add code line and comment to code panel data @@ -220,7 +224,7 @@ private static CodePanelRowData GetCodePanelRowData(ReviewLine reviewLine, strin { CodePanelRowData codePanelRowData = new() { - Type = reviewLine.Tokens.Any(t => t.IsDocumentation == true)? CodePanelRowDatatype.Documentation : CodePanelRowDatatype.CodeLine, + Type = reviewLine.Tokens.Any(t => t.IsDocumentation == true) ? CodePanelRowDatatype.Documentation : CodePanelRowDatatype.CodeLine, NodeIdHashed = nodeIdHashed, NodeId = reviewLine.LineId, Indent = indent, @@ -238,7 +242,7 @@ private static CodePanelRowData GetCodePanelRowData(ReviewLine reviewLine, strin return codePanelRowData; } - if(reviewLine.DiffKind == DiffKind.Added) + if (reviewLine.DiffKind == DiffKind.Added) { rowClasses.Add("added"); } @@ -269,7 +273,7 @@ private static CodePanelRowData GetCodePanelRowData(ReviewLine reviewLine, strin return codePanelRowData; } - + private static CodePanelRowData CollectUserCommentsForRow(CodePanelRawData codePanelRawData, string nodeId, string nodeIdHashed, CodePanelRowData codePanelRowData) { var commentRowData = new CodePanelRowData(); @@ -301,7 +305,7 @@ private static void InsertCodePanelRowData(CodePanelData codePanelData, CodePane codePanelData.NodeMetaDataObj[nodeIdHashed] = new CodePanelNodeMetaData(); if (rowData.Type == CodePanelRowDatatype.Documentation) - { + { rowData.RowPositionInGroup = codePanelData.NodeMetaDataObj[nodeIdHashed].DocumentationObj.Count(); codePanelData.NodeMetaDataObj[nodeIdHashed].DocumentationObj.Add(rowData); } @@ -314,7 +318,7 @@ private static void InsertCodePanelRowData(CodePanelData codePanelData, CodePane { codePanelData.NodeMetaDataObj[nodeIdHashed].IsNodeWithDiff = true; var parentId = codePanelData.NodeMetaDataObj[nodeIdHashed].ParentNodeIdHashed; - while (parentId != null && !parentId.Equals("root") && codePanelData.NodeMetaDataObj.ContainsKey(parentId) + while (parentId != null && !parentId.Equals("root") && codePanelData.NodeMetaDataObj.ContainsKey(parentId) && !codePanelData.NodeMetaDataObj[parentId].IsNodeWithDiffInDescendants) { codePanelData.NodeMetaDataObj[parentId].IsNodeWithDiffInDescendants = true; @@ -370,14 +374,14 @@ private static bool AreReviewLinesSame(List reviewLinesA, List FindDiff(List activeLines, List diffLines) { List resultLines = []; - Dictionary refCountMap = []; + Dictionary refCountMap = []; //Set lines from diff revision as not from active revision foreach (var line in diffLines) @@ -388,11 +392,11 @@ public static List FindDiff(List activeLines, List FindDiff(List activeLines, List FindDiff(List activeLines, List l.LineId == line.LineId && l.Processed == false && l.Equals(line)); - var diffLineChildren = diffLine != null ? diffLine.Children: new List(); + var diffLineChildren = diffLine != null ? diffLine.Children : new List(); var resultantSubTree = FindDiff(activeLine.Children, diffLineChildren); //Update new resulting subtree as children of current node activeLine.Children.Clear(); @@ -449,17 +453,17 @@ private static void MarkTreeNodeAsModified(ReviewLine line, DiffKind diffKind) line.DiffKind = diffKind; foreach (var child in line.Children) { - if(!child.IsDocumentation) - MarkTreeNodeAsModified(child, diffKind); + if (!child.IsDocumentation) + MarkTreeNodeAsModified(child, diffKind); } } /*** * This method collects all documentation lines from the review line and generate a CodePanelRow object for each documentation line. * These documentation rows will be stored in a dictionary so it can be mapped and connected tp code line when processing code lines. * */ - private static void CollectDocumentationLines(List reviewLines, Dictionary> documentationRowMap, int indent, string parentNodeIdHash, bool enableSkipDiff = false) + private static void CollectDocumentationLines(List reviewLines, Dictionary> documentationRowMap, int indent, string parentNodeIdHash, bool enableSkipDiff = false) { - if(reviewLines?.Count == 0) + if (reviewLines?.Count == 0) return; List docRows = []; @@ -469,7 +473,7 @@ private static void CollectDocumentationLines(List reviewLines, Dict { //Find if current line has at least one token that's not marked as skip from diff check bool hasNonSkippedTokens = line.Tokens.Any(t => t.SkipDiff != true); - if(line.IsDocumentation && (!enableSkipDiff ||hasNonSkippedTokens)) + if (line.IsDocumentation && (!enableSkipDiff || hasNonSkippedTokens)) { docRows.Add(GetCodePanelRowData(line, parentNodeIdHash, indent)); continue; @@ -477,7 +481,8 @@ private static void CollectDocumentationLines(List reviewLines, Dict //Create hash id for code line and set node ID and hash Id for documentation rows var nodeIdHashed = line.GetTokenNodeIdHash(parentNodeIdHash, idx); - docRows.ForEach( d=> { + docRows.ForEach(d => + { d.NodeIdHashed = nodeIdHashed; d.NodeId = line.LineId; }); @@ -519,7 +524,7 @@ private static void RemapDocumentationLines(List activeLines, Dictio if (line.Children.Count > 0) { RemapDocumentationLines(line.Children, documentationRowMap, oldHashId, newHashId); - } + } idx++; // Move previous review line index before diff calculation based on diff type so we can calculate old HashId correctly. @@ -528,7 +533,7 @@ private static void RemapDocumentationLines(List activeLines, Dictio activeIdx++; diffIdx++; } - else if(line.DiffKind == DiffKind.Added) + else if (line.DiffKind == DiffKind.Added) { activeIdx++; } @@ -542,7 +547,7 @@ private static void RemapDocumentationLines(List activeLines, Dictio private static void FindModifiedTokens(ReviewLine lineA, ReviewLine lineB) { var lineATokenMap = new Dictionary(); - foreach(var token in lineA.Tokens) + foreach (var token in lineA.Tokens) { lineATokenMap[token.Value] = token; } @@ -552,7 +557,7 @@ private static void FindModifiedTokens(ReviewLine lineA, ReviewLine lineB) lineBTokenMap[token.Value] = token; } - foreach( var key in lineBTokenMap.Keys.Except(lineATokenMap.Keys)) + foreach (var key in lineBTokenMap.Keys.Except(lineATokenMap.Keys)) { lineBTokenMap[key].RenderClasses.Add("diff-change"); } @@ -562,5 +567,41 @@ private static void FindModifiedTokens(ReviewLine lineA, ReviewLine lineB) } } + private static void CreateNavigationTree(CodePanelData codePanelData, CodeFile codeFile) + { + if (codeFile.Navigation != null && codeFile.Navigation.Count() > 0) + { + //Use navigation tree generated by the parser + foreach (var navigation in codeFile.Navigation) + { + codePanelData.NavigationTreeNodesObj.Add(ProcessNavigationNodeFromOldModel(codePanelData.LineIdToNodeIdHashed, navigation)); + } + } + } + + private static NavigationTreeNode ProcessNavigationNodeFromOldModel(Dictionary nodeIdMap, NavigationItem navItem) + { + NavigationTreeNode node = new NavigationTreeNode() + { + Label = navItem.Text, + Expanded = true, + Data = new NavigationTreeNodeData() + }; + + if(navItem.Tags != null) + { + node.Data.Kind = navItem.Tags["TypeKind"]; + node.Data.Icon = node.Data.Kind; + } + + if (nodeIdMap.ContainsKey(navItem.NavigationId)) + node.Data.NodeIdHashed = nodeIdMap[navItem.NavigationId]; + + foreach (var childItem in navItem.ChildItems) + { + node.ChildrenObj.Add(ProcessNavigationNodeFromOldModel(nodeIdMap, childItem)); + } + return node; + } } } diff --git a/src/dotnet/APIView/APIViewWeb/LeanModels/CodePanelModels.cs b/src/dotnet/APIView/APIViewWeb/LeanModels/CodePanelModels.cs index 6ab77bc264b..285718c7849 100644 --- a/src/dotnet/APIView/APIViewWeb/LeanModels/CodePanelModels.cs +++ b/src/dotnet/APIView/APIViewWeb/LeanModels/CodePanelModels.cs @@ -105,6 +105,9 @@ public class CodePanelData public Dictionary> ActiveDocumentationMap { get; set; } = new Dictionary>(); [JsonIgnore] public Dictionary> DiffDocumentationMap { get; set; } = new Dictionary>(); + [JsonIgnore] + public List NavigationTreeNodesObj { get; set; } = []; + public NavigationTreeNode[] NavigationTreeNodes => NavigationTreeNodesObj != null ? NavigationTreeNodesObj.ToArray() : null; public void AddLineIdNodeHashMapping(string lineId, string nodeId) { diff --git a/src/dotnet/APIView/ClientSPA/src/app/_models/codePanelModels.ts b/src/dotnet/APIView/ClientSPA/src/app/_models/codePanelModels.ts index ecb495ef2fe..a451da15a53 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_models/codePanelModels.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_models/codePanelModels.ts @@ -57,6 +57,7 @@ export class CodePanelRowData { export interface CodePanelData { nodeMetaData: { [key: string]: CodePanelNodeMetaData }; hasDiff: boolean; + navigationTreeNodes: NavigationTreeNode[]; } export class CodePanelNodeMetaData { diff --git a/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts b/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts index 5e52d97a1b2..00a76e2901b 100644 --- a/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts +++ b/src/dotnet/APIView/ClientSPA/src/app/_workers/apitree-builder.worker.ts @@ -17,6 +17,7 @@ let toggleDocumentationClassPart = "bi-arrow-up-square"; let hasHiddenAPI: boolean = false; let visibleNodes: Set = new Set(); let addPostDiffContext: boolean = false; +let isNavigationTreeCreated: boolean = false; addEventListener('message', ({ data }) => { if (data instanceof ArrayBuffer) { @@ -27,6 +28,11 @@ addEventListener('message', ({ data }) => { apiTreeBuilderData!.diffStyle = FULL_DIFF_STYLE; // If there is no diff nodes and tree diff will not work } + if (codePanelData?.navigationTreeNodes && codePanelData?.navigationTreeNodes.length > 0) + { + isNavigationTreeCreated = true; + navigationTree = codePanelData?.navigationTreeNodes; + } buildCodePanelRows("root", navigationTree); const codePanelRowDataMessage : InsertCodePanelRowDataMessage = { directive: ReviewPageWorkerMessageDirective.UpdateCodePanelRowData, @@ -60,6 +66,7 @@ addEventListener('message', ({ data }) => { apiTreeBuilderData = null; visibleNodes = new Set(); addPostDiffContext = false; + isNavigationTreeCreated = false; } else { apiTreeBuilderData = data; @@ -106,12 +113,12 @@ function buildCodePanelRows(nodeIdHashed: string, navigationTree: NavigationTree } let navigationChildren = navigationTree; - if (node.navigationTreeNode) { + if (!isNavigationTreeCreated && node.navigationTreeNode) { if (!node.navigationTreeNode.children) { node.navigationTreeNode.children = []; } navigationChildren = node.navigationTreeNode.children; - } + } if ((!node.childrenNodeIdsInOrder || Object.keys(node.childrenNodeIdsInOrder).length === 0) && node.isNodeWithDiff) { codePanelRowData.push(...diffBuffer); @@ -191,7 +198,7 @@ function buildCodePanelRows(nodeIdHashed: string, navigationTree: NavigationTree } } - if (buildNode && node.navigationTreeNode) { + if (buildNode && node.navigationTreeNode && !isNavigationTreeCreated) { navigationTree.push(node.navigationTreeNode); } From 3c52ad48467cab251466d5eb765af7ea36ce582e Mon Sep 17 00:00:00 2001 From: Praven Kuttappan <55455725+praveenkuttappan@users.noreply.github.com> Date: Wed, 18 Sep 2024 16:19:44 -0400 Subject: [PATCH 4/6] Apply suggestions from code review Co-authored-by: Mariana Rios Flores --- src/dotnet/APIView/APIViewJsonValidator/Program.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/dotnet/APIView/APIViewJsonValidator/Program.cs b/src/dotnet/APIView/APIViewJsonValidator/Program.cs index 909b02fc128..533265755d1 100644 --- a/src/dotnet/APIView/APIViewJsonValidator/Program.cs +++ b/src/dotnet/APIView/APIViewJsonValidator/Program.cs @@ -18,7 +18,7 @@ public static int Main(string[] args) jsonFilePath.IsRequired = true; var outputDir = new Option("--outputDir", "Path to the output Directory(Optional)."); var dumpOption = new Option("--dumpApiText", "Dump the API text to a txt file."); - var convertOption = new Option("--convertToTree", "Convert old APIView token model to new tree token model."); + var convertOption = new Option("--convertToTree", "Convert APIView flat token model into a parent - child token model"); var rootCommand = new RootCommand("Generate API review output from token JSON file to verify the input json file") { @@ -43,7 +43,7 @@ public static int Main(string[] args) { var outputFilePath = Path.Combine(outputFileDirectory, jsonFilePath.Name.Replace(".json", ".txt")); - Console.WriteLine($"Dumping API text to {outputFilePath}"); + Console.WriteLine($"Writing API text to {outputFilePath}"); using (var stream = jsonFilePath.OpenRead()) { await GenerateReviewTextFromJson(stream, outputFilePath); @@ -53,7 +53,7 @@ public static int Main(string[] args) if (convertOption) { var outputFilePath = Path.Combine(outputFileDirectory, jsonFilePath.Name.Replace(".json", "_new.json")); - Console.WriteLine($"Converting old Json API view tokens to new Json model API tokens. New json file: {outputFilePath}"); + Console.WriteLine($"Converting previous Json APIView flat tokens to new Json parent - child token model. New file: {outputFilePath}"); using (var stream = jsonFilePath.OpenRead()) { await ConvertToTreeModel(stream, outputFilePath); @@ -86,18 +86,18 @@ private static async Task ConvertToTreeModel(Stream stream, string outputFilePat } catch (Exception ex) { - Console.Error.WriteLine($"Input json is probably not using old token model. Error reading input json file. : {ex.Message}"); + Console.Error.WriteLine($"Input json is probably not using flat token model. Error reading input json file. : {ex.Message}"); throw; } if(codeFile != null) { codeFile.ConvertToTreeTokenModel(); - Console.WriteLine("Converted APIView token model to new schema"); + Console.WriteLine("Converted APIView token model to parent - child token model"); codeFile.Tokens = null; using var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write); await codeFile.SerializeAsync(fileStream); - Console.WriteLine($"New APIView json token file generated at {outputFilePath}"); + Console.WriteLine($"New APIView json parent - child token model file generated at {outputFilePath}"); } } } From 1b4b8edbb26af2ef761588f27f68ac5f9a7f5d63 Mon Sep 17 00:00:00 2001 From: Praveen Kuttappan Date: Wed, 18 Sep 2024 19:15:51 -0400 Subject: [PATCH 5/6] Add CI to publish the utility --- .../APIViewJsonUtility.csproj | 0 .../Program.cs | 0 src/dotnet/APIView/APIViewJsonUtility/ci.yml | 27 +++++++++++++++++++ 3 files changed, 27 insertions(+) rename src/dotnet/APIView/{APIViewJsonValidator => APIViewJsonUtility}/APIViewJsonUtility.csproj (100%) rename src/dotnet/APIView/{APIViewJsonValidator => APIViewJsonUtility}/Program.cs (100%) create mode 100644 src/dotnet/APIView/APIViewJsonUtility/ci.yml diff --git a/src/dotnet/APIView/APIViewJsonValidator/APIViewJsonUtility.csproj b/src/dotnet/APIView/APIViewJsonUtility/APIViewJsonUtility.csproj similarity index 100% rename from src/dotnet/APIView/APIViewJsonValidator/APIViewJsonUtility.csproj rename to src/dotnet/APIView/APIViewJsonUtility/APIViewJsonUtility.csproj diff --git a/src/dotnet/APIView/APIViewJsonValidator/Program.cs b/src/dotnet/APIView/APIViewJsonUtility/Program.cs similarity index 100% rename from src/dotnet/APIView/APIViewJsonValidator/Program.cs rename to src/dotnet/APIView/APIViewJsonUtility/Program.cs diff --git a/src/dotnet/APIView/APIViewJsonUtility/ci.yml b/src/dotnet/APIView/APIViewJsonUtility/ci.yml new file mode 100644 index 00000000000..c266eb35ecb --- /dev/null +++ b/src/dotnet/APIView/APIViewJsonUtility/ci.yml @@ -0,0 +1,27 @@ +# NOTE: Please refer to https://aka.ms/azsdk/engsys/ci-yaml before editing this file. +trigger: + branches: + include: + - main + - feature/* + - release/* + - hotfix/* + paths: + include: + - src/dotnet/APIView/APIViewJsonUtility + +pr: + branches: + include: + - main + - feature/* + - release/* + - hotfix/* + paths: + include: + - src/dotnet/APIView/APIViewJsonUtility + +extends: + template: /eng/pipelines/templates/stages/archetype-sdk-tool-dotnet.yml + parameters: + PackageDirectory: $(Build.SourcesDirectory)/src/dotnet/APIView/APIViewJsonUtility From e99f537adfee84315f1c96a76bacaea33c13e7ce Mon Sep 17 00:00:00 2001 From: Praveen Kuttappan Date: Wed, 18 Sep 2024 19:36:18 -0400 Subject: [PATCH 6/6] Fix build warnings --- src/dotnet/APIView/APIView.sln | 10 +++---- .../APIView/APIViewJsonUtility/Program.cs | 27 +++++++++---------- 2 files changed, 18 insertions(+), 19 deletions(-) diff --git a/src/dotnet/APIView/APIView.sln b/src/dotnet/APIView/APIView.sln index f4d2a54df53..0af2fc2164e 100644 --- a/src/dotnet/APIView/APIView.sln +++ b/src/dotnet/APIView/APIView.sln @@ -23,7 +23,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpAPIParserTests", "..\ EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestReferenceWithInternalsVisibleTo", "..\Azure.ClientSdk.Analyzers\TestReferenceWithInternalsVisibleTo\TestReferenceWithInternalsVisibleTo.csproj", "{0FE36A2D-EB25-4119-A7DA-2605BB2516C2}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APIViewJsonUtility", "APIViewJsonValidator\APIViewJsonUtility.csproj", "{424B9F9A-1518-40C3-B7D3-88B53A038A23}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "APIViewJsonUtility", "APIViewJsonUtility\APIViewJsonUtility.csproj", "{C1C37681-2D54-4BDF-A4B8-834EC29AAFCF}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -71,10 +71,10 @@ Global {0FE36A2D-EB25-4119-A7DA-2605BB2516C2}.Debug|Any CPU.Build.0 = Debug|Any CPU {0FE36A2D-EB25-4119-A7DA-2605BB2516C2}.Release|Any CPU.ActiveCfg = Release|Any CPU {0FE36A2D-EB25-4119-A7DA-2605BB2516C2}.Release|Any CPU.Build.0 = Release|Any CPU - {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Debug|Any CPU.Build.0 = Debug|Any CPU - {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Release|Any CPU.ActiveCfg = Release|Any CPU - {424B9F9A-1518-40C3-B7D3-88B53A038A23}.Release|Any CPU.Build.0 = Release|Any CPU + {C1C37681-2D54-4BDF-A4B8-834EC29AAFCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C1C37681-2D54-4BDF-A4B8-834EC29AAFCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C1C37681-2D54-4BDF-A4B8-834EC29AAFCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C1C37681-2D54-4BDF-A4B8-834EC29AAFCF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/dotnet/APIView/APIViewJsonUtility/Program.cs b/src/dotnet/APIView/APIViewJsonUtility/Program.cs index 533265755d1..92232f5ba56 100644 --- a/src/dotnet/APIView/APIViewJsonUtility/Program.cs +++ b/src/dotnet/APIView/APIViewJsonUtility/Program.cs @@ -38,7 +38,8 @@ public static int Main(string[] args) try { - var outputFileDirectory = outputDir?.FullName ?? jsonFilePath.Directory.FullName; + var outputFileDirectory = outputDir?.FullName ?? jsonFilePath.Directory?.FullName; + outputFileDirectory = outputFileDirectory == null? Path.GetTempPath() : outputFileDirectory; if (dumpOption) { @@ -79,25 +80,23 @@ private static async Task GenerateReviewTextFromJson(Stream stream, string outpu private static async Task ConvertToTreeModel(Stream stream, string outputFilePath) { - CodeFile codeFile = null; try { - codeFile = await CodeFile.DeserializeAsync(stream, false, false); + var codeFile = await CodeFile.DeserializeAsync(stream, false, false); + if (codeFile != null) + { + codeFile.ConvertToTreeTokenModel(); + Console.WriteLine("Converted APIView token model to parent - child token model"); + codeFile.Tokens = null; + using var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write); + await codeFile.SerializeAsync(fileStream); + Console.WriteLine($"New APIView json parent - child token model file generated at {outputFilePath}"); + } } catch (Exception ex) { Console.Error.WriteLine($"Input json is probably not using flat token model. Error reading input json file. : {ex.Message}"); throw; - } - - if(codeFile != null) - { - codeFile.ConvertToTreeTokenModel(); - Console.WriteLine("Converted APIView token model to parent - child token model"); - codeFile.Tokens = null; - using var fileStream = new FileStream(outputFilePath, FileMode.Create, FileAccess.Write); - await codeFile.SerializeAsync(fileStream); - Console.WriteLine($"New APIView json parent - child token model file generated at {outputFilePath}"); - } + } } }