Skip to content

Commit 7bd1ccd

Browse files
authored
[WIP] Configuration detection (#409)
* WIP automatic coding style detection * detect multiple instances * fix phrasing of indentation violations * added unittest for DetectCodingStyle
1 parent 405d9bd commit 7bd1ccd

36 files changed

+674
-90
lines changed

CHANGES.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
## dev branch / next version (2.x.x)
2-
- Added `extendsConfigPath` field to config files fixes [#401](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/401) ([#407](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/407) + [#408](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/408))
2+
- New `extendsConfigPath` field to config files fixes [#401](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/401) ([#407](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/407) + [#408](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/408))
3+
- New experimental command line option `-detect <filename>` to generate a checkstyle configuration file based on a source folder [#409](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/409)
4+
- Refactored indentation check messages [#409](https://github.com/HaxeCheckstyle/haxe-checkstyle/issues/409)
35

46
## version 2.2.2
57

src/checkstyle/CheckMessage.hx

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,14 +2,6 @@ package checkstyle;
22

33
import checkstyle.checks.Category;
44

5-
@:enum
6-
abstract SeverityLevel(String) from String {
7-
var INFO = "INFO";
8-
var WARNING = "WARNING";
9-
var ERROR = "ERROR";
10-
var IGNORE = "IGNORE";
11-
}
12-
135
typedef CheckMessage = {
146
var fileName:String;
157
var message:String;

src/checkstyle/Config.hx

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package checkstyle;
22

3-
import checkstyle.CheckMessage.SeverityLevel;
4-
53
typedef Config = {
64
@:optional var extendsConfigPath:String;
75
@:optional var defaultSeverity:SeverityLevel;

src/checkstyle/Main.hx

Lines changed: 32 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ package checkstyle;
22

33
import checkstyle.ChecksInfo;
44
import checkstyle.Config;
5-
import checkstyle.CheckMessage.SeverityLevel;
65
import checkstyle.checks.Check;
76
import checkstyle.reporter.IReporter;
87
import checkstyle.reporter.JSONReporter;
@@ -13,11 +12,14 @@ import checkstyle.reporter.CodeClimateReporter;
1312
import checkstyle.reporter.ExitCodeReporter;
1413
import checkstyle.reporter.ReporterManager;
1514
import checkstyle.errors.Error;
15+
import checkstyle.detect.DetectCodingStyle;
16+
import checkstyle.utils.ConfigUtils;
1617
import haxe.CallStack;
1718
import haxe.Json;
1819
import hxargs.Args;
1920
import sys.FileSystem;
2021
import sys.io.File;
22+
import haxe.io.Path;
2123

2224
class Main {
2325

@@ -45,6 +47,7 @@ class Main {
4547
var numberOfCheckerThreads:Int;
4648
var overrideCheckerThreads:Int;
4749
var disableThreads:Bool;
50+
var detectConfig:String;
4851
var seenConfigPaths:Array<String>;
4952

5053
function new() {
@@ -59,6 +62,7 @@ class Main {
5962
excludePath = null;
6063
numberOfCheckerThreads = 5;
6164
overrideCheckerThreads = 0;
65+
detectConfig = null;
6266
}
6367

6468
function run(args:Array<String>) {
@@ -82,6 +86,7 @@ class Main {
8286
@doc("Show checks missing from active config") ["-show-missing-checks"] => function () SHOW_MISSING_CHECKS = true,
8387
@doc("Sets the number of checker threads") ["-checkerthreads"] => function (num:Int) overrideCheckerThreads = num,
8488
@doc("Do not use checker threads") ["-nothreads"] => function () disableThreads = true,
89+
@doc("Try to detect your coding style (experimental)") ["-detect"] => function (path) detectCodingStyle(path),
8590
@doc("Show report [DEPRECATED]") ["-report"] => function() Sys.println("\n-report is no longer needed."),
8691
_ => function(arg:String) failWith("Unknown command: " + arg)
8792
]);
@@ -120,6 +125,7 @@ class Main {
120125
loadConfig(configPath);
121126

122127
if (excludePath != null) loadExcludeConfig(excludePath);
128+
123129
start();
124130
}
125131

@@ -133,7 +139,7 @@ class Main {
133139

134140
public function parseAndValidateConfig(config:Config) {
135141

136-
validateAllowedFields(config, Reflect.fields(getEmptyConfig()), "Config");
142+
validateAllowedFields(config, Reflect.fields(ConfigUtils.getEmptyConfig()), "Config");
137143

138144
if (!config.extendsConfigPath.isEmpty()) {
139145
if (seenConfigPaths.contains(config.extendsConfigPath)) failWith("extendsConfig: config file loop detected!");
@@ -337,47 +343,22 @@ class Main {
337343
Sys.exit(0);
338344
}
339345

340-
function getEmptyConfig():Config {
341-
return {
342-
defaultSeverity: SeverityLevel.INFO,
343-
extendsConfigPath: "",
344-
numberOfCheckerThreads: 5,
345-
baseDefines: [],
346-
defineCombinations: [],
347-
checks: [],
348-
exclude: {}
349-
};
350-
}
351-
352346
function generateDefaultConfig(path) {
353347
addAllChecks();
354-
var propsNotAllowed:Array<String> = [
355-
"moduleName", "severity", "type", "categories",
356-
"points", "desc", "currentState", "skipOverStringStart",
357-
"commentStartRE", "commentBlockEndRE", "stringStartRE",
358-
"stringInterpolatedEndRE", "stringLiteralEndRE"
359-
];
360-
var config = getEmptyConfig();
361-
for (check in checker.checks) {
362-
var checkConfig:CheckConfig = {
363-
type: check.getModuleName(),
364-
props: {}
365-
};
366-
for (prop in Reflect.fields(check)) {
367-
if (propsNotAllowed.contains(prop)) continue;
368-
Reflect.setField(checkConfig.props, prop, Reflect.field(check, prop));
369-
}
370-
config.checks.push(checkConfig);
371-
}
372-
373-
var file = File.write(path, false);
374-
file.writeString(Json.stringify(config, null, "\t"));
375-
file.close();
348+
ConfigUtils.saveConfig(checker, path);
376349
Sys.exit(0);
377350
}
378351

379-
function pathJoin(s:String, t:String):String {
380-
return s + "/" + t;
352+
function detectCodingStyle(path:String) {
353+
var checks:Array<Check> = [];
354+
for (checkInfo in info.checks()) {
355+
if (checkInfo.isAlias) continue;
356+
var check:Check = info.build(checkInfo.name);
357+
checks.push(check);
358+
}
359+
var detectedChecks:Array<CheckConfig> = DetectCodingStyle.detectCodingStyle(checks, buildFileList());
360+
if (detectedChecks.length > 0) ConfigUtils.saveCheckConfigList(detectedChecks, path);
361+
Sys.exit(0);
381362
}
382363

383364
function start() {
@@ -386,15 +367,10 @@ class Main {
386367
Sys.exit(0);
387368
}
388369

389-
var files:Array<String> = [];
390-
for (path in paths) traverse(path, files);
391-
files.sortStrings();
392-
393-
var i:Int = 0;
394-
var toProcess:Array<CheckFile> = [for (file in files) { name:file, content:null, index:i++ }];
370+
var toProcess:Array<CheckFile> = buildFileList();
395371

396-
ReporterManager.INSTANCE.addReporter(createReporter(files.length));
397-
if (SHOW_PROGRESS) ReporterManager.INSTANCE.addReporter(new ProgressReporter(files.length));
372+
ReporterManager.INSTANCE.addReporter(createReporter(toProcess.length));
373+
if (SHOW_PROGRESS) ReporterManager.INSTANCE.addReporter(new ProgressReporter(toProcess.length));
398374
if (EXIT_CODE) ReporterManager.INSTANCE.addReporter(new ExitCodeReporter());
399375

400376
#if (neko || cpp)
@@ -418,11 +394,20 @@ class Main {
418394
#end
419395
}
420396

397+
function buildFileList():Array<CheckFile> {
398+
var files:Array<String> = [];
399+
for (path in paths) traverse(path, files);
400+
files.sortStrings();
401+
402+
var i:Int = 0;
403+
return [for (file in files) { name:file, content:null, index:i++ }];
404+
}
405+
421406
function traverse(path:String, files:Array<String>) {
422407
try {
423408
if (FileSystem.isDirectory(path) && !isExcludedFromAll(path)) {
424409
var nodes = FileSystem.readDirectory(path);
425-
for (child in nodes) traverse(pathJoin(path, child), files);
410+
for (child in nodes) traverse(Path.join([path, child]), files);
426411
}
427412
else if (~/(.hx)$/i.match(path) && !isExcludedFromAll(path)) {
428413
files.push(path);

src/checkstyle/SeverityLevel.hx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
package checkstyle;
2+
3+
@:enum
4+
abstract SeverityLevel(String) from String {
5+
var INFO = "INFO";
6+
var WARNING = "WARNING";
7+
var ERROR = "ERROR";
8+
var IGNORE = "IGNORE";
9+
}

src/checkstyle/checks/Check.hx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ class Check {
232232
}
233233
return false;
234234
}
235+
236+
public function detectableInstances():DetectableInstances {
237+
return [];
238+
}
235239
}
236240

237241
enum CheckType {

src/checkstyle/checks/block/ConditionalCompilationCheck.hx

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,20 @@ class ConditionalCompilationCheck extends Check {
135135
}
136136
return -1;
137137
}
138+
139+
override public function detectableInstances():DetectableInstances {
140+
return [{
141+
fixed: [],
142+
properties: [{
143+
propertyName: "policy",
144+
values: [START_OF_LINE, ALIGNED]
145+
},
146+
{
147+
propertyName: "allowSingleline",
148+
values: [true, false]
149+
}]
150+
}];
151+
}
138152
}
139153

140154
@:enum

src/checkstyle/checks/block/LeftCurlyCheck.hx

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,51 @@ class LeftCurlyCheck extends Check {
193193
throw "exit";
194194
}
195195
}
196+
197+
override public function detectableInstances():DetectableInstances {
198+
return [{
199+
fixed: [{
200+
propertyName: "tokens",
201+
value: [
202+
CLASS_DEF,
203+
ENUM_DEF,
204+
ABSTRACT_DEF,
205+
INTERFACE_DEF,
206+
FUNCTION,
207+
FOR,
208+
IF,
209+
WHILE,
210+
SWITCH,
211+
TRY,
212+
CATCH
213+
]
214+
}],
215+
properties: [{
216+
propertyName: "option",
217+
values: [EOL, NLOW, NL]
218+
},
219+
{
220+
propertyName: "ignoreEmptySingleline",
221+
values: [true, false]
222+
}]
223+
},
224+
{
225+
fixed: [{
226+
propertyName: "tokens",
227+
value: [
228+
TYPEDEF_DEF,
229+
]
230+
}],
231+
properties: [{
232+
propertyName: "option",
233+
values: [EOL, NLOW, NL]
234+
},
235+
{
236+
propertyName: "ignoreEmptySingleline",
237+
values: [true, false]
238+
}]
239+
}];
240+
}
196241
}
197242

198243
typedef ParentToken = {

src/checkstyle/checks/block/RightCurlyCheck.hx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -159,6 +159,16 @@ class RightCurlyCheck extends Check {
159159
throw "exit";
160160
}
161161
}
162+
163+
override public function detectableInstances():DetectableInstances {
164+
return [{
165+
fixed: [],
166+
properties: [{
167+
propertyName: "option",
168+
values: [SAME, ALONE, ALONE_OR_SINGLELINE]
169+
}]
170+
}];
171+
}
162172
}
163173

164174
@:enum

src/checkstyle/checks/modifier/RedundantModifierCheck.hx

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,24 +18,46 @@ class RedundantModifierCheck extends Check {
1818
}
1919

2020
override function actualRun() {
21+
var forcePrivate:Bool = enforcePrivate;
22+
var forcePublic:Bool = enforcePublic;
2123
if (enforcePublicPrivate) {
22-
enforcePrivate = true;
23-
enforcePublic = true;
24+
forcePrivate = true;
25+
forcePublic = true;
2426
}
25-
forEachField(checkField);
27+
forEachField(function(f:Field, p:ParentType) {
28+
checkField(f, p, forcePrivate, forcePublic);
29+
});
2630
}
2731

28-
function checkField(f:Field, p:ParentType) {
32+
function checkField(f:Field, p:ParentType, forcePrivate:Bool, forcePublic:Bool) {
2933
var isDefaultPrivate = f.isDefaultPrivate(p);
3034
var implicitAccess = isDefaultPrivate ? "private" : "public";
3135
if (!f.access.contains(APublic) && !f.access.contains(APrivate)) {
32-
if ((!isDefaultPrivate && enforcePublic) || (isDefaultPrivate && enforcePrivate)) {
36+
if ((!isDefaultPrivate && forcePublic) || (isDefaultPrivate && forcePrivate)) {
3337
logPos('Missing "$implicitAccess" keyword for "${f.name}"', f.pos);
3438
}
3539
}
3640

37-
if ((!enforcePrivate && isDefaultPrivate && f.access.contains(APrivate)) || (!enforcePublic && !isDefaultPrivate && f.access.contains(APublic))) {
41+
if ((!forcePrivate && isDefaultPrivate && f.access.contains(APrivate)) || (!forcePublic && !isDefaultPrivate && f.access.contains(APublic))) {
3842
logPos('"$implicitAccess" keyword is redundant for "${f.name}"', f.pos);
3943
}
4044
}
45+
46+
override public function detectableInstances():DetectableInstances {
47+
return [{
48+
fixed: [],
49+
properties: [{
50+
propertyName: "enforcePrivate",
51+
values: [true, false]
52+
},
53+
{
54+
propertyName: "enforcePublic",
55+
values: [true, false]
56+
},
57+
{
58+
propertyName: "enforcePublicPrivate",
59+
values: [true, false]
60+
}]
61+
}];
62+
}
4163
}

0 commit comments

Comments
 (0)