Skip to content

Commit c8d525c

Browse files
author
msftbot[bot]
authored
Merge pull request #44357 from dotnet/merges/master-to-master-vs-deps
Merge master to master-vs-deps
2 parents 6057e3c + 14a785e commit c8d525c

File tree

71 files changed

+2061
-94
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

71 files changed

+2061
-94
lines changed

eng/Versions.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<MajorVersion>3</MajorVersion>
99
<MinorVersion>7</MinorVersion>
1010
<PatchVersion>0</PatchVersion>
11-
<PreReleaseVersionLabel>2</PreReleaseVersionLabel>
11+
<PreReleaseVersionLabel>3</PreReleaseVersionLabel>
1212
<VersionPrefix>$(MajorVersion).$(MinorVersion).$(PatchVersion)</VersionPrefix>
1313
<!--
1414
By default the assembly version in official builds is "$(MajorVersion).$(MinorVersion).0.0".

eng/config/PublishData.json

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -72,21 +72,21 @@
7272
"vsBranch": "rel/d16.6",
7373
"vsMajorVersion": 16
7474
},
75-
"release/dev16.7-preview1-vs-deps": {
75+
"release/dev16.7-preview2-vs-deps": {
7676
"nugetKind": ["Shipping", "NonShipping"],
7777
"version": "3.7.*",
7878
"nuget": [ "https://dotnet.myget.org/F/roslyn/api/v2/package" ],
7979
"vsix": [ "https://dotnet.myget.org/F/roslyn/vsix/upload" ],
80-
"channels": [ "dev16.7", "dev16.7p1" ],
81-
"vsBranch": "rel/d16.7",
80+
"channels": [ "dev16.7", "dev16.7p2" ],
81+
"vsBranch": "master",
8282
"vsMajorVersion": 16
8383
},
8484
"master-vs-deps": {
8585
"nugetKind": ["Shipping", "NonShipping"],
8686
"version": "3.7.*",
8787
"nuget": [ "https://dotnet.myget.org/F/roslyn/api/v2/package" ],
8888
"vsix": [ "https://dotnet.myget.org/F/roslyn/vsix/upload" ],
89-
"channels": [ "dev16.7", "dev16.7p2" ],
89+
"channels": [ "dev16.7", "dev16.7p3" ],
9090
"vsBranch": "master",
9191
"vsMajorVersion": 16
9292
},

src/Analyzers/CSharp/Analyzers/CSharpAnalyzers.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
<Compile Include="$(MSBuildThisFileDirectory)AddAccessibilityModifiers\CSharpAddAccessibilityModifiersDiagnosticAnalyzer.cs" />
1919
<Compile Include="$(MSBuildThisFileDirectory)AddBraces\CSharpAddBracesDiagnosticAnalyzer.cs" />
2020
<Compile Include="$(MSBuildThisFileDirectory)AddRequiredParentheses\CSharpAddRequiredPatternParenthesesDiagnosticAnalyzer.cs" />
21+
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessarySuppressions\CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs" />
2122
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessaryParentheses\CSharpRemoveUnnecessaryPatternParenthesesDiagnosticAnalyzer.cs" />
2223
<Compile Include="..\..\..\Analyzers\CSharp\Analyzers\AddRequiredParentheses\CSharpAddRequiredExpressionParenthesesDiagnosticAnalyzer.cs" />
2324
<Compile Include="$(MSBuildThisFileDirectory)ConvertAnonymousTypeToTuple\CSharpConvertAnonymousTypeToTupleDiagnosticAnalyzer.cs" />
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
using Microsoft.CodeAnalysis.CSharp.Syntax;
6+
using Microsoft.CodeAnalysis.Diagnostics;
7+
using Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions;
8+
9+
#nullable enable
10+
11+
namespace Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions
12+
{
13+
[DiagnosticAnalyzer(LanguageNames.CSharp)]
14+
internal sealed class CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer
15+
: AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer
16+
{
17+
protected override void RegisterAttributeSyntaxAction(CompilationStartAnalysisContext context, CompilationAnalyzer compilationAnalyzer)
18+
{
19+
context.RegisterSyntaxNodeAction(context =>
20+
{
21+
var attributeList = (AttributeListSyntax)context.Node;
22+
switch (attributeList.Target?.Identifier.Kind())
23+
{
24+
case SyntaxKind.AssemblyKeyword:
25+
case SyntaxKind.ModuleKeyword:
26+
foreach (var attribute in attributeList.Attributes)
27+
{
28+
compilationAnalyzer.AnalyzeAssemblyOrModuleAttribute(attribute, context.SemanticModel, context.ReportDiagnostic, context.CancellationToken);
29+
}
30+
31+
break;
32+
}
33+
}, SyntaxKind.AttributeList);
34+
}
35+
}
36+
}

src/Analyzers/CSharp/Tests/CSharpAnalyzers.UnitTests.projitems

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
<Compile Include="$(MSBuildThisFileDirectory)MisplacedUsingDirectives\MisplacedUsingDirectivesCodeFixProviderTests.cs" />
2626
<Compile Include="$(MSBuildThisFileDirectory)OrderModifiers\OrderModifiersCompilerErrorTests.cs" />
2727
<Compile Include="$(MSBuildThisFileDirectory)OrderModifiers\OrderModifiersTests.cs" />
28+
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessarySuppressions\RemoveUnnecessarySuppressionsTests.cs" />
2829
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessaryParentheses\RemoveUnnecessaryPatternParenthesesTests.cs" />
2930
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnreachableCode\RemoveUnreachableCodeTests.cs" />
3031
<Compile Include="$(MSBuildThisFileDirectory)SimplifyBooleanExpression\SimplifyConditionalTests.cs" />
Lines changed: 186 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,186 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System.Threading.Tasks;
8+
using Microsoft.CodeAnalysis.Test.Utilities;
9+
using Roslyn.Test.Utilities;
10+
using Xunit;
11+
using VerifyCS = Microsoft.CodeAnalysis.Editor.UnitTests.CodeActions.CSharpCodeFixVerifier<
12+
Microsoft.CodeAnalysis.CSharp.RemoveUnnecessarySuppressions.CSharpRemoveUnnecessarySuppressionsDiagnosticAnalyzer,
13+
Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions.RemoveUnnecessarySuppressionsCodeFixProvider>;
14+
15+
namespace Microsoft.CodeAnalysis.Editor.CSharp.UnitTests.RemoveUnnecessarySuppressions
16+
{
17+
[Trait(Traits.Feature, Traits.Features.CodeActionsRemoveUnnecessarySuppressions)]
18+
[WorkItem(44176, "https://github.com/dotnet/roslyn/issues/44176")]
19+
public class RemoveUnnecessarySuppressionsTests
20+
{
21+
[Fact]
22+
public void TestStandardProperties()
23+
=> VerifyCS.VerifyStandardProperties();
24+
25+
[Theory]
26+
// Field
27+
[InlineData(@"Scope = ""member""", @"Target = ""~F:N.C.F""", "assembly")]
28+
// Property
29+
[InlineData(@"Scope = ""member""", @"Target = ""~P:N.C.P""", "assembly")]
30+
// Method
31+
[InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M()""", "assembly")]
32+
// Type
33+
[InlineData(@"Scope = ""member""", @"Target = ""~T:N.C""", "assembly")]
34+
// Namespace
35+
[InlineData(@"Scope = ""namespace""", @"Target = ""~N:N""", "assembly")]
36+
// NamespaceAndDescendants
37+
[InlineData(@"Scope = ""namespaceanddescendants""", @"Target = ""~N:N""", "assembly")]
38+
// Module - no scope, no target
39+
[InlineData(null, null, "assembly")]
40+
// Module - no target
41+
[InlineData(@"Scope = ""module""", null, "assembly")]
42+
// Module - null target
43+
[InlineData(@"Scope = ""module""", @"Target = null", "assembly")]
44+
// Resource - not handled
45+
[InlineData(@"Scope = ""resource""", @"Target = """"", "assembly")]
46+
// 'module' attribute target
47+
[InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M()""", "module")]
48+
// Member with non-matching scope (seems to be respected by suppression decoder)
49+
[InlineData(@"Scope = ""type""", @"Target = ""~M:N.C.M()""", "assembly")]
50+
[InlineData(@"Scope = ""namespace""", @"Target = ""~F:N.C.F""", "assembly")]
51+
// Case insensitive scope
52+
[InlineData(@"Scope = ""Member""", @"Target = ""~F:N.C.F""", "assembly")]
53+
[InlineData(@"Scope = ""MEMBER""", @"Target = ""~F:N.C.F""", "assembly")]
54+
public async Task ValidSuppressions(string? scope, string? target, string attributeTarget)
55+
{
56+
var scopeString = scope != null ? $@", {scope}" : string.Empty;
57+
var targetString = target != null ? $@", {target}" : string.Empty;
58+
59+
var input = $@"
60+
[{attributeTarget}: System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeString}{targetString})]
61+
62+
namespace N
63+
{{
64+
class C
65+
{{
66+
public int F;
67+
public int P {{ get; }}
68+
public void M() {{ }}
69+
}}
70+
}}";
71+
await VerifyCS.VerifyCodeFixAsync(input, input);
72+
}
73+
74+
[Theory]
75+
// Field - no matching symbol
76+
[InlineData(@"Scope = ""member""", @"Target = ""~F:N.C.F2""", "assembly")]
77+
// Field - no matching symbol (case insensitive)
78+
[InlineData(@"Scope = ""Member""", @"Target = ""~F:N.C.F2""", "assembly")]
79+
[InlineData(@"Scope = ""MEMBER""", @"Target = ""~F:N.C.F2""", "assembly")]
80+
// Property - invalid scope
81+
[InlineData(@"Scope = ""invalid""", @"Target = ""~P:N.C.P""", "assembly")]
82+
// Method - wrong signature
83+
[InlineData(@"Scope = ""member""", @"Target = ""~M:N.C.M(System.Int32)""", "assembly")]
84+
// Method - module scope
85+
[InlineData(@"Scope = ""module""", @"Target = ""~M:N.C.M()""", "assembly")]
86+
// Method - null scope
87+
[InlineData(@"Scope = null", @"Target = ""~M:N.C.M()""", "assembly")]
88+
// Method - no scope
89+
[InlineData(null, @"Target = ""~M:N.C.M()""", "assembly")]
90+
// Member scope - null target
91+
[InlineData(@"Scope = ""member""", @"Target = null", "assembly")]
92+
// Member scope - no target
93+
[InlineData(@"Scope = ""member""", null, "assembly")]
94+
// Type - no matching namespace
95+
[InlineData(@"Scope = ""type""", @"Target = ""~T:N2.C""", "assembly")]
96+
// Namespace - extra namespace qualification
97+
[InlineData(@"Scope = ""namespace""", @"Target = ""~N:N.N2""", "assembly")]
98+
// NamespaceAndDescendants - empty target
99+
[InlineData(@"Scope = ""namespaceanddescendants""", @"Target = """"", "assembly")]
100+
// Module - no scope, empty target
101+
[InlineData(null, @"Target = """"", "assembly")]
102+
// Module - no scope, non-empty target
103+
[InlineData(null, @"Target = ""~T:N.C""", "assembly")]
104+
// Module scope, empty target
105+
[InlineData(@"Scope = ""module""", @"Target = """"", "assembly")]
106+
// Module no scope, non-empty target
107+
[InlineData(@"Scope = ""module""", @"Target = ""~T:N.C""", "assembly")]
108+
public async Task InvalidSuppressions(string? scope, string? target, string attributeTarget)
109+
{
110+
var scopeString = scope != null ? $@", {scope}" : string.Empty;
111+
var targetString = target != null ? $@", {target}" : string.Empty;
112+
113+
var input = $@"
114+
[{attributeTarget}: [|System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeString}{targetString})|]]
115+
116+
namespace N
117+
{{
118+
class C
119+
{{
120+
public int F;
121+
public int P {{ get; }}
122+
public void M() {{ }}
123+
}}
124+
}}";
125+
126+
var fixedCode = $@"
127+
128+
namespace N
129+
{{
130+
class C
131+
{{
132+
public int F;
133+
public int P {{ get; }}
134+
public void M() {{ }}
135+
}}
136+
}}";
137+
await VerifyCS.VerifyCodeFixAsync(input, fixedCode);
138+
}
139+
140+
[Fact]
141+
public async Task ValidAndInvalidSuppressions()
142+
{
143+
var attributePrefix = @"System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""";
144+
var validSuppression = $@"{attributePrefix}, Scope = ""member"", Target = ""~T:C"")";
145+
var invalidSuppression = $@"[|{attributePrefix}, Scope = ""member"", Target = """")|]";
146+
147+
var input = $@"
148+
[assembly: {validSuppression}]
149+
[assembly: {invalidSuppression}]
150+
[assembly: {validSuppression}, {validSuppression}]
151+
[assembly: {invalidSuppression}, {invalidSuppression}]
152+
[assembly: {validSuppression}, {invalidSuppression}]
153+
[assembly: {invalidSuppression}, {validSuppression}]
154+
[assembly: {invalidSuppression}, {validSuppression}, {invalidSuppression}, {validSuppression}]
155+
156+
class C {{ }}
157+
";
158+
159+
var fixedCode = $@"
160+
[assembly: {validSuppression}]
161+
[assembly: {validSuppression}, {validSuppression}]
162+
[assembly: {validSuppression}]
163+
[assembly: {validSuppression}]
164+
[assembly: {validSuppression}, {validSuppression}]
165+
166+
class C {{ }}
167+
";
168+
await VerifyCS.VerifyCodeFixAsync(input, fixedCode);
169+
}
170+
171+
[Theory]
172+
[InlineData("")]
173+
[InlineData(@", Scope = ""member"", Target = ""~M:C.M()""")]
174+
[InlineData(@", Scope = ""invalid"", Target = ""invalid""")]
175+
public async Task LocalSuppressions(string scopeAndTarget)
176+
{
177+
var input = $@"
178+
[System.Diagnostics.CodeAnalysis.SuppressMessage(""Category"", ""Id: Title"", Justification = ""Pending""{scopeAndTarget})]
179+
class C
180+
{{
181+
public void M() {{ }}
182+
}}";
183+
await VerifyCS.VerifyCodeFixAsync(input, input);
184+
}
185+
}
186+
}

