Fix #457 - Allow to apply checks only for specific modules

This commit is contained in:
Sebastian Wilzbach 2017-06-15 21:02:04 +02:00
parent cf3d702720
commit d9df33a53f
3 changed files with 170 additions and 44 deletions

View File

@ -296,3 +296,31 @@ For more readable output, pipe the command through [xmllint](http://xmlsoft.org/
using its formatting switch.
$ dscanner --ast helloworld.d | xmllint --format -
Selecting modules for a specific check
--------------------------------------
It is possible to create a new section `analysis.config.ModuleFilters` in the `.dscanner.ini`.
In this optional section a comma-separated list of inclusion and exclusion selectors can
be specified for every check on which selective filtering should be applied.
These given selectors match on the module name and partial matches (`std.` or `.foo.`) are possible.
Morover, every selectors must begin with either `+` (inclusion) or `-` (exclusion).
Exclusion selectors take precedence over all inclusion operators.
Of course, for every check a different selector set can given:
```ini
[analysis.config.ModuleFilters]
final_attribute_check = "+std.foo,+std.bar"
useless_initializer = "-std."
;pseudo variable (workaround against an inifiled bug)
foo=""
```
A few examples:
- `+std.`: Includes all modules matching `std.`
- `+std.bitmanip,+std.json`: Applies the check only for these two modules
- `-std.bitmanip,-std.json`: Applies the check for all modules, but these two
- `+.bar`: Includes all modules matching `.bar` (e.g. `foo.bar`, `a.b.c.barros`)
- `-etc.`: Excludes all modules from `.etc`
- `+std,-std.internal`: Includes entire `std`, except for the internal modules

View File

@ -185,4 +185,25 @@ struct StaticAnalysisConfig
@INI("Check for redundant attributes")
string redundant_attributes_check = Check.enabled;
@INI("Module-specific filters")
ModuleFilters filters;
}
private template ModuleFiltersMixin(A)
{
const string ModuleFiltersMixin = () {
string s;
foreach (mem; __traits(allMembers, StaticAnalysisConfig))
static if (is(typeof(__traits(getMember, StaticAnalysisConfig, mem)) == string))
s ~= `@INI("Exclude/Import modules") string[] ` ~ mem ~ ";\n";
return s;
}();
}
@INI("ModuleFilters. +std.,-std.internal")
struct ModuleFilters
{
mixin(ModuleFiltersMixin!int);
}

View File

@ -205,6 +205,77 @@ const(Module) parseModule(string fileName, ubyte[] code, RollbackAllocator* p,
? &messageFunctionJSON : &messageFunction, errorCount, warningCount);
}
/**
Checks whether a module is part of a user-specified include/exclude list.
The user can specify a comma-separated list of filters, everyone needs to start with
either a '+' (inclusion) or '-' (exclusion).
If no includes are specified, all modules are included.
*/
bool shouldRun(string a)(string moduleName, const ref StaticAnalysisConfig config)
{
if (mixin("config." ~ a) == Check.disabled)
return false;
// By default, run the check
if (!moduleName.length)
return true;
auto filters = mixin("config.filters." ~ a);
// Check if there are filters are defined
// filters starting with a comma are invalid
if (filters.length == 0 || filters[0].length == 0)
return true;
auto includers = filters.filter!(f => f[0] == '+').map!(f => f[1..$]);
auto excluders = filters.filter!(f => f[0] == '-').map!(f => f[1..$]);
// exclusion has preference over inclusion
if (!excluders.empty && excluders.any!(s => moduleName.canFind(s)))
return false;
if (!includers.empty)
return includers.any!(s => moduleName.canFind(s));
// by default: include all modules
return true;
}
///
unittest
{
bool test(string moduleName, string filters)
{
StaticAnalysisConfig config;
// it doesn't matter which check we test here
config.asm_style_check = Check.enabled;
// this is done automatically by inifiled
config.filters.asm_style_check = filters.split(",");
return shouldRun!"asm_style_check"(moduleName, config);
}
// test inclusion
assert(test("std.foo", "+std."));
// partial matches are ok
assert(test("std.foo", "+bar,+foo"));
// full as well
assert(test("std.foo", "+bar,+std.foo,+foo"));
// mismatch
assert(!test("std.foo", "+bar,+banana"));
// test exclusion
assert(!test("std.foo", "-std."));
assert(!test("std.foo", "-bar,-std.foo"));
assert(!test("std.foo", "-bar,-foo"));
// mismatch
assert(test("std.foo", "-bar,-banana"));
// test combination (exclusion has precedence)
assert(!test("std.foo", "+foo,-foo"));
assert(test("std.foo", "+foo,-bar"));
assert(test("std.bar.foo", "-barr,+bar"));
}
MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig analysisConfig,
ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true)
{
@ -219,6 +290,11 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
else
enum ut = false;
string moduleName;
if (m !is null && m.moduleDeclaration !is null &&
m.moduleDeclaration.moduleName !is null &&
m.moduleDeclaration.moduleName.identifiers !is null)
moduleName = m.moduleDeclaration.moduleName.identifiers.map!(e => e.text).join(".");
auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
symbolAllocator, true, &moduleCache, null);
@ -231,176 +307,177 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
scope(exit) typeid(Scope).destroy(first.moduleScope);
BaseAnalyzer[] checks;
if (analysisConfig.asm_style_check != Check.disabled)
with(analysisConfig)
if (moduleName.shouldRun!"asm_style_check"(analysisConfig))
checks ~= new AsmStyleCheck(fileName, moduleScope,
analysisConfig.asm_style_check == Check.skipTests && !ut);
asm_style_check == Check.skipTests && !ut);
if (analysisConfig.backwards_range_check != Check.disabled)
if (moduleName.shouldRun!"backwards_range_check"(analysisConfig))
checks ~= new BackwardsRangeCheck(fileName, moduleScope,
analysisConfig.backwards_range_check == Check.skipTests && !ut);
if (analysisConfig.builtin_property_names_check != Check.disabled)
if (moduleName.shouldRun!"builtin_property_names_check"(analysisConfig))
checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope,
analysisConfig.builtin_property_names_check == Check.skipTests && !ut);
if (analysisConfig.comma_expression_check != Check.disabled)
if (moduleName.shouldRun!"comma_expression_check"(analysisConfig))
checks ~= new CommaExpressionCheck(fileName, moduleScope,
analysisConfig.comma_expression_check == Check.skipTests && !ut);
if (analysisConfig.constructor_check != Check.disabled)
if (moduleName.shouldRun!"constructor_check"(analysisConfig))
checks ~= new ConstructorCheck(fileName, moduleScope,
analysisConfig.constructor_check == Check.skipTests && !ut);
if (analysisConfig.could_be_immutable_check != Check.disabled)
if (moduleName.shouldRun!"could_be_immutable_check"(analysisConfig))
checks ~= new UnmodifiedFinder(fileName, moduleScope,
analysisConfig.could_be_immutable_check == Check.skipTests && !ut);
if (analysisConfig.delete_check != Check.disabled)
if (moduleName.shouldRun!"delete_check"(analysisConfig))
checks ~= new DeleteCheck(fileName, moduleScope,
analysisConfig.delete_check == Check.skipTests && !ut);
if (analysisConfig.duplicate_attribute != Check.disabled)
if (moduleName.shouldRun!"duplicate_attribute"(analysisConfig))
checks ~= new DuplicateAttributeCheck(fileName, moduleScope,
analysisConfig.duplicate_attribute == Check.skipTests && !ut);
if (analysisConfig.enum_array_literal_check != Check.disabled)
if (moduleName.shouldRun!"enum_array_literal_check"(analysisConfig))
checks ~= new EnumArrayLiteralCheck(fileName, moduleScope,
analysisConfig.enum_array_literal_check == Check.skipTests && !ut);
if (analysisConfig.exception_check != Check.disabled)
if (moduleName.shouldRun!"exception_check"(analysisConfig))
checks ~= new PokemonExceptionCheck(fileName, moduleScope,
analysisConfig.exception_check == Check.skipTests && !ut);
if (analysisConfig.float_operator_check != Check.disabled)
if (moduleName.shouldRun!"float_operator_check"(analysisConfig))
checks ~= new FloatOperatorCheck(fileName, moduleScope,
analysisConfig.float_operator_check == Check.skipTests && !ut);
if (analysisConfig.function_attribute_check != Check.disabled)
if (moduleName.shouldRun!"function_attribute_check"(analysisConfig))
checks ~= new FunctionAttributeCheck(fileName, moduleScope,
analysisConfig.function_attribute_check == Check.skipTests && !ut);
if (analysisConfig.if_else_same_check != Check.disabled)
if (moduleName.shouldRun!"if_else_same_check"(analysisConfig))
checks ~= new IfElseSameCheck(fileName, moduleScope,
analysisConfig.if_else_same_check == Check.skipTests&& !ut);
if (analysisConfig.label_var_same_name_check != Check.disabled)
if (moduleName.shouldRun!"label_var_same_name_check"(analysisConfig))
checks ~= new LabelVarNameCheck(fileName, moduleScope,
analysisConfig.label_var_same_name_check == Check.skipTests && !ut);
if (analysisConfig.length_subtraction_check != Check.disabled)
if (moduleName.shouldRun!"length_subtraction_check"(analysisConfig))
checks ~= new LengthSubtractionCheck(fileName, moduleScope,
analysisConfig.length_subtraction_check == Check.skipTests && !ut);
if (analysisConfig.local_import_check != Check.disabled)
if (moduleName.shouldRun!"local_import_check"(analysisConfig))
checks ~= new LocalImportCheck(fileName, moduleScope,
analysisConfig.local_import_check == Check.skipTests && !ut);
if (analysisConfig.logical_precedence_check != Check.disabled)
if (moduleName.shouldRun!"logical_precedence_check"(analysisConfig))
checks ~= new LogicPrecedenceCheck(fileName, moduleScope,
analysisConfig.logical_precedence_check == Check.skipTests && !ut);
if (analysisConfig.mismatched_args_check != Check.disabled)
if (moduleName.shouldRun!"mismatched_args_check"(analysisConfig))
checks ~= new MismatchedArgumentCheck(fileName, moduleScope,
analysisConfig.mismatched_args_check == Check.skipTests && !ut);
if (analysisConfig.number_style_check != Check.disabled)
if (moduleName.shouldRun!"number_style_check"(analysisConfig))
checks ~= new NumberStyleCheck(fileName, moduleScope,
analysisConfig.number_style_check == Check.skipTests && !ut);
if (analysisConfig.object_const_check != Check.disabled)
if (moduleName.shouldRun!"object_const_check"(analysisConfig))
checks ~= new ObjectConstCheck(fileName, moduleScope,
analysisConfig.object_const_check == Check.skipTests && !ut);
if (analysisConfig.opequals_tohash_check != Check.disabled)
if (moduleName.shouldRun!"opequals_tohash_check"(analysisConfig))
checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope,
analysisConfig.opequals_tohash_check == Check.skipTests && !ut);
if (analysisConfig.redundant_parens_check != Check.disabled)
if (moduleName.shouldRun!"redundant_parens_check"(analysisConfig))
checks ~= new RedundantParenCheck(fileName, moduleScope,
analysisConfig.redundant_parens_check == Check.skipTests && !ut);
if (analysisConfig.style_check != Check.disabled)
if (moduleName.shouldRun!"style_check"(analysisConfig))
checks ~= new StyleChecker(fileName, moduleScope,
analysisConfig.style_check == Check.skipTests && !ut);
if (analysisConfig.undocumented_declaration_check != Check.disabled)
if (moduleName.shouldRun!"undocumented_declaration_check"(analysisConfig))
checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope,
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut);
if (analysisConfig.unused_label_check != Check.disabled)
if (moduleName.shouldRun!"unused_label_check"(analysisConfig))
checks ~= new UnusedLabelCheck(fileName, moduleScope,
analysisConfig.unused_label_check == Check.skipTests && !ut);
if (analysisConfig.unused_variable_check != Check.disabled)
if (moduleName.shouldRun!"unused_variable_check"(analysisConfig))
checks ~= new UnusedVariableCheck(fileName, moduleScope,
analysisConfig.unused_variable_check == Check.skipTests && !ut);
if (analysisConfig.long_line_check != Check.disabled)
if (moduleName.shouldRun!"long_line_check"(analysisConfig))
checks ~= new LineLengthCheck(fileName, tokens,
analysisConfig.long_line_check == Check.skipTests && !ut);
if (analysisConfig.auto_ref_assignment_check != Check.disabled)
if (moduleName.shouldRun!"auto_ref_assignment_check"(analysisConfig))
checks ~= new AutoRefAssignmentCheck(fileName,
analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut);
if (analysisConfig.incorrect_infinite_range_check != Check.disabled)
if (moduleName.shouldRun!"incorrect_infinite_range_check"(analysisConfig))
checks ~= new IncorrectInfiniteRangeCheck(fileName,
analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut);
if (analysisConfig.useless_assert_check != Check.disabled)
if (moduleName.shouldRun!"useless_assert_check"(analysisConfig))
checks ~= new UselessAssertCheck(fileName,
analysisConfig.useless_assert_check == Check.skipTests && !ut);
if (analysisConfig.alias_syntax_check != Check.disabled)
if (moduleName.shouldRun!"alias_syntax_check"(analysisConfig))
checks ~= new AliasSyntaxCheck(fileName,
analysisConfig.alias_syntax_check == Check.skipTests && !ut);
if (analysisConfig.static_if_else_check != Check.disabled)
if (moduleName.shouldRun!"static_if_else_check"(analysisConfig))
checks ~= new StaticIfElse(fileName,
analysisConfig.static_if_else_check == Check.skipTests && !ut);
if (analysisConfig.lambda_return_check != Check.disabled)
if (moduleName.shouldRun!"lambda_return_check"(analysisConfig))
checks ~= new LambdaReturnCheck(fileName,
analysisConfig.lambda_return_check == Check.skipTests && !ut);
if (analysisConfig.auto_function_check != Check.disabled)
if (moduleName.shouldRun!"auto_function_check"(analysisConfig))
checks ~= new AutoFunctionChecker(fileName,
analysisConfig.auto_function_check == Check.skipTests && !ut);
if (analysisConfig.imports_sortedness != Check.disabled)
if (moduleName.shouldRun!"imports_sortedness"(analysisConfig))
checks ~= new ImportSortednessCheck(fileName,
analysisConfig.imports_sortedness == Check.skipTests && !ut);
if (analysisConfig.explicitly_annotated_unittests != Check.disabled)
if (moduleName.shouldRun!"explicitly_annotated_unittests"(analysisConfig))
checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName,
analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut);
if (analysisConfig.properly_documented_public_functions != Check.disabled)
if (moduleName.shouldRun!"properly_documented_public_functions"(analysisConfig))
checks ~= new ProperlyDocumentedPublicFunctions(fileName,
analysisConfig.properly_documented_public_functions == Check.skipTests && !ut);
if (analysisConfig.final_attribute_check != Check.disabled)
if (moduleName.shouldRun!"final_attribute_check"(analysisConfig))
checks ~= new FinalAttributeChecker(fileName,
analysisConfig.final_attribute_check == Check.skipTests && !ut);
if (analysisConfig.vcall_in_ctor != Check.disabled)
if (moduleName.shouldRun!"vcall_in_ctor"(analysisConfig))
checks ~= new VcallCtorChecker(fileName,
analysisConfig.vcall_in_ctor == Check.skipTests && !ut);
if (analysisConfig.useless_initializer != Check.disabled)
if (moduleName.shouldRun!"useless_initializer"(analysisConfig))
checks ~= new UselessInitializerChecker(fileName,
analysisConfig.useless_initializer == Check.skipTests && !ut);
if (analysisConfig.allman_braces_check != Check.disabled)
if (moduleName.shouldRun!"allman_braces_check"(analysisConfig))
checks ~= new AllManCheck(fileName, tokens,
analysisConfig.allman_braces_check == Check.skipTests && !ut);
if (analysisConfig.redundant_attributes_check != Check.disabled)
if (moduleName.shouldRun!"redundant_attributes_check"(analysisConfig))
checks ~= new RedundantAttributesCheck(fileName, moduleScope,
analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
version (none)
if (analysisConfig.redundant_if_check != Check.disabled)
if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
checks ~= new IfStatementCheck(fileName, moduleScope,
analysisConfig.redundant_if_check == Check.skipTests && !ut);