From daf067981b2968c3a11a83108e7797f95db0653d Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Fri, 15 Mar 2024 10:52:23 -0700 Subject: [PATCH 1/7] CSharp Parser Conversion from Flat to Tree Tokens Parse C# Assemblly to Token Tree Add Token Tree Documentation Add LineDefinitionId and HideFromNavigation Undo internals visible to setting Sort NameSpaces and Types Ensure Each node has Name and Id Serialize to Message Pack Add Tags to StructuredToken update tree Token Parser Documentation Attempt to Improve Serialized JSON Size Properties for Serialization and Deserialization Use shorter names for diagnostics update APIView Parser Contributing guide Delete TreeTokenCodeFile Update APIView C# Parser Project Name Add MessagePack Serialization Switch to System.Json.Text working Update Contributing Doc Add Navigate to ID Make Parser a dotnet Tool Update Parser Version for CodeFile App Parser Style Property Output GZipped TokenFile Switch to Human Readable Json Property Names, use compression Fix some issues raised in pull request Updating Contributing.md Update Contributing Docs --- .../APIView/APIView/Analysis/Analyzer.cs | 4 +- .../APIView/APIView/CodeFileTokensBuilder.cs | 4 +- .../Languages/CodeFileBuilderSymbolSorter.cs | 6 +- .../APIView/APIView/Model/CodeDiagnostic.cs | 7 +- src/dotnet/APIView/APIView/Model/CodeFile.cs | 22 +- .../APIView/Model/StructuredTokenModel.cs | 157 ++++ .../APIView/APIView/Model/TokenTreeModel.cs | 82 ++ .../AbstractSymbolDisplayVisitor.cs | 8 +- .../SymbolDisplay/ArrayBuilder.Enumerator.cs | 6 +- .../APIView/SymbolDisplay/ArrayBuilder.cs | 4 +- .../APIView/SymbolDisplay/ObjectDisplay.cs | 4 +- .../SymbolDisplay/ObjectDisplayOptions.cs | 4 +- .../APIView/SymbolDisplay/ObjectPool`1.cs | 4 +- tools/apiview/parsers/APITree.svg | 211 +++++ tools/apiview/parsers/CONTRIBUTING.md | 108 +++ .../csharp-api-parser/CSharpAPIParser.csproj | 22 + .../parsers/csharp-api-parser/Program.cs | 146 ++++ .../TreeToken/CodeFileBuilder.cs | 800 ++++++++++++++++++ .../csharp-api-parser/csharp-api-parser.sln | 37 + 19 files changed, 1589 insertions(+), 47 deletions(-) create mode 100644 src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs create mode 100644 src/dotnet/APIView/APIView/Model/TokenTreeModel.cs create mode 100644 tools/apiview/parsers/APITree.svg create mode 100644 tools/apiview/parsers/CONTRIBUTING.md create mode 100644 tools/apiview/parsers/csharp-api-parser/CSharpAPIParser.csproj create mode 100644 tools/apiview/parsers/csharp-api-parser/Program.cs create mode 100644 tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs create mode 100644 tools/apiview/parsers/csharp-api-parser/csharp-api-parser.sln diff --git a/src/dotnet/APIView/APIView/Analysis/Analyzer.cs b/src/dotnet/APIView/APIView/Analysis/Analyzer.cs index 3d841036717..8a7f3f160be 100644 --- a/src/dotnet/APIView/APIView/Analysis/Analyzer.cs +++ b/src/dotnet/APIView/APIView/Analysis/Analyzer.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using ApiView; @@ -9,7 +9,7 @@ namespace APIView.Analysis { - internal class Analyzer : SymbolVisitor + public class Analyzer : SymbolVisitor { public List Results { get; } = new List(); diff --git a/src/dotnet/APIView/APIView/CodeFileTokensBuilder.cs b/src/dotnet/APIView/APIView/CodeFileTokensBuilder.cs index e6d3617d435..e00fecc2951 100644 --- a/src/dotnet/APIView/APIView/CodeFileTokensBuilder.cs +++ b/src/dotnet/APIView/APIView/CodeFileTokensBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using APIView; @@ -78,4 +78,4 @@ public void Comment(string text) Append(text, CodeFileTokenKind.Comment); } } -} \ No newline at end of file +} diff --git a/src/dotnet/APIView/APIView/Languages/CodeFileBuilderSymbolSorter.cs b/src/dotnet/APIView/APIView/Languages/CodeFileBuilderSymbolSorter.cs index f873ecf4698..d31dc55359f 100644 --- a/src/dotnet/APIView/APIView/Languages/CodeFileBuilderSymbolSorter.cs +++ b/src/dotnet/APIView/APIView/Languages/CodeFileBuilderSymbolSorter.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. using Microsoft.CodeAnalysis; @@ -8,7 +8,7 @@ namespace ApiView { - class CodeFileBuilderSymbolOrderProvider : ICodeFileBuilderSymbolOrderProvider + public class CodeFileBuilderSymbolOrderProvider : ICodeFileBuilderSymbolOrderProvider { public IEnumerable OrderTypes(IEnumerable symbols) where T : ITypeSymbol { @@ -92,4 +92,4 @@ private static int GetMemberOrder(ISymbol symbol) } } } -} \ No newline at end of file +} diff --git a/src/dotnet/APIView/APIView/Model/CodeDiagnostic.cs b/src/dotnet/APIView/APIView/Model/CodeDiagnostic.cs index 08b97df7445..27c6d9eae7a 100644 --- a/src/dotnet/APIView/APIView/Model/CodeDiagnostic.cs +++ b/src/dotnet/APIView/APIView/Model/CodeDiagnostic.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. +// Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. namespace APIView @@ -17,15 +17,10 @@ public CodeDiagnostic(string diagnosticId, string targetId, string text, string HelpLinkUri = helpLinkUri; Level = level; } - public string DiagnosticId { get; set; } - public string Text { get; set; } - public string HelpLinkUri { get; set; } - public string TargetId { get; set; } - public CodeDiagnosticLevel Level { get; set; } } } diff --git a/src/dotnet/APIView/APIView/Model/CodeFile.cs b/src/dotnet/APIView/APIView/Model/CodeFile.cs index 7de36caea3c..ee83a06bdef 100644 --- a/src/dotnet/APIView/APIView/Model/CodeFile.cs +++ b/src/dotnet/APIView/APIView/Model/CodeFile.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using APIView; +using APIView.TreeToken; using System; using System.Collections.Generic; using System.IO; @@ -20,12 +21,9 @@ public class CodeFile }; private string _versionString; - private static HashSet _collapsibleLanguages = new HashSet(new string[] { "Swagger" }); - [Obsolete("This is only for back compat, VersionString should be used")] public int Version { get; set; } - public string VersionString { #pragma warning disable 618 @@ -33,38 +31,24 @@ public string VersionString #pragma warning restore 618 set => _versionString = value; } - public string Name { get; set; } - public string Language { get; set; } - public string LanguageVariant { get; set; } - public string PackageName { get; set; } - public string ServiceName { get; set; } - public string PackageDisplayName { get; set; } - public string PackageVersion { get; set; } - public string CrossLanguagePackageId { get; set; } - public CodeFileToken[] Tokens { get; set; } = Array.Empty(); - + public List APIForest { get; set; } = new List(); public List LeafSections { get; set; } - public NavigationItem[] Navigation { get; set; } - public CodeDiagnostic[] Diagnostics { get; set; } - public override string ToString() { return new CodeFileRenderer().Render(this).CodeLines.ToString(); - } - + } public static bool IsCollapsibleSectionSSupported(string language) => _collapsibleLanguages.Contains(language); - public static async Task DeserializeAsync(Stream stream, bool hasSections = false) { var codeFile = await JsonSerializer.DeserializeAsync( diff --git a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs new file mode 100644 index 00000000000..48b9af0b6fc --- /dev/null +++ b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs @@ -0,0 +1,157 @@ +using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Text.Json.Serialization; + +namespace APIView.TreeToken +{ + public enum StructuredTokenKind + { + Content = 0, + LineBreak = 1, + NonBreakingSpace = 2, + TabSpace = 3, + ParameterSeparator = 4, + Url = 5 + } + + /// + /// Used to represent a APIView token its properties and tags for APIView parsers. + /// + + public class StructuredToken + { + public HashSet Tags + { + get { return TagsObj.Count > 0 ? TagsObj : null; } + set { TagsObj = value ?? new HashSet(); } + } + public Dictionary Properties + { + get { return PropertiesObj.Count > 0 ? PropertiesObj : null; } + set { PropertiesObj = value ?? new Dictionary(); } + } + public HashSet RenderClasses + { + get { return RenderClassesObj.Count > 0 ? RenderClassesObj : null; } + set { RenderClassesObj = value ?? new HashSet(); } + } + public string Value { get; set; } = string.Empty; + public string Id { get; set; } + public StructuredTokenKind Kind { get; set; } = StructuredTokenKind.Content; + [JsonIgnore] + public HashSet TagsObj { get; set; } = new HashSet(); + [JsonIgnore] + public Dictionary PropertiesObj { get; set; } = new Dictionary(); + [JsonIgnore] + public HashSet RenderClassesObj { get; set; } = new HashSet(); + + public StructuredToken() + { + new StructuredToken(string.Empty); + } + + public StructuredToken(string value) + { + Value = value; + Kind = StructuredTokenKind.Content; + } + + public static StructuredToken CreateLineBreakToken() + { + var token = new StructuredToken(); + token.Kind = StructuredTokenKind.LineBreak; + return token; + } + + public static StructuredToken CreateEmptyToken(string id = null) + { + var token = new StructuredToken(); + token.Id = id; + token.Kind = StructuredTokenKind.Content; + return token; + } + + public static StructuredToken CreateSpaceToken() + { + var token = new StructuredToken(); + token.Kind = StructuredTokenKind.NonBreakingSpace; + return token; + } + + public static StructuredToken CreateParameterSeparatorToken() + { + var token = new StructuredToken(); + token.Kind = StructuredTokenKind.ParameterSeparator; + return token; + } + + public static StructuredToken CreateTextToken(string value, string id = null) + { + var token = new StructuredToken(value); + if (!string.IsNullOrEmpty(id)) + { + token.Id = id; + } + token.RenderClassesObj.Add("text"); + return token; + } + + public static StructuredToken CreateKeywordToken(string value) + { + var token = new StructuredToken(value); + token.RenderClassesObj.Add("keyword"); + return token; + } + + public static StructuredToken CreateKeywordToken(SyntaxKind syntaxKind) + { + return CreateKeywordToken(SyntaxFacts.GetText(syntaxKind)); + } + + public static StructuredToken CreateKeywordToken(Accessibility accessibility) + { + return CreateKeywordToken(SyntaxFacts.GetText(accessibility)); + } + + public static StructuredToken CreatePunctuationToken(string value) + { + var token = new StructuredToken(value); + token.RenderClassesObj.Add("punc"); + return token; + } + + public static StructuredToken CreatePunctuationToken(SyntaxKind syntaxKind) + { + return CreatePunctuationToken(SyntaxFacts.GetText(syntaxKind)); + } + + public static StructuredToken CreateTypeNameToken(string value) + { + var token = new StructuredToken(value); + token.RenderClassesObj.Add("tname"); + return token; + } + + public static StructuredToken CreateMemberNameToken(string value) + { + var token = new StructuredToken(value); + token.RenderClassesObj.Add("mname"); + return token; + } + + public static StructuredToken CreateLiteralToken(string value) + { + var token = new StructuredToken(value); + token.RenderClassesObj.Add("literal"); + return token; + } + + public static StructuredToken CreateStringLiteralToken(string value) + { + var token = new StructuredToken(value); + token.RenderClassesObj.Add("sliteral"); + return token; + } + } +} diff --git a/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs b/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs new file mode 100644 index 00000000000..f6c2e8f8408 --- /dev/null +++ b/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs @@ -0,0 +1,82 @@ +using System.Collections.Generic; +using System.Text.Json.Serialization; + +namespace APIView.TreeToken +{ + public class APITreeNode + { + public HashSet Tags + { + get { return TagsObj.Count > 0 ? TagsObj : null; } + set { TagsObj = value ?? new HashSet(); } + } + public Dictionary Properties + { + get { return PropertiesObj.Count > 0 ? PropertiesObj : null; } + set { PropertiesObj = value ?? new Dictionary(); } + } + public List TopTokens + { + get { return TopTokensObj.Count > 0 ? TopTokensObj : null; } + set { TopTokensObj = value ?? new List(); } + } + public List BottomTokens + { + get { return BottomTokensObj.Count > 0 ? BottomTokensObj : null; } + set { BottomTokensObj = value ?? new List(); } + } + public List Children + { + get { return ChildrenObj.Count > 0 ? ChildrenObj : null; } + set { ChildrenObj = value ?? new List(); } + } + + public string Name { get; set; } + public string Id { get; set; } + public string Kind { get; set; } + [JsonIgnore] + public HashSet TagsObj { get; set; } = new HashSet(); + [JsonIgnore] + public Dictionary PropertiesObj { get; set; } = new Dictionary(); + [JsonIgnore] + public List TopTokensObj { get; set; } = new List(); + [JsonIgnore] + public List BottomTokensObj { get; set; } = new List(); + [JsonIgnore] + public List ChildrenObj { get; set; } = new List(); + + public override int GetHashCode() + { + int hash = 17; + hash = hash * 23 + (Name != null ? Name.GetHashCode() : 0); + hash = hash * 23 + (Id != null ? Id.GetHashCode() : 0); + hash = hash * 23 + (Kind != null ? Kind.GetHashCode() : 0); + return hash; + } + + public override bool Equals(object obj) + { + var other = obj as APITreeNode; + if (other == null) + { + return false; + } + return Name == other.Name && Id == other.Id && Kind == other.Kind; + } + + public void SortChildren() + { + if (ChildrenObj != null) + { + if (Kind.Equals("Namespace") || Kind.Equals("Type") || Kind.Equals("Member")) + { + ChildrenObj.Sort((x, y) => x.Name.CompareTo(y.Name)); + } + foreach (var child in ChildrenObj) + { + child.SortChildren(); + } + } + } + } +} diff --git a/src/dotnet/APIView/APIView/SymbolDisplay/AbstractSymbolDisplayVisitor.cs b/src/dotnet/APIView/APIView/SymbolDisplay/AbstractSymbolDisplayVisitor.cs index 647ee35fd7c..2ab7a53af06 100644 --- a/src/dotnet/APIView/APIView/SymbolDisplay/AbstractSymbolDisplayVisitor.cs +++ b/src/dotnet/APIView/APIView/SymbolDisplay/AbstractSymbolDisplayVisitor.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using Microsoft.CodeAnalysis.PooledObjects; using Roslyn.Utilities; @@ -7,9 +7,9 @@ namespace Microsoft.CodeAnalysis.SymbolDisplay { - internal abstract partial class AbstractSymbolDisplayVisitor : SymbolVisitor + public abstract partial class AbstractSymbolDisplayVisitor : SymbolVisitor { - protected readonly ArrayBuilder builder; + public readonly ArrayBuilder builder; protected readonly SymbolDisplayFormat format; protected readonly bool isFirstSymbolVisited; protected readonly bool inNamespaceOrType; @@ -20,7 +20,7 @@ internal abstract partial class AbstractSymbolDisplayVisitor : SymbolVisitor private AbstractSymbolDisplayVisitor _lazyNotFirstVisitor; private AbstractSymbolDisplayVisitor _lazyNotFirstVisitorNamespaceOrType; - protected AbstractSymbolDisplayVisitor( + public AbstractSymbolDisplayVisitor( ArrayBuilder builder, SymbolDisplayFormat format, bool isFirstSymbolVisited, diff --git a/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.Enumerator.cs b/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.Enumerator.cs index aa4fe5eda7e..8862d0ebb16 100644 --- a/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.Enumerator.cs +++ b/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.Enumerator.cs @@ -1,15 +1,15 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.Collections.Generic; namespace Microsoft.CodeAnalysis.PooledObjects { - internal partial class ArrayBuilder + public partial class ArrayBuilder { /// /// struct enumerator used in foreach. /// - internal struct Enumerator : IEnumerator + public struct Enumerator : IEnumerator { private readonly ArrayBuilder _builder; private int _index; diff --git a/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.cs b/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.cs index d915ed00eb8..da8e0b0c2f6 100644 --- a/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.cs +++ b/src/dotnet/APIView/APIView/SymbolDisplay/ArrayBuilder.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; @@ -9,7 +9,7 @@ namespace Microsoft.CodeAnalysis.PooledObjects { [DebuggerDisplay("Count = {Count,nq}")] [DebuggerTypeProxy(typeof(ArrayBuilder<>.DebuggerProxy))] - internal sealed partial class ArrayBuilder : IReadOnlyCollection, IReadOnlyList + public sealed partial class ArrayBuilder : IReadOnlyCollection, IReadOnlyList { #region DebuggerProxy diff --git a/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplay.cs b/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplay.cs index 4bf7aa37221..f94426fd21b 100644 --- a/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplay.cs +++ b/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplay.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Globalization; @@ -17,7 +17,7 @@ namespace Microsoft.CodeAnalysis.CSharp /// /// #pragma warning restore CA1200 // Avoid using cref tags with a prefix - internal static class ObjectDisplay + public static class ObjectDisplay { /// /// Returns a string representation of an object of primitive type. diff --git a/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplayOptions.cs b/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplayOptions.cs index 08ff3839722..7daffcb5687 100644 --- a/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplayOptions.cs +++ b/src/dotnet/APIView/APIView/SymbolDisplay/ObjectDisplayOptions.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; @@ -8,7 +8,7 @@ namespace Microsoft.CodeAnalysis /// Specifies the options for how generics are displayed in the description of a symbol. /// [Flags] - internal enum ObjectDisplayOptions + public enum ObjectDisplayOptions { /// /// Format object using default options. diff --git a/src/dotnet/APIView/APIView/SymbolDisplay/ObjectPool`1.cs b/src/dotnet/APIView/APIView/SymbolDisplay/ObjectPool`1.cs index 2f03191a227..04793d4720e 100644 --- a/src/dotnet/APIView/APIView/SymbolDisplay/ObjectPool`1.cs +++ b/src/dotnet/APIView/APIView/SymbolDisplay/ObjectPool`1.cs @@ -1,4 +1,4 @@ -// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +// Copyright (c) Microsoft. All Rights Reserved. Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // define TRACE_LEAKS to get additional diagnostics that can lead to the leak sources. note: it will // make everything about 2-3x slower @@ -37,7 +37,7 @@ namespace Microsoft.CodeAnalysis.PooledObjects /// Rationale: /// If there is no intent for reusing the object, do not use pool - just use "new". /// - internal class ObjectPool where T : class + public class ObjectPool where T : class { [DebuggerDisplay("{Value,nq}")] private struct Element diff --git a/tools/apiview/parsers/APITree.svg b/tools/apiview/parsers/APITree.svg new file mode 100644 index 00000000000..eeeaf3fde9c --- /dev/null +++ b/tools/apiview/parsers/APITree.svg @@ -0,0 +1,211 @@ + + + + + + + + + + Page-1 + + + Rectangle.1000 + + + + + + + Rectangle + + + + + + + Rectangle.11 + + + + + + + Rectangle.12 + + + + + + + Rectangle.13 + + + + + + + Rectangle.14 + + + + + + + Rectangle.15 + + + + + + + Sheet.16 + namespace + + + + namespace + + Sheet.18 + } + + + + } + + Sheet.19 + Public + + + + Public + + Sheet.20 + } + + + + } + + Sheet.21 + Public + + + + Public + + Sheet.22 + Azure.Template + + + + Azure.Template + + Sheet.23 + + + + Sheet.25 + + + + Sheet.26 + { + + + + { + + Sheet.27 + TemplateClient + + + + TemplateClient + + Sheet.28 + + + + Sheet.29 + class + + + + class + + Sheet.30 + + + + Sheet.31 + TemplateClient + + + + TemplateClient + + Sheet.33 + + + + Sheet.34 + ( + + + + ( + + Sheet.35 + string + + + + string + + Sheet.36 + vaultBaseUrl + + + + vaultBaseUrl + + Sheet.37 + ) + + + + ) + + Sheet.38 + ; + + + + ; + + Sheet.39 + Top Token + + + + Top Token + + Sheet.40 + Bottom Token + + + + Bottom Token + + diff --git a/tools/apiview/parsers/CONTRIBUTING.md b/tools/apiview/parsers/CONTRIBUTING.md new file mode 100644 index 00000000000..6c0f455bf98 --- /dev/null +++ b/tools/apiview/parsers/CONTRIBUTING.md @@ -0,0 +1,108 @@ +# Contributing + +## Overview +This page describes how to contribute to [APIView](../../../src//dotnet/APIView/APIViewWeb/APIViewWeb.csproj) language level parsers. +Specifically, how to create or update a language parser to produce tree style tokens for APIView. + +Previously APIview tokens were created as a flat list assigned to the `CodeFileToken[] Tokens` property of the [CodeFile](../../../src/dotnet/APIView/APIView/Model/CodeFile.cs). Then the page navigation is created and assigned to `NavigationItem[] Navigation`. For tree style tokens these two properties are no longer required, instead a `List APIForest` property will be used to capture the generated tree of tokens. + +The main idea is to capture the hierarchy of the API using a tree data structure, then maintain a flat list of tokens for each node of the tree. + +![APITree](APITree.svg) + +Each module of the API (namespace, class, methods) should be its own node. Members of a module (methods, in a class), (classes in a namespace) should be added as child nodes of its parent module. +Each tree node has top tokens which should be used to capture the main tokens on the node, these can span multiple lines. Module name, decorators, and parameters should be modeled as topTokens. If the language requires it, use the bottom tokens to capture tokens that closes out the node, this is usually just the closing bracket and/or empty lines. + +## Object Definitions + +- Here are the models needed + ``` + object APITreeNode + string Name + string Id + string Kind + Set Tags + Dictionary Properties + List TopTokens + List BottomTokens + List Children + + object StructuredToken + string Value + string Id + StructuredTokenKind Kind + Set Tags + Dictionary Properties + Set RenderClasses + + enum StructuredTokenKind + Content + LineBreak + NoneBreakingSpace + TabSpace + ParameterSeparator + Url + ``` + +### APITreeNode +- `Name`: *(Required)* The name of the tree node which will be used as label for the API Navigation. Generally use the name of the module (class, method). +- `Id`: *(Required)* Id of the node, which should be unique at the node level. i.e. unique among its siblings. Use whatever your existing parser is assigning to DefinitionId for the main Token of the node. Each node must have an Id. +- `Kind` *(Required)* : What kind of node is it. Please ensure you set a Kind for each node. Using any of the following `assembly`, `class`, `delegate`, `enum`, `interface`, `method` , `namespace`, `package`, `struct`, `type` will get you the corresponding default icons for the page navigation but feel free to use something language specific then reach out to APIView to support any new name used. +- `Tags` : Use this for opt in or opt out boolean properties. The currently supported tags are + - `Deprecated` Mark a node as deprecated + - `Hidden` Mark a node as Hidden + - `HideFromNav` Indicate that anode should be hidden from the page navigation. + - `SkipDiff` Indicate that a node should not be used in computation of diff. + - `CrossLangDefId` The cross language definitionId for the node. +- `Properties` : Use this for other properties of the node. The currently supported keys are + - `SubKind` Similar to kind, use this to make the node more specific. e.g. `Kind = 'Type'`, and `SubKind = 'class'` or something specific to you language. We also use this to make the navigation icon it will override kind. + - `IconName` Use this only if you are looking to add a custom icon different from language wide defaults. New additions will need to be supported APIView side. +- `TopTokens` : The main data of the node. This is all the tokens that actually define the node. e.g. For a class this would include the access modifier, the class name, any attributes or decorators e.t.c. For a method this would include the return type, method name, parameters e.t.c. See StructuredToken description below for more info. +- `BottomToken` : Data that closes out the node. Depending on the language this would include the closing curly brace and/or empty lines. You can simulate an empty line by adding an empty token (content token with no value) and a line Break token. +- `Children` : The nodes immediate children. For a namespace this would be classes, for a class this would be the class constructors and methods. + +Sort each node at each level of the tree by your desired property, this is to ensure that difference in node order does not result in diff. + +Ensure each node has an Id and Kind. The combination of `Id`, `Kind` and `SubKind` should make the node unique across all nodes in the tree. This is very important. For example a class and a method can potentially have the same Id, but the kind should differentiate them from each other. + +Don’t worry about indentation that will be handled by the tree structure, unless you want to have indentation between the tokens then use `TabSpace` token kind. + +### StructuredToken +- `Value` : The token value which will be displayed. Spacing tokens don't need to have value. +- `Id` : This is essentially whatever existing parser was assigning to the token DefinitionId. You don’t have to assign a tokenId to every token. APIView comments are tied to this ID so for backward compatibility it essential to use the same Id that existing parser is using for DefinitionId. +- `Kind` *(Required)* : An enum + - `Content` Specifies that the token is content + - `LineBreak` Space token indicating switch to new line. + - `NonBreakingSpace` Regular single space + - `TabSpace` 4 NonBreakingSpaces + - `ParameterSeparator` Use this between method parameters. Depending on user setting this would result in a single space or new line + - `Url` A url token should have `LinkText` property i.e `token.Properties["LinkText"]` and the url/link should be the token value. + All tokens should be content except for spacing tokens and url. ParameterSeparator should be used between method or function parameters. +- `Tags` : Use this for opt in or opt out boolean properties.The currently supported tags are + - `SkippDiff` Indicate that a token should not be used in computation of diff. + - `Deprecated` Mark a token as deprecated +- `Properties` : Properties of the token. + - `GroupId` : `doc` to group consecutive comment tokens as documentation. + - `NavigateToId` Id for navigating to where the node where the token is defined. Should match the definitionId of the referenced node. +- `RenderClasses` : Add css classes for how the tokens will be rendred. Classes currently being used are `text` `keyword` `punc` `tname` `mname` `literal` `sliteral` `comment` Feel free to add your own custom class. Whatever custom classes you use please provide us the appriopriate css for the class so we can update APIView. + +Assign the final parsed value to a `List APIForest` property of the `CodeFile` + +## Serialization + +Serialize the generated code file to JSON them compress the file using Gzip compression. Try to make the json as small as possible by ignoring null values and empty collections. + +## How to handle commons Scenarios +- TEXT, KEYWORD, COMMENT : Add `text`, `keyword`, `comment` to RenderClasses of the token +- NEW_LINE : Create a token with `Kind = LineBreak` +- WHITE_SPACE : Create token with `Kind = NonBreakingSpace` +- PUNCTUATION : Create a token with `Kind = Content` and the `Value = the punctuation` +- DOCUMENTATION : Add `GroupId = doc` in the properties of the token. This identifies a range of consecutive tokens as belonging to a group. +- SKIP_DIFF : Add `SkipDiff` to the Tag to indicate that node or token should not be included in diff computation +- LINE_ID_MARKER : You can add a empty token. `Kind = Content` and `Value = ""` then give it an `Id`. +- EXTERNAL_LINK : Create a single token set `Kind = Url`, `Value = link` then add the link text as a properties `LinkText`; +- Common Tags: `Deprecated`, `Hidden`, `HideFromNav`, `SkipDiff` +- Cross Language Id: Use `CrossLangId` as key with value in the node properties. + +Please reach out at [APIView Teams Channel](https://teams.microsoft.com/l/channel/19%3A3adeba4aa1164f1c889e148b1b3e3ddd%40thread.skype/APIView?groupId=3e17dcb0-4257-4a30-b843-77f47f1d4121&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) if you need more information. + diff --git a/tools/apiview/parsers/csharp-api-parser/CSharpAPIParser.csproj b/tools/apiview/parsers/csharp-api-parser/CSharpAPIParser.csproj new file mode 100644 index 00000000000..914dbe16205 --- /dev/null +++ b/tools/apiview/parsers/csharp-api-parser/CSharpAPIParser.csproj @@ -0,0 +1,22 @@ + + + + Exe + true + CSharpAPIParserForAPIView + net7.0 + CSharpAPIParser + enable + enable + + + + + + + + + + + + diff --git a/tools/apiview/parsers/csharp-api-parser/Program.cs b/tools/apiview/parsers/csharp-api-parser/Program.cs new file mode 100644 index 00000000000..4ab1598aaa3 --- /dev/null +++ b/tools/apiview/parsers/csharp-api-parser/Program.cs @@ -0,0 +1,146 @@ +using System.CommandLine; +using System.IO.Compression; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Xml.Linq; +using ApiView; + +var inputOption = new Option("--packageFilePath", "C# Package (.nupkg) file").ExistingOnly(); +inputOption.IsRequired = true; + +var outputOption1 = new Option("--outputDirectoryPath", "Directory for the output Token File").ExistingOnly(); +var outputOption2 = new Option("--outputFileName", "Output File Name"); +var runAnalysis = new Argument("runAnalysis", "Run Analysis on the package"); +runAnalysis.SetDefaultValue(true); + +var rootCommand = new RootCommand("Parse C# Package (.nupkg) to APIView Tokens") +{ + inputOption, + outputOption1, + outputOption2, + runAnalysis +}; + +rootCommand.SetHandler((FileInfo packageFilePath, DirectoryInfo outputDirectory, string outputFileName, bool runAnalysis) => +{ + try + { + using (var stream = packageFilePath.OpenRead()) + { + HandlePackageFileParsing(stream, packageFilePath, outputDirectory, outputFileName, runAnalysis); + } + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error Reading PackageFile : {ex.Message}"); + } +}, inputOption, outputOption1,outputOption2, runAnalysis); + +return rootCommand.InvokeAsync(args).Result; + + +static void HandlePackageFileParsing(Stream stream, FileInfo packageFilePath, DirectoryInfo OutputDirectory, string outputFileName, bool runAnalysis) +{ + ZipArchive? zipArchive = null; + Stream? dllStream = stream; + Stream? docStream = null; + List? dependencies = null; + + try + { + if (IsNuget(packageFilePath.FullName)) + { + zipArchive = new ZipArchive(stream, ZipArchiveMode.Read); + var nuspecEntry = zipArchive.Entries.Single(entry => IsNuspec(entry.Name)); + var dllEntries = zipArchive.Entries.Where(entry => IsDll(entry.Name)).ToArray(); + + if (dllEntries.Length == 0) + { + Console.Error.WriteLine($"PackageFile {packageFilePath.FullName} contains no dlls."); + return; + } + + var dllEntry = dllEntries.First(); + if (dllEntries.Length > 1) + { + // If there are multiple dlls in the nupkg (e.g. Cosmos), try to find the first that matches the nuspec name, but + // fallback to just using the first one. + dllEntry = dllEntries.FirstOrDefault( + dll => Path.GetFileNameWithoutExtension(nuspecEntry.Name) + .Equals(Path.GetFileNameWithoutExtension(dll.Name), StringComparison.OrdinalIgnoreCase)) ?? dllEntry; + } + + dllStream = dllEntry.Open(); + var docEntry = zipArchive.GetEntry(Path.ChangeExtension(dllEntry.FullName, ".xml")); + if (docEntry != null) + { + docStream = docEntry.Open(); + } + using var nuspecStream = nuspecEntry.Open(); + var document = XDocument.Load(nuspecStream); + var dependencyElements = document.Descendants().Where(e => e.Name.LocalName == "dependency"); + dependencies = new List(); + dependencies.AddRange( + dependencyElements.Select(dependency => new DependencyInfo( + dependency.Attribute("id")?.Value, + dependency.Attribute("version")?.Value))); + // filter duplicates and sort + if (dependencies.Any()) + { + dependencies = dependencies + .GroupBy(d => d.Name) + .Select(d => d.First()) + .OrderBy(d => d.Name).ToList(); + } + } + + var assemblySymbol = CompilationFactory.GetCompilation(dllStream, docStream); + if (assemblySymbol == null) + { + Console.Error.WriteLine($"PackageFile {packageFilePath.FullName} contains no Assembly Symbol."); + return; + } + var parsedFileName = string.IsNullOrEmpty(outputFileName) ? assemblySymbol.Name : outputFileName; + var treeTokenCodeFile = new CSharpAPIParser.TreeToken.CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies); + var gzipJsonTokenFilePath = Path.Combine(OutputDirectory.FullName, $"{parsedFileName}"); + + + var options = new JsonSerializerOptions() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull + }; + + { + using FileStream gzipFileStream = new FileStream(gzipJsonTokenFilePath, FileMode.Create, FileAccess.Write); + using GZipStream gZipStream = new GZipStream(gzipFileStream, CompressionLevel.Optimal); + JsonSerializer.Serialize(new Utf8JsonWriter(gZipStream, new JsonWriterOptions { Indented = false }), treeTokenCodeFile, options); + } + + Console.WriteLine($"TokenCodeFile File {gzipJsonTokenFilePath} Generated Successfully."); + Console.WriteLine(); + } + catch (Exception ex) + { + Console.Error.WriteLine($"Error Parsing PackageFile : {ex.Message}"); + } + finally + { + zipArchive?.Dispose(); + } + +} + +static bool IsNuget(string name) +{ + return name.EndsWith(".nupkg", StringComparison.OrdinalIgnoreCase); +} + +static bool IsNuspec(string name) +{ + return name.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase); +} + +static bool IsDll(string name) +{ + return name.EndsWith(".dll", StringComparison.OrdinalIgnoreCase); +} diff --git a/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs b/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs new file mode 100644 index 00000000000..76d21b60e64 --- /dev/null +++ b/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs @@ -0,0 +1,800 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using ApiView; +using APIView.Analysis; +using APIView.TreeToken; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.SymbolDisplay; +using System.Collections.Immutable; +using System.ComponentModel; + +namespace CSharpAPIParser.TreeToken +{ + internal class CodeFileBuilder + { + private static readonly char[] _newlineChars = new char[] { '\r', '\n' }; + + SymbolDisplayFormat _defaultDisplayFormat = new SymbolDisplayFormat( + SymbolDisplayGlobalNamespaceStyle.Omitted, + delegateStyle: SymbolDisplayDelegateStyle.NameAndSignature, + extensionMethodStyle: SymbolDisplayExtensionMethodStyle.StaticMethod, + propertyStyle: SymbolDisplayPropertyStyle.ShowReadWriteDescriptor, + miscellaneousOptions: SymbolDisplayMiscellaneousOptions.AllowDefaultLiteral | + SymbolDisplayMiscellaneousOptions.EscapeKeywordIdentifiers | + SymbolDisplayMiscellaneousOptions.RemoveAttributeSuffix | + SymbolDisplayMiscellaneousOptions.UseSpecialTypes | + SymbolDisplayMiscellaneousOptions.IncludeNullableReferenceTypeModifier, + kindOptions: SymbolDisplayKindOptions.IncludeMemberKeyword, + parameterOptions: SymbolDisplayParameterOptions.IncludeDefaultValue | + SymbolDisplayParameterOptions.IncludeExtensionThis | + SymbolDisplayParameterOptions.IncludeName | + SymbolDisplayParameterOptions.IncludeParamsRefOut | + SymbolDisplayParameterOptions.IncludeType, + genericsOptions: SymbolDisplayGenericsOptions.IncludeTypeConstraints | + SymbolDisplayGenericsOptions.IncludeTypeConstraints | + SymbolDisplayGenericsOptions.IncludeTypeParameters | + SymbolDisplayGenericsOptions.IncludeVariance, + memberOptions: SymbolDisplayMemberOptions.IncludeExplicitInterface | + SymbolDisplayMemberOptions.IncludeConstantValue | + SymbolDisplayMemberOptions.IncludeModifiers | + SymbolDisplayMemberOptions.IncludeParameters | + SymbolDisplayMemberOptions.IncludeType + ); + + private IAssemblySymbol? _assembly; + + public ICodeFileBuilderSymbolOrderProvider SymbolOrderProvider { get; set; } = new CodeFileBuilderSymbolOrderProvider(); + + public const string CurrentVersion = "27"; + + private IEnumerable EnumerateNamespaces(IAssemblySymbol assemblySymbol) + { + var stack = new Stack(); + stack.Push(assemblySymbol.GlobalNamespace); + while (stack.TryPop(out var currentNamespace)) + { + if (HasAnyPublicTypes(currentNamespace)) + { + yield return currentNamespace; + } + foreach (var subNamespace in currentNamespace.GetNamespaceMembers()) + { + stack.Push(subNamespace); + } + } + } + + public CodeFile Build(IAssemblySymbol assemblySymbol, bool runAnalysis, List? dependencies) + { + _assembly = assemblySymbol; + var analyzer = new Analyzer(); + + if (runAnalysis) + { + analyzer.VisitAssembly(assemblySymbol); + } + + var apiTreeNode = new APITreeNode(); + apiTreeNode.Kind = "Assembly"; + apiTreeNode.Id = assemblySymbol.Name; + apiTreeNode.Name = assemblySymbol.Name + ".dll"; + + if (dependencies != null) + { + BuildDependencies(apiTreeNode.ChildrenObj, dependencies); + } + BuildInternalsVisibleToAttributes(apiTreeNode.ChildrenObj, assemblySymbol); + + foreach (var namespaceSymbol in SymbolOrderProvider.OrderNamespaces(EnumerateNamespaces(assemblySymbol))) + { + if (namespaceSymbol.IsGlobalNamespace) + { + foreach (var namedTypeSymbol in SymbolOrderProvider.OrderTypes(namespaceSymbol.GetTypeMembers())) + { + BuildType(apiTreeNode.ChildrenObj, namedTypeSymbol, false); + } + } + else + { + BuildNamespace(apiTreeNode.ChildrenObj, namespaceSymbol); + } + } + + // Sort API Tree by name + apiTreeNode.SortChildren(); + + var treeTokenCodeFile = new CodeFile() + { + Name = $"{assemblySymbol.Name} ({assemblySymbol.Identity.Version})", + Language = "C#", + APIForest = new List() { apiTreeNode }, + VersionString = CurrentVersion, + Diagnostics = analyzer.Results.ToArray(), + PackageName = assemblySymbol.Name, + PackageVersion = assemblySymbol.Identity.Version.ToString() + }; + + return treeTokenCodeFile; + } + + public static void BuildInternalsVisibleToAttributes(List apiTree, IAssemblySymbol assemblySymbol) + { + var assemblyAttributes = assemblySymbol.GetAttributes() + .Where(a => + a.AttributeClass?.Name == "InternalsVisibleToAttribute" && + !a.ConstructorArguments[0].Value?.ToString()?.Contains(".Tests") == true && + !a.ConstructorArguments[0].Value?.ToString()?.Contains(".Perf") == true && + !a.ConstructorArguments[0].Value?.ToString()?.Contains("DynamicProxyGenAssembly2") == true); + if (assemblyAttributes != null && assemblyAttributes.Any()) + { + var apiTreeNode = new APITreeNode(); + apiTreeNode.Kind = apiTreeNode.Name = apiTreeNode.Id = "InternalsVisibleTo"; + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateTextToken(value: "Exposes internals to:")); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); + + foreach (AttributeData attribute in assemblyAttributes) + { + if (attribute.ConstructorArguments.Length > 0) + { + var param = attribute.ConstructorArguments[0].Value?.ToString(); + if (!String.IsNullOrEmpty(param)) + { + var firstComma = param?.IndexOf(','); + param = firstComma > 0 ? param?[..(int)firstComma] : param; + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateTextToken(value: param, id: attribute.AttributeClass?.Name)); + } + } + } + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateEmptyToken()); + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateLineBreakToken()); + apiTree.Add(apiTreeNode); + } + } + + public static void BuildDependencies(List apiTree, List dependencies) + { + if (dependencies != null && dependencies.Any()) + { + var apiTreeNode = new APITreeNode(); + apiTreeNode.Kind = apiTreeNode.Name = apiTreeNode.Id = "Dependencies"; + + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateTextToken(value: "Dependencies:")); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); + + foreach (DependencyInfo dependency in dependencies) + { + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateTextToken(value: dependency.Name, id: dependency.Name)); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateTextToken(value: $"-{dependency.Version}")); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); + } + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateEmptyToken()); + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateLineBreakToken()); + apiTree.Add(apiTreeNode); + } + } + + private void BuildNamespace(List apiTree, INamespaceSymbol namespaceSymbol) + { + bool isHidden = HasOnlyHiddenTypes(namespaceSymbol); + + var apiTreeNode = new APITreeNode(); + apiTreeNode.Id = namespaceSymbol.GetId(); + apiTreeNode.Name = namespaceSymbol.ToDisplayString(); + apiTreeNode.Kind = "Namespace"; + + if (isHidden) + { + apiTreeNode.TagsObj.Add("Hidden"); + } + + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.NamespaceKeyword)); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateSpaceToken()); + BuildNamespaceName(apiTreeNode.TopTokensObj, namespaceSymbol); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateSpaceToken()); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenBraceToken)); + + foreach (var namedTypeSymbol in SymbolOrderProvider.OrderTypes(namespaceSymbol.GetTypeMembers())) + { + BuildType(apiTreeNode.ChildrenObj, namedTypeSymbol, isHidden); + } + + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseBraceToken)); + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateLineBreakToken()); + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateEmptyToken()); + + apiTree.Add(apiTreeNode); + } + + private void BuildNamespaceName(List tokenList, INamespaceSymbol namespaceSymbol) + { + if (!namespaceSymbol.ContainingNamespace.IsGlobalNamespace) + { + BuildNamespaceName(tokenList, namespaceSymbol.ContainingNamespace); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.DotToken)); + } + DisplayName(tokenList, namespaceSymbol, namespaceSymbol); + } + + + private bool HasAnyPublicTypes(INamespaceSymbol subNamespaceSymbol) + { + return subNamespaceSymbol.GetTypeMembers().Any(IsAccessible); + } + + private bool HasOnlyHiddenTypes(INamespaceSymbol namespaceSymbol) + { + return namespaceSymbol.GetTypeMembers().All(t => IsHiddenFromIntellisense(t) || !IsAccessible(t)); + } + + private void BuildType(List apiTree, INamedTypeSymbol namedType, bool inHiddenScope) + { + if (!IsAccessible(namedType)) + { + return; + } + + bool isHidden = IsHiddenFromIntellisense(namedType); + var apiTreeNode = new APITreeNode(); + apiTreeNode.Kind = "Type"; + apiTreeNode.PropertiesObj.Add("SubKind", namedType.TypeKind.ToString().ToLowerInvariant()); + apiTreeNode.Id = namedType.GetId(); + apiTreeNode.Name = namedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); + + if (isHidden && !inHiddenScope) + { + apiTreeNode.TagsObj.Add("Hidden"); + } + + BuildDocumentation(apiTreeNode.TopTokensObj, namedType); + BuildAttributes(apiTreeNode.TopTokensObj, namedType.GetAttributes()); + BuildVisibility(apiTreeNode.TopTokensObj, namedType); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateSpaceToken()); + + switch (namedType.TypeKind) + { + case TypeKind.Class: + BuildClassModifiers(apiTreeNode.TopTokensObj, namedType); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.ClassKeyword)); + break; + case TypeKind.Delegate: + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.DelegateKeyword)); + break; + case TypeKind.Enum: + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.EnumKeyword)); + break; + case TypeKind.Interface: + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.InterfaceKeyword)); + break; + case TypeKind.Struct: + if (namedType.IsReadOnly) + { + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.ReadOnlyKeyword)); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateSpaceToken()); + } + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.StructKeyword)); + break; + } + + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateSpaceToken()); + DisplayName(apiTreeNode.TopTokensObj, namedType, namedType); + + if (namedType.TypeKind == TypeKind.Delegate) + { + apiTreeNode.TopTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.SemicolonToken)); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); + return; + } + + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateSpaceToken()); + BuildBaseType(apiTreeNode.TopTokensObj, namedType); + apiTreeNode.TopTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenBraceToken)); + + foreach (var namedTypeSymbol in SymbolOrderProvider.OrderTypes(namedType.GetTypeMembers())) + { + BuildType(apiTreeNode.ChildrenObj, namedTypeSymbol, inHiddenScope || isHidden); + } + + foreach (var member in SymbolOrderProvider.OrderMembers(namedType.GetMembers())) + { + if (member.Kind == SymbolKind.NamedType || member.IsImplicitlyDeclared || !IsAccessible(member)) continue; + if (member is IMethodSymbol method) + { + if (method.MethodKind == MethodKind.PropertyGet || + method.MethodKind == MethodKind.PropertySet || + method.MethodKind == MethodKind.EventAdd || + method.MethodKind == MethodKind.EventRemove || + method.MethodKind == MethodKind.EventRaise) + { + continue; + } + } + BuildMember(apiTreeNode.ChildrenObj, member, inHiddenScope); + } + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseBraceToken)); + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateLineBreakToken()); + apiTreeNode.BottomTokensObj.Add(StructuredToken.CreateEmptyToken()); + + apiTree.Add(apiTreeNode); + } + + private void BuildDocumentation(List tokenList, ISymbol symbol) + { + var lines = symbol.GetDocumentationCommentXml()?.Trim().Split(_newlineChars); + + if (lines != null) + { + if (lines.All(string.IsNullOrWhiteSpace)) + { + return; + } + foreach (var line in lines) + { + var docToken = new StructuredToken("// " + line.Trim()); + docToken.RenderClassesObj.Add("comment"); + docToken.PropertiesObj.Add("GroupId", "doc"); + tokenList.Add(docToken); + tokenList.Add(StructuredToken.CreateLineBreakToken()); + } + } + } + + private static void BuildClassModifiers(List tokenList, INamedTypeSymbol namedType) + { + if (namedType.IsAbstract) + { + tokenList.Add(StructuredToken.CreateKeywordToken(SyntaxKind.AbstractKeyword)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + + if (namedType.IsStatic) + { + tokenList.Add(StructuredToken.CreateKeywordToken(SyntaxKind.StaticKeyword)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + + if (namedType.IsSealed) + { + tokenList.Add(StructuredToken.CreateKeywordToken(SyntaxKind.SealedKeyword)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + } + + private void BuildBaseType(List tokenList, INamedTypeSymbol namedType) + { + bool first = true; + + if (namedType.BaseType != null && + namedType.BaseType.SpecialType == SpecialType.None) + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.ColonToken)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + first = false; + + DisplayName(tokenList, namedType.BaseType); + } + + foreach (var typeInterface in namedType.Interfaces) + { + if (!IsAccessible(typeInterface)) continue; + + if (!first) + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CommaToken)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + else + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.ColonToken)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + first = false; + } + + DisplayName(tokenList, typeInterface); + } + + if (!first) + { + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + } + + private void BuildMember(List apiTree, ISymbol member, bool inHiddenScope) + { + bool isHidden = IsHiddenFromIntellisense(member); + var apiTreeNode = new APITreeNode(); + apiTreeNode.Kind = "Member"; + apiTreeNode.PropertiesObj.Add("SubKind", member.Kind.ToString()); + apiTreeNode.Id = member.GetId(); + apiTreeNode.Name = member.ToDisplayString(); + apiTreeNode.TagsObj.Add("HideFromNav"); + + if (isHidden && !inHiddenScope) + { + apiTreeNode.TagsObj.Add("Hidden"); + } + + BuildDocumentation(apiTreeNode.TopTokensObj, member); + BuildAttributes(apiTreeNode.TopTokensObj, member.GetAttributes()); + DisplayName(apiTreeNode.TopTokensObj, member); + + if (member.Kind == SymbolKind.Field && member.ContainingType.TypeKind == TypeKind.Enum) + { + apiTreeNode.TopTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CommaToken)); + } + else if (member.Kind != SymbolKind.Property) + { + apiTreeNode.TopTokensObj.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.SemicolonToken)); + } + + apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); + apiTree.Add(apiTreeNode); + } + + private void BuildAttributes(List tokenList, ImmutableArray attributes) + { + const string attributeSuffix = "Attribute"; + foreach (var attribute in attributes) + { + if (attribute.AttributeClass != null) + { + if ((!IsAccessible(attribute.AttributeClass) && + attribute.AttributeClass.Name != "FriendAttribute" && + attribute.AttributeClass.ContainingNamespace.ToString() != "System.Diagnostics.CodeAnalysis") + || IsSkippedAttribute(attribute.AttributeClass)) + { + continue; + } + + if (attribute.AttributeClass.DeclaredAccessibility == Accessibility.Internal || attribute.AttributeClass.DeclaredAccessibility == Accessibility.Friend) + { + tokenList.Add(StructuredToken.CreateKeywordToken("internal")); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenBracketToken)); + var name = attribute.AttributeClass.Name; + if (name.EndsWith(attributeSuffix)) + { + name = name.Substring(0, name.Length - attributeSuffix.Length); + } + tokenList.Add(StructuredToken.CreateTypeNameToken(name)); + if (attribute.ConstructorArguments.Any()) + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenParenToken)); + bool first = true; + + foreach (var argument in attribute.ConstructorArguments) + { + if (!first) + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CommaToken)); + tokenList.Add(StructuredToken.CreateParameterSeparatorToken()); + } + else + { + first = false; + } + BuildTypedConstant(tokenList, argument); + } + + foreach (var argument in attribute.NamedArguments) + { + if (!first) + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CommaToken)); + tokenList.Add(StructuredToken.CreateParameterSeparatorToken()); + } + else + { + first = false; + } + tokenList.Add(StructuredToken.CreateTextToken(argument.Key)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.EqualsToken)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + BuildTypedConstant(tokenList, argument.Value); + } + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseParenToken)); + } + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseBracketToken)); + tokenList.Add(StructuredToken.CreateLineBreakToken()); + } + } + } + + private bool IsSkippedAttribute(INamedTypeSymbol attributeAttributeClass) + { + switch (attributeAttributeClass.Name) + { + case "DebuggerStepThroughAttribute": + case "AsyncStateMachineAttribute": + case "IteratorStateMachineAttribute": + case "DefaultMemberAttribute": + case "AsyncIteratorStateMachineAttribute": + case "EditorBrowsableAttribute": + case "NullableAttribute": + case "NullableContextAttribute": + return true; + default: + return false; + } + } + + private bool IsHiddenFromIntellisense(ISymbol member) => + member.GetAttributes().Any(d => d.AttributeClass?.Name == "EditorBrowsableAttribute" + && (EditorBrowsableState)d.ConstructorArguments[0].Value! == EditorBrowsableState.Never); + + private bool IsDecoratedWithAttribute(ISymbol member, string attributeName) => + member.GetAttributes().Any(d => d.AttributeClass?.Name == attributeName); + + private void BuildTypedConstant(List tokenList, TypedConstant typedConstant) + { + if (typedConstant.IsNull) + { + tokenList.Add(StructuredToken.CreateKeywordToken(SyntaxKind.NullKeyword)); + } + else if (typedConstant.Kind == TypedConstantKind.Enum) + { + new CodeFileBuilderEnumFormatter(tokenList).Format(typedConstant.Type, typedConstant.Value); + } + else if (typedConstant.Kind == TypedConstantKind.Type) + { + tokenList.Add(StructuredToken.CreateKeywordToken(SyntaxKind.TypeOfKeyword)); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenParenToken)); + DisplayName(tokenList, (ITypeSymbol)typedConstant.Value!); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseParenToken)); + } + else if (typedConstant.Kind == TypedConstantKind.Array) + { + tokenList.Add(StructuredToken.CreateKeywordToken(SyntaxKind.NewKeyword)); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenBracketToken)); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseBracketToken)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.OpenBraceToken)); + + bool first = true; + + foreach (var value in typedConstant.Values) + { + if (!first) + { + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CommaToken)); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + else + { + first = false; + } + + BuildTypedConstant(tokenList, value); + } + tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.CloseBraceToken)); + } + else + { + if (typedConstant.Value is string s) + { + tokenList.Add(StructuredToken.CreateStringLiteralToken(ObjectDisplay.FormatLiteral(s, ObjectDisplayOptions.UseQuotes | ObjectDisplayOptions.EscapeNonPrintableCharacters))); + } + else + { + tokenList.Add(StructuredToken.CreateLiteralToken(ObjectDisplay.FormatPrimitive(typedConstant.Value, ObjectDisplayOptions.None))); + } + } + } + + private void BuildVisibility(List tokenList, ISymbol symbol) + { + tokenList.Add(StructuredToken.CreateKeywordToken(ToEffectiveAccessibility(symbol.DeclaredAccessibility))); + } + + private void DisplayName(List tokenList, ISymbol symbol, ISymbol? definedSymbol = null) + { + tokenList.Add(StructuredToken.CreateEmptyToken(id: symbol.GetId())); + if (NeedsAccessibility(symbol)) + { + tokenList.Add(StructuredToken.CreateKeywordToken(ToEffectiveAccessibility(symbol.DeclaredAccessibility))); + tokenList.Add(StructuredToken.CreateSpaceToken()); + } + if (symbol is IPropertySymbol propSymbol && propSymbol.DeclaredAccessibility != Accessibility.Internal) + { + var parts = propSymbol.ToDisplayParts(_defaultDisplayFormat); + for (int i = 0; i < parts.Length; i++) + { + // Skip internal setters + if (parts[i].Kind == SymbolDisplayPartKind.Keyword && parts[i].ToString() == "internal") + { + while (i < parts.Length && parts[i].ToString() != "}") + { + i++; + } + } + tokenList.Add(MapToken(definedSymbol!, parts[i])); + } + } + else + { + foreach (var symbolDisplayPart in symbol.ToDisplayParts(_defaultDisplayFormat)) + { + tokenList.Add(MapToken(definedSymbol!, symbolDisplayPart)); + } + } + } + + private bool NeedsAccessibility(ISymbol symbol) + { + return symbol switch + { + INamespaceSymbol => false, + INamedTypeSymbol => false, + IFieldSymbol fieldSymbol => fieldSymbol.ContainingType.TypeKind != TypeKind.Enum, + IMethodSymbol methodSymbol => !methodSymbol.ExplicitInterfaceImplementations.Any() && + methodSymbol.ContainingType.TypeKind != TypeKind.Interface, + IPropertySymbol propertySymbol => !propertySymbol.ExplicitInterfaceImplementations.Any() && + propertySymbol.ContainingType.TypeKind != TypeKind.Interface, + _ => true + }; + } + + private StructuredToken MapToken(ISymbol definedSymbol, SymbolDisplayPart symbolDisplayPart) + { + string? navigateToId = null; + var symbol = symbolDisplayPart.Symbol; + + if (symbol is INamedTypeSymbol && + (definedSymbol == null || !SymbolEqualityComparer.Default.Equals(definedSymbol, symbol)) && + SymbolEqualityComparer.Default.Equals(_assembly, symbol.ContainingAssembly)) + { + navigateToId = symbol.GetId(); + } + + var definitionId = (definedSymbol != null && SymbolEqualityComparer.Default.Equals(definedSymbol, symbol)) ? definedSymbol.GetId() : null; + var tokenValue = symbolDisplayPart.ToString(); + + StructuredToken? token = null; + + switch (symbolDisplayPart.Kind) + { + case SymbolDisplayPartKind.TypeParameterName: + case SymbolDisplayPartKind.AliasName: + case SymbolDisplayPartKind.AssemblyName: + case SymbolDisplayPartKind.ClassName: + case SymbolDisplayPartKind.DelegateName: + case SymbolDisplayPartKind.EnumName: + case SymbolDisplayPartKind.ErrorTypeName: + case SymbolDisplayPartKind.InterfaceName: + case SymbolDisplayPartKind.StructName: + token = StructuredToken.CreateTypeNameToken(tokenValue); + break; + case SymbolDisplayPartKind.Keyword: + token = StructuredToken.CreateKeywordToken(tokenValue); + break; + case SymbolDisplayPartKind.LineBreak: + token = StructuredToken.CreateLineBreakToken(); + break; + case SymbolDisplayPartKind.StringLiteral: + token = StructuredToken.CreateStringLiteralToken(tokenValue); + break; + case SymbolDisplayPartKind.Punctuation: + token = StructuredToken.CreatePunctuationToken(tokenValue); + break; + case SymbolDisplayPartKind.Space: + token = StructuredToken.CreateSpaceToken(); + break; + case SymbolDisplayPartKind.PropertyName: + case SymbolDisplayPartKind.EventName: + case SymbolDisplayPartKind.FieldName: + case SymbolDisplayPartKind.MethodName: + case SymbolDisplayPartKind.Operator: + case SymbolDisplayPartKind.EnumMemberName: + case SymbolDisplayPartKind.ExtensionMethodName: + case SymbolDisplayPartKind.ConstantName: + token = StructuredToken.CreateMemberNameToken(tokenValue); + break; + default: + token = StructuredToken.CreateTextToken(tokenValue); + break; + } + + if (!String.IsNullOrWhiteSpace(definitionId)) + { + token.Id = definitionId!; + } + + if (!String.IsNullOrWhiteSpace(navigateToId)) + { + token.PropertiesObj.Add("NavigateToId", navigateToId!); + } + + return token; + } + + private Accessibility ToEffectiveAccessibility(Accessibility accessibility) + { + switch (accessibility) + { + case Accessibility.ProtectedAndInternal: + return Accessibility.Internal; + case Accessibility.ProtectedOrInternal: + return Accessibility.Protected; + default: + return accessibility; + } + } + + private bool IsAccessible(ISymbol s) + { + switch (s.DeclaredAccessibility) + { + case Accessibility.Protected: + case Accessibility.ProtectedOrInternal: + case Accessibility.Public: + return true; + case Accessibility.Internal: + return s.GetAttributes().Any(a => a.AttributeClass.Name == "FriendAttribute"); + default: + return IsAccessibleExplicitInterfaceImplementation(s); + } + } + + private bool IsAccessibleExplicitInterfaceImplementation(ISymbol s) + { + return s switch + { + IMethodSymbol methodSymbol => methodSymbol.ExplicitInterfaceImplementations.Any(i => IsAccessible(i.ContainingType)), + IPropertySymbol propertySymbol => propertySymbol.ExplicitInterfaceImplementations.Any(i => IsAccessible(i.ContainingType)), + _ => false + }; + } + + internal class CodeFileBuilderEnumFormatter : AbstractSymbolDisplayVisitor + { + private readonly List _tokenList; + + public CodeFileBuilderEnumFormatter(List tokenList) : base(null, SymbolDisplayFormat.FullyQualifiedFormat, false, null, 0, false) + { + _tokenList = tokenList; + } + + protected override AbstractSymbolDisplayVisitor MakeNotFirstVisitor(bool inNamespaceOrType = false) + { + return this; + } + + protected override void AddLiteralValue(SpecialType type, object value) + { + _tokenList.Add(StructuredToken.CreateLiteralToken(ObjectDisplay.FormatPrimitive(value, ObjectDisplayOptions.None))); + } + + protected override void AddExplicitlyCastedLiteralValue(INamedTypeSymbol namedType, SpecialType type, object value) + { + _tokenList.Add(StructuredToken.CreateLiteralToken(ObjectDisplay.FormatPrimitive(value, ObjectDisplayOptions.None))); + } + + protected override void AddSpace() + { + _tokenList.Add(StructuredToken.CreateSpaceToken()); + } + + protected override void AddBitwiseOr() + { + _tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.BarToken)); + } + + public override void VisitField(IFieldSymbol symbol) + { + _tokenList.Add(StructuredToken.CreateTypeNameToken(symbol.Type.Name)); + _tokenList.Add(StructuredToken.CreatePunctuationToken(SyntaxKind.DotToken)); + _tokenList.Add(StructuredToken.CreateMemberNameToken(symbol.Name)); + } + + public void Format(ITypeSymbol? type, object? typedConstantValue) + { + AddNonNullConstantValue(type, typedConstantValue); + } + } + } +} diff --git a/tools/apiview/parsers/csharp-api-parser/csharp-api-parser.sln b/tools/apiview/parsers/csharp-api-parser/csharp-api-parser.sln new file mode 100644 index 00000000000..ad79aafb026 --- /dev/null +++ b/tools/apiview/parsers/csharp-api-parser/csharp-api-parser.sln @@ -0,0 +1,37 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.8.34601.278 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "APIView", "..\..\..\..\src\dotnet\APIView\APIView\APIView.csproj", "{B12E5F4E-D63D-411F-A22D-9DD0DE1769A3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Azure.ClientSdk.Analyzers", "..\..\..\..\src\dotnet\Azure.ClientSdk.Analyzers\Azure.ClientSdk.Analyzers\Azure.ClientSdk.Analyzers.csproj", "{4ECD9A58-ABEB-4285-A5D7-7CDA8A558AE0}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CSharpAPIParser", "CSharpAPIParser.csproj", "{EB159E3D-9732-4EB3-A3C2-817475B9D749}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {B12E5F4E-D63D-411F-A22D-9DD0DE1769A3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B12E5F4E-D63D-411F-A22D-9DD0DE1769A3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B12E5F4E-D63D-411F-A22D-9DD0DE1769A3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B12E5F4E-D63D-411F-A22D-9DD0DE1769A3}.Release|Any CPU.Build.0 = Release|Any CPU + {4ECD9A58-ABEB-4285-A5D7-7CDA8A558AE0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4ECD9A58-ABEB-4285-A5D7-7CDA8A558AE0}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4ECD9A58-ABEB-4285-A5D7-7CDA8A558AE0}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4ECD9A58-ABEB-4285-A5D7-7CDA8A558AE0}.Release|Any CPU.Build.0 = Release|Any CPU + {EB159E3D-9732-4EB3-A3C2-817475B9D749}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {EB159E3D-9732-4EB3-A3C2-817475B9D749}.Debug|Any CPU.Build.0 = Debug|Any CPU + {EB159E3D-9732-4EB3-A3C2-817475B9D749}.Release|Any CPU.ActiveCfg = Release|Any CPU + {EB159E3D-9732-4EB3-A3C2-817475B9D749}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D2777671-F5DD-4279-BF51-B9C81E05BD5A} + EndGlobalSection +EndGlobal From b847427fe70c635661e3d3472ede2c679520429e Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Fri, 15 Mar 2024 10:52:23 -0700 Subject: [PATCH 2/7] CSharp Parser Re-Write Parse C# Assemblly to Token Tree Add Token Tree Documentation Add LineDefinitionId and HideFromNavigation Undo internals visible to setting Sort NameSpaces and Types Ensure Each node has Name and Id Serialize to Message Pack Add Tags to StructuredToken update tree Token Parser Documentation Attempt to Improve Serialized JSON Size Properties for Serialization and Deserialization Use shorter names for diagnostics update APIView Parser Contributing guide Delete TreeTokenCodeFile Update APIView C# Parser Project Name Add MessagePack Serialization Switch to System.Json.Text working Update Contributing Doc Add Navigate to ID Make Parser a dotnet Tool Update Parser Version for CodeFile App Parser Style Property Output GZipped TokenFile Switch to Human Readable Json Property Names, use compression Fix some issues raised in pull request Updating Contributing.md Update Contributing Docs initial pass to documentation fix pr feedback one missed --- .../APIView/Model/StructuredTokenModel.cs | 116 ++++++++++++++++-- .../APIView/APIView/Model/TokenTreeModel.cs | 107 +++++++++++++--- tools/apiview/parsers/CONTRIBUTING.md | 92 ++++++-------- 3 files changed, 230 insertions(+), 85 deletions(-) diff --git a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs index 48b9af0b6fc..c3f74d781ca 100644 --- a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs +++ b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs @@ -5,44 +5,138 @@ namespace APIView.TreeToken { + /// + /// Represents the type of a structured token. + /// All tokens should be content except for spacing tokens and url. + /// public enum StructuredTokenKind { + /// + /// Specifies that the token is content. + /// This is the default value for a token. + /// Content = 0, + /// + /// Space token indicating switch to new line. + /// LineBreak = 1, - NonBreakingSpace = 2, + /// + /// Regular single space. + /// + NoneBreakingSpace = 2, + /// + /// 4 NonBreakingSpaces. + /// TabSpace = 3, + /// + /// Use this between method parameters. Depending on user setting this would result in a single space or new line. + /// ParameterSeparator = 4, + /// + /// A url token should have `LinkText` property i.e `token.Properties["LinkText"]` and the url/link should be the token value. + /// Url = 5 } /// - /// Used to represent a APIView token its properties and tags for APIView parsers. + /// Represents an APIView token its properties and tags for APIView parsers. /// public class StructuredToken { - public HashSet Tags - { - get { return TagsObj.Count > 0 ? TagsObj : null; } - set { TagsObj = value ?? new HashSet(); } - } + /// + /// Token Id. + /// Previously known as DefinitionId. + /// + public string Id { get; set; } + + /// + /// Represents the type of a structured token. + /// + public StructuredTokenKind Kind { get; set; } = StructuredTokenKind.Content; + + /// + /// The token value which will be displayed. Spacing tokens don't need to have value as it will be + /// replaced at deserialization based on the Token Kind. + /// + public string Value { get; set; } = string.Empty; + + /// + /// Properties of the token. + /// + /// + /// GroupId: `doc` to group consecutive comment tokens as documentation. + /// + /// + /// NavigateToId: When the token is clicked, will navigate to the location that matches the provided token id. + /// + /// + /// public Dictionary Properties { get { return PropertiesObj.Count > 0 ? PropertiesObj : null; } set { PropertiesObj = value ?? new Dictionary(); } } + + /// + /// List of default CSS configuration for any language. + /// Languages can override these or add new classes by reaching out to the APIView team. + /// + /// + /// comment + /// + /// + /// keyword + /// + /// + /// literal + /// + /// + /// mname: member name + /// + /// + /// punc + /// + /// + /// sliteral: string literal + /// + /// + /// text + /// + /// + /// tname: type name + /// + /// + /// public HashSet RenderClasses { get { return RenderClassesObj.Count > 0 ? RenderClassesObj : null; } set { RenderClassesObj = value ?? new HashSet(); } } - public string Value { get; set; } = string.Empty; - public string Id { get; set; } - public StructuredTokenKind Kind { get; set; } = StructuredTokenKind.Content; + + /// + /// Behavioral boolean properties + /// + /// + /// Deprecated: Mark a node as deprecated + /// + /// + /// SkipDiff: Indicate that a node should not be used in computation of diff. + /// + /// + /// + public HashSet Tags + { + get { return TagsObj.Count > 0 ? TagsObj : null; } + set { TagsObj = value ?? new HashSet(); } + } + [JsonIgnore] public HashSet TagsObj { get; set; } = new HashSet(); + [JsonIgnore] public Dictionary PropertiesObj { get; set; } = new Dictionary(); + [JsonIgnore] public HashSet RenderClassesObj { get; set; } = new HashSet(); @@ -75,7 +169,7 @@ public static StructuredToken CreateEmptyToken(string id = null) public static StructuredToken CreateSpaceToken() { var token = new StructuredToken(); - token.Kind = StructuredTokenKind.NonBreakingSpace; + token.Kind = StructuredTokenKind.NoneBreakingSpace; return token; } diff --git a/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs b/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs index f6c2e8f8408..a1ec2d15c6d 100644 --- a/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs +++ b/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs @@ -1,47 +1,118 @@ using System.Collections.Generic; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using System.Text.Json; using System.Text.Json.Serialization; namespace APIView.TreeToken -{ +{ public class APITreeNode { - public HashSet Tags + /// + /// Id of the node, which should be unique at the node level. i.e. unique among its siblings. + /// This was previously represented by the DefinitionId for the main Token of the node. + /// + public string Id { get; set; } + + /// + /// The type of node it is. Using any of the following `assembly`, `class`, `delegate`, `enum`, `interface`, + /// `method` , `namespace`, `package`, `struct`, `type` will get you the corresponding default icons + /// for the page navigation. + /// + public string Kind { get; set; } + + /// + /// The name of the tree node which will be used as label for the API Navigation. + /// Generally use the name of the module (class, method). + /// + public string Name { get; set; } + + /// + /// Tokens which are rendered after all child nodes. + /// Depending on the language this would include the closing curly brace and/or empty lines. + /// You can simulate an empty line by adding an empty token (Content token with no value) and a LineBreak token. + /// + public List BottomTokens { - get { return TagsObj.Count > 0 ? TagsObj : null; } - set { TagsObj = value ?? new HashSet(); } + get { return BottomTokensObj.Count > 0 ? BottomTokensObj : null; } + set { BottomTokensObj = value ?? new List(); } + } + /// + /// The nodes immediate children. + /// For a namespace this would be classes, for a class this would be the class constructors and methods. + /// Children are rendered after TopTokens but before BottomTokens, and are automatically indented. + /// + public List Children + { + get { return ChildrenObj.Count > 0 ? ChildrenObj : null; } + set { ChildrenObj = value ?? new List(); } } + /// + /// Properties of a node. + /// + /// + /// SubKind: Similar to kind, use this to make the node more specific. + /// e.g. `Kind = 'Type'`, and `SubKind = 'class'`. We also use this to make the navigation icon it will override kind. + /// + /// + /// IconName: Use this only if you are looking to add a custom icon different from language wide defaults. New additions will need to be supported APIView side. + /// + /// + /// public Dictionary Properties { get { return PropertiesObj.Count > 0 ? PropertiesObj : null; } set { PropertiesObj = value ?? new Dictionary(); } } + + /// + /// Behavioral boolean properties. + /// + /// + /// Deprecated: Mark a node as deprecated + /// + /// + /// Hidden: Mark a node as Hidden + /// + /// + /// HideFromNavigation: Indicate that a node should be hidden from the page navigation. + /// + /// + /// SkipDiff: Indicate that a node should not be used in computation of diff. + /// + /// + /// CrossLangDefId: The cross language definitionId for the node. + /// + /// + /// + public HashSet Tags + { + get { return TagsObj.Count > 0 ? TagsObj : null; } + set { TagsObj = value ?? new HashSet(); } + } + + /// + /// The main data of the node. This is all the tokens that actually define the node. + /// When rendering, TopTokens are rendered first, followed by any Children, and then finally BottomTokens + /// public List TopTokens { get { return TopTokensObj.Count > 0 ? TopTokensObj : null; } set { TopTokensObj = value ?? new List(); } } - public List BottomTokens - { - get { return BottomTokensObj.Count > 0 ? BottomTokensObj : null; } - set { BottomTokensObj = value ?? new List(); } - } - public List Children - { - get { return ChildrenObj.Count > 0 ? ChildrenObj : null; } - set { ChildrenObj = value ?? new List(); } - } - - public string Name { get; set; } - public string Id { get; set; } - public string Kind { get; set; } + [JsonIgnore] public HashSet TagsObj { get; set; } = new HashSet(); + [JsonIgnore] public Dictionary PropertiesObj { get; set; } = new Dictionary(); + [JsonIgnore] public List TopTokensObj { get; set; } = new List(); + [JsonIgnore] public List BottomTokensObj { get; set; } = new List(); + [JsonIgnore] public List ChildrenObj { get; set; } = new List(); diff --git a/tools/apiview/parsers/CONTRIBUTING.md b/tools/apiview/parsers/CONTRIBUTING.md index 6c0f455bf98..ce4182578a1 100644 --- a/tools/apiview/parsers/CONTRIBUTING.md +++ b/tools/apiview/parsers/CONTRIBUTING.md @@ -1,21 +1,33 @@ -# Contributing +# Tree token parser -## Overview This page describes how to contribute to [APIView](../../../src//dotnet/APIView/APIViewWeb/APIViewWeb.csproj) language level parsers. -Specifically, how to create or update a language parser to produce tree style tokens for APIView. +Specifically how to create or update a language parser to produce a hierarchy of the API using a tree data tokens, instead of a flat token list. -Previously APIview tokens were created as a flat list assigned to the `CodeFileToken[] Tokens` property of the [CodeFile](../../../src/dotnet/APIView/APIView/Model/CodeFile.cs). Then the page navigation is created and assigned to `NavigationItem[] Navigation`. For tree style tokens these two properties are no longer required, instead a `List APIForest` property will be used to capture the generated tree of tokens. +## Tree style token parser benefits + +- Ability to granularly identify a specific class and it’s methods or a specific API alone within a large review without rendering entire tokens. +- Faster diffing using tree shaker instead of current text based comparison. +- Provide diffing with context of where the line that changed belong to in the tree, instead of showing the 5 lines before and after the change. +- Provide cross language view for a granular section within an API review. +- Support for collapsible views at each class and namespace level to make it easier to review the change. User will be able to expand/collapse an entire class or a namespace and it’s children. +- Support dynamic representation of APIs for e.g. some users prefer to see all method arguments in same line and some users prefer to see them in multiple lines. +- Less data to be stored in token file which are located in azure storage blob. -The main idea is to capture the hierarchy of the API using a tree data structure, then maintain a flat list of tokens for each node of the tree. +## Key concepts + +Previously APIview tokens were created as a flat list assigned to the `CodeFileToken[] Tokens` property of the [CodeFile](../../../src/dotnet/APIView/APIView/Model/CodeFile.cs). Then the page navigation is created and assigned to `NavigationItem[] Navigation`. For tree style tokens these two properties are no longer required, instead a `List APIForest` property will be used to capture the generated tree of tokens. ![APITree](APITree.svg) -Each module of the API (namespace, class, methods) should be its own node. Members of a module (methods, in a class), (classes in a namespace) should be added as child nodes of its parent module. -Each tree node has top tokens which should be used to capture the main tokens on the node, these can span multiple lines. Module name, decorators, and parameters should be modeled as topTokens. If the language requires it, use the bottom tokens to capture tokens that closes out the node, this is usually just the closing bracket and/or empty lines. +- Each module of the API (namespace, class, methods) should be its own node. Members of a module (methods in a class), (classes in a namespace) should be added as child nodes of its parent module. +- Each tree node has top tokens which should be used to capture the main tokens on the node, these can span multiple lines. +- Module name, decorators, and parameters should be modeled as `TopTokens`. +- If the language requires it use the bottom tokens to capture tokens that closes out the node, this is usually just the closing bracket and/or empty lines. -## Object Definitions +### Object Definitions - Here are the models needed + ``` object APITreeNode string Name @@ -45,64 +57,32 @@ Each tree node has top tokens which should be used to capture the main tokens on ``` ### APITreeNode -- `Name`: *(Required)* The name of the tree node which will be used as label for the API Navigation. Generally use the name of the module (class, method). -- `Id`: *(Required)* Id of the node, which should be unique at the node level. i.e. unique among its siblings. Use whatever your existing parser is assigning to DefinitionId for the main Token of the node. Each node must have an Id. -- `Kind` *(Required)* : What kind of node is it. Please ensure you set a Kind for each node. Using any of the following `assembly`, `class`, `delegate`, `enum`, `interface`, `method` , `namespace`, `package`, `struct`, `type` will get you the corresponding default icons for the page navigation but feel free to use something language specific then reach out to APIView to support any new name used. -- `Tags` : Use this for opt in or opt out boolean properties. The currently supported tags are - - `Deprecated` Mark a node as deprecated - - `Hidden` Mark a node as Hidden - - `HideFromNav` Indicate that anode should be hidden from the page navigation. - - `SkipDiff` Indicate that a node should not be used in computation of diff. - - `CrossLangDefId` The cross language definitionId for the node. -- `Properties` : Use this for other properties of the node. The currently supported keys are - - `SubKind` Similar to kind, use this to make the node more specific. e.g. `Kind = 'Type'`, and `SubKind = 'class'` or something specific to you language. We also use this to make the navigation icon it will override kind. - - `IconName` Use this only if you are looking to add a custom icon different from language wide defaults. New additions will need to be supported APIView side. -- `TopTokens` : The main data of the node. This is all the tokens that actually define the node. e.g. For a class this would include the access modifier, the class name, any attributes or decorators e.t.c. For a method this would include the return type, method name, parameters e.t.c. See StructuredToken description below for more info. -- `BottomToken` : Data that closes out the node. Depending on the language this would include the closing curly brace and/or empty lines. You can simulate an empty line by adding an empty token (content token with no value) and a line Break token. -- `Children` : The nodes immediate children. For a namespace this would be classes, for a class this would be the class constructors and methods. - -Sort each node at each level of the tree by your desired property, this is to ensure that difference in node order does not result in diff. - -Ensure each node has an Id and Kind. The combination of `Id`, `Kind` and `SubKind` should make the node unique across all nodes in the tree. This is very important. For example a class and a method can potentially have the same Id, but the kind should differentiate them from each other. - -Don’t worry about indentation that will be handled by the tree structure, unless you want to have indentation between the tokens then use `TabSpace` token kind. + +- Ensure each node has an Id and Kind. The combination of `Id`, `Kind`, and `SubKind` should make the node unique across all nodes in the tree. For example a class and a method can potentially have the same Id, but the kind should differentiate them from each other. +- Sort each node at each level of the tree by your desired property, this is to ensure that difference in node order does not result in diff. ### StructuredToken -- `Value` : The token value which will be displayed. Spacing tokens don't need to have value. -- `Id` : This is essentially whatever existing parser was assigning to the token DefinitionId. You don’t have to assign a tokenId to every token. APIView comments are tied to this ID so for backward compatibility it essential to use the same Id that existing parser is using for DefinitionId. -- `Kind` *(Required)* : An enum - - `Content` Specifies that the token is content - - `LineBreak` Space token indicating switch to new line. - - `NonBreakingSpace` Regular single space - - `TabSpace` 4 NonBreakingSpaces - - `ParameterSeparator` Use this between method parameters. Depending on user setting this would result in a single space or new line - - `Url` A url token should have `LinkText` property i.e `token.Properties["LinkText"]` and the url/link should be the token value. - All tokens should be content except for spacing tokens and url. ParameterSeparator should be used between method or function parameters. -- `Tags` : Use this for opt in or opt out boolean properties.The currently supported tags are - - `SkippDiff` Indicate that a token should not be used in computation of diff. - - `Deprecated` Mark a token as deprecated -- `Properties` : Properties of the token. - - `GroupId` : `doc` to group consecutive comment tokens as documentation. - - `NavigateToId` Id for navigating to where the node where the token is defined. Should match the definitionId of the referenced node. -- `RenderClasses` : Add css classes for how the tokens will be rendred. Classes currently being used are `text` `keyword` `punc` `tname` `mname` `literal` `sliteral` `comment` Feel free to add your own custom class. Whatever custom classes you use please provide us the appriopriate css for the class so we can update APIView. - -Assign the final parsed value to a `List APIForest` property of the `CodeFile` + +- Assign the final parsed value to a `List APIForest` property of the `CodeFile` ## Serialization -Serialize the generated code file to JSON them compress the file using Gzip compression. Try to make the json as small as possible by ignoring null values and empty collections. +Serialize the generated code file to JSON and then compress the file using Gzip compression. Try to make the json as small as possible by ignoring null values and empty collections. +Don't worry about indentation that will be handled by the tree structure. In the case you want to have indentation between the tokens then use `TabSpace` token kind. ## How to handle commons Scenarios -- TEXT, KEYWORD, COMMENT : Add `text`, `keyword`, `comment` to RenderClasses of the token -- NEW_LINE : Create a token with `Kind = LineBreak` -- WHITE_SPACE : Create token with `Kind = NonBreakingSpace` -- PUNCTUATION : Create a token with `Kind = Content` and the `Value = the punctuation` + +- TEXT, KEYWORD, COMMENT : Use `text`, `keyword`, `comment` to property `RenderClasses` of `StructuredToken`. +- NEW_LINE : Create a token with `Kind = LineBreak`. +- WHITE_SPACE : Create token with `Kind = NoneBreakingSpace`. +- PUNCTUATION : Create a token with `Kind = Content` and the `Value = `. - DOCUMENTATION : Add `GroupId = doc` in the properties of the token. This identifies a range of consecutive tokens as belonging to a group. -- SKIP_DIFF : Add `SkipDiff` to the Tag to indicate that node or token should not be included in diff computation -- LINE_ID_MARKER : You can add a empty token. `Kind = Content` and `Value = ""` then give it an `Id`. +- SKIP_DIFF : Add `SkipDiff` to the Tag to indicate that the node or token should not be included in diff computation. +- LINE_ID_MARKER : You can add a empty token. `Kind = Content` and `Value = ""` then give it an `Id` to make it commentable. - EXTERNAL_LINK : Create a single token set `Kind = Url`, `Value = link` then add the link text as a properties `LinkText`; - Common Tags: `Deprecated`, `Hidden`, `HideFromNav`, `SkipDiff` - Cross Language Id: Use `CrossLangId` as key with value in the node properties. -Please reach out at [APIView Teams Channel](https://teams.microsoft.com/l/channel/19%3A3adeba4aa1164f1c889e148b1b3e3ddd%40thread.skype/APIView?groupId=3e17dcb0-4257-4a30-b843-77f47f1d4121&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) if you need more information. +## Get help +Please reach out at [APIView Teams Channel](https://teams.microsoft.com/l/channel/19%3A3adeba4aa1164f1c889e148b1b3e3ddd%40thread.skype/APIView?groupId=3e17dcb0-4257-4a30-b843-77f47f1d4121&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) if you need more information. From 76ce86c4173994c94cc031a0826d44555db4ea84 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Wed, 19 Jun 2024 14:22:37 -0700 Subject: [PATCH 3/7] Convert Magic strings to static properties --- .../APIView/Model/StructuredTokenModel.cs | 65 ++++++++++++++++--- .../APIView/APIView/Model/TokenTreeModel.cs | 40 +++++++++++- .../parsers/csharp-api-parser/Program.cs | 2 +- .../TreeToken/CodeFileBuilder.cs | 28 ++++---- 4 files changed, 108 insertions(+), 27 deletions(-) diff --git a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs index c3f74d781ca..451f9fc25f8 100644 --- a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs +++ b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs @@ -5,6 +5,7 @@ namespace APIView.TreeToken { + /// /// Represents the type of a structured token. /// All tokens should be content except for spacing tokens and url. @@ -23,7 +24,7 @@ public enum StructuredTokenKind /// /// Regular single space. /// - NoneBreakingSpace = 2, + NonBreakingSpace = 2, /// /// 4 NonBreakingSpaces. /// @@ -44,6 +45,52 @@ public enum StructuredTokenKind public class StructuredToken { + /// + /// Property key to indicate that a range of tokens is a group + /// + public static string GROUP_ID = "GroupId"; + /// + /// Property key to indicate id to be navigated to when a token is clicked. + /// + public static string NAVIGATE_TO_ID = "NavigateToId"; + /// + /// Property value that marks a token as documentation + /// + public static string DOCUMENTATION = "doc"; + /// + /// Style class for text + /// + public static string TEXT = "text"; + /// + /// Style class for keyword + /// + public static string KEYWORD = "keyword"; + /// + /// Style class for literal + /// + public static string LITERAL = "literal"; + /// + /// Style class for member-name + /// + public static string MEMBER_NAME = "mname"; + /// + /// Style class for punctuation + /// + public static string PUNCTUATION = "punc"; + /// + /// Style class for string-literal + /// + public static string STRING_LITERAL = "sliteral"; + /// + /// Style class for type-name + /// + public static string TYPE_NAME = "tname"; + /// + /// Style class for comment + /// + public static string COMMENT = "comment"; + + /// /// Token Id. /// Previously known as DefinitionId. @@ -169,7 +216,7 @@ public static StructuredToken CreateEmptyToken(string id = null) public static StructuredToken CreateSpaceToken() { var token = new StructuredToken(); - token.Kind = StructuredTokenKind.NoneBreakingSpace; + token.Kind = StructuredTokenKind.NonBreakingSpace; return token; } @@ -187,14 +234,14 @@ public static StructuredToken CreateTextToken(string value, string id = null) { token.Id = id; } - token.RenderClassesObj.Add("text"); + token.RenderClassesObj.Add(TEXT); return token; } public static StructuredToken CreateKeywordToken(string value) { var token = new StructuredToken(value); - token.RenderClassesObj.Add("keyword"); + token.RenderClassesObj.Add(KEYWORD); return token; } @@ -211,7 +258,7 @@ public static StructuredToken CreateKeywordToken(Accessibility accessibility) public static StructuredToken CreatePunctuationToken(string value) { var token = new StructuredToken(value); - token.RenderClassesObj.Add("punc"); + token.RenderClassesObj.Add(PUNCTUATION); return token; } @@ -223,28 +270,28 @@ public static StructuredToken CreatePunctuationToken(SyntaxKind syntaxKind) public static StructuredToken CreateTypeNameToken(string value) { var token = new StructuredToken(value); - token.RenderClassesObj.Add("tname"); + token.RenderClassesObj.Add(TYPE_NAME); return token; } public static StructuredToken CreateMemberNameToken(string value) { var token = new StructuredToken(value); - token.RenderClassesObj.Add("mname"); + token.RenderClassesObj.Add(MEMBER_NAME); return token; } public static StructuredToken CreateLiteralToken(string value) { var token = new StructuredToken(value); - token.RenderClassesObj.Add("literal"); + token.RenderClassesObj.Add(LITERAL); return token; } public static StructuredToken CreateStringLiteralToken(string value) { var token = new StructuredToken(value); - token.RenderClassesObj.Add("sliteral"); + token.RenderClassesObj.Add(STRING_LITERAL); return token; } } diff --git a/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs b/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs index a1ec2d15c6d..f6ff4043519 100644 --- a/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs +++ b/src/dotnet/APIView/APIView/Model/TokenTreeModel.cs @@ -1,13 +1,47 @@ using System.Collections.Generic; -using Microsoft.CodeAnalysis; -using Microsoft.CodeAnalysis.CSharp; -using System.Text.Json; using System.Text.Json.Serialization; namespace APIView.TreeToken { public class APITreeNode { + /// + /// Indicates a node is an assembly kind + /// + public static string ASSEMBLY = "Assembly"; + /// + /// Indicates a node is a InternalsVisibleTo kind + /// + public static string INTERNALS_VISIBLE_TO = "InternalsVisibleTo"; + /// + /// Indicates a node is a Dependencies kind + /// + public static string DEPENDENCIES = "Dependencies"; + /// + /// Indicates a node is a Namespace kind + /// + public static string NAMESPACE = "Namespace"; + /// + /// Indicates a node is a Type kind + /// + public static string TYPE = "Type"; + /// + /// Indicates a node is a Member kind + /// + public static string MEMBER = "Member"; + /// + /// Indicates a node is hidden + /// + public static string HIDDEN = "Hidden"; + /// + /// Indicates that a node should be hidden from Navigation + /// + public static string HIDE_FROM_NAV = "HideFromNav"; + /// + /// Property key to use to make a nodes kind more specific. + /// + public static string SUB_KIND = "SubKind"; + /// /// Id of the node, which should be unique at the node level. i.e. unique among its siblings. /// This was previously represented by the DefinitionId for the main Token of the node. diff --git a/tools/apiview/parsers/csharp-api-parser/Program.cs b/tools/apiview/parsers/csharp-api-parser/Program.cs index 4ab1598aaa3..375bd05fd4b 100644 --- a/tools/apiview/parsers/csharp-api-parser/Program.cs +++ b/tools/apiview/parsers/csharp-api-parser/Program.cs @@ -102,7 +102,7 @@ static void HandlePackageFileParsing(Stream stream, FileInfo packageFilePath, Di } var parsedFileName = string.IsNullOrEmpty(outputFileName) ? assemblySymbol.Name : outputFileName; var treeTokenCodeFile = new CSharpAPIParser.TreeToken.CodeFileBuilder().Build(assemblySymbol, runAnalysis, dependencies); - var gzipJsonTokenFilePath = Path.Combine(OutputDirectory.FullName, $"{parsedFileName}"); + var gzipJsonTokenFilePath = Path.Combine(OutputDirectory.FullName, $"{parsedFileName}.json.tgz"); var options = new JsonSerializerOptions() diff --git a/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs b/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs index 76d21b60e64..166d704513c 100644 --- a/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs +++ b/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs @@ -77,7 +77,7 @@ public CodeFile Build(IAssemblySymbol assemblySymbol, bool runAnalysis, List apiTree, if (assemblyAttributes != null && assemblyAttributes.Any()) { var apiTreeNode = new APITreeNode(); - apiTreeNode.Kind = apiTreeNode.Name = apiTreeNode.Id = "InternalsVisibleTo"; + apiTreeNode.Kind = apiTreeNode.Name = apiTreeNode.Id = APITreeNode.INTERNALS_VISIBLE_TO; apiTreeNode.TopTokensObj.Add(StructuredToken.CreateTextToken(value: "Exposes internals to:")); apiTreeNode.TopTokensObj.Add(StructuredToken.CreateLineBreakToken()); @@ -158,7 +158,7 @@ public static void BuildDependencies(List apiTree, List apiTree, INamespaceSymbol namespac var apiTreeNode = new APITreeNode(); apiTreeNode.Id = namespaceSymbol.GetId(); apiTreeNode.Name = namespaceSymbol.ToDisplayString(); - apiTreeNode.Kind = "Namespace"; + apiTreeNode.Kind = APITreeNode.NAMESPACE; if (isHidden) { - apiTreeNode.TagsObj.Add("Hidden"); + apiTreeNode.TagsObj.Add(APITreeNode.HIDDEN); } apiTreeNode.TopTokensObj.Add(StructuredToken.CreateKeywordToken(SyntaxKind.NamespaceKeyword)); @@ -238,14 +238,14 @@ private void BuildType(List apiTree, INamedTypeSymbol namedType, bo bool isHidden = IsHiddenFromIntellisense(namedType); var apiTreeNode = new APITreeNode(); - apiTreeNode.Kind = "Type"; - apiTreeNode.PropertiesObj.Add("SubKind", namedType.TypeKind.ToString().ToLowerInvariant()); + apiTreeNode.Kind = APITreeNode.TYPE; + apiTreeNode.PropertiesObj.Add(APITreeNode.SUB_KIND, namedType.TypeKind.ToString().ToLowerInvariant()); apiTreeNode.Id = namedType.GetId(); apiTreeNode.Name = namedType.ToDisplayString(SymbolDisplayFormat.MinimallyQualifiedFormat); if (isHidden && !inHiddenScope) { - apiTreeNode.TagsObj.Add("Hidden"); + apiTreeNode.TagsObj.Add(APITreeNode.HIDDEN); } BuildDocumentation(apiTreeNode.TopTokensObj, namedType); @@ -334,7 +334,7 @@ private void BuildDocumentation(List tokenList, ISymbol symbol) { var docToken = new StructuredToken("// " + line.Trim()); docToken.RenderClassesObj.Add("comment"); - docToken.PropertiesObj.Add("GroupId", "doc"); + docToken.PropertiesObj.Add(StructuredToken.GROUP_ID, StructuredToken.DOCUMENTATION); tokenList.Add(docToken); tokenList.Add(StructuredToken.CreateLineBreakToken()); } @@ -405,15 +405,15 @@ private void BuildMember(List apiTree, ISymbol member, bool inHidde { bool isHidden = IsHiddenFromIntellisense(member); var apiTreeNode = new APITreeNode(); - apiTreeNode.Kind = "Member"; - apiTreeNode.PropertiesObj.Add("SubKind", member.Kind.ToString()); + apiTreeNode.Kind = APITreeNode.MEMBER; + apiTreeNode.PropertiesObj.Add(APITreeNode.SUB_KIND, member.Kind.ToString()); apiTreeNode.Id = member.GetId(); apiTreeNode.Name = member.ToDisplayString(); - apiTreeNode.TagsObj.Add("HideFromNav"); + apiTreeNode.TagsObj.Add(APITreeNode.HIDE_FROM_NAV); if (isHidden && !inHiddenScope) { - apiTreeNode.TagsObj.Add("Hidden"); + apiTreeNode.TagsObj.Add(APITreeNode.HIDDEN); } BuildDocumentation(apiTreeNode.TopTokensObj, member); @@ -706,7 +706,7 @@ private StructuredToken MapToken(ISymbol definedSymbol, SymbolDisplayPart symbol if (!String.IsNullOrWhiteSpace(navigateToId)) { - token.PropertiesObj.Add("NavigateToId", navigateToId!); + token.PropertiesObj.Add(StructuredToken.NAVIGATE_TO_ID, navigateToId!); } return token; From 61511c656abadd0401e9744b028bb5c5556c8772 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Tue, 25 Jun 2024 09:52:51 -0700 Subject: [PATCH 4/7] Build Parameter Separator --- .../TreeToken/CodeFileBuilder.cs | 21 +++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs b/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs index 166d704513c..0660e7aad4c 100644 --- a/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs +++ b/tools/apiview/parsers/csharp-api-parser/TreeToken/CodeFileBuilder.cs @@ -601,6 +601,7 @@ private void DisplayName(List tokenList, ISymbol symbol, ISymbo } if (symbol is IPropertySymbol propSymbol && propSymbol.DeclaredAccessibility != Accessibility.Internal) { + SymbolDisplayPart previous = default(SymbolDisplayPart); var parts = propSymbol.ToDisplayParts(_defaultDisplayFormat); for (int i = 0; i < parts.Length; i++) { @@ -612,14 +613,19 @@ private void DisplayName(List tokenList, ISymbol symbol, ISymbo i++; } } - tokenList.Add(MapToken(definedSymbol!, parts[i])); + tokenList.Add(MapToken(definedSymbol: definedSymbol!, symbolDisplayPart: parts[i], + previousSymbolDisplayPart: previous)); + previous = parts[i]; } } else { + SymbolDisplayPart previous = default(SymbolDisplayPart); foreach (var symbolDisplayPart in symbol.ToDisplayParts(_defaultDisplayFormat)) { - tokenList.Add(MapToken(definedSymbol!, symbolDisplayPart)); + tokenList.Add(MapToken(definedSymbol: definedSymbol!, symbolDisplayPart: symbolDisplayPart, + previousSymbolDisplayPart: previous)); + previous = symbolDisplayPart; } } } @@ -639,7 +645,7 @@ private bool NeedsAccessibility(ISymbol symbol) }; } - private StructuredToken MapToken(ISymbol definedSymbol, SymbolDisplayPart symbolDisplayPart) + private StructuredToken MapToken(ISymbol definedSymbol, SymbolDisplayPart symbolDisplayPart, SymbolDisplayPart previousSymbolDisplayPart) { string? navigateToId = null; var symbol = symbolDisplayPart.Symbol; @@ -682,7 +688,14 @@ private StructuredToken MapToken(ISymbol definedSymbol, SymbolDisplayPart symbol token = StructuredToken.CreatePunctuationToken(tokenValue); break; case SymbolDisplayPartKind.Space: - token = StructuredToken.CreateSpaceToken(); + if (previousSymbolDisplayPart.Kind == SymbolDisplayPartKind.Punctuation && previousSymbolDisplayPart.ToString().Equals(",")) + { + token = StructuredToken.CreateParameterSeparatorToken(); + } + else + { + token = StructuredToken.CreateSpaceToken(); + } break; case SymbolDisplayPartKind.PropertyName: case SymbolDisplayPartKind.EventName: From 4d39732381667b7cea491d038e602691ac8cce76 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Tue, 25 Jun 2024 13:52:17 -0700 Subject: [PATCH 5/7] Add Image Folder --- tools/apiview/parsers/{ => images}/APITree.svg | 0 tools/apiview/parsers/images/navigation.png | Bin 0 -> 6866 bytes 2 files changed, 0 insertions(+), 0 deletions(-) rename tools/apiview/parsers/{ => images}/APITree.svg (100%) create mode 100644 tools/apiview/parsers/images/navigation.png diff --git a/tools/apiview/parsers/APITree.svg b/tools/apiview/parsers/images/APITree.svg similarity index 100% rename from tools/apiview/parsers/APITree.svg rename to tools/apiview/parsers/images/APITree.svg diff --git a/tools/apiview/parsers/images/navigation.png b/tools/apiview/parsers/images/navigation.png new file mode 100644 index 0000000000000000000000000000000000000000..38ed23051c6de398b4b813877d7e35c2b489caa4 GIT binary patch literal 6866 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 Date: Tue, 25 Jun 2024 16:41:59 -0700 Subject: [PATCH 6/7] Update Page Navigation --- .../APIView/Model/StructuredTokenModel.cs | 6 +- tools/apiview/parsers/CONTRIBUTING.md | 455 +++++++++++++++++- .../parsers/images/deprecated-node.png | Bin 0 -> 4938 bytes .../parsers/images/deprecated-token.png | Bin 0 -> 10393 bytes tools/apiview/parsers/images/hidden-api.png | Bin 0 -> 44301 bytes 5 files changed, 446 insertions(+), 15 deletions(-) create mode 100644 tools/apiview/parsers/images/deprecated-node.png create mode 100644 tools/apiview/parsers/images/deprecated-token.png create mode 100644 tools/apiview/parsers/images/hidden-api.png diff --git a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs index 451f9fc25f8..437db04f4da 100644 --- a/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs +++ b/src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs @@ -32,11 +32,7 @@ public enum StructuredTokenKind /// /// Use this between method parameters. Depending on user setting this would result in a single space or new line. /// - ParameterSeparator = 4, - /// - /// A url token should have `LinkText` property i.e `token.Properties["LinkText"]` and the url/link should be the token value. - /// - Url = 5 + ParameterSeparator = 4 } /// diff --git a/tools/apiview/parsers/CONTRIBUTING.md b/tools/apiview/parsers/CONTRIBUTING.md index ce4182578a1..24c98fcaf45 100644 --- a/tools/apiview/parsers/CONTRIBUTING.md +++ b/tools/apiview/parsers/CONTRIBUTING.md @@ -17,7 +17,7 @@ Specifically how to create or update a language parser to produce a hierarchy of Previously APIview tokens were created as a flat list assigned to the `CodeFileToken[] Tokens` property of the [CodeFile](../../../src/dotnet/APIView/APIView/Model/CodeFile.cs). Then the page navigation is created and assigned to `NavigationItem[] Navigation`. For tree style tokens these two properties are no longer required, instead a `List APIForest` property will be used to capture the generated tree of tokens. -![APITree](APITree.svg) +![APITree](images/APITree.svg) - Each module of the API (namespace, class, methods) should be its own node. Members of a module (methods in a class), (classes in a namespace) should be added as child nodes of its parent module. - Each tree node has top tokens which should be used to capture the main tokens on the node, these can span multiple lines. @@ -30,9 +30,9 @@ Previously APIview tokens were created as a flat list assigned to the `CodeFileT ``` object APITreeNode - string Name - string Id - string Kind + string Name (Required) + string Id (Required) + string Kind (Required) Set Tags Dictionary Properties List TopTokens @@ -42,7 +42,7 @@ Previously APIview tokens were created as a flat list assigned to the `CodeFileT object StructuredToken string Value string Id - StructuredTokenKind Kind + StructuredTokenKind Kind (Required) Set Tags Dictionary Properties Set RenderClasses @@ -50,18 +50,16 @@ Previously APIview tokens were created as a flat list assigned to the `CodeFileT enum StructuredTokenKind Content LineBreak - NoneBreakingSpace + NonBreakingSpace TabSpace ParameterSeparator - Url ``` -### APITreeNode - +### [APITreeNode](../../../src/dotnet/APIView/APIView/Model/TokenTreeModel.cs) - Ensure each node has an Id and Kind. The combination of `Id`, `Kind`, and `SubKind` should make the node unique across all nodes in the tree. For example a class and a method can potentially have the same Id, but the kind should differentiate them from each other. - Sort each node at each level of the tree by your desired property, this is to ensure that difference in node order does not result in diff. -### StructuredToken +### [StructuredToken](../../../src/dotnet/APIView/APIView/Model/StructuredTokenModel.cs) - Assign the final parsed value to a `List APIForest` property of the `CodeFile` @@ -72,6 +70,442 @@ Don't worry about indentation that will be handled by the tree structure. In the ## How to handle commons Scenarios +### Navigation +![Navigation](images/navigation.png) + +Ensure you set approppriate `Name` for the token node. And also set a value for the `Kind`. You can use one of the following values `assembly`, `class`, `delegate`, `enum`, `interface`, `method` , `namespace`, `package`, `struct`, `type` to get the default navigation icon. You can aslo use values more appriopriate for your language then reach out to APIView to provide support for those. Use tag `HideFromNav` to exclude a node from showing up in the page navigation. +
+click to expand json + + ```json + { + "Id": "Azure.Storage.Blobs.Models", + "Kind": "Namespace", + "Name": "Azure.Storage.Blobs.Models", + "Children": [ + { + "Id": "Azure.Storage.Blobs.Models.AccessTier", + "Kind": "Type", + "Name": "AccessTier", + "TopTokens": [], + "BottomTokens": [], + "Children": [ + { + "Id": "Azure.Storage.Blobs.Models.AccessTier.AccessTier(System.String)", + "Kind": "Member", + "Name": "Azure.Storage.Blobs.Models.AccessTier.AccessTier(string)", + "TopTokens": [], + "BottomTokens": [], + "Properties": { + "SubKind": "Method" + }, + "Tags": [ + "HideFromNav" + ] + } + ], + "Properties": { + "SubKind": "struct" + } + }, + { + "Id": "Azure.Storage.Blobs.Models.AccountInfo", + "Kind": "Type", + "Name": "AccountInfo" + } + ] + } + ``` +
+ +### Deprecated Node +![Navigation](images/deprecated-node.png) + +Add `Deprecated` tag to a node to mark all the token of the node as deprecated. +
+click to expand json + + ```json + { + "Name": "Azure.Template.Models.SecretBundle.ContentType", + "Id": "Azure.Template.Models.SecretBundle.ContentType", + "Kind": "Member", + "Tags": [ + "HideFromNav", + "Deprecated" + ], + "Properties": { + "SubKind": "Property" + }, + "TopTokens": [ + { + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ], + "Value": "// \u003Csummary\u003E The content type of the secret. \u003C/summary\u003E", + "Kind": 0 + }, + { + "Value": "", + "Kind": 1 + }, + { + "Value": "", + "Id": "Azure.Template.Models.SecretBundle.ContentType", + "Kind": 0 + }, + { + "RenderClasses": [ + "keyword" + ], + "Value": "public", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "RenderClasses": [ + "keyword" + ], + "Value": "string", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "RenderClasses": [ + "mname" + ], + "Value": "ContentType", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "RenderClasses": [ + "punc" + ], + "Value": "{", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "RenderClasses": [ + "keyword" + ], + "Value": "get", + "Kind": 0 + }, + { + "RenderClasses": [ + "punc" + ], + "Value": ";", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "RenderClasses": [ + "punc" + ], + "Value": "}", + "Kind": 0 + }, + { + "Value": "", + "Kind": 1 + } + ] +}, + ``` +
+ +### Deprecated Token +![Navigation](images/deprecated-token.png) + +Add `Deprecated` tag to a token to mark the token as deprecated. +
+click to expand json + + ```json + { + "Tags": [ + "Deprecated" + ], + "RenderClasses": [ + "text" + ], + "Value": "cancellationToken", + "Kind": 0 + } + ``` +
+ +### Hidden API +![Navigation](images/hidden-api.png) + +Add `Hidden` tag to a node to mark the node as hidden. Hidden nodes show up with low contrast in APIView. Hidden node visibility can also be toggled on or off in page settings. +
+click to expand json + + ```json + { + "Id": "Azure.Storage.Blobs.BlobServiceClient.UndeleteBlobContainer(System.String, System.String, System.String, System.Threading.CancellationToken)", + "Kind": "Member", + "Name": "Azure.Storage.Blobs.BlobServiceClient.UndeleteBlobContainer(string, string, string, System.Threading.CancellationToken)", + "Properties": { + "SubKind": "Method" + }, + "Tags": [ + "HideFromNav", + "Hidden" + ], + "TopTokens": [ + { + "Id": "Azure.Storage.Blobs.BlobServiceClient.UndeleteBlobContainer(System.String, System.String, System.String, System.Threading.CancellationToken)", + "Kind": 0, + "Value": "" + }, + { + "Kind": 0, + "Value": "public", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "virtual", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "Response", + "RenderClasses": [ + "tname" + ] + }, + { + "Kind": 0, + "Value": "\u003C", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 0, + "Value": "BlobContainerClient", + "Properties": { + "NavigateToId": "Azure.Storage.Blobs.BlobContainerClient" + }, + "RenderClasses": [ + "tname" + ] + }, + { + "Kind": 0, + "Value": "\u003E", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "UndeleteBlobContainer", + "RenderClasses": [ + "mname" + ] + }, + { + "Kind": 0, + "Value": "(", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 0, + "Value": "string", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "deletedContainerName", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 0, + "Value": ",", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 4, + "Value": "" + }, + { + "Kind": 0, + "Value": "string", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "deletedContainerVersion", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 0, + "Value": ",", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 4, + "Value": "" + }, + { + "Kind": 0, + "Value": "string", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "destinationContainerName", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 0, + "Value": ",", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 4, + "Value": "" + }, + { + "Kind": 0, + "Value": "CancellationToken", + "RenderClasses": [ + "tname" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "cancellationToken", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "=", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "default", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 0, + "Value": ")", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 0, + "Value": ";", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 1, + "Value": "" + } + ] +} + ``` +
+ + + + + - TEXT, KEYWORD, COMMENT : Use `text`, `keyword`, `comment` to property `RenderClasses` of `StructuredToken`. - NEW_LINE : Create a token with `Kind = LineBreak`. - WHITE_SPACE : Create token with `Kind = NoneBreakingSpace`. @@ -83,6 +517,7 @@ Don't worry about indentation that will be handled by the tree structure. In the - Common Tags: `Deprecated`, `Hidden`, `HideFromNav`, `SkipDiff` - Cross Language Id: Use `CrossLangId` as key with value in the node properties. + ## Get help Please reach out at [APIView Teams Channel](https://teams.microsoft.com/l/channel/19%3A3adeba4aa1164f1c889e148b1b3e3ddd%40thread.skype/APIView?groupId=3e17dcb0-4257-4a30-b843-77f47f1d4121&tenantId=72f988bf-86f1-41af-91ab-2d7cd011db47) if you need more information. diff --git a/tools/apiview/parsers/images/deprecated-node.png b/tools/apiview/parsers/images/deprecated-node.png new file mode 100644 index 0000000000000000000000000000000000000000..24dc34235cc6ab44d0378376b2fa61799d1f994e GIT binary patch literal 4938 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 literal 0 HcmV?d00001 diff --git a/tools/apiview/parsers/images/deprecated-token.png b/tools/apiview/parsers/images/deprecated-token.png new file mode 100644 index 0000000000000000000000000000000000000000..578b53d151b229b19684cc1a1f0a18b33cc5e5e8 GIT binary patch literal 10393 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 Zi6f_{fs*OB;^Tz3R1`GdHoP$p`#)uu2)Y0O literal 0 HcmV?d00001 diff --git a/tools/apiview/parsers/images/hidden-api.png b/tools/apiview/parsers/images/hidden-api.png new file mode 100644 index 0000000000000000000000000000000000000000..dd35e0b4bfa4eb31353fdd61647885e7bbce8cbe GIT binary patch literal 44301 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