src/Analyzers/Core/Analyzers/Analyzers.projitems

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@
3030
<Compile Include="$(MSBuildThisFileDirectory)Helpers\DiagnosticHelper.cs" />
3131
<Compile Include="$(MSBuildThisFileDirectory)IDEDiagnosticIds.cs" />
3232
<Compile Include="$(MSBuildThisFileDirectory)IDEDiagnosticIdToOptionMappingHelper.cs" />
33+
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessarySuppressions\SuppressMessageAttributeState.cs" />
34+
<Compile Include="$(MSBuildThisFileDirectory)RemoveUnnecessarySuppressions\AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer.cs" />
35+
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\Compilers\Core\Portable\DiagnosticAnalyzer\SuppressMessageAttributeState.TargetScope.cs" Link="RemoveUnnecessarySuppressions\SuppressMessageAttributeState.TargetScope.cs" />
36+
<Compile Include="$(MSBuildThisFileDirectory)..\..\..\Compilers\Core\Portable\DiagnosticAnalyzer\SuppressMessageAttributeState.TargetSymbolResolver.cs" Link="RemoveUnnecessarySuppressions\SuppressMessageAttributeState.TargetSymbolResolver.cs" />
3337
<Compile Include="$(MSBuildThisFileDirectory)MakeFieldReadonly\MakeFieldReadonlyDiagnosticAnalyzer.cs" />
3438
<Compile Include="$(MSBuildThisFileDirectory)NamingStyle\NamingStyleDiagnosticAnalyzerBase.cs" />
3539
<Compile Include="$(MSBuildThisFileDirectory)OrderModifiers\AbstractOrderModifiersDiagnosticAnalyzer.cs" />

