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

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

* update inifiled to 1.0.2

* Compile dependencies separately, s.t. their unittests don't get executed
This commit is contained in:
Sebastian Wilzbach 2017-06-30 04:31:07 +02:00 committed by Basile Burg
parent 9bf010a6e7
commit 45ef861268
6 changed files with 181 additions and 52 deletions

View File

@ -304,3 +304,29 @@ 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.
Moreover, 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."
```
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

@ -12,7 +12,7 @@
"dependencies": {
"libdparse": "~>0.7.1-beta.5",
"dsymbol": "~>0.2.0",
"inifiled": ">=0.0.6",
"inifiled": ">=1.0.2",
"emsi_containers": "~>0.5.3",
"libddoc": "~>0.2.0"
},

@ -1 +1 @@
Subproject commit e4f63f126ddddb3e496574fec0f76b24e61b1d51
Subproject commit 35f8d2d914560f8c73cf5e6b80b8e0f47f498d64

View File

@ -5,14 +5,15 @@ DMD := $(DC)
GDC := gdc
LDC := ldc2
OBJ_DIR := obj
SRC := \
LIB_SRC := \
$(shell find containers/src -name "*.d")\
$(shell find dsymbol/src -name "*.d")\
$(shell find inifiled/source/ -name "*.d")\
$(shell find libdparse/src/std/experimental/ -name "*.d")\
$(shell find libdparse/src/dparse/ -name "*.d")\
$(shell find libddoc/src -name "*.d")\
$(shell find src/ -name "*.d")
$(shell find libddoc/src -name "*.d")
PROJECT_SRC := $(shell find src/ -name "*.d")
SRC := $(LIB_SRC) $(PROJECT_SRC)
INCLUDE_PATHS = \
-Iinifiled/source -Isrc\
-Ilibdparse/src\
@ -21,7 +22,7 @@ INCLUDE_PATHS = \
VERSIONS =
DEBUG_VERSIONS = -version=dparse_verbose
DMD_FLAGS = -w -inline -release -O -J. -od${OBJ_DIR} -version=StdLoggerDisableWarning
DMD_TEST_FLAGS = -w -g -unittest -J.
DMD_TEST_FLAGS = -w -g -J. -version=StdLoggerDisableWarning
all: dmdbuild
ldc: ldcbuild
@ -46,8 +47,12 @@ ldcbuild: githash
mkdir -p bin
${LDC} -O5 -release -oq -of=bin/dscanner ${VERSIONS} ${INCLUDE_PATHS} ${SRC} -J.
test: githash
${DC} -w -g -J. -unittest ${INCLUDE_PATHS} ${SRC} -ofbin/dscanner-unittest -version=StdLoggerDisableWarning
# compile the dependencies separately, s.t. their unittests don't get executed
bin/dscanner-unittest-lib.a: ${LIB_SRC}
${DC} ${DMD_TEST_FLAGS} -c ${INCLUDE_PATHS} ${LIB_SRC} -of$@
test: bin/dscanner-unittest-lib.a githash
${DC} ${DMD_TEST_FLAGS} -unittest ${INCLUDE_PATHS} bin/dscanner-unittest-lib.a ${PROJECT_SRC} -ofbin/dscanner-unittest
./bin/dscanner-unittest
rm -f bin/dscanner-unittest

View File

@ -188,4 +188,25 @@ struct StaticAnalysisConfig
@INI("Check public declarations without a documented unittest")
string has_public_example = Check.disabled;
@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

@ -206,6 +206,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)
{
@ -220,6 +291,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);
@ -232,171 +308,172 @@ 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);
@ -405,7 +482,7 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a
analysisConfig.has_public_example == 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);