diff --git a/docs/DocEvent.md b/docs/DocEvent.md new file mode 100644 index 0000000..bf9218c --- /dev/null +++ b/docs/DocEvent.md @@ -0,0 +1,17 @@ +# [Summary.DocEvent](../src/Core/DocEvent.cs#L9) +```cs +public record DocEvent : DocMember +``` + +A [`DocMember`](./DocMember.md) that represents a documented event in the parsed source code. + +_Similar to [`DocProperty`](./DocProperty.md) but with its own set of accessors._ + +## Properties +### [Type](../src/Core/DocEvent.cs#L14) +```cs +public required DocType Type { get; init; } +``` + +The type of the event. + diff --git a/docs/DocProperty.md b/docs/DocProperty.md index 906a50d..549843c 100644 --- a/docs/DocProperty.md +++ b/docs/DocProperty.md @@ -1,4 +1,4 @@ -# [Summary.DocProperty](../src/Core/DocProperty.cs#L6) +# [Summary.DocProperty](../src/Core/DocProperty.cs#L8) ```cs public record DocProperty : DocMember ``` @@ -6,24 +6,38 @@ public record DocProperty : DocMember A [`DocMember`](./DocMember.md) that represents a documented property in the parsed source code. ## Properties -### [Type](../src/Core/DocProperty.cs#L11) +### [Type](../src/Core/DocProperty.cs#L13) ```cs public required DocType Type { get; init; } ``` The type of the property. -### [Generated](../src/Core/DocProperty.cs#L16) +### [Accessors](../src/Core/DocProperty.cs#L18) +```cs +public required DocPropertyAccessor[] Accessors { get; init; } +``` + +The accessors of the property (e.g., `get`, `set`, `init`). + +### [Generated](../src/Core/DocProperty.cs#L23) ```cs public required bool Generated { get; init; } ``` Whether this property was generated by compiler (e.g., it's a property of a record). -### [Event](../src/Core/DocProperty.cs#L21) +### [Event](../src/Core/DocProperty.cs#L29) ```cs public required bool Event { get; init; } ``` Whether this property represents an event. +### [AccessorsDeclaration](../src/Core/DocProperty.cs#L34) +```cs +public string AccessorsDeclaration { get; } +``` + +The declaration of property accessors as they declared in the C# source code. + diff --git a/docs/DocPropertyAccessor.md b/docs/DocPropertyAccessor.md new file mode 100644 index 0000000..08fb626 --- /dev/null +++ b/docs/DocPropertyAccessor.md @@ -0,0 +1,38 @@ +# [Summary.DocPropertyAccessor](../src/Core/DocPropertyAccessor.cs#L6) +```cs +public record DocPropertyAccessor +``` + +One of the [`DocProperty`](./DocProperty.md) accessors (e.g., `get`, `set`, `init`). + +## Fields +### [Access](../src/Core/DocPropertyAccessor.cs#L29) +```cs +public AccessModifier? Access +``` + +The access modifier of the accessor. +If the value is `null`, then the access modifier is inherited from the property declaration. + +### [Kind](../src/Core/DocPropertyAccessor.cs#L34) +```cs +public AccessorKind Kind +``` + +The kind of the accessor. + +## Methods +### [Defaults()](../src/Core/DocPropertyAccessor.cs#L11) +```cs +public static DocPropertyAccessor[] Defaults() +``` + +The sequence that consists only of a default `public get` property accessor. + +### [Default()](../src/Core/DocPropertyAccessor.cs#L19) +```cs +public static DocPropertyAccessor Default() +``` + +The default `public get` property accessor. + diff --git a/docs/FilterMemberPipe.md b/docs/FilterMemberPipe.md index d849770..3bc27df 100644 --- a/docs/FilterMemberPipe.md +++ b/docs/FilterMemberPipe.md @@ -1,12 +1,13 @@ -# [Summary.Pipes.Filters.FilterMemberPipe](../src/Core/Pipes/Filters/FilterMemberPipe.cs#L6) +# [Summary.Pipes.Filters.FilterMemberPipe](../src/Core/Pipes/Filters/FilterMemberPipe.cs#L7) ```cs public class FilterMemberPipe : IPipe ``` -A simple pipe that filters all members inside the document using the specified predicate. +A simple pipe that filters all members inside the document using the specified predicate +and then applies the given map function on them. ## Methods -### [Run(Doc)](../src/Core/Pipes/Filters/FilterMemberPipe.cs#L9) +### [Run(Doc)](../src/Core/Pipes/Filters/FilterMemberPipe.cs#L12) ```cs public Task Run(Doc input) ``` diff --git a/docs/FilteringExtensions.md b/docs/FilteringExtensions.md new file mode 100644 index 0000000..44f7aff --- /dev/null +++ b/docs/FilteringExtensions.md @@ -0,0 +1,61 @@ +# [Summary.Pipelines.FilteringExtensions](../src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs#L9) +```cs +public static class FilteringExtensions +``` + +A set of extensions for [`SummaryPipeline`](./SummaryPipeline.md) that add support for filtering functionality. + +## Methods +### [UseDefaultFilters(SummaryPipeline)](../src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs#L14) +```cs +public static SummaryPipeline UseDefaultFilters(this SummaryPipeline self) +``` + +Enables default filters for the given pipeline (i.e. a filter that removes all non-public members). + +### [IncludeAtLeast(SummaryPipeline, AccessModifier)](../src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs#L29) +```cs +public static SummaryPipeline IncludeAtLeast(this SummaryPipeline self, AccessModifier access) +``` + +Includes only members that have at least the given access modifier. + +#### Example +In order to include both `internal` and `public` members in the generated docs, +you can call this method as follows: +```cs +var pipeline = ...; + +pipeline.IncludeAtLeast(AccessModifier.Internal); +``` + +### [IncludeOnly(SummaryPipeline, AccessModifier)](../src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs#L44) +```cs +public static SummaryPipeline IncludeOnly(this SummaryPipeline self, AccessModifier access) +``` + +Includes only members that have at least the given access modifier. + +#### Example +In order to include onl `internal` members in the generated docs, +you can call this method as follows: +```cs +var pipeline = ...; + +pipeline.IncludeOnly(AccessModifier.Internal); +``` + +### [WithAccess(SummaryPipeline, Func)](../src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs#L50) +```cs +public static SummaryPipeline WithAccess(this SummaryPipeline self, Func p) +``` + +Includes only members that have the access modifier that satisfies the given predicate. + +### [UseFilter(SummaryPipeline, IPipe)](../src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs#L67) +```cs +public static SummaryPipeline UseFilter(this SummaryPipeline self, IPipe filter) +``` + +Adds the given filter into the pipeline. + diff --git a/docs/GlobalDelegate.md b/docs/GlobalDelegate.md index 6db43ba..fab48f5 100644 --- a/docs/GlobalDelegate.md +++ b/docs/GlobalDelegate.md @@ -1,4 +1,4 @@ -# [GlobalDelegate(int, int)](../src/Core/Samples/Sample.cs#L6) +# [GlobalDelegate(int, int)](../src/Core/Samples/Sample.cs#L15) ```cs public void GlobalDelegate(int x, int y) ``` diff --git a/docs/Sample{T0,T1}.Child.md b/docs/Sample{T0,T1}.Child.md index 57bf81e..7d55859 100644 --- a/docs/Sample{T0,T1}.Child.md +++ b/docs/Sample{T0,T1}.Child.md @@ -1,4 +1,4 @@ -# [~~Summary.Samples.Sample.Child~~](../src/Core/Samples/Sample.cs#L33) +# [~~Summary.Samples.Sample.Child~~](../src/Core/Samples/Sample.cs#L42) > [!WARNING] > The type is deprecated. @@ -10,7 +10,7 @@ public class Child A child of the [`Sample{T0,T1}`](./Sample{T0,T1}.md) class. ## Fields -### [Field](../src/Core/Samples/Sample.cs#L38) +### [Field](../src/Core/Samples/Sample.cs#L47) ```cs public int Field ``` diff --git a/docs/Sample{T0,T1}.md b/docs/Sample{T0,T1}.md index 3e0fd28..54570d2 100644 --- a/docs/Sample{T0,T1}.md +++ b/docs/Sample{T0,T1}.md @@ -1,4 +1,4 @@ -# [Summary.Samples.Sample](../src/Core/Samples/Sample.cs#L26) +# [Summary.Samples.Sample](../src/Core/Samples/Sample.cs#L35) ```cs public class Sample ``` @@ -22,7 +22,7 @@ _Btw, this type has a child: [`Sample{T0,T1}.Child`](./Sample{T0,T1}.Child.md)._ - `T1`: A second type parameter. ## Delegates -### [Delegate1(int, int)](../src/Core/Samples/Sample.cs#L44) +### [Delegate1(int, int)](../src/Core/Samples/Sample.cs#L53) ```cs public void Delegate1(int x, int y) ``` @@ -30,29 +30,29 @@ public void Delegate1(int x, int y) A sample delegate. ## Events -### [Event1](../src/Core/Samples/Sample.cs#L92) +### [Event1](../src/Core/Samples/Sample.cs#L101) ```cs public event Action Event1 ``` A sample field event. -### [Event2](../src/Core/Samples/Sample.cs#L97) +### [Event2](../src/Core/Samples/Sample.cs#L106) ```cs -public Action Event2 { add; remove; } +public Action Event2 ``` A sample property event. ## Fields -### [Field](../src/Core/Samples/Sample.cs#L38) +### [Field](../src/Core/Samples/Sample.cs#L47) ```cs public int Field ``` A field of the child class. -### [~~Field1~~](../src/Core/Samples/Sample.cs#L50) +### [~~Field1~~](../src/Core/Samples/Sample.cs#L59) > [!WARNING] > The field is deprecated. @@ -63,14 +63,14 @@ public int Field1 A sample field. -### [Field2](../src/Core/Samples/Sample.cs#L55) +### [Field2](../src/Core/Samples/Sample.cs#L64) ```cs public int Field2 ``` A pair of fields. -### [Field3](../src/Core/Samples/Sample.cs#L55) +### [Field3](../src/Core/Samples/Sample.cs#L64) ```cs public int Field3 ``` @@ -78,23 +78,23 @@ public int Field3 A pair of fields. ## Properties -### [Property1](../src/Core/Samples/Sample.cs#L60) +### [Property1](../src/Core/Samples/Sample.cs#L69) ```cs public int Property1 { get; set; } ``` A sample property. -### [Property2](../src/Core/Samples/Sample.cs#L65) +### [Property2](../src/Core/Samples/Sample.cs#L74) ```cs -public int Property2 { private get; set; } +public int Property2 { set; } ``` A sample property with custom visibility. -### [Property3](../src/Core/Samples/Sample.cs#L71) +### [Property3](../src/Core/Samples/Sample.cs#L80) ```cs -public int Property3 { get; protected set; } +public int Property3 { get; } ``` A sample property with custom visibility and an exception. @@ -102,7 +102,7 @@ A sample property with custom visibility and an exception. #### Exceptions - `ArithmeticException`: Invalid number. -### [Property4](../src/Core/Samples/Sample.cs#L76) +### [Property4](../src/Core/Samples/Sample.cs#L85) ```cs public int Property4 { get; set; } ``` @@ -110,7 +110,7 @@ public int Property4 { get; set; } A sample property with custom accessors. ## Indexers -### [this[int]](../src/Core/Samples/Sample.cs#L87) +### [this[int]](../src/Core/Samples/Sample.cs#L96) ```cs public int this[int i] { get; } ``` @@ -124,7 +124,7 @@ A sample indexer. What indexer returns. ## Methods -### [Method(int, string)](../src/Core/Samples/Sample.cs#L121) +### [Method(int, string)](../src/Core/Samples/Sample.cs#L130) ```cs public TimeSpan Method(int x, string y) ``` @@ -154,7 +154,7 @@ The `TimeSpan` instance. - `ArgumentException`: The argument is incorrect. - `ApplicationException`: Something went wrong. -### [Method(short, string)](../src/Core/Samples/Sample.cs#L128) +### [Method(short, string)](../src/Core/Samples/Sample.cs#L137) ```cs public TimeSpan Method(short x, string y) ``` diff --git a/docs/SummaryPipelineExtensions.md b/docs/SummaryPipelineExtensions.md index c09ae03..61305e5 100644 --- a/docs/SummaryPipelineExtensions.md +++ b/docs/SummaryPipelineExtensions.md @@ -16,49 +16,3 @@ Specifies the logger factory to use for pipes inside the pipeline. _This method should be called _before_ anything else so that_ _given logger factory is passed into all subsequent calls._ -### [UseDefaultFilters(SummaryPipeline)](../src/Core/Pipelines/SummaryPipelineExtensions.cs#L25) -```cs -public static SummaryPipeline UseDefaultFilters(this SummaryPipeline self) -``` - -Enables default filters for the given pipeline (i.e. a filter that removes all non-public members). - -### [IncludeAtLeast(SummaryPipeline, AccessModifier)](../src/Core/Pipelines/SummaryPipelineExtensions.cs#L40) -```cs -public static SummaryPipeline IncludeAtLeast(this SummaryPipeline self, AccessModifier access) -``` - -Includes only members that have at least the given access modifier. - -#### Example -In order to include both `internal` and `public` members in the generated docs, -you can call this method as follows: -```cs -var pipeline = ...; - -pipeline.IncludeAtLeast(AccessModifier.Internal); -``` - -### [IncludeOnly(SummaryPipeline, AccessModifier)](../src/Core/Pipelines/SummaryPipelineExtensions.cs#L55) -```cs -public static SummaryPipeline IncludeOnly(this SummaryPipeline self, AccessModifier access) -``` - -Includes only members that have at least the given access modifier. - -#### Example -In order to include onl `internal` members in the generated docs, -you can call this method as follows: -```cs -var pipeline = ...; - -pipeline.IncludeOnly(AccessModifier.Internal); -``` - -### [UseFilter(SummaryPipeline, IPipe)](../src/Core/Pipelines/SummaryPipelineExtensions.cs#L62) -```cs -public static SummaryPipeline UseFilter(this SummaryPipeline self, IPipe filter) -``` - -Adds the given filter into the pipeline. - diff --git a/src/Core/AccessorKind.cs b/src/Core/AccessorKind.cs new file mode 100644 index 0000000..69114f0 --- /dev/null +++ b/src/Core/AccessorKind.cs @@ -0,0 +1,27 @@ +namespace Summary; + +/// +/// The kind of accessor. +/// +/// +/// We intentionally omit event accessors like add or remove since +/// each event has both, and the modifiers are not supported by event accessors. +/// +public enum AccessorKind +{ + /// + /// It's a get accessor. + /// + Get, + + /// + /// It's a set accessor. + /// + Set, + + /// + /// It's a init accessor. + /// + Init, +} + diff --git a/src/Core/DocEvent.cs b/src/Core/DocEvent.cs new file mode 100644 index 0000000..69102fa --- /dev/null +++ b/src/Core/DocEvent.cs @@ -0,0 +1,15 @@ +namespace Summary; + +/// +/// A that represents a documented event in the parsed source code. +/// +/// +/// Similar to but with its own set of accessors. +/// +public record DocEvent : DocMember +{ + /// + /// The type of the event. + /// + public required DocType Type { get; init; } +} \ No newline at end of file diff --git a/src/Core/DocProperty.cs b/src/Core/DocProperty.cs index e2898e6..fe23a19 100644 --- a/src/Core/DocProperty.cs +++ b/src/Core/DocProperty.cs @@ -1,4 +1,6 @@ -namespace Summary; +using Summary.Extensions; + +namespace Summary; /// /// A that represents a documented property in the parsed source code. @@ -10,13 +12,30 @@ public record DocProperty : DocMember /// public required DocType Type { get; init; } + /// + /// The accessors of the property (e.g., get, set, init). + /// + public required DocPropertyAccessor[] Accessors { get; init; } + /// /// Whether this property was generated by compiler (e.g., it's a property of a record). /// public required bool Generated { get; init; } + // TODO: Consider having a separate `DocEvent` type since events are more specialized properties (@j.light). /// /// Whether this property represents an event. /// public required bool Event { get; init; } + + /// + /// The declaration of property accessors as they declared in the C# source code. + /// + public string AccessorsDeclaration => Accessors + .Select(x => $"{AccessDeclaration(x).ToLower().Space() ?? ""}{x.Kind.ToString().ToLower()};") + .Separated(with: " ") + .Surround("{ ", " }"); + + private string AccessDeclaration(DocPropertyAccessor x) => + x.Access is null || x.Access == Access ? "" : x.Access.Value.ToString(); } \ No newline at end of file diff --git a/src/Core/DocPropertyAccessor.cs b/src/Core/DocPropertyAccessor.cs new file mode 100644 index 0000000..dc7ca65 --- /dev/null +++ b/src/Core/DocPropertyAccessor.cs @@ -0,0 +1,35 @@ +namespace Summary; + +/// +/// One of the accessors (e.g., get, set, init). +/// +public record DocPropertyAccessor +{ + /// + /// The sequence that consists only of a default public get property accessor. + /// + public static DocPropertyAccessor[] Defaults() => new[] + { + Default(), + }; + + /// + /// The default public get property accessor. + /// + public static DocPropertyAccessor Default() => new DocPropertyAccessor + { + Kind = AccessorKind.Get, + Access = AccessModifier.Public, + }; + + /// + /// The access modifier of the accessor. + /// If the value is null, then the access modifier is inherited from the property declaration. + /// + public AccessModifier? Access; + + /// + /// The kind of the accessor. + /// + public AccessorKind Kind; +} \ No newline at end of file diff --git a/src/Core/Pipelines/SummaryPipelineExtensions.cs b/src/Core/Pipelines/SummaryPipelineExtensions.cs index 25c34f6..7666090 100644 --- a/src/Core/Pipelines/SummaryPipelineExtensions.cs +++ b/src/Core/Pipelines/SummaryPipelineExtensions.cs @@ -18,50 +18,4 @@ public static class SummaryPipelineExtensions /// public static SummaryPipeline UseLoggerFactory(this SummaryPipeline self, ILoggerFactory factory) => self.Customize(options => options.UseLoggerFactory(factory)); - - /// - /// Enables default filters for the given pipeline (i.e. a filter that removes all non-public members). - /// - public static SummaryPipeline UseDefaultFilters(this SummaryPipeline self) => self - .IncludeOnly(AccessModifier.Public); - - /// - /// Includes only members that have at least the given access modifier. - /// - /// - /// In order to include both internal and public members in the generated docs, - /// you can call this method as follows: - /// - /// var pipeline = ...; - /// - /// pipeline.IncludeAtLeast(AccessModifier.Internal); - /// - /// - public static SummaryPipeline IncludeAtLeast(this SummaryPipeline self, AccessModifier access) => self - .UseFilter(new FilterMemberPipe(x => x.Access >= access)); - - /// - /// Includes only members that have at least the given access modifier. - /// - /// - /// In order to include onl internal members in the generated docs, - /// you can call this method as follows: - /// - /// var pipeline = ...; - /// - /// pipeline.IncludeOnly(AccessModifier.Internal); - /// - /// - public static SummaryPipeline IncludeOnly(this SummaryPipeline self, AccessModifier access) => self - .UseFilter(new FilterMemberPipe(x => x.Access == access)); - - /// - /// Adds the given filter into the pipeline. - /// - /// - public static SummaryPipeline UseFilter(this SummaryPipeline self, IPipe filter) - { - self.Filters.Add(filter); - return self; - } } \ No newline at end of file diff --git a/src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs b/src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs new file mode 100644 index 0000000..35b4b94 --- /dev/null +++ b/src/Core/Pipelines/SummaryPipelineFilteringExtensions.cs @@ -0,0 +1,72 @@ +using Summary.Pipes; +using Summary.Pipes.Filters; + +namespace Summary.Pipelines; + +/// +/// A set of extensions for that add support for filtering functionality. +/// +public static class FilteringExtensions +{ + /// + /// Enables default filters for the given pipeline (i.e. a filter that removes all non-public members). + /// + public static SummaryPipeline UseDefaultFilters(this SummaryPipeline self) => self + .IncludeOnly(AccessModifier.Public); + + /// + /// Includes only members that have at least the given access modifier. + /// + /// + /// In order to include both internal and public members in the generated docs, + /// you can call this method as follows: + /// + /// var pipeline = ...; + /// + /// pipeline.IncludeAtLeast(AccessModifier.Internal); + /// + /// + public static SummaryPipeline IncludeAtLeast(this SummaryPipeline self, AccessModifier access) => self + .WithAccess(x => x >= access); + + /// + /// Includes only members that have at least the given access modifier. + /// + /// + /// In order to include onl internal members in the generated docs, + /// you can call this method as follows: + /// + /// var pipeline = ...; + /// + /// pipeline.IncludeOnly(AccessModifier.Internal); + /// + /// + public static SummaryPipeline IncludeOnly(this SummaryPipeline self, AccessModifier access) => self + .WithAccess(x => x == access); + + /// + /// Includes only members that have the access modifier that satisfies the given predicate. + /// + public static SummaryPipeline WithAccess(this SummaryPipeline self, Func p) => self + .UseFilter(new FilterMemberPipe(x => p(x.Access), x => x.WithAccess(p))); + + private static DocMember WithAccess(this DocMember member, Func p) => member switch + { + DocProperty property => property with + { + Accessors = property.Accessors.Where(x => x.Access is not null && p(x.Access.Value)).ToArray(), + }, + + _ => member, + }; + + /// + /// Adds the given filter into the pipeline. + /// + /// + public static SummaryPipeline UseFilter(this SummaryPipeline self, IPipe filter) + { + self.Filters.Add(filter); + return self; + } +} \ No newline at end of file diff --git a/src/Core/Pipes/Filters/FilterMemberPipe.cs b/src/Core/Pipes/Filters/FilterMemberPipe.cs index 00f81c2..e2a287c 100644 --- a/src/Core/Pipes/Filters/FilterMemberPipe.cs +++ b/src/Core/Pipes/Filters/FilterMemberPipe.cs @@ -1,18 +1,22 @@ namespace Summary.Pipes.Filters; /// -/// A simple pipe that filters all members inside the document using the specified predicate. +/// A simple pipe that filters all members inside the document using the specified predicate +/// and then applies the given map function on them. /// -public class FilterMemberPipe(Predicate p) : IPipe +public class FilterMemberPipe(Func predicate, Func? map = null) : IPipe { + private readonly Func _map = map ?? (static x => x); + /// public Task Run(Doc input) => Task.FromResult(new Doc(Filtered(input.Members))); private DocMember[] Filtered(DocMember[] members) => members - .Where(x => p(x)) + .Where(predicate) .Select(Filtered) + .Select(_map) .ToArray(); private DocMember Filtered(DocMember member) => member switch diff --git a/src/Plugins/Markdown/MdRenderer.cs b/src/Plugins/Markdown/MdRenderer.cs index 57e57f7..6a9bf15 100644 --- a/src/Plugins/Markdown/MdRenderer.cs +++ b/src/Plugins/Markdown/MdRenderer.cs @@ -52,6 +52,7 @@ public string Text() => DocTypeDeclaration type => TypeDeclaration(type), DocMethod method => Method(method), DocProperty property => Property(parent!, property), + _ => Header(member), }; @@ -155,7 +156,12 @@ private MdRenderer Deprecation(DocDeprecation? deprecation) private MdRenderer Declaration(DocMember member) => this .Line("```cs") - .Line(member.Declaration) + .Line(member switch + { + DocProperty property => $"{property.Declaration}{property.AccessorsDeclaration.Surround(" ", "")}", + + _ => member.Declaration, + }) .Line("```") .Line(); diff --git a/src/Plugins/Roslyn/CSharp/Extensions/FieldSyntaxExtensions.cs b/src/Plugins/Roslyn/CSharp/Extensions/FieldSyntaxExtensions.cs index b9a38eb..26e44ce 100644 --- a/src/Plugins/Roslyn/CSharp/Extensions/FieldSyntaxExtensions.cs +++ b/src/Plugins/Roslyn/CSharp/Extensions/FieldSyntaxExtensions.cs @@ -28,7 +28,7 @@ private static DocField Field(this VariableDeclaratorSyntax self, FieldDeclarati Type = field.Declaration.Type.Type(), FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, - Declaration = $"{field.Attributes()}{field.Modifiers} {field.Declaration.Type} {self.Identifier}", + Declaration = $"{field.AttributesDeclaration()}{field.Modifiers} {field.Declaration.Type} {self.Identifier}", Access = field.Access(), Comment = field.Comment(), DeclaringType = self.DeclaringType(), diff --git a/src/Plugins/Roslyn/CSharp/Extensions/MethodSyntaxExtensions.cs b/src/Plugins/Roslyn/CSharp/Extensions/MethodSyntaxExtensions.cs index 3a5624c..2f16e23 100644 --- a/src/Plugins/Roslyn/CSharp/Extensions/MethodSyntaxExtensions.cs +++ b/src/Plugins/Roslyn/CSharp/Extensions/MethodSyntaxExtensions.cs @@ -16,7 +16,7 @@ public static DocMethod Method(this MethodDeclarationSyntax self) => FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, Declaration = - $"{self.Attributes()}{self.Modifiers} {self.ReturnType} {self.Identifier}{self.TypeParameterList}{self.ParameterList}", + $"{self.AttributesDeclaration()}{self.Modifiers} {self.ReturnType} {self.Identifier}{self.TypeParameterList}{self.ParameterList}", Access = self.Access(), Comment = self.Comment(), DeclaringType = self.DeclaringType(), @@ -35,7 +35,7 @@ public static DocMethod Method(this MethodDeclarationSyntax self) => FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, Declaration = - $"{self.Attributes()}{self.Modifiers} {self.ReturnType} {self.Identifier}{self.TypeParameterList}{self.ParameterList}", + $"{self.AttributesDeclaration()}{self.Modifiers} {self.ReturnType} {self.Identifier}{self.TypeParameterList}{self.ParameterList}", Access = self.Access(), Comment = self.Comment(), Params = self.ParameterList.Params(), diff --git a/src/Plugins/Roslyn/CSharp/Extensions/PropertySyntaxExtensions.cs b/src/Plugins/Roslyn/CSharp/Extensions/PropertySyntaxExtensions.cs index 557499a..85d7c09 100644 --- a/src/Plugins/Roslyn/CSharp/Extensions/PropertySyntaxExtensions.cs +++ b/src/Plugins/Roslyn/CSharp/Extensions/PropertySyntaxExtensions.cs @@ -1,4 +1,6 @@ -using Microsoft.CodeAnalysis.CSharp.Syntax; +using Microsoft.CodeAnalysis; +using Microsoft.CodeAnalysis.CSharp; +using Microsoft.CodeAnalysis.CSharp.Syntax; using Summary.Extensions; namespace Summary.Roslyn.CSharp.Extensions; @@ -23,12 +25,13 @@ public static DocProperty Property(this PropertyDeclarationSyntax self) => Type = self.Type.Type(), FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, - Declaration = $"{self.Attributes()}{self.Modifiers} {self.Type} {self.Identifier} {self.Accessors()}", + Declaration = $"{self.AttributesDeclaration()}{self.Modifiers} {self.Type} {self.Identifier}", Access = self.Access(), Comment = self.Comment(), DeclaringType = self.DeclaringType(), Deprecation = self.AttributeLists.Deprecation(), Location = self.Identifier.Location(), + Accessors = self.Accessors(), Generated = false, Event = false, }; @@ -45,12 +48,13 @@ public static DocProperty Property(this ParameterSyntax self) => Type = self.Type!.Type(), FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, - Declaration = $"{self.AttributeLists.Attributes()}public {self.Type} {self.Identifier} {{ get; }}", + Declaration = $"{self.AttributeLists.AttributesDeclaration()}public {self.Type} {self.Identifier}", Access = AccessModifier.Public, Comment = DocComment.Empty, DeclaringType = self.DeclaringType(), Deprecation = self.AttributeLists.Deprecation(), Location = self.Identifier.Location(), + Accessors = DocPropertyAccessor.Defaults(), Generated = true, Event = false, }; @@ -64,12 +68,14 @@ public static DocProperty Property(this EventDeclarationSyntax self) => Type = self.Type!.Type(), FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, - Declaration = $"{self.Attributes()}{self.Modifiers} {self.Type} {self.Identifier} {self.Accessors()}", + Declaration = $"{self.AttributesDeclaration()}{self.Modifiers} {self.Type} {self.Identifier}", Access = self.Access(), Comment = self.Comment(), DeclaringType = self.DeclaringType(), Deprecation = self.AttributeLists.Deprecation(), Location = self.Identifier.Location(), + // All events have `add` and `remove` accessors by default. + Accessors = Array.Empty(), Generated = false, Event = true, }; @@ -92,15 +98,16 @@ public static DocIndexer Indexer(this IndexerDeclarationSyntax self) => Type = self.Type.Type(), FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, - Declaration = $"{self.Attributes()}{self.Modifiers} {self.Type} this{self.ParameterList} {self.Accessors()}", + Declaration = $"{self.AttributesDeclaration()}{self.Modifiers} {self.Type} this{self.ParameterList}", Access = self.Access(), Comment = self.Comment(), DeclaringType = self.DeclaringType(), Deprecation = self.AttributeLists.Deprecation(), Location = self.ThisKeyword.Location(), + Accessors = self.Accessors(), + Params = self.ParameterList.Params(), Generated = false, Event = false, - Params = self.ParameterList.Params(), }; private static DocProperty Property(this VariableDeclaratorSyntax self, EventFieldDeclarationSyntax field) => @@ -109,25 +116,63 @@ private static DocProperty Property(this VariableDeclaratorSyntax self, EventFie Type = field.Declaration.Type.Type(), FullyQualifiedName = self.FullyQualifiedName(), Name = self.Name()!, - Declaration = $"{field.Attributes()}{field.Modifiers} event {field.Declaration.Type} {self.Identifier}", + Declaration = $"{field.AttributesDeclaration()}{field.Modifiers} event {field.Declaration.Type} {self.Identifier}", Access = field.Access(), Comment = field.Comment(), DeclaringType = self.DeclaringType(), Deprecation = field.AttributeLists.Deprecation(), Location = self.Identifier.Location(), + // All events have `add` and `remove` accessors by default. + Accessors = Array.Empty(), Generated = false, Event = true, }; - private static string Accessors(this PropertyDeclarationSyntax self) => - self.AccessorList?.Accessors() ?? "{ get; }"; + private static DocPropertyAccessor[] Accessors(this PropertyDeclarationSyntax self) => + self.AccessorList?.Accessors.Select(x => x.Accessor(self)).ToArray() ?? DocPropertyAccessor.Defaults(); + + private static DocPropertyAccessor[] Accessors(this IndexerDeclarationSyntax self) => + self.AccessorList?.Accessors.Select(x => x.Accessor(self)).ToArray() ?? DocPropertyAccessor.Defaults(); + + private static DocPropertyAccessor Accessor(this AccessorDeclarationSyntax self, MemberDeclarationSyntax parent) => + new() + { + Access = self.Access() ?? parent.Access(), + Kind = Kind(self), + }; + + private static AccessModifier? Access(this AccessorDeclarationSyntax self) + { + if (self.Modifiers.Any(SyntaxKind.PublicKeyword)) + return AccessModifier.Public; + if (self.Modifiers.Any(SyntaxKind.ProtectedKeyword)) + return AccessModifier.Protected; + if (self.Modifiers.Any(SyntaxKind.InternalKeyword)) + return AccessModifier.Internal; + if (self.Modifiers.Any(SyntaxKind.PrivateKeyword)) + return AccessModifier.Private; + + return null; + } + + private static AccessorKind Kind(this AccessorDeclarationSyntax self) => self.Keyword.Kind() switch + { + SyntaxKind.GetKeyword => AccessorKind.Get, + SyntaxKind.SetKeyword => AccessorKind.Set, + SyntaxKind.InitKeyword => AccessorKind.Init, + + _ => throw new InvalidOperationException($"Unrecognized accessor: {self}"), + }; + + private static string AccessorsDeclaration(this PropertyDeclarationSyntax self) => + self.AccessorList?.AccessorsDeclaration() ?? "{ get; }"; - private static string Accessors(this IndexerDeclarationSyntax self) => - self.AccessorList?.Accessors() ?? "{ get; }"; + private static string AccessorsDeclaration(this IndexerDeclarationSyntax self) => + self.AccessorList?.AccessorsDeclaration() ?? "{ get; }"; - private static string Accessors(this EventDeclarationSyntax self) => - self.AccessorList?.Accessors() ?? "{ add; remove; }"; + private static string AccessorsDeclaration(this EventDeclarationSyntax self) => + self.AccessorList?.AccessorsDeclaration() ?? "{ add; remove; }"; - private static string Accessors(this AccessorListSyntax self) => + private static string AccessorsDeclaration(this AccessorListSyntax self) => $"{{ {self.Accessors.Select(x => $"{x.Modifiers.ToString().Space()}{x.Keyword}; ").Separated("")}}}"; } \ No newline at end of file diff --git a/src/Plugins/Roslyn/CSharp/Extensions/SyntaxExtensions.cs b/src/Plugins/Roslyn/CSharp/Extensions/SyntaxExtensions.cs index f2bbae0..0b39d22 100644 --- a/src/Plugins/Roslyn/CSharp/Extensions/SyntaxExtensions.cs +++ b/src/Plugins/Roslyn/CSharp/Extensions/SyntaxExtensions.cs @@ -137,13 +137,13 @@ static bool Error(AttributeSyntax attribute) /// /// A list of attributes of the specified member formatted as a string. /// - public static string Attributes(this MemberDeclarationSyntax self) => - self.AttributeLists.Attributes(); + public static string AttributesDeclaration(this MemberDeclarationSyntax self) => + self.AttributeLists.AttributesDeclaration(); /// /// Formats the specified list of attributes. /// - public static string Attributes(this SyntaxList self) => + public static string AttributesDeclaration(this SyntaxList self) => self .Select(x => $"{x}") .Separated(NewLine) is { } attributes and not "" diff --git a/src/Plugins/Roslyn/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs b/src/Plugins/Roslyn/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs index d957c5b..5f3d1df 100644 --- a/src/Plugins/Roslyn/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs +++ b/src/Plugins/Roslyn/CSharp/Extensions/TypeDeclarationSyntaxExtensions.cs @@ -51,10 +51,10 @@ private static DocTypeParam[] TypeParams(this TypeDeclarationSyntax self) => private static string Declaration(this TypeDeclarationSyntax self) => self switch { RecordDeclarationSyntax record => - $"{self.Attributes()}{self.Modifiers} {record.Keyword()} {self.Identifier}{self.TypeParameterList}{record.ParameterList} {self.BaseList}" + $"{self.AttributesDeclaration()}{self.Modifiers} {record.Keyword()} {self.Identifier}{self.TypeParameterList}{record.ParameterList} {self.BaseList}" .TrimEnd(), _ => - $"{self.Attributes()}{self.Modifiers} {self.Keyword} {self.Identifier}{self.TypeParameterList} {self.BaseList}" + $"{self.AttributesDeclaration()}{self.Modifiers} {self.Keyword} {self.Identifier}{self.TypeParameterList} {self.BaseList}" .TrimEnd(), };