src/Analyzers/Core/Analyzers/AnalyzersResources.resx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,4 +307,13 @@
307307
<data name="Conditional_expression_can_be_simplified" xml:space="preserve">
308308
<value>Conditional expression can be simplified</value>
309309
</data>
310+
<data name="Invalid_global_SuppressMessageAttribute" xml:space="preserve">
311+
<value>Invalid global 'SuppressMessageAttribute'</value>
312+
</data>
313+
<data name="Invalid_scope_for_SuppressMessageAttribute" xml:space="preserve">
314+
<value>Invalid scope for 'SuppressMessageAttribute'</value>
315+
</data>
316+
<data name="Invalid_or_missing_target_for_SuppressMessageAttribute" xml:space="preserve">
317+
<value>Invalid or missing target for 'SuppressMessageAttribute'</value>
318+
</data>
310319
</root>

src/Analyzers/Core/Analyzers/IDEDiagnosticIds.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ internal static class IDEDiagnosticIds
130130

131131
public const string SimplifyConditionalExpressionDiagnosticId = "IDE0075";
132132

133+
public const string InvalidSuppressMessageAttributeDiagnosticId = "IDE0076";
134+
133135
// Analyzer error Ids
134136
public const string AnalyzerChangedId = "IDE1001";
135137
public const string AnalyzerDependencyConflictId = "IDE1002";
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
// Licensed to the .NET Foundation under one or more agreements.
2+
// The .NET Foundation licenses this file to you under the MIT license.
3+
// See the LICENSE file in the project root for more information.
4+
5+
#nullable enable
6+
7+
using System;
8+
using System.Collections.Immutable;
9+
using System.Threading;
10+
using Microsoft.CodeAnalysis.CodeQuality;
11+
using Microsoft.CodeAnalysis.Diagnostics;
12+
using Microsoft.CodeAnalysis.Shared.Extensions;
13+
14+
namespace Microsoft.CodeAnalysis.RemoveUnnecessarySuppressions
15+
{
16+
internal abstract class AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer
17+
: AbstractCodeQualityDiagnosticAnalyzer
18+
{
19+
private static readonly LocalizableResourceString s_localizableTitle = new LocalizableResourceString(
20+
nameof(AnalyzersResources.Invalid_global_SuppressMessageAttribute), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
21+
private static readonly LocalizableResourceString s_localizableInvalidScopeMessage = new LocalizableResourceString(
22+
nameof(AnalyzersResources.Invalid_scope_for_SuppressMessageAttribute), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
23+
private static readonly LocalizableResourceString s_localizableInvalidOrMissingTargetMessage = new LocalizableResourceString(
24+
nameof(AnalyzersResources.Invalid_or_missing_target_for_SuppressMessageAttribute), AnalyzersResources.ResourceManager, typeof(AnalyzersResources));
25+
26+
private static readonly DiagnosticDescriptor s_invalidScopeDescriptor = CreateDescriptor(
27+
IDEDiagnosticIds.InvalidSuppressMessageAttributeDiagnosticId, s_localizableTitle, s_localizableInvalidScopeMessage, isUnnecessary: true);
28+
private static readonly DiagnosticDescriptor s_invalidOrMissingTargetDescriptor = CreateDescriptor(
29+
IDEDiagnosticIds.InvalidSuppressMessageAttributeDiagnosticId, s_localizableTitle, s_localizableInvalidOrMissingTargetMessage, isUnnecessary: true);
30+
31+
public AbstractRemoveUnnecessarySuppressionsDiagnosticAnalyzer()
32+
: base(ImmutableArray.Create(s_invalidScopeDescriptor, s_invalidOrMissingTargetDescriptor), GeneratedCodeAnalysisFlags.None)
33+
{
34+
}
35+
36+
protected abstract void RegisterAttributeSyntaxAction(CompilationStartAnalysisContext context, CompilationAnalyzer compilationAnalyzer);
37+
public sealed override DiagnosticAnalyzerCategory GetAnalyzerCategory() => DiagnosticAnalyzerCategory.SemanticDocumentAnalysis;
38+
39+
protected sealed override void InitializeWorker(AnalysisContext context)
40+
{
41+
context.RegisterCompilationStartAction(context =>
42+
{
43+
var suppressMessageAttributeType = context.Compilation.SuppressMessageAttributeType();
44+
if (suppressMessageAttributeType == null)
45+
{
46+
return;
47+
}
48+
49+
RegisterAttributeSyntaxAction(context, new CompilationAnalyzer(context.Compilation, suppressMessageAttributeType));
50+
});
51+
}
52+
53+
protected sealed class CompilationAnalyzer
54+
{
55+
private readonly SuppressMessageAttributeState _state;
56+
57+
public CompilationAnalyzer(Compilation compilation, INamedTypeSymbol suppressMessageAttributeType)
58+
{
59+
_state = new SuppressMessageAttributeState(compilation, suppressMessageAttributeType);
60+
}
61+
62+
public void AnalyzeAssemblyOrModuleAttribute(SyntaxNode attributeSyntax, SemanticModel model, Action<Diagnostic> reportDiagnostic, CancellationToken cancellationToken)
63+
{
64+
if (!_state.IsSuppressMessageAttributeWithNamedArguments(attributeSyntax, model, cancellationToken, out var namedAttributeArguments))
65+
{
66+
return;
67+
}
68+
69+
DiagnosticDescriptor rule;
70+
if (_state.HasInvalidScope(namedAttributeArguments, out var targetScope))
71+
{
72+
rule = s_invalidScopeDescriptor;
73+
}
74+
else if (_state.HasInvalidOrMissingTarget(namedAttributeArguments, targetScope))
75+
{
76+
rule = s_invalidOrMissingTargetDescriptor;
77+
}
78+
else
79+
{
80+
return;
81+
}
82+
83+
reportDiagnostic(Diagnostic.Create(rule, attributeSyntax.GetLocation()));
84+
}
85+
}
86+
}
87+
}

0 commit comments

Comments
 (0)