-X|Zo=?!8hBK6>#28JEg9jsgvNchs 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 DMnH}3 literal 0 HcmV?d00001 From 22a72dbf3817a533a0427d315fe76c688c556513 Mon Sep 17 00:00:00 2001 From: Chidozie Ononiwu Date: Wed, 26 Jun 2024 11:17:27 -0700 Subject: [PATCH 7/7] Update APIView Parser Contributing.md --- tools/apiview/parsers/CONTRIBUTING.md | 667 +++++++++++++++++- .../images/apitree-node-and-tokens.png | Bin 0 -> 14563 bytes .../apiview/parsers/images/documentation.png | Bin 0 -> 11969 bytes .../parameter-separator-with-line-break.png | Bin 0 -> 10314 bytes .../parameter-separator-with-single-space.png | Bin 0 -> 7479 bytes tools/apiview/parsers/images/url-token.png | Bin 0 -> 7809 bytes 6 files changed, 655 insertions(+), 12 deletions(-) create mode 100644 tools/apiview/parsers/images/apitree-node-and-tokens.png create mode 100644 tools/apiview/parsers/images/documentation.png create mode 100644 tools/apiview/parsers/images/parameter-separator-with-line-break.png create mode 100644 tools/apiview/parsers/images/parameter-separator-with-single-space.png create mode 100644 tools/apiview/parsers/images/url-token.png diff --git a/tools/apiview/parsers/CONTRIBUTING.md b/tools/apiview/parsers/CONTRIBUTING.md index 24c98fcaf45..b5b3c31b52f 100644 --- a/tools/apiview/parsers/CONTRIBUTING.md +++ b/tools/apiview/parsers/CONTRIBUTING.md @@ -68,12 +68,410 @@ Previously APIview tokens were created as a flat list assigned to the `CodeFileT Serialize the generated code file to JSON and then compress the file using Gzip compression. Try to make the json as small as possible by ignoring null values and empty collections. Don't worry about indentation that will be handled by the tree structure. In the case you want to have indentation between the tokens then use `TabSpace` token kind. -## How to handle commons Scenarios +## Examples + +### APITreeNode and StructuredToken + +Example showing a full node with all its children and tokens. + +![Navigation](images/apitree-node-and-tokens.png) + + + +

+click to expand json + + ```json + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo", + "Kind": "Type", + "Name": "BlobContainerInfo", + "BottomTokens": [ + { + "Kind": 0, + "Value": "}", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "" + } + ], + "Children": [ + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo.ETag", + "Kind": "Member", + "Name": "Azure.Storage.Blobs.Models.BlobContainerInfo.ETag", + "Properties": { + "SubKind": "Property" + }, + "Tags": [ + "HideFromNav" + ], + "TopTokens": [ + { + "Kind": 0, + "Value": "// \u003Csummary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// The ETag contains a value that you can use to perform operations conditionally. If the request version is 2011-08-18 or newer, the ETag value will be in quotes.", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// \u003C/summary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo.ETag", + "Kind": 0, + "Value": "" + }, + { + "Kind": 0, + "Value": "public", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "ETag", + "RenderClasses": [ + "tname" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "ETag", + "RenderClasses": [ + "mname" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "{", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "get", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 0, + "Value": ";", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "}", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 1, + "Value": "" + } + ] + }, + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo.LastModified", + "Kind": "Member", + "Name": "Azure.Storage.Blobs.Models.BlobContainerInfo.LastModified", + "Properties": { + "SubKind": "Property" + }, + "Tags": [ + "HideFromNav" + ], + "TopTokens": [ + { + "Kind": 0, + "Value": "// \u003Csummary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// Returns the date and time the container was last modified. Any operation that modifies the blob, including an update of the blob\u0027s metadata or properties, changes the last-modified time of the blob.", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// \u003C/summary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo.LastModified", + "Kind": 0, + "Value": "" + }, + { + "Kind": 0, + "Value": "public", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "DateTimeOffset", + "RenderClasses": [ + "tname" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "LastModified", + "RenderClasses": [ + "mname" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "{", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "get", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 0, + "Value": ";", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "}", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 1, + "Value": "" + } + ] + } + ], + "Properties": { + "SubKind": "class" + }, + "TopTokens": [ + { + "Kind": 0, + "Value": "// \u003Csummary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// BlobContainerInfo", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// \u003C/summary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "public", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "class", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo", + "Kind": 0, + "Value": "" + }, + { + "Id": "Azure.Storage.Blobs.Models.BlobContainerInfo", + "Kind": 0, + "Value": "BlobContainerInfo", + "RenderClasses": [ + "tname" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "{", + "RenderClasses": [ + "punc" + ] + } + ] +} + ``` +
### Navigation ![Navigation](images/navigation.png) -Ensure you set approppriate `Name` for the token node. And also set a value for the `Kind`. You can use one of the following values `assembly`, `class`, `delegate`, `enum`, `interface`, `method` , `namespace`, `package`, `struct`, `type` to get the default navigation icon. You can aslo use values more appriopriate for your language then reach out to APIView to provide support for those. Use tag `HideFromNav` to exclude a node from showing up in the page navigation. +Ensure you set appropriate `Name` for the token node. And also set a value for the `Kind`. You can use one of the following values `assembly`, `class`, `delegate`, `enum`, `interface`, `method` , `namespace`, `package`, `struct`, `type` to get the default navigation icon. You can also use values more appropriate for your language then reach out to APIView to provide support for those. Use tag `HideFromNav` to exclude a node from showing up in the page navigation.
click to expand json @@ -118,6 +516,82 @@ Ensure you set approppriate `Name` for the token node. And also set a value for ```
+### Documentation +![Navigation](images/documentation.png) + +You can group a sequence of similar type tokens using GroupId. Set `GroupId` property to `doc` to mark a sequence of comment tokens as documentation. +
+click to expand json + + ```json + { + "Id": "Azure.Storage.Blobs.BlobClient.BlobClient()", + "Kind": "Member", + "Name": "Azure.Storage.Blobs.BlobClient.BlobClient()", + "Properties": { + "SubKind": "Method" + }, + "Tags": [ + "HideFromNav" + ], + "TopTokens": [ + { + "Kind": 0, + "Value": "// \u003Csummary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// Initializes a new instance of the \u003Csee cref=\u0022T:Azure.Storage.Blobs.BlobClient\u0022 /\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// class for mocking.", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + }, + { + "Kind": 1, + "Value": "" + }, + { + "Kind": 0, + "Value": "// \u003C/summary\u003E", + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ] + } + ] + } + ``` +
+ ### Deprecated Node ![Navigation](images/deprecated-node.png) @@ -504,19 +978,188 @@ Add `Hidden` tag to a node to mark the node as hidden. Hidden nodes show up with +### Parameter Separator +Use token with kind Kind `ParameterSeparator` between method parameters to allow APIView to either render the methods parameters on the same line or on separate lines based on language or user preference. +![Navigation](images/parameter-separator-with-single-space.png) -- TEXT, KEYWORD, COMMENT : Use `text`, `keyword`, `comment` to property `RenderClasses` of `StructuredToken`. -- NEW_LINE : Create a token with `Kind = LineBreak`. -- WHITE_SPACE : Create token with `Kind = NoneBreakingSpace`. -- PUNCTUATION : Create a token with `Kind = Content` and the `Value = `. -- DOCUMENTATION : Add `GroupId = doc` in the properties of the token. This identifies a range of consecutive tokens as belonging to a group. -- SKIP_DIFF : Add `SkipDiff` to the Tag to indicate that the node or token should not be included in diff computation. -- LINE_ID_MARKER : You can add a empty token. `Kind = Content` and `Value = ""` then give it an `Id` to make it commentable. -- EXTERNAL_LINK : Create a single token set `Kind = Url`, `Value = link` then add the link text as a properties `LinkText`; -- Common Tags: `Deprecated`, `Hidden`, `HideFromNav`, `SkipDiff` -- Cross Language Id: Use `CrossLangId` as key with value in the node properties. +![Navigation](images/parameter-separator-with-line-break.png) + + +
+click to expand json + ```json + [ + { + "Kind": 0, + "Value": "string", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "connectionString", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 0, + "Value": ",", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 4, + "Value": "" + }, + { + "Kind": 0, + "Value": "string", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "blobContainerName", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 0, + "Value": ",", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 4, + "Value": "" + }, + { + "Kind": 0, + "Value": "string", + "RenderClasses": [ + "keyword" + ] + }, + { + "Kind": 2, + "Value": "" + }, + { + "Kind": 0, + "Value": "blobName", + "RenderClasses": [ + "text" + ] + }, + { + "Kind": 0, + "Value": ")", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 0, + "Value": ";", + "RenderClasses": [ + "punc" + ] + }, + { + "Kind": 1, + "Value": "" + } + ] + ``` +
+ +### Navigation Token + +Make a token a navigation token by adding key `NavigateToId` to the token property with a value corresponding the the Id of the APINode to navigate to on click. +
+click to expand json + + ```json + { + "Kind": 0, + "Value": "BlobClientOptions", + "Properties": { + "NavigateToId": "Azure.Storage.Blobs.BlobClientOptions" + }, + "RenderClasses": [ + "tname" + ] + } + ``` +
+ +### URL Token +![Navigation](images/url-token.png) + +Make a token a Url by adding key `NavigateToUrl` to the token property and setting the value to the target Url. +
+click to expand json + + ```json + [ + { + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ], + "Value": "// This", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "Properties": { + "GroupId": "doc", + "NavigateToUrl": "https://github.com/Azure/azure-sdk-for-net/blob/main/sdk/core/Azure.Core/samples/ProtocolMethods.md" + }, + "RenderClasses": [ + "comment" + ], + "Value": "protocol method", + "Kind": 0 + }, + { + "Value": "", + "Kind": 2 + }, + { + "Properties": { + "GroupId": "doc" + }, + "RenderClasses": [ + "comment" + ], + "Value": "allows explicit creation of the request and processing of the response for advanced scenarios.", + "Kind": 0 + } + ] + ``` +
## Get help diff --git a/tools/apiview/parsers/images/apitree-node-and-tokens.png b/tools/apiview/parsers/images/apitree-node-and-tokens.png new file mode 100644 index 0000000000000000000000000000000000000000..3bff57c865c8e6e2febb8f37a1e573c55a5eeee2 GIT binary patch literal 14563 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ⅈ2UAbzVTN_B7{9vs@QnOB#TK~LG|M?lje5DgMH*Mr? z+x+&vJn#OJnxuj^%ZIxtw*P9({Uv{!Hs;tBKTvt(u?AQgz7v=Cmb-!JbTV1NV{zs8 zPcR5cXP3Zx{F9cbtJOBfae0*=(6Lbk>QkYWbrz95u&DXp*EvcP!ZXXD(jW`SR>I?} zv0(SG>Ys+KpbiB6ukr$BeI-WKZp#1Yvi95)r6f>-&Ta`}{BG`1cqw|j#ERHMs z;o$|xuaYcw!zEiaVt8%WomPMtLwzULnIv14#-H9v103e=$|27ZlPx#)UtmODIlY zC&bZHa*Z!nl_~K(q@u0&(PwIykrJP}ix(tu*19kNNsY(D^3XwkSbf|U8*Fysxy8?JZ zfJ0^}CMBd9UsPx=eNVkpE_$T-P2fa~haDwq(}3ct_ep-8o*_$!C3&Z=s@bx=9c~5n z4TA48T8U){Y2h|$-@6MD9bj((<4LPs_p5}CR(SFf><^QbP)H}A2pXyY^JQP8p2oEg zTW4Z2$;st>i`J0Z&9dUK*Xc%Q9Qdeu5N##vVKtlkcp;87+VoRl8=?NP!}eaO5RzaY znh7>nPU6f0xE?H<73PPCo!m6t>$@KL=rmA{>UpMo1~_>E+>|P{M8PrSG#PK|pBdj1&&a!w zDHU_v^XYsGp7#OHQ%SMv&@yFECFIi*{E*ggn41Jw2G%8sUQ51fFDp|mp6eeNKT38~S8j<-w&9Ek>iNiI z&VNqg(`DaLzK>w`g5zpr>R+{OIk`=VmT2n$3buIE>h*&*g6X?*u}gZLYS?6eL-EgjWQW-zi+3Q_fhWS%N_o|Dfk@WazzLj|eD zB&)Hj9)PXS()at*^E=ErD?1FmO+^r!<1}2oulq~{6zqdpEBW-D8q0jJE^EY zp?B%H?L;c1x08^Y3$^fp?{V|b_~WxRRNT~r-y9_FtcVtipy?u9m}mpXE*Dy-cWh{6 z8(c+aAO<`UqHlJU(2M2=;xEN?vI2UBoW`A!_O>|40&5@Tot>r^U2AvSMSsXdx8w7V ziXIiPL{KG;e!QiUqAXTZ)07pboBP(&(bMG2$1rfFxcPvF{Q-@$O*MHMDLY4zlTwnb J{A3jPKL8iTx&QzG literal 0 HcmV?d00001 diff --git a/tools/apiview/parsers/images/parameter-separator-with-line-break.png b/tools/apiview/parsers/images/parameter-separator-with-line-break.png new file mode 100644 index 0000000000000000000000000000000000000000..e6b1372278b3b0c1a252ea593eb5bc7e79345410 GIT binary patch literal 10314 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 literal 0 HcmV?d00001 diff --git a/tools/apiview/parsers/images/parameter-separator-with-single-space.png b/tools/apiview/parsers/images/parameter-separator-with-single-space.png new file mode 100644 index 0000000000000000000000000000000000000000..71b5c25a895a28f3252e9c56691c95062b86b6ca GIT binary patch literal 7479 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 literal 0 HcmV?d00001 diff --git a/tools/apiview/parsers/images/url-token.png b/tools/apiview/parsers/images/url-token.png new file mode 100644 index 0000000000000000000000000000000000000000..c5545b95bd1c049a811a8a773a6071c52ea38c05 GIT binary patch literal 7809 zcmcJ!XE}oI4J2V0RZ014Rdb*fTHW43$M@p zn;iha@<3Bn$>g2Y9^4;h(G%Dg?7fG1QjqG>tqZ$P;cZLX-$_+H-P`*@l}eq7S6waI z)%1gwg-K6Jg_j}TP2NXi$ra%uyTP<-H8n0WZnn%2>bmeo9bqa>GeKJc(AdXTurKzw ze(S^qIWMu_DrN55o-SJ`J9XdmmF1y7z?qx)4E(em95q5b5+^5?p zyod?f(h=+F@sF|Nm?$JX2cW}L*y{!I6O>A{3$+X9KP@;q#^`->$l7X+Ze>Wzh6B%` zmwuMgvhO>l;&eG;{|oo2DYMeS><`{WKKBl2I-m zRzt4`m=D#Z{#nHBN;H}s!)e}_xuK#n$Qjq&wqhBFBW9)E8?*(mXHj3+Z|FALT~u_9 znu;*~WE1mKex8Ipm2@Z_V6|oe^qu;kZ$P2>dB0|60rZZ8tnlOmWZM&4iIzfz^gx>q zw${@XrGf11>6zXt88flj-VUa4k9KnaeMjal{jbwtXksx9H<^dk52tEYIeNb~G0Jty zD6;>#H{wU;v>gS4OE}5s3D+jtY6HNQF0(5BeduxZsdlG}T4L3OQ8t}Xu z+S(`8q6)}{pq3>4!_7K*(b|ClLOWO6*W;@6hypspBn65{ZqWy%|tvz+v=zla@a zEBusY1MNX768jRF$0Zp$&~rCT}S5vhD{=QVf6g|lK&Kr<^tN$#>!leZGa zbUMQxs~sd^TmmPOR&4x|&>9|H)Aa~*UYLs68HYTEXr=Z}l@x$ zkiV6vB-hm$^gz{%y49Q`i;i&pNv*GmR!j{CK$L&@d0Y^qdQ%sn?_aR3RycFzPf6h2 zeIcZiFr!Vt21&ZEU?_7Na}@~<1xG)i!TzId2a59l+a?h(y-7Tt?gdbrH2Wpt8ojXX z*y~+7-c5dNsKURIhWQHgv3XOF6*#ww^$iodOluI``EQ7AJA&37|DOl2w1!#&wqwg%F_67s{bsB(Zmqt|Ki79!0~@;^qnJK_MHFXl+}Zx zOzI8VNJ>&Tg6e|bj?j@g?)CU4a9jWV;mz~mX&<52Z|`gy z;*R;I7ZLZjZJidsbq8mrX!wGm3OTv1NqRQtZN_x^f9tCDxhrA;~LhMEPT+Q3aQka;@Mo^TzV?b}*F)ouhk# z|JnodcDs5EvroKOm)B5y*~XuoWdUbFiwgPI8y%&(f;lLL@PI-6XK=Rmrmb>qA$_L$E=HcLm8fy*}ys(shF z%iG0K?c8z+m-z+qXS#uMzP}03tv6m%9w}>GI#HK33F!UAg@WVjt*Lt#T|y_AsNuoj zu`O3&Q9Q(bK&QTCovBug*Q-5eMIniX%OF0cph)?;$w+g;N(#~V3u=?!>dH40{qcx=V zYU;(wY3_Uf9+1L*)VP?=Yv6Z@wTb4dY6f`7^ONQ2Ne#iTb~TF2YqZ&#q+muaD_!yO zt`4}+@VYR05(yoJVz(`ppugFlF`Ob%v$=7G(EY?Sp*fGVM%Qo7WxjX5@z%EJ#9ncil48OHU4k}JpOEw9;3=Gh#^AY z>zXytflxU2phRQZtxU+Q4&R z{4A`1IQ$K$>glfOy;$=S{J`kWWPAEON#X4hs{;8Eu06~qCmc9BjN&DMxQ9p`tT=X?}!={76$T64V*vX6WIyv^r_O0dh z%ft$=jWqT3)iNPi_Do&BO1lx=i^^2z1ZiH%g?eLLs(Wf6kCnho_U9Cda&JW9SsTwC zX{tu!qJoPUJY;Piic%TGk@UXq8jb4dDLs_p=C^D|XHdvglI<1niLsf|qQXfFE*!4x z^LGq;H_KJJqiqk{nj&!pDy!^``@??zzo)FB>?vC=cMeL~*bB;SG9U(cyWg z5sI_@ww!X02CRd0x4vIENNDpdaZI!-MkTi$XO>=aLz*B94lb|>P~YdH@(KFjD^0FG zO08u-g-44C!RDz0uCKtk9+;@qVFgRgX2Eb8A!CvGy!MNYA^v3opC@%UC_YOwK$P?adlZ<5ZATyPX!zR_^aV za&sA%N>2jfmu#I}IcZ!iqG-|Dy{7)Eg*P&v2G6ABDpm!ywD%ETuSFb+A+Nl&2-Q+q zCW#-!51^m(9?TaUyUOT%&G+2n{pHN}O>P`)m0Fm#9mrjk7|P9)BDt-wi6x{woL@dV`hSfVz}+v@AD~+KT8=0#Izgn% zf!9HHA8lW|1&OE>1MLe>G^Rc@$(n5SVA56ug=7|$oUN}q1<4=#TY7)Pq!^tIl#R+w z^Ev4IbmHEUk~KtnsYyNfyjkGxEcHI6;yZN#<6&j0n*uWhz9SMYUU(EG&UrQ@Kaia| z?U~XYUH3@Qra82h&m@Q+)>8`}bt z^+H-z+1$6^-PSG~FhhB3yt2uj#qQJ!XFD^m^miv?FwNTf(YHX72~mX%0+haSU`$9; zO=^Vq$kfmuaHAc{4YetTSiw2i?}r`P1=^qWJa6<4Jlo^RShOiogK$=+K#Jx%a(|z= z^g$G#P5*IvV{=DEB>PM_WlXpXBB6CU%6RfR05*FS;>1Mp(jSiN&D@>w2ahnFZb;EBEAX6n~!Nr@v zyy*lgk0Z2*U%l7noFGXih;ggd{kGu_u^=sDdp@s9d?Yw#gnAA4u&X;4jjk{$rrbdc-JXcA}b zSnc1qo`BRc^!cGPC#{KnHZ{h7me1rADsgXnEadOLNz_k18U)jb>?aXrqC4)30J}Su zBD1#{gRQm^VxdhNCv8cMzOo{JE3jBS#P7181me(7pC~z%&4}HRcw{CL6=={h7xw&H zDppvUiEuKKt##~l)j(d@Wt;cUB#lBY%{x)cMD!=Q4Ou>BkzCPO$d50%=Va0g7k_f~ zGM~`ELV+siF#k3yOm*ujVeMd7Rpql8*Ni#EmwOP~KepLo+dttxn4*lsKa(K2ZO`-P zhEo{{@!gLPakiE7MM2K4$5dX18y`HnBKPRhETiT@nRR`b*OArZSdfsY#mv@w43O=n zc5YqRcb@>O-UwnPE?^cfA2Lc68f0)hLQtwy)bSH{FJJwIKw~9!p;deO1H~_(aZq*+ z4jSX2=eb}4pv~zsrfOIf9v_w)7So6A z489ou@~ZSBsL_7S-^*tH*z1#%TN=`J{>jjEW*i^#^VZb&mJ=EO;l}V``P^Ch0?wS% zC65WnZ09?i3;`J#;azuj@n)uWtW9nxVQ6SIgMm6XuIytTCKq7lYeP6jU#{La>_7k_AvU@g!QX{+xQZH2J2iddW+ybD4_Mv*X*Oz_}@CT8KB~WulLY#h6msh zwcHiq)KaXZ2f%T~XJ>9|Om4W!A{B14|vBj=DsTgA1+ zVRghDRRH8q9_p+kCT$cW80d>Qwg?FIHl^(*ZuZog1YmwGPZ@8@>nn3ZbAE`=!&@%l z2FxARvwCGOCP%9oxymUnLPy@Xw)R3yA-=DveV^nKZHGLPr4e6M>KhGtmOJUV8U?H| z{eh5ozSIA8;-J!yrQ8`vpj;*MMB|Uo`w3RMvW#9B0NpL zR`#*c11qYBP3I#-3B!3p&L2P{K#KRdT|~#keV#612@M&3i;d&{)((I$Z48%~E`zeY zyFjamXJ6bTQNsYn6UsR=nWg7l(}5H|?oSwrYc9YClcMcys{9xZ4R9@nhb+iP%fJ$) z)#(V9BdbbGriiHSzvm7x;!IVpQrdK@3bbZao*A`6<^^X2^AQHgo;cT;pWJXv-J8egxln!9OTvQ zFUa1{Tjq!p3qTPV1AXT+8M zZKh~fZ!pvDQE7G$*!u92Q0(gicI#-(OTcrza#ZcnPIHSE~&(9F# z$l>v~{w3F)i#bMzVc)S#qwK?^%M0Df$)*7e{>S3teY4397vZ;eFA!P#+#_V~g*R9k zP2Mbr=i=3xe@T?v4d7}%{N3H!jc;SDQB`=?EHZ?F02~tv^(O*b+bCwA%q~m}4=cV0 zfH;^f+s{Du`yYI%19$CH+ek9bKfQ*FipG3<*5OG7Hvgz=&?dZFAX#nlQCn|%W|H-S z(lj(pqL1v;IOVqJlGhJ>ve^S)a-8jB577_EY%vofX3`dhu4Cz|UV+!ygP0-~=EyE8 za><~kiU;7Mfy$e`apMQyH^;E+vusR@?B7f3vcKh%+l5x(<^(kvY@{fP&^G6+a09yFSW4?lr6h8K zfXX_b(s)p25y!1g|8Gm?8)lr1Yrt1cUn+l2cb&lq#E4%@N8%|AI9^FdddH%uzqGz~ zxT<`ec`Wy6PqUg6H4cpD{~> z*mhoX5w~0SRd@MVi#Bwf;vW(~sAUHlS=!lnZB)nCj{T^>pjL5UUKnA+y(?v>s}6JRBGOJuL<;egmy{$zKEDKHyOX z(c-&5&71<+bk-B;b1(LbK6IzaZlsH&o`|A|my2T*1vu@UyL2>-3X1@-P?yHp4N!Fz z@bt)cRw-BS)^B*2mb~(@?diKi{O;hbw|)(lrG|YWqm?p?HD&5x(}0}`f@ZfnN+)%U z?bV^0tXZaUE%r4&C<+DM#UN=WRTx=VXPtdmjX;aS%PAI=Z=8b{kN(u951Gv|S;LxG zfQ_*>Sji-{P(3iRVGlAV?LKtJ^UW(kLU}pLC&a%v6y^aSNX-jN$J;r{Tr!*#>cihy zA58$8Wy4%VHKGSLLt(PLHrPI79*i2$~Z0 zdM0)ex-l6yAmK8t9Up`P6(g(`M1Jrhu_Z4{$X9q zUV|r#c#@1%#V#WE(4&~K;beXtRVGAT&=nN$ph(sUmGH7D6zCID7-{!V61*^EJ}h_~5Fo)G zF>L%F>n!N8nU1|GWBh9)R|gFcy&aNhV9KZ1-1Q>+AG0TBFQ6SG*Z!A*{yzYp5M@}? z06aZ$po~J3&)jR&B)JKqBf|Ox694Lx^{fWv#E0XN2e73BWc!m39NwcD(|A&`i+`re z2ZjCxw|~dEdAgrB9vxWLOkQJdk-gMQwaAUn%+s|{ap|oA7b4)*tww7h$y?WQ2fC5@QW5hKp-Kt6= z9(`|OS`$4+f5fjF2a3F#zo*&q1v2`KGK;ljXvuMCnEYQa$1r=Yk6WYKZ$iAyMyR#Z z27e|}Z+&BVF<6)*rA)EVtdi|BYC^vxVEqc3-r_MsemLtadBCq(WhO+IBf{qX56VDi zf_YR_RLBQyvBEw-3%Zx$q0;OhfocQgUsBeOTJJF@NI7%?w^XkGmLJRiP?V8&arzK< zL^Bhwv3aREke!@l6l^H-81_6jE&lyE?3Vm$;xIqDZ_>X7SjorX`FY+ouQLYMgD--GfCCckXUUsIo>fi`g&MQ`sW&wSf5`GP3cOGV1~cPc8h`kDmB zXzU1?E@(9SP_7G$$yOdES%1!;_A||vJG8Uz^Y(4>AlHl3kbhjlZ_;FXj@kXY4GDCD zL<;=>agn~!@;`$eC$w=Y1pbm*YEWxya8k7U2@~+vOGIgoc}4%DSjB$}b%wiNjZB0a z1uLDmBl^9I!e7n?2;amQq5}$FBZ=v6^_lLaES6^+t;S_M1^d@s$0yi6LQbE7be8DM z-9{Rbf5@-3AM$Ys@XgNGgTbS}Hn>ncJ$y;(09KY?p!+M&41fV4kT-;PvVCMs6idnq zx_S}B625Hgw+hO(a{w+PIW1d#aAvQ8Jtq{J!dxQBK{Hizm_(vc#V~V&H&L?j)oxEu zB7_X)R#0ULh5joPZ1T85l*?f!sdDTrpI}R$!xfZ#8M6H^Ao8l$tO5c8#b)sS=v&zspsA*(TB&Rk G_J07co=1}a literal 0 HcmV?d00001