Browse Source

moved Ambience to NRefactory

newNRvisualizers
Siegfried Pammer 15 years ago
parent
commit
e6d84ff111
  1. 1
      ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
  2. 243
      ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs
  3. 235
      ICSharpCode.NRefactory.Tests/CSharp/CSharpAmbienceTests.cs
  4. 1
      ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj
  5. 1
      ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj
  6. 94
      ICSharpCode.NRefactory/TypeSystem/IAmbience.cs

1
ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj

@ -169,6 +169,7 @@ @@ -169,6 +169,7 @@
<Compile Include="Formatter\CSharpFormattingOptions.cs" />
<Compile Include="Formatter\Indent.cs" />
<Compile Include="Formatter\ITextEditorAdapter.cs" />
<Compile Include="OutputVisitor\CSharpAmbience.cs" />
<Compile Include="OutputVisitor\InsertParenthesesVisitor.cs" />
<Compile Include="OutputVisitor\IOutputFormatter.cs" />
<Compile Include="OutputVisitor\OutputVisitor.cs" />

243
ICSharpCode.NRefactory.CSharp/OutputVisitor/CSharpAmbience.cs

@ -0,0 +1,243 @@ @@ -0,0 +1,243 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.IO;
using ICSharpCode.NRefactory.CSharp.Refactoring;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
namespace ICSharpCode.NRefactory.CSharp
{
/// <summary>
/// C# ambience.
/// </summary>
public class CSharpAmbience : IAmbience
{
public ConversionFlags ConversionFlags { get; set; }
#region ConvertEntity
public string ConvertEntity(IEntity e, ITypeResolveContext context)
{
using (var ctx = context.Synchronize()) {
StringWriter writer = new StringWriter();
if (e.EntityType == EntityType.TypeDefinition) {
ConvertTypeDeclaration((ITypeDefinition)e, ctx, writer);
} else {
ConvertMember((IMember)e, ctx, writer);
}
return writer.ToString().TrimEnd();
}
}
void ConvertMember(IMember member, ISynchronizedTypeResolveContext ctx, StringWriter writer)
{
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(ctx);
astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers;
astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility;
astBuilder.ShowParameterNames = (ConversionFlags & ConversionFlags.ShowParameterNames) == ConversionFlags.ShowParameterNames;
AttributedNode node = (AttributedNode)astBuilder.ConvertEntity(member);
PrintModifiers(node.Modifiers, writer);
if ((ConversionFlags & ConversionFlags.ShowReturnType) == ConversionFlags.ShowReturnType) {
var rt = node.GetChildByRole(AstNode.Roles.Type);
if (rt != AstNode.Roles.Type.NullObject) {
writer.Write(rt.AcceptVisitor(CreatePrinter(writer), null));
writer.Write(' ');
}
}
WriteMemberDeclarationName(member, ctx, writer);
if ((ConversionFlags & ConversionFlags.ShowParameterList) == ConversionFlags.ShowParameterList
&& member is IParameterizedMember && member.EntityType != EntityType.Property) {
writer.Write((node is IndexerDeclaration) ? '[' : '(');
bool first = true;
foreach (var param in node.GetChildrenByRole(AstNode.Roles.Parameter)) {
if (first)
first = false;
else
writer.Write(", ");
param.AcceptVisitor(CreatePrinter(writer), null);
}
writer.Write((node is IndexerDeclaration) ? ']' : ')');
}
}
void ConvertTypeDeclaration(ITypeDefinition typeDef, ITypeResolveContext ctx, StringWriter writer)
{
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(ctx);
astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers;
astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility;
TypeDeclaration typeDeclaration = (TypeDeclaration)astBuilder.ConvertEntity(typeDef);
PrintModifiers(typeDeclaration.Modifiers, writer);
if ((ConversionFlags & ConversionFlags.ShowDefinitionKeyWord) == ConversionFlags.ShowDefinitionKeyWord) {
switch (typeDeclaration.ClassType) {
case ClassType.Class:
writer.Write("class");
break;
case ClassType.Struct:
writer.Write("struct");
break;
case ClassType.Interface:
writer.Write("interface");
break;
case ClassType.Enum:
writer.Write("enum");
break;
default:
throw new Exception("Invalid value for ClassType");
}
writer.Write(' ');
}
WriteTypeDeclarationName(typeDef, ctx, writer);
}
void WriteTypeDeclarationName(ITypeDefinition typeDef, ITypeResolveContext ctx, StringWriter writer)
{
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(ctx);
if (typeDef.DeclaringTypeDefinition != null) {
WriteTypeDeclarationName(typeDef.DeclaringTypeDefinition, ctx, writer);
writer.Write('.');
} else if ((ConversionFlags & ConversionFlags.UseFullyQualifiedMemberNames) == ConversionFlags.UseFullyQualifiedMemberNames) {
writer.Write(typeDef.Namespace);
writer.Write('.');
}
writer.Write(typeDef.Name);
if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList) {
CreatePrinter(writer).WriteTypeParameters(((TypeDeclaration)astBuilder.ConvertEntity(typeDef)).TypeParameters);
}
}
void WriteMemberDeclarationName(IMember member, ITypeResolveContext ctx, StringWriter writer)
{
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(ctx);
if ((ConversionFlags & ConversionFlags.UseFullyQualifiedMemberNames) == ConversionFlags.UseFullyQualifiedMemberNames) {
writer.Write(ConvertType(member.DeclaringType));
writer.Write('.');
}
switch (member.EntityType) {
case EntityType.Indexer:
writer.Write("this");
break;
case EntityType.Constructor:
writer.Write(member.DeclaringType.Name);
break;
case EntityType.Destructor:
writer.Write('~');
writer.Write(member.DeclaringType.Name);
break;
case EntityType.Operator:
switch (member.Name) {
case "op_Implicit":
writer.Write("implicit operator ");
writer.Write(ConvertType(member.ReturnType, ctx));
break;
case "op_Explicit":
writer.Write("explicit operator ");
writer.Write(ConvertType(member.ReturnType, ctx));
break;
default:
writer.Write("operator ");
var operatorType = OperatorDeclaration.GetOperatorType(member.Name);
if (operatorType.HasValue)
writer.Write(OperatorDeclaration.GetToken(operatorType.Value));
else
writer.Write(member.Name);
break;
}
break;
default:
writer.Write(member.Name);
break;
}
if ((ConversionFlags & ConversionFlags.ShowTypeParameterList) == ConversionFlags.ShowTypeParameterList && member.EntityType == EntityType.Method) {
CreatePrinter(writer).WriteTypeParameters(astBuilder.ConvertEntity(member).GetChildrenByRole(AstNode.Roles.TypeParameter));
}
}
OutputVisitor CreatePrinter(StringWriter writer)
{
return new OutputVisitor(writer, new CSharpFormattingOptions());
}
void PrintModifiers(Modifiers modifiers, StringWriter writer)
{
foreach (var m in CSharpModifierToken.AllModifiers) {
if ((modifiers & m) == m) {
writer.Write(CSharpModifierToken.GetModifierName(m));
writer.Write(' ');
}
}
}
#endregion
public string ConvertVariable(IVariable v, ITypeResolveContext context)
{
using (var ctx = context.Synchronize()) {
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(ctx);
astBuilder.ShowModifiers = (ConversionFlags & ConversionFlags.ShowModifiers) == ConversionFlags.ShowModifiers;
astBuilder.ShowAccessibility = (ConversionFlags & ConversionFlags.ShowAccessibility) == ConversionFlags.ShowAccessibility;
astBuilder.ShowParameterNames = (ConversionFlags & ConversionFlags.ShowParameterNames) == ConversionFlags.ShowParameterNames;
astBuilder.AlwaysUseShortTypeNames = (ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) != ConversionFlags.UseFullyQualifiedTypeNames;
AstNode astNode = astBuilder.ConvertVariable(v);
CSharpFormattingOptions formatting = new CSharpFormattingOptions();
StringWriter writer = new StringWriter();
astNode.AcceptVisitor(new OutputVisitor(writer, formatting), null);
return writer.ToString().TrimEnd(';', '\r', '\n');
}
}
public string ConvertType(IType type)
{
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(MinimalResolveContext.Instance);
astBuilder.AlwaysUseShortTypeNames = (ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) != ConversionFlags.UseFullyQualifiedTypeNames;
AstType astType = astBuilder.ConvertType(type);
CSharpFormattingOptions formatting = new CSharpFormattingOptions();
StringWriter writer = new StringWriter();
astType.AcceptVisitor(new OutputVisitor(writer, formatting), null);
return writer.ToString();
}
public string ConvertType(ITypeReference type, ITypeResolveContext context)
{
using (var ctx = context.Synchronize()) {
TypeSystemAstBuilder astBuilder = new TypeSystemAstBuilder(ctx);
astBuilder.AlwaysUseShortTypeNames = (ConversionFlags & ConversionFlags.UseFullyQualifiedTypeNames) != ConversionFlags.UseFullyQualifiedTypeNames;
AstType astType = astBuilder.ConvertTypeReference(type);
CSharpFormattingOptions formatting = new CSharpFormattingOptions();
StringWriter writer = new StringWriter();
astType.AcceptVisitor(new OutputVisitor(writer, formatting), null);
return writer.ToString();
}
}
public string WrapAttribute(string attribute)
{
return "[" + attribute + "]";
}
public string WrapComment(string comment)
{
return "// " + comment;
}
}
}

235
ICSharpCode.NRefactory.Tests/CSharp/CSharpAmbienceTests.cs

@ -0,0 +1,235 @@ @@ -0,0 +1,235 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
using System.Collections.Generic;
using System.Linq;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.TypeSystem.Implementation;
using NUnit.Framework;
namespace ICSharpCode.NRefactory.CSharp
{
[TestFixture]
public class CSharpAmbienceTests
{
IProjectContent mscorlib;
IProjectContent myLib;
CompositeTypeResolveContext compositeContext;
CSharpAmbience ambience;
public CSharpAmbienceTests()
{
ambience = new CSharpAmbience();
mscorlib = CecilLoaderTests.Mscorlib;
var loader = new CecilLoader();
loader.IncludeInternalMembers = true;
myLib = loader.LoadAssemblyFile(typeof(CSharpAmbienceTests).Assembly.Location);
compositeContext = new CompositeTypeResolveContext(new[] { mscorlib, myLib });
}
#region ITypeDefinition tests
[Test]
public void GenericType()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(Dictionary<,>));
ambience.ConversionFlags = ConversionFlags.UseFullyQualifiedMemberNames | ConversionFlags.ShowTypeParameterList;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("System.Collections.Generic.Dictionary<TKey, TValue>", result);
}
[Test]
public void GenericTypeShortName()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(Dictionary<,>));
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("Dictionary<TKey, TValue>", result);
}
[Test]
public void SimpleType()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(Object));
ambience.ConversionFlags = ConversionFlags.UseFullyQualifiedMemberNames | ConversionFlags.ShowTypeParameterList;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("System.Object", result);
}
[Test]
public void SimpleTypeDefinition()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(Object));
ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.UseFullyQualifiedMemberNames);
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("public class Object", result);
}
[Test]
public void SimpleTypeDefinitionWithoutModifiers()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(Object));
ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.UseFullyQualifiedMemberNames | ConversionFlags.ShowModifiers | ConversionFlags.ShowAccessibility);
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("class Object", result);
}
[Test]
public void GenericTypeDefinitionFull()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(List<>));
ambience.ConversionFlags = ConversionFlags.All;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("public class System.Collections.Generic.List<T>", result);
}
[Test]
public void SimpleTypeShortName()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(Object));
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("Object", result);
}
[Test]
public void GenericTypeWithNested()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(List<>.Enumerator));
ambience.ConversionFlags = ConversionFlags.UseFullyQualifiedMemberNames | ConversionFlags.ShowTypeParameterList;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("System.Collections.Generic.List<T>.Enumerator", result);
}
[Test]
public void GenericTypeWithNestedShortName()
{
var typeDef = mscorlib.GetTypeDefinition(typeof(List<>.Enumerator));
ambience.ConversionFlags = ConversionFlags.ShowTypeParameterList;
string result = ambience.ConvertEntity(typeDef, mscorlib);
Assert.AreEqual("List<T>.Enumerator", result);
}
#endregion
#region IField tests
[Test]
public void SimpleField()
{
var field = typeof(CSharpAmbienceTests.Program).ToTypeReference().Resolve(myLib)
.GetDefinition().Fields.Single(f => f.Name == "test");
ambience.ConversionFlags = ConversionFlags.All;
string result = ambience.ConvertEntity(field, compositeContext);
Assert.AreEqual("private int ICSharpCode.SharpDevelop.Tests.CSharpAmbienceTests.Program.test", result);
}
[Test]
public void SimpleConstField()
{
var field = typeof(CSharpAmbienceTests.Program).ToTypeReference().Resolve(myLib)
.GetDefinition().Fields.Single(f => f.Name == "TEST2");
ambience.ConversionFlags = ConversionFlags.All;
string result = ambience.ConvertEntity(field, compositeContext);
Assert.AreEqual("private const int ICSharpCode.SharpDevelop.Tests.CSharpAmbienceTests.Program.TEST2", result);
}
[Test]
public void SimpleFieldWithoutModifiers()
{
var field = typeof(CSharpAmbienceTests.Program).ToTypeReference().Resolve(myLib)
.GetDefinition().Fields.Single(f => f.Name == "test");
ambience.ConversionFlags = ConversionFlags.All & ~(ConversionFlags.UseFullyQualifiedMemberNames | ConversionFlags.ShowModifiers | ConversionFlags.ShowAccessibility);
string result = ambience.ConvertEntity(field, compositeContext);
Assert.AreEqual("int test", result);
}
#endregion
#region Test types
#pragma warning disable 169
class Test {}
class Program
{
int test;
const int TEST2 = 2;
public int Test { get; set; }
public int this[int index] {
get {
return index;
}
}
public event EventHandler ProgramChanged;
public event EventHandler SomeEvent {
add {}
remove {}
}
public static bool operator +(Program lhs, Program rhs)
{
throw new NotImplementedException();
}
public static implicit operator Test(Program lhs)
{
throw new NotImplementedException();
}
public static explicit operator int(Program lhs)
{
throw new NotImplementedException();
}
public Program(int x)
{
}
~Program()
{
}
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");
// TODO: Implement Functionality Here
Console.Write("Press any key to continue . . . ");
Console.ReadKey(true);
}
}
#endregion
}
}

1
ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

@ -78,6 +78,7 @@ @@ -78,6 +78,7 @@
<ItemGroup>
<Compile Include="CSharp\Analysis\DefiniteAssignmentTests.cs" />
<Compile Include="CSharp\AstStructureTests.cs" />
<Compile Include="CSharp\CSharpAmbienceTests.cs" />
<Compile Include="CSharp\InsertParenthesesVisitorTests.cs" />
<Compile Include="CSharp\OutputVisitorTests.cs" />
<Compile Include="CSharp\Parser\Expression\AnonymousTypeCreateExpressionTests.cs" />

1
ICSharpCode.NRefactory/ICSharpCode.NRefactory.csproj

@ -103,6 +103,7 @@ @@ -103,6 +103,7 @@
<Compile Include="TypeSystem\EntityType.cs" />
<Compile Include="TypeSystem\ExtensionMethods.cs" />
<Compile Include="TypeSystem\IAccessor.cs" />
<Compile Include="TypeSystem\IAmbience.cs" />
<Compile Include="TypeSystem\IAttribute.cs" />
<Compile Include="TypeSystem\IConstantValue.cs" />
<Compile Include="TypeSystem\IDocumentationProvider.cs" />

94
ICSharpCode.NRefactory/TypeSystem/IAmbience.cs

@ -0,0 +1,94 @@ @@ -0,0 +1,94 @@
// Copyright (c) AlphaSierraPapa for the SharpDevelop Team
//
// Permission is hereby granted, free of charge, to any person obtaining a copy of this
// software and associated documentation files (the "Software"), to deal in the Software
// without restriction, including without limitation the rights to use, copy, modify, merge,
// publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons
// to whom the Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or
// substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
// INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
// PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE
// FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.
using System;
namespace ICSharpCode.NRefactory.TypeSystem
{
[Flags]
public enum ConversionFlags
{
/// <summary>
/// Convert only the name.
/// </summary>
None = 0,
/// <summary>
/// Show the parameter list
/// </summary>
ShowParameterList = 1,
/// <summary>
/// Show names for parameters
/// </summary>
ShowParameterNames = 2,
/// <summary>
/// Show the accessibility (private, public, etc.)
/// </summary>
ShowAccessibility = 4,
/// <summary>
/// Show the definition key word (class, struct, Sub, Function, etc.)
/// </summary>
ShowDefinitionKeyWord = 8,
/// <summary>
/// Show the fully qualified name for the member
/// </summary>
UseFullyQualifiedMemberNames = 0x10,
/// <summary>
/// Show modifiers (virtual, override, etc.)
/// </summary>
ShowModifiers = 0x20,
/// <summary>
/// Show the return type
/// </summary>
ShowReturnType = 0x100,
/// <summary>
/// Use fully qualified names for return type and parameters.
/// </summary>
UseFullyQualifiedTypeNames = 0x200,
/// <summary>
/// Show the list of type parameters on method and class declarations.
/// Type arguments for parameter/return types are always shown.
/// </summary>
ShowTypeParameterList = 0x800,
StandardConversionFlags = ShowParameterNames |
ShowAccessibility |
ShowParameterList |
ShowReturnType |
ShowModifiers |
ShowTypeParameterList |
ShowDefinitionKeyWord,
All = 0xfff,
}
public interface IAmbience
{
ConversionFlags ConversionFlags {
get;
set;
}
string ConvertEntity(IEntity e, ITypeResolveContext context);
string ConvertType(IType type);
string ConvertType(ITypeReference type, ITypeResolveContext context);
string ConvertVariable(IVariable variable, ITypeResolveContext context);
string WrapAttribute(string attribute);
string WrapComment(string comment);
}
}
Loading…
Cancel
Save