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. using its formatting switch.
$ dscanner --ast helloworld.d | xmllint --format - $ 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") @INI("Check for redundant attributes")
string redundant_attributes_check = Check.enabled; 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); ? &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, MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig analysisConfig,
ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true) ref ModuleCache moduleCache, const(Token)[] tokens, bool staticAnalyze = true)
{ {
@ -219,6 +290,11 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
else else
enum ut = false; 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, auto first = scoped!FirstPass(m, internString(fileName), symbolAllocator,
symbolAllocator, true, &moduleCache, null); 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); scope(exit) typeid(Scope).destroy(first.moduleScope);
BaseAnalyzer[] checks; BaseAnalyzer[] checks;
if (analysisConfig.asm_style_check != Check.disabled) with(analysisConfig)
if (moduleName.shouldRun!"asm_style_check"(analysisConfig))
checks ~= new AsmStyleCheck(fileName, moduleScope, 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, checks ~= new BackwardsRangeCheck(fileName, moduleScope,
analysisConfig.backwards_range_check == Check.skipTests && !ut); 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, checks ~= new BuiltinPropertyNameCheck(fileName, moduleScope,
analysisConfig.builtin_property_names_check == Check.skipTests && !ut); 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, checks ~= new CommaExpressionCheck(fileName, moduleScope,
analysisConfig.comma_expression_check == Check.skipTests && !ut); analysisConfig.comma_expression_check == Check.skipTests && !ut);
if (analysisConfig.constructor_check != Check.disabled) if (moduleName.shouldRun!"constructor_check"(analysisConfig))
checks ~= new ConstructorCheck(fileName, moduleScope, checks ~= new ConstructorCheck(fileName, moduleScope,
analysisConfig.constructor_check == Check.skipTests && !ut); 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, checks ~= new UnmodifiedFinder(fileName, moduleScope,
analysisConfig.could_be_immutable_check == Check.skipTests && !ut); 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, checks ~= new DeleteCheck(fileName, moduleScope,
analysisConfig.delete_check == Check.skipTests && !ut); analysisConfig.delete_check == Check.skipTests && !ut);
if (analysisConfig.duplicate_attribute != Check.disabled) if (moduleName.shouldRun!"duplicate_attribute"(analysisConfig))
checks ~= new DuplicateAttributeCheck(fileName, moduleScope, checks ~= new DuplicateAttributeCheck(fileName, moduleScope,
analysisConfig.duplicate_attribute == Check.skipTests && !ut); 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, checks ~= new EnumArrayLiteralCheck(fileName, moduleScope,
analysisConfig.enum_array_literal_check == Check.skipTests && !ut); 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, checks ~= new PokemonExceptionCheck(fileName, moduleScope,
analysisConfig.exception_check == Check.skipTests && !ut); 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, checks ~= new FloatOperatorCheck(fileName, moduleScope,
analysisConfig.float_operator_check == Check.skipTests && !ut); 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, checks ~= new FunctionAttributeCheck(fileName, moduleScope,
analysisConfig.function_attribute_check == Check.skipTests && !ut); 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, checks ~= new IfElseSameCheck(fileName, moduleScope,
analysisConfig.if_else_same_check == Check.skipTests&& !ut); 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, checks ~= new LabelVarNameCheck(fileName, moduleScope,
analysisConfig.label_var_same_name_check == Check.skipTests && !ut); 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, checks ~= new LengthSubtractionCheck(fileName, moduleScope,
analysisConfig.length_subtraction_check == Check.skipTests && !ut); 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, checks ~= new LocalImportCheck(fileName, moduleScope,
analysisConfig.local_import_check == Check.skipTests && !ut); 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, checks ~= new LogicPrecedenceCheck(fileName, moduleScope,
analysisConfig.logical_precedence_check == Check.skipTests && !ut); 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, checks ~= new MismatchedArgumentCheck(fileName, moduleScope,
analysisConfig.mismatched_args_check == Check.skipTests && !ut); 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, checks ~= new NumberStyleCheck(fileName, moduleScope,
analysisConfig.number_style_check == Check.skipTests && !ut); 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, checks ~= new ObjectConstCheck(fileName, moduleScope,
analysisConfig.object_const_check == Check.skipTests && !ut); 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, checks ~= new OpEqualsWithoutToHashCheck(fileName, moduleScope,
analysisConfig.opequals_tohash_check == Check.skipTests && !ut); 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, checks ~= new RedundantParenCheck(fileName, moduleScope,
analysisConfig.redundant_parens_check == Check.skipTests && !ut); analysisConfig.redundant_parens_check == Check.skipTests && !ut);
if (analysisConfig.style_check != Check.disabled) if (moduleName.shouldRun!"style_check"(analysisConfig))
checks ~= new StyleChecker(fileName, moduleScope, checks ~= new StyleChecker(fileName, moduleScope,
analysisConfig.style_check == Check.skipTests && !ut); 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, checks ~= new UndocumentedDeclarationCheck(fileName, moduleScope,
analysisConfig.undocumented_declaration_check == Check.skipTests && !ut); 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, checks ~= new UnusedLabelCheck(fileName, moduleScope,
analysisConfig.unused_label_check == Check.skipTests && !ut); 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, checks ~= new UnusedVariableCheck(fileName, moduleScope,
analysisConfig.unused_variable_check == Check.skipTests && !ut); 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, checks ~= new LineLengthCheck(fileName, tokens,
analysisConfig.long_line_check == Check.skipTests && !ut); 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, checks ~= new AutoRefAssignmentCheck(fileName,
analysisConfig.auto_ref_assignment_check == Check.skipTests && !ut); 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, checks ~= new IncorrectInfiniteRangeCheck(fileName,
analysisConfig.incorrect_infinite_range_check == Check.skipTests && !ut); 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, checks ~= new UselessAssertCheck(fileName,
analysisConfig.useless_assert_check == Check.skipTests && !ut); 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, checks ~= new AliasSyntaxCheck(fileName,
analysisConfig.alias_syntax_check == Check.skipTests && !ut); 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, checks ~= new StaticIfElse(fileName,
analysisConfig.static_if_else_check == Check.skipTests && !ut); 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, checks ~= new LambdaReturnCheck(fileName,
analysisConfig.lambda_return_check == Check.skipTests && !ut); 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, checks ~= new AutoFunctionChecker(fileName,
analysisConfig.auto_function_check == Check.skipTests && !ut); analysisConfig.auto_function_check == Check.skipTests && !ut);
if (analysisConfig.imports_sortedness != Check.disabled) if (moduleName.shouldRun!"imports_sortedness"(analysisConfig))
checks ~= new ImportSortednessCheck(fileName, checks ~= new ImportSortednessCheck(fileName,
analysisConfig.imports_sortedness == Check.skipTests && !ut); analysisConfig.imports_sortedness == Check.skipTests && !ut);
if (analysisConfig.explicitly_annotated_unittests != Check.disabled) if (moduleName.shouldRun!"explicitly_annotated_unittests"(analysisConfig))
checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName, checks ~= new ExplicitlyAnnotatedUnittestCheck(fileName,
analysisConfig.explicitly_annotated_unittests == Check.skipTests && !ut); 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, checks ~= new ProperlyDocumentedPublicFunctions(fileName,
analysisConfig.properly_documented_public_functions == Check.skipTests && !ut); 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, checks ~= new FinalAttributeChecker(fileName,
analysisConfig.final_attribute_check == Check.skipTests && !ut); 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, checks ~= new VcallCtorChecker(fileName,
analysisConfig.vcall_in_ctor == Check.skipTests && !ut); analysisConfig.vcall_in_ctor == Check.skipTests && !ut);
if (analysisConfig.useless_initializer != Check.disabled) if (moduleName.shouldRun!"useless_initializer"(analysisConfig))
checks ~= new UselessInitializerChecker(fileName, checks ~= new UselessInitializerChecker(fileName,
analysisConfig.useless_initializer == Check.skipTests && !ut); 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, checks ~= new AllManCheck(fileName, tokens,
analysisConfig.allman_braces_check == Check.skipTests && !ut); 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, checks ~= new RedundantAttributesCheck(fileName, moduleScope,
analysisConfig.redundant_attributes_check == Check.skipTests && !ut); analysisConfig.redundant_attributes_check == Check.skipTests && !ut);
version (none) version (none)
if (analysisConfig.redundant_if_check != Check.disabled) if (moduleName.shouldRun!"redundant_if_check"(analysisConfig))
checks ~= new IfStatementCheck(fileName, moduleScope, checks ~= new IfStatementCheck(fileName, moduleScope,
analysisConfig.redundant_if_check == Check.skipTests && !ut); analysisConfig.redundant_if_check == Check.skipTests && !ut);