Browse Source

[Refactoring, Competion] Add NamingHelper, use it from CSharpCompletionEngine and add convenience functions to RefactoringContext.

newNRvisualizers
Simon Lindgren 14 years ago
parent
commit
da5bbf381e
  1. 61
      ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs
  2. 1
      ICSharpCode.NRefactory.CSharp/ICSharpCode.NRefactory.CSharp.csproj
  3. 235
      ICSharpCode.NRefactory.CSharp/Refactoring/NamingHelper.cs
  4. 17
      ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs
  5. 251
      ICSharpCode.NRefactory.Tests/CSharp/Refactoring/NamingHelperTests.cs
  6. 1
      ICSharpCode.NRefactory.Tests/ICSharpCode.NRefactory.Tests.csproj

61
ICSharpCode.NRefactory.CSharp/Completion/CSharpCompletionEngine.cs

@ -130,61 +130,6 @@ namespace ICSharpCode.NRefactory.CSharp.Completion @@ -130,61 +130,6 @@ namespace ICSharpCode.NRefactory.CSharp.Completion
return Enumerable.Empty<ICompletionData>();
}
IEnumerable<string> GenerateNameProposals(AstType type)
{
if (type is PrimitiveType) {
var pt = (PrimitiveType)type;
switch (pt.Keyword) {
case "object":
yield return "o";
yield return "obj";
break;
case "bool":
yield return "b";
yield return "pred";
break;
case "double":
case "float":
case "decimal":
yield return "d";
yield return "f";
yield return "m";
break;
default:
yield return "i";
yield return "j";
yield return "k";
break;
}
yield break;
}
string name;
if (type is SimpleType) {
name = ((SimpleType)type).Identifier;
} else if (type is MemberType) {
name = ((SimpleType)type).Identifier;
} else {
yield break;
}
var names = WordParser.BreakWords(name);
var possibleName = new StringBuilder();
for (int i = 0; i < names.Count; i++) {
possibleName.Length = 0;
for (int j = i; j < names.Count; j++) {
if (string.IsNullOrEmpty(names [j])) {
continue;
}
if (j == i) {
names [j] = Char.ToLower(names [j] [0]) + names [j].Substring(1);
}
possibleName.Append(names [j]);
}
yield return possibleName.ToString();
}
}
IEnumerable<ICompletionData> HandleMemberReferenceCompletion(ExpressionResult expr)
{
if (expr == null)
@ -405,7 +350,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion @@ -405,7 +350,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion
if (parent.Variables.Count != 1)
return DefaultControlSpaceItems(isAsExpression, controlSpace);
foreach (var possibleName in GenerateNameProposals (parent.Type)) {
foreach (var possibleName in NamingHelper.GenerateNameProposals (parent.Type)) {
if (possibleName.Length > 0) {
proposeNameList.Result.Add(factory.CreateLiteralCompletionData(possibleName.ToString()));
}
@ -1115,7 +1060,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion @@ -1115,7 +1060,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion
}
if (node is Identifier && node.Parent is ForeachStatement) {
var foreachStmt = (ForeachStatement)node.Parent;
foreach (var possibleName in GenerateNameProposals (foreachStmt.VariableType)) {
foreach (var possibleName in NamingHelper.GenerateNameProposals (foreachStmt.VariableType)) {
if (possibleName.Length > 0) {
wrapper.Result.Add(factory.CreateLiteralCompletionData(possibleName.ToString()));
}
@ -1133,7 +1078,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion @@ -1133,7 +1078,7 @@ namespace ICSharpCode.NRefactory.CSharp.Completion
// Try Parameter name case
var param = node.Parent as ParameterDeclaration;
if (param != null) {
foreach (var possibleName in GenerateNameProposals (param.Type)) {
foreach (var possibleName in NamingHelper.GenerateNameProposals (param.Type)) {
if (possibleName.Length > 0) {
wrapper.Result.Add(factory.CreateLiteralCompletionData(possibleName.ToString()));
}

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

@ -381,6 +381,7 @@ @@ -381,6 +381,7 @@
<Compile Include="Refactoring\CodeActions\ExtractFieldAction.cs" />
<Compile Include="Completion\ICompletionContextProvider.cs" />
<Compile Include="Refactoring\CodeIssues\ValueParameterUnusedIssue.cs" />
<Compile Include="Refactoring\NamingHelper.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\ICSharpCode.NRefactory\ICSharpCode.NRefactory.csproj">

235
ICSharpCode.NRefactory.CSharp/Refactoring/NamingHelper.cs

@ -0,0 +1,235 @@ @@ -0,0 +1,235 @@
//
// NamingHelper.cs
//
// Author:
// Simon Lindgren <simon.n.lindgren@gmail.com>
//
// Copyright (c) 2012 Simon Lindgren
//
// 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.Text;
using ICSharpCode.NRefactory.TypeSystem;
using ICSharpCode.NRefactory.Semantics;
namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
public class NamingHelper
{
ISet<string> UsedVariableNames;
RefactoringContext context;
public NamingHelper(RefactoringContext context)
{
this.context = context;
var astNode = context.GetNode<Statement>();
if (UsedVariableNames == null) {
var visitor = new VariableFinderVisitor();
astNode.AcceptVisitor(visitor);
UsedVariableNames = visitor.VariableNames;
}
}
public static IEnumerable<string> GenerateNameProposals(AstType type)
{
if (type is PrimitiveType) {
var pt = (PrimitiveType)type;
switch (pt.Keyword) {
case "object":
yield return "o";
yield return "obj";
break;
case "bool":
yield return "b";
yield return "pred";
break;
case "double":
case "float":
case "decimal":
yield return "d";
yield return "f";
yield return "m";
break;
case "char":
yield return "c";
break;
default:
yield return "i";
yield return "j";
yield return "k";
break;
}
yield break;
}
string name;
if (type is SimpleType) {
name = ((SimpleType)type).Identifier;
} else if (type is MemberType) {
name = ((MemberType)type).MemberName;
} else {
yield break;
}
var names = WordParser.BreakWords(name);
var possibleName = new StringBuilder();
for (int i = 0; i < names.Count; i++) {
possibleName.Length = 0;
for (int j = i; j < names.Count; j++) {
if (string.IsNullOrEmpty(names [j])) {
continue;
}
if (j == i) {
names [j] = Char.ToLower(names [j] [0]) + names [j].Substring(1);
}
possibleName.Append(names [j]);
}
yield return possibleName.ToString();
}
}
/// <summary>
/// Generates a variable name for a variable of the specified type.
/// </summary>
/// <returns>
/// The variable name.
/// </returns>
/// <param name='type'>
/// The type of the variable.
/// </param>
public string GenerateVariableName(AstType type)
{
string firstSuggestion = null;
foreach (var name in NamingHelper.GenerateNameProposals(type)) {
firstSuggestion = firstSuggestion ?? name;
if (!UsedVariableNames.Contains(name) && LookupVariable(name) == null)
return name;
}
// If we get here, all of the standard suggestions are already used.
// This will at least be the second variable named based on firstSuggestion, so start at 2
int counter = 2;
string proposedName;
do {
proposedName = firstSuggestion + counter++;
} while (UsedVariableNames.Contains(proposedName));
return proposedName;
}
/// <summary>
/// Generates a variable name for a variable of the specified type.
/// </summary>
/// <returns>
/// The variable name.
/// </returns>
/// <param name='type'>
/// The type of the variable.
/// </param>
public string GenerateVariableName(IType type)
{
AstType astType = ToAstType(type);
return GenerateVariableName(astType);
}
AstType ToAstType(IType type)
{
switch (type.FullName) {
case "System.Object":
return new PrimitiveType("object");
case "System.String":
return new PrimitiveType("string");
case "System.Boolean":
return new PrimitiveType("bool");
case "System.Char":
return new PrimitiveType("char");
case "System.SByte":
return new PrimitiveType("sbyte");
case "System.Byte":
return new PrimitiveType("byte");
case "System.Int16":
return new PrimitiveType("short");
case "System.UInt16":
return new PrimitiveType("ushort");
case "System.Int32":
return new PrimitiveType("int");
case "System.UInt32":
return new PrimitiveType("uint");
case "System.Int64":
return new PrimitiveType("long");
case "System.UInt64":
return new PrimitiveType("ulong");
case "System.Single":
return new PrimitiveType("float");
case "System.Double":
return new PrimitiveType("double");
case "System.Decimal":
return new PrimitiveType("decimal");
default:
return new SimpleType(type.Name);
}
}
IVariable LookupVariable(string name)
{
var astNode = context.GetNode<BlockStatement>();
var resolverState = context.Resolver.GetResolverStateAfter(astNode.LastChild.PrevSibling);
var simpleNameRR = resolverState.ResolveSimpleName(name, new List<IType>()) as LocalResolveResult;
if (simpleNameRR == null)
return null;
return simpleNameRR.Variable;
}
class VariableFinderVisitor : DepthFirstAstVisitor
{
public ISet<string> VariableNames = new HashSet<string>();
public override void VisitVariableInitializer(VariableInitializer variableInitializer)
{
ProcessName(variableInitializer.Name);
base.VisitVariableInitializer(variableInitializer);
}
public override void VisitQueryLetClause(QueryLetClause queryLetClause)
{
ProcessName(queryLetClause.Identifier);
base.VisitQueryLetClause(queryLetClause);
}
public override void VisitQueryFromClause(QueryFromClause queryFromClause)
{
ProcessName(queryFromClause.Identifier);
base.VisitQueryFromClause(queryFromClause);
}
public override void VisitQueryContinuationClause(QueryContinuationClause queryContinuationClause)
{
ProcessName(queryContinuationClause.Identifier);
base.VisitQueryContinuationClause(queryContinuationClause);
}
void ProcessName(string name)
{
if (!VariableNames.Contains(name))
VariableNames.Add(name);
}
}
}
}

17
ICSharpCode.NRefactory.CSharp/Refactoring/RefactoringContext.cs

@ -133,6 +133,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring @@ -133,6 +133,7 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring
public abstract string GetText (ISegment segment);
#endregion
#region Naming
public virtual string GetNameProposal (string name, bool camelCase = true)
{
string baseName = (camelCase ? char.ToLower (name [0]) : char.ToUpper (name [0])) + name.Substring (1);
@ -153,6 +154,22 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring @@ -153,6 +154,22 @@ namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
return baseName + (number > 0 ? (number + 1).ToString () : "");
}
NamingHelper namingHelper = null;
public string GenerateVariableName(AstType type)
{
if (namingHelper == null)
namingHelper = new NamingHelper(this);
return namingHelper.GenerateVariableName(type);
}
public string GenerateVariableName(IType type)
{
if (namingHelper == null)
namingHelper = new NamingHelper(this);
return namingHelper.GenerateVariableName(type);
}
#endregion
}
}

251
ICSharpCode.NRefactory.Tests/CSharp/Refactoring/NamingHelperTests.cs

@ -0,0 +1,251 @@ @@ -0,0 +1,251 @@
//
// BaseRefactoringContextTests.cs
//
// Author:
// Simon Lindgren <simon.n.lindgren@gmail.com>
//
// Copyright (c) 2012 Simon Lindgren
//
// 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 NUnit.Framework;
using ICSharpCode.NRefactory.CSharp.Refactoring;
using ICSharpCode.NRefactory.CSharp.CodeActions;
using ICSharpCode.NRefactory.CSharp;
using System.Linq;
namespace ICSharpCode.NRefactory.CSharp.Refactoring
{
[TestFixture]
public class NamingHelperTests
{
RefactoringContext MakeContext(string input, bool expectErrors = false)
{
var context = TestRefactoringContext.Create (input, expectErrors);
return context;
}
[Test]
public void GenerateVariableNameTest()
{
var context = MakeContext(@"
class A
{
void F()
{ $ }
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.AreEqual("i", name);
}
[Test]
public void GenerateVariableNameIgnoresNamesUsedPreviouslyInScope()
{
var context = MakeContext(@"
class A
{
void F()
{
int i;
$
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsFalse(name == "i", "i was already used and should not be proposed.");
}
[Test]
public void GenerateVariableNameIgnoresNamesUsedLaterInScope()
{
var context = MakeContext(@"
class A
{
void F()
{
$
int i;
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsFalse(name == "i", "i was already used and should not be proposed.");
}
[Test]
public void GenerateVariableNameIgnoresNamesUsedInNestedScope()
{
var context = MakeContext(@"
class A
{
void F()
{
$
for (int i; i < 0; i++);
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsFalse(name == "i", "i was already used and should not be proposed.");
}
[Test]
public void GenerateVariableNameInForIgnoresIterator()
{
var context = MakeContext(@"
class A
{
void F()
{
for (int i; i < 0; i++)
$
}
}", true);
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsFalse(name == "i", "i was already used and should not be proposed.");
}
[Test]
public void GenerateVariableNameInMethodIgnoresParameters()
{
var context = MakeContext(@"
class A
{
void F(int i)
{
$
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsFalse(name == "i", "i was already used and should not be proposed.");
}
[Test]
public void GenerateVariableNameInForInitializerList()
{
var context = MakeContext(@"
class A
{
void F(int i)
{
for($ ; i < 0; i++);
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsFalse(name == "i", "i was already used and should not be proposed.");
}
[Test]
public void GenerateVariableNameShouldNotIgnoreBasedOnMethodCallIdentifiers()
{
var context = MakeContext(@"
class B
{
void i()
{
}
}
class A
{
void F()
{
for($ ;;)
B.i();
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.IsTrue(name == "i");
}
[Test]
public void GenerateVariableNameIgnoresLinqIdentifiers()
{
// Snippet tests that identifiers from in, into and let clauses are found
var context = MakeContext(@"
class A
{
void F()
{
$
var ints = from i in new int [] {} group i by i % 2 into j let k = 2 select j.Count() + k;
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.AreEqual("i2", name);
}
[Test]
public void GenerateVariableNameIgnoresFixedVariables()
{
// Snippet tests that identifiers from in, into and let clauses are found
var context = MakeContext(@"
class A
{
unsafe void F()
{
$
fixed (int i = 13) {}
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.AreEqual("j", name);
}
[Test]
public void GenerateVariableNameFallsBackToNumbering()
{
var context = MakeContext(@"
class A
{
void F()
{
int i, j, k;
$
}
}");
var name = context.GenerateVariableName(new PrimitiveType("int"));
Assert.NotNull(name);
Assert.AreEqual("i2", name);
}
[Test]
public void GenerateVariableNameForComposedType()
{
var context = MakeContext(@"
class A
{
void F()
{
$
}
}");
var name = context.GenerateVariableName(new SimpleType() { Identifier = "VariableNameGenerationTester" });
Assert.NotNull(name);
Assert.AreEqual("variableNameGenerationTester", name);
}
}
}

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

@ -279,6 +279,7 @@ @@ -279,6 +279,7 @@
<Compile Include="CSharp\CodeActions\ExtractFieldTests.cs" />
<Compile Include="CSharp\CodeIssues\ValueParameterUnusedTests.cs" />
<Compile Include="CSharp\Parser\Statements\InvalidStatementsTests.cs" />
<Compile Include="CSharp\Refactoring\NamingHelperTests.cs" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\Mono.Cecil\Mono.Cecil.csproj">

Loading…
Cancel
Save