Skip to content
This repository was archived by the owner on Jul 12, 2022. It is now read-only.

Commit 6a82211

Browse files
committed
Enable / disable rules at command line
This change allows for rules to be enabled / disabled at the command line. It essentially fixes #124. I still need to more before putting into master: - More local test runs - Add some unit tests for command line parsing as it's getting a bit complex. - Remove some redundant command line options. But that should happen later today.
1 parent f33ede4 commit 6a82211

File tree

10 files changed

+130
-60
lines changed

10 files changed

+130
-60
lines changed

src/CodeFormatter/Program.cs

Lines changed: 96 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ internal static class Program
2020
private const string ConfigSwitch = "/c:";
2121
private const string CopyrightSwitch = "/copyright:";
2222
private const string LanguageSwitch = "/lang:";
23+
private const string RuleEnabledSwitch1 = "/rule+:";
24+
private const string RuleEnabledSwitch2 = "/rule:";
25+
private const string RuleDisabledSwitch = "/rule-:";
2326

2427
private static int Main(string[] args)
2528
{
@@ -29,7 +32,8 @@ private static int Main(string[] args)
2932
@"CodeFormatter <project, solution or responsefile> [/file:<filename>]
3033
[/lang:<language>] [/c:<config>[,<config>...]>]
3134
[/copyright:<file> | /nocopyright] [/tables] [/nounicode]
32-
[/simple|/agressive] [/verbose]
35+
[/rule(+|-):rule1,rule2,...
36+
[/verbose]
3337
3438
/file - Only apply changes to files with specified name.
3539
/lang - Specifies the language to use when a responsefile is
@@ -42,17 +46,16 @@ Use ConvertTests to convert MSTest tests to xUnit.
4246
/tables - Let tables opt out of formatting by defining
4347
DOTNET_FORMATTER
4448
/nounicode - Do not convert unicode strings to escape sequences
45-
/simple - Only run simple formatters (default)
46-
/agressive - Run agressive form
47-
/list - List the available rules
49+
/rule(+|-) - Enable (default) or disable the specified rule
50+
/rules - List the available rules
4851
/verbose - Verbose output
4952
");
5053
return -1;
5154
}
5255

5356
var comparer = StringComparer.OrdinalIgnoreCase;
5457
var projectOrSolutionPath = args[0];
55-
if (comparer.Equals(projectOrSolutionPath, "/list"))
58+
if (comparer.Equals(projectOrSolutionPath, "/rules"))
5659
{
5760
RunListRules();
5861
return 0;
@@ -65,14 +68,13 @@ Use ConvertTests to convert MSTest tests to xUnit.
6568
}
6669

6770
var fileNamesBuilder = ImmutableArray.CreateBuilder<string>();
68-
var ruleTypeBuilder = ImmutableArray.CreateBuilder<string>();
6971
var configBuilder = ImmutableArray.CreateBuilder<string[]>();
7072
var copyrightHeader = FormattingConstants.DefaultCopyrightHeader;
73+
var ruleMap = ImmutableDictionary<string, bool>.Empty;
7174
var language = LanguageNames.CSharp;
7275
var convertUnicode = true;
7376
var allowTables = false;
7477
var verbose = false;
75-
var formattingLevel = FormattingLevel.Simple;
7678

7779
for (int i = 1; i < args.Length; i++)
7880
{
@@ -119,42 +121,88 @@ Use ConvertTests to convert MSTest tests to xUnit.
119121
{
120122
verbose = true;
121123
}
122-
else if (comparer.Equals(arg, "/tables"))
124+
else if (comparer.Equals(arg, RuleEnabledSwitch1))
123125
{
124-
allowTables = true;
126+
UpdateRuleMap(ref ruleMap, arg.Substring(RuleEnabledSwitch1.Length), enabled: true);
125127
}
126-
else if (comparer.Equals(arg, "/simple"))
128+
else if (comparer.Equals(arg, RuleEnabledSwitch2))
127129
{
128-
formattingLevel = FormattingLevel.Simple;
130+
UpdateRuleMap(ref ruleMap, arg.Substring(RuleEnabledSwitch2.Length), enabled: true);
129131
}
130-
else if (comparer.Equals(arg, "/aggressive"))
132+
else if (comparer.Equals(arg, RuleDisabledSwitch))
131133
{
132-
formattingLevel = FormattingLevel.Agressive;
134+
UpdateRuleMap(ref ruleMap, arg.Substring(RuleDisabledSwitch.Length), enabled: false);
135+
}
136+
else if (comparer.Equals(arg, "/tables"))
137+
{
138+
allowTables = true;
133139
}
134140
else
135141
{
136-
ruleTypeBuilder.Add(arg);
142+
Console.WriteLine("Unrecognized option: {0}", arg);
143+
return 1;
137144
}
138145
}
139146

147+
return RunFormat(
148+
projectOrSolutionPath,
149+
fileNamesBuilder.ToImmutableArray(),
150+
configBuilder.ToImmutableArray(),
151+
copyrightHeader,
152+
ruleMap,
153+
language,
154+
allowTables,
155+
convertUnicode,
156+
verbose);
157+
}
158+
159+
private static void RunListRules()
160+
{
161+
var rules = FormattingEngine.GetFormattingRules();
162+
Console.WriteLine("{0,-20} {1}", "Name", "Description");
163+
Console.WriteLine("==============================================");
164+
foreach (var rule in rules)
165+
{
166+
Console.WriteLine("{0,-20} :{1}", rule.Name, rule.Description);
167+
}
168+
}
169+
170+
private static void UpdateRuleMap(ref ImmutableDictionary<string, bool> ruleMap, string data, bool enabled)
171+
{
172+
foreach (var current in data.Split(','))
173+
{
174+
ruleMap = ruleMap.SetItem(current, enabled);
175+
}
176+
}
177+
178+
private static int RunFormat(
179+
string projectSolutionOrRspPath,
180+
ImmutableArray<string> fileNames,
181+
ImmutableArray<string[]> preprocessorConfigurations,
182+
ImmutableArray<string> copyrightHeader,
183+
ImmutableDictionary<string, bool> ruleMap,
184+
string language,
185+
bool allowTables,
186+
bool convertUnicode,
187+
bool verbose)
188+
{
140189
var cts = new CancellationTokenSource();
141190
var ct = cts.Token;
142191

143192
Console.CancelKeyPress += delegate { cts.Cancel(); };
144193

145194
try
146195
{
147-
RunAsync(
148-
projectOrSolutionPath,
149-
ruleTypeBuilder.ToImmutableArray(),
150-
fileNamesBuilder.ToImmutableArray(),
151-
configBuilder.ToImmutableArray(),
196+
RunFormatAsync(
197+
projectSolutionOrRspPath,
198+
fileNames,
199+
preprocessorConfigurations,
152200
copyrightHeader,
201+
ruleMap,
153202
language,
154203
allowTables,
155204
convertUnicode,
156205
verbose,
157-
formattingLevel,
158206
ct).Wait(ct);
159207
Console.WriteLine("Completed formatting.");
160208
return 0;
@@ -174,38 +222,30 @@ Use ConvertTests to convert MSTest tests to xUnit.
174222
}
175223
}
176224

177-
private static void RunListRules()
178-
{
179-
var rules = FormattingEngine.GetFormattingRules();
180-
Console.WriteLine("{0,-20} {1}", "Name", "Description");
181-
Console.WriteLine("==============================================");
182-
foreach (var rule in rules)
183-
{
184-
Console.WriteLine("{0,-20} :{1}", rule.Name, rule.Description);
185-
}
186-
}
187-
188-
private static async Task RunAsync(
225+
private static async Task<int> RunFormatAsync(
189226
string projectSolutionOrRspPath,
190-
ImmutableArray<string> ruleTypes,
191227
ImmutableArray<string> fileNames,
192228
ImmutableArray<string[]> preprocessorConfigurations,
193229
ImmutableArray<string> copyrightHeader,
230+
ImmutableDictionary<string, bool> ruleMap,
194231
string language,
195232
bool allowTables,
196233
bool convertUnicode,
197234
bool verbose,
198-
FormattingLevel formattingLevel,
199235
CancellationToken cancellationToken)
200236
{
201-
var engine = FormattingEngine.Create(ruleTypes);
237+
var engine = FormattingEngine.Create();
202238
engine.PreprocessorConfigurations = preprocessorConfigurations;
203239
engine.FileNames = fileNames;
204240
engine.CopyrightHeader = copyrightHeader;
205241
engine.AllowTables = allowTables;
206242
engine.ConvertUnicodeCharacters = convertUnicode;
207243
engine.Verbose = verbose;
208-
engine.FormattingLevel = formattingLevel;
244+
245+
if (!SetRuleMap(engine, ruleMap))
246+
{
247+
return 1;
248+
}
209249

210250
Console.WriteLine(Path.GetFileName(projectSolutionOrRspPath));
211251
string extension = Path.GetExtension(projectSolutionOrRspPath);
@@ -235,6 +275,26 @@ private static async Task RunAsync(
235275
await engine.FormatProjectAsync(project, cancellationToken);
236276
}
237277
}
278+
279+
return 0;
280+
}
281+
282+
private static bool SetRuleMap(IFormattingEngine engine, ImmutableDictionary<string, bool> ruleMap)
283+
{
284+
var comparer = StringComparer.OrdinalIgnoreCase;
285+
foreach (var entry in ruleMap)
286+
{
287+
var rule = engine.AllRules.Where(x => comparer.Equals(x.Name, entry.Key)).FirstOrDefault();
288+
if (rule == null)
289+
{
290+
Console.WriteLine("Could not find rule with name {0}", entry.Key);
291+
return false;
292+
}
293+
294+
engine.ToggleRuleEnabled(rule, entry.Value);
295+
}
296+
297+
return true;
238298
}
239299
}
240300
}

src/Microsoft.DotNet.CodeFormatting.Tests/Rules/CombinationTest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ public sealed class CombinationTest : CodeFormattingTestBase, IDisposable
2222

2323
static CombinationTest()
2424
{
25-
s_formattingEngine = (FormattingEngineImplementation)FormattingEngine.Create(ImmutableArray<string>.Empty);
25+
s_formattingEngine = (FormattingEngineImplementation)FormattingEngine.Create();
2626
}
2727

2828
public CombinationTest()

src/Microsoft.DotNet.CodeFormatting/FormattingEngine.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ namespace Microsoft.DotNet.CodeFormatting
1414
{
1515
public static class FormattingEngine
1616
{
17-
public static IFormattingEngine Create(ImmutableArray<string> ruleTypes)
17+
public static IFormattingEngine Create()
1818
{
1919
var container = CreateCompositionContainer();
2020
var engine = container.GetExportedValue<IFormattingEngine>();

src/Microsoft.DotNet.CodeFormatting/FormattingEngineImplementation.cs

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ internal sealed class FormattingEngineImplementation : IFormattingEngine
3333
private readonly IEnumerable<Lazy<ILocalSemanticFormattingRule, IRuleMetadata>> _localSemanticRules;
3434
private readonly IEnumerable<Lazy<IGlobalSemanticFormattingRule, IRuleMetadata>> _globalSemanticRules;
3535
private readonly Stopwatch _watch = new Stopwatch();
36+
private readonly Dictionary<string, bool> _ruleMap = new Dictionary<string, bool>(StringComparer.OrdinalIgnoreCase);
3637
private bool _allowTables;
3738
private bool _verbose;
3839

@@ -78,10 +79,16 @@ public bool ConvertUnicodeCharacters
7879
set { _options.ConvertUnicodeCharacters = value; }
7980
}
8081

81-
public FormattingLevel FormattingLevel
82+
public ImmutableArray<IRuleMetadata> AllRules
8283
{
83-
get { return _options.FormattingLevel; }
84-
set { _options.FormattingLevel = value; }
84+
get
85+
{
86+
var list = new List<IRuleMetadata>();
87+
list.AddRange(_syntaxRules.Select(x => x.Metadata));
88+
list.AddRange(_localSemanticRules.Select(x => x.Metadata));
89+
list.AddRange(_globalSemanticRules.Select(x => x.Metadata));
90+
return list.ToImmutableArray();
91+
}
8592
}
8693

8794
[ImportingConstructor]
@@ -97,14 +104,19 @@ internal FormattingEngineImplementation(
97104
_syntaxRules = syntaxRules;
98105
_localSemanticRules = localSemanticRules;
99106
_globalSemanticRules = globalSemanticRules;
107+
108+
foreach (var rule in AllRules)
109+
{
110+
_ruleMap[rule.Name] = rule.DefaultRule;
111+
}
100112
}
101113

102114
private IEnumerable<TRule> GetOrderedRules<TRule>(IEnumerable<Lazy<TRule, IRuleMetadata>> rules)
103115
where TRule : IFormattingRule
104116
{
105117
return rules
106118
.OrderBy(r => r.Metadata.Order)
107-
.Where(r => r.Metadata.FormattingLevel <= FormattingLevel)
119+
.Where(r => _ruleMap[r.Metadata.Name])
108120
.Select(r => r.Value)
109121
.ToList();
110122
}
@@ -120,6 +132,11 @@ public Task FormatProjectAsync(Project project, CancellationToken cancellationTo
120132
return FormatAsync(project.Solution.Workspace, project.DocumentIds, cancellationToken);
121133
}
122134

135+
public void ToggleRuleEnabled(IRuleMetadata ruleMetaData, bool enabled)
136+
{
137+
_ruleMap[ruleMetaData.Name] = enabled;
138+
}
139+
123140
private async Task FormatAsync(Workspace workspace, IReadOnlyList<DocumentId> documentIds, CancellationToken cancellationToken)
124141
{
125142
var watch = new Stopwatch();

src/Microsoft.DotNet.CodeFormatting/IFormattingEngine.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,11 @@ public interface IFormattingEngine
1515
ImmutableArray<string> CopyrightHeader { get; set; }
1616
ImmutableArray<string[]> PreprocessorConfigurations { get; set; }
1717
ImmutableArray<string> FileNames { get; set; }
18+
ImmutableArray<IRuleMetadata> AllRules { get; }
1819
bool AllowTables { get; set; }
1920
bool ConvertUnicodeCharacters { get; set; }
2021
bool Verbose { get; set; }
21-
FormattingLevel FormattingLevel { get; set; }
22+
void ToggleRuleEnabled(IRuleMetadata ruleMetaData, bool enabled);
2223
Task FormatSolutionAsync(Solution solution, CancellationToken cancellationToken);
2324
Task FormatProjectAsync(Project project, CancellationToken cancellationToken);
2425
}

src/Microsoft.DotNet.CodeFormatting/IFormattingRule.cs

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,6 @@
99

1010
namespace Microsoft.DotNet.CodeFormatting
1111
{
12-
public enum FormattingLevel
13-
{
14-
Simple = 0,
15-
Agressive,
16-
}
17-
1812
/// <summary>
1913
/// Base formatting rule which helps establish which language the rule applies to.
2014
/// </summary>

src/Microsoft.DotNet.CodeFormatting/IRuleMetadata.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public interface IRuleMetadata
1616
[DefaultValue(int.MaxValue)]
1717
int Order { get; }
1818

19-
[DefaultValue(FormattingLevel.Simple)]
20-
FormattingLevel FormattingLevel { get; }
19+
[DefaultValue(true)]
20+
bool DefaultRule { get; }
2121
}
2222
}

src/Microsoft.DotNet.CodeFormatting/Options.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,6 @@ internal sealed class Options
2929

3030
internal bool ConvertUnicodeCharacters { get; set; }
3131

32-
internal FormattingLevel FormattingLevel { get; set; }
33-
3432
[ImportingConstructor]
3533
internal Options()
3634
{

src/Microsoft.DotNet.CodeFormatting/RuleAttribute.cs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,8 @@ public SyntaxRuleAttribute(string name, string description, int order)
3232
[DefaultValue(int.MaxValue)]
3333
public int Order { get; private set; }
3434

35-
[DefaultValue(FormattingLevel.Simple)]
36-
public FormattingLevel FormattingLevel { get; set; }
35+
[DefaultValue(true)]
36+
public bool DefaultRule { get; set; }
3737
}
3838

3939
[MetadataAttribute]
@@ -57,8 +57,8 @@ public LocalSemanticRuleAttribute(string name, string description, int order)
5757
[DefaultValue(int.MaxValue)]
5858
public int Order { get; private set; }
5959

60-
[DefaultValue(FormattingLevel.Simple)]
61-
public FormattingLevel FormattingLevel { get; set; }
60+
[DefaultValue(true)]
61+
public bool DefaultRule { get; set; }
6262
}
6363

6464
[MetadataAttribute]
@@ -82,7 +82,7 @@ public GlobalSemanticRuleAttribute(string name, string description, int order)
8282
[DefaultValue(int.MaxValue)]
8383
public int Order { get; private set; }
8484

85-
[DefaultValue(FormattingLevel.Simple)]
86-
public FormattingLevel FormattingLevel { get; set; }
85+
[DefaultValue(true)]
86+
public bool DefaultRule { get; set; }
8787
}
8888
}

src/Microsoft.DotNet.CodeFormatting/Rules/MarkReadonlyFieldsRule.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ namespace Microsoft.DotNet.CodeFormatting.Rules
1616
/// <summary>
1717
/// Mark any fields that can provably be marked as readonly.
1818
/// </summary>
19-
[GlobalSemanticRule(MarkReadonlyFieldsRule.Name, MarkReadonlyFieldsRule.Description, GlobalSemanticRuleOrder.MarkReadonlyFieldsRule, FormattingLevel = FormattingLevel.Agressive)]
19+
[GlobalSemanticRule(MarkReadonlyFieldsRule.Name, MarkReadonlyFieldsRule.Description, GlobalSemanticRuleOrder.MarkReadonlyFieldsRule, DefaultRule = false)]
2020
internal sealed class MarkReadonlyFieldsRule : IGlobalSemanticFormattingRule
2121
{
2222
internal const string Name = "ReadonlyFields";

0 commit comments

Comments
 (0)