diff --git a/src/dscanner/analysis/autofix.d b/src/dscanner/analysis/autofix.d new file mode 100644 index 00000000..fc444773 --- /dev/null +++ b/src/dscanner/analysis/autofix.d @@ -0,0 +1,194 @@ +module dscanner.analysis.autofix; + +import std.algorithm : filter, findSplit; +import std.conv : to; +import std.functional : toDelegate; +import std.stdio; + +import dparse.lexer; +import dparse.rollback_allocator; +import dparse.ast : Module; + +import dsymbol.modulecache : ModuleCache; + +import dscanner.analysis.base : AutoFix, AutoFixFormatting, BaseAnalyzer, Message; +import dscanner.analysis.config : StaticAnalysisConfig; +import dscanner.analysis.run : analyze, doNothing; +import dscanner.utils : readFile, readStdin; + +void resolveAutoFixes( + ref Message message, + string fileName, + ref ModuleCache moduleCache, + scope const(Token)[] tokens, + const Module m, + const StaticAnalysisConfig analysisConfig, + const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid +) +{ + resolveAutoFixes(message.checkName, message.autofixes, fileName, moduleCache, + tokens, m, analysisConfig, overrideFormattingConfig); +} + +void resolveAutoFixes(string messageCheckName, AutoFix[] autofixes, string fileName, + ref ModuleCache moduleCache, + scope const(Token)[] tokens, const Module m, + const StaticAnalysisConfig analysisConfig, + const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) +{ + import core.memory : GC; + import dsymbol.conversion.first : FirstPass; + import dsymbol.conversion.second : secondPass; + import dsymbol.scope_ : Scope; + import dsymbol.semantic : SemanticSymbol; + import dsymbol.string_interning : internString; + import dsymbol.symbol : DSymbol; + import dscanner.analysis.run : getAnalyzersForModuleAndConfig; + + const(AutoFixFormatting) formattingConfig = + overrideFormattingConfig is AutoFixFormatting.invalid + ? analysisConfig.getAutoFixFormattingConfig() + : overrideFormattingConfig; + + scope first = new FirstPass(m, internString(fileName), &moduleCache, null); + first.run(); + + secondPass(first.rootSymbol, first.moduleScope, moduleCache); + auto moduleScope = first.moduleScope; + scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol); + scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol); + scope(exit) typeid(Scope).destroy(first.moduleScope); + + GC.disable; + scope (exit) + GC.enable; + + foreach (BaseAnalyzer check; getAnalyzersForModuleAndConfig(fileName, tokens, m, analysisConfig, moduleScope)) + { + if (check.getName() == messageCheckName) + { + foreach (ref autofix; autofixes) + autofix.resolveAutoFixFromCheck(check, m, tokens, formattingConfig); + return; + } + } + + throw new Exception("Cannot find analyzer " ~ messageCheckName + ~ " to resolve autofix with."); +} + +void resolveAutoFixFromCheck( + ref AutoFix autofix, + BaseAnalyzer check, + const Module m, + scope const(Token)[] tokens, + const AutoFixFormatting formattingConfig +) +{ + import std.sumtype : match; + + autofix.replacements.match!( + (AutoFix.ResolveContext context) { + autofix.replacements = check.resolveAutoFix(m, tokens, context, formattingConfig); + }, + (_) {} + ); +} + +AutoFix.CodeReplacement[] resolveAutoFix(string messageCheckName, AutoFix.ResolveContext context, + string fileName, + ref ModuleCache moduleCache, + scope const(Token)[] tokens, const Module m, + const StaticAnalysisConfig analysisConfig, + const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) +{ + AutoFix temp; + temp.replacements = context; + resolveAutoFixes(messageCheckName, (&temp)[0 .. 1], fileName, moduleCache, + tokens, m, analysisConfig, overrideFormattingConfig); + return temp.expectReplacements("resolving didn't work?!"); +} + +void listAutofixes( + StaticAnalysisConfig config, + string resolveMessage, + bool usingStdin, + string fileName, + StringCache* cache, + ref ModuleCache moduleCache +) +{ + import dparse.parser : parseModule; + import dscanner.analysis.base : Message; + import std.format : format; + import std.json : JSONValue; + + union RequestedLocation + { + struct + { + uint line, column; + } + ulong bytes; + } + + RequestedLocation req; + bool isBytes = resolveMessage[0] == 'b'; + if (isBytes) + req.bytes = resolveMessage[1 .. $].to!ulong; + else + { + auto parts = resolveMessage.findSplit(":"); + req.line = parts[0].to!uint; + req.column = parts[2].to!uint; + } + + bool matchesCursor(Message m) + { + return isBytes + ? req.bytes >= m.startIndex && req.bytes <= m.endIndex + : req.line >= m.startLine && req.line <= m.endLine + && (req.line > m.startLine || req.column >= m.startColumn) + && (req.line < m.endLine || req.column <= m.endColumn); + } + + RollbackAllocator rba; + LexerConfig lexerConfig; + lexerConfig.fileName = fileName; + lexerConfig.stringBehavior = StringBehavior.source; + auto tokens = getTokensForParser(usingStdin ? readStdin() + : readFile(fileName), lexerConfig, cache); + auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing)); + + auto messages = analyze(fileName, mod, config, moduleCache, tokens); + + with (stdout.lockingTextWriter) + { + put("["); + foreach (message; messages[].filter!matchesCursor) + { + resolveAutoFixes(message, fileName, moduleCache, tokens, mod, config); + + foreach (i, autofix; message.autofixes) + { + put(i == 0 ? "\n" : ",\n"); + put("\t{\n"); + put(format!"\t\t\"name\": %s,\n"(JSONValue(autofix.name))); + put("\t\t\"replacements\": ["); + foreach (j, replacement; autofix.expectReplacements) + { + put(j == 0 ? "\n" : ",\n"); + put(format!"\t\t\t{\"range\": [%d, %d], \"newText\": %s}"( + replacement.range[0], + replacement.range[1], + JSONValue(replacement.newText))); + } + put("\n"); + put("\t\t]\n"); + put("\t}"); + } + } + put("\n]"); + } + stdout.flush(); +} diff --git a/src/dscanner/analysis/helpers.d b/src/dscanner/analysis/helpers.d index 8bfedd7a..ebf8b5f4 100644 --- a/src/dscanner/analysis/helpers.d +++ b/src/dscanner/analysis/helpers.d @@ -369,6 +369,8 @@ void assertAnalyzerWarningsDMD(string code, const StaticAnalysisConfig config, b import std.stdio : File; import std.file : exists, remove; + import dscanner.analysis.rundmd : analyzeDmd; + auto deleteme = "test.txt"; File f = File(deleteme, "w"); scope(exit) diff --git a/src/dscanner/analysis/run.d b/src/dscanner/analysis/run.d index fa87e11e..6bbee17b 100644 --- a/src/dscanner/analysis/run.d +++ b/src/dscanner/analysis/run.d @@ -11,6 +11,7 @@ import dparse.ast; import dparse.lexer; import dparse.parser; import dparse.rollback_allocator; + import std.algorithm; import std.array; import std.conv; @@ -398,6 +399,8 @@ bool analyze(string[] fileNames, const StaticAnalysisConfig config, string error import std.string : toStringz; import dmd.arraytypes : Strings; + import dscanner.analysis.rundmd : analyzeDmd; + bool hasErrors; foreach (fileName; fileNames) { @@ -521,90 +524,6 @@ bool autofix(string[] fileNames, const StaticAnalysisConfig config, string error return hasErrors; } -void listAutofixes( - StaticAnalysisConfig config, - string resolveMessage, - bool usingStdin, - string fileName, - StringCache* cache, - ref ModuleCache moduleCache -) -{ - import dparse.parser : parseModule; - import dscanner.analysis.base : Message; - import std.format : format; - import std.json : JSONValue; - - union RequestedLocation - { - struct - { - uint line, column; - } - ulong bytes; - } - - RequestedLocation req; - bool isBytes = resolveMessage[0] == 'b'; - if (isBytes) - req.bytes = resolveMessage[1 .. $].to!ulong; - else - { - auto parts = resolveMessage.findSplit(":"); - req.line = parts[0].to!uint; - req.column = parts[2].to!uint; - } - - bool matchesCursor(Message m) - { - return isBytes - ? req.bytes >= m.startIndex && req.bytes <= m.endIndex - : req.line >= m.startLine && req.line <= m.endLine - && (req.line > m.startLine || req.column >= m.startColumn) - && (req.line < m.endLine || req.column <= m.endColumn); - } - - RollbackAllocator rba; - LexerConfig lexerConfig; - lexerConfig.fileName = fileName; - lexerConfig.stringBehavior = StringBehavior.source; - auto tokens = getTokensForParser(usingStdin ? readStdin() - : readFile(fileName), lexerConfig, cache); - auto mod = parseModule(tokens, fileName, &rba, toDelegate(&doNothing)); - - auto messages = analyze(fileName, mod, config, moduleCache, tokens); - - with (stdout.lockingTextWriter) - { - put("["); - foreach (message; messages[].filter!matchesCursor) - { - resolveAutoFixes(message, fileName, moduleCache, tokens, mod, config); - - foreach (i, autofix; message.autofixes) - { - put(i == 0 ? "\n" : ",\n"); - put("\t{\n"); - put(format!"\t\t\"name\": %s,\n"(JSONValue(autofix.name))); - put("\t\t\"replacements\": ["); - foreach (j, replacement; autofix.expectReplacements) - { - put(j == 0 ? "\n" : ",\n"); - put(format!"\t\t\t{\"range\": [%d, %d], \"newText\": %s}"( - replacement.range[0], - replacement.range[1], - JSONValue(replacement.newText))); - } - put("\n"); - put("\t\t]\n"); - put("\t}"); - } - } - put("\n]"); - } - stdout.flush(); -} - private struct UserSelect { import std.string : strip; @@ -736,82 +655,7 @@ bool shouldRun(check : BaseAnalyzer)(string moduleName, const ref StaticAnalysis return true; } -/** - * 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 shouldRunDmd(check : BaseAnalyzerDmd)(const char[] moduleName, const ref StaticAnalysisConfig config) -{ - enum string a = check.name; - - 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 moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(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")); -} - -private BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName, +BaseAnalyzer[] getAnalyzersForModuleAndConfig(string fileName, const(Token)[] tokens, const Module m, const StaticAnalysisConfig analysisConfig, const Scope* moduleScope) { @@ -862,6 +706,7 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) { import dsymbol.symbol : DSymbol; + import dscanner.analysis.autofix : resolveAutoFixFromCheck; if (!staticAnalyze) return null; @@ -900,88 +745,6 @@ MessageSet analyze(string fileName, const Module m, const StaticAnalysisConfig a return set; } -private void resolveAutoFixFromCheck( - ref AutoFix autofix, - BaseAnalyzer check, - const Module m, - scope const(Token)[] tokens, - const AutoFixFormatting formattingConfig -) -{ - import std.sumtype : match; - - autofix.replacements.match!( - (AutoFix.ResolveContext context) { - autofix.replacements = check.resolveAutoFix(m, tokens, context, formattingConfig); - }, - (_) {} - ); -} - -void resolveAutoFixes(ref Message message, string fileName, - ref ModuleCache moduleCache, - scope const(Token)[] tokens, const Module m, - const StaticAnalysisConfig analysisConfig, - const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) -{ - resolveAutoFixes(message.checkName, message.autofixes, fileName, moduleCache, - tokens, m, analysisConfig, overrideFormattingConfig); -} - -AutoFix.CodeReplacement[] resolveAutoFix(string messageCheckName, AutoFix.ResolveContext context, - string fileName, - ref ModuleCache moduleCache, - scope const(Token)[] tokens, const Module m, - const StaticAnalysisConfig analysisConfig, - const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) -{ - AutoFix temp; - temp.replacements = context; - resolveAutoFixes(messageCheckName, (&temp)[0 .. 1], fileName, moduleCache, - tokens, m, analysisConfig, overrideFormattingConfig); - return temp.expectReplacements("resolving didn't work?!"); -} - -void resolveAutoFixes(string messageCheckName, AutoFix[] autofixes, string fileName, - ref ModuleCache moduleCache, - scope const(Token)[] tokens, const Module m, - const StaticAnalysisConfig analysisConfig, - const AutoFixFormatting overrideFormattingConfig = AutoFixFormatting.invalid) -{ - import dsymbol.symbol : DSymbol; - - const(AutoFixFormatting) formattingConfig = - overrideFormattingConfig is AutoFixFormatting.invalid - ? analysisConfig.getAutoFixFormattingConfig() - : overrideFormattingConfig; - - scope first = new FirstPass(m, internString(fileName), &moduleCache, null); - first.run(); - - secondPass(first.rootSymbol, first.moduleScope, moduleCache); - auto moduleScope = first.moduleScope; - scope(exit) typeid(DSymbol).destroy(first.rootSymbol.acSymbol); - scope(exit) typeid(SemanticSymbol).destroy(first.rootSymbol); - scope(exit) typeid(Scope).destroy(first.moduleScope); - - GC.disable; - scope (exit) - GC.enable; - - foreach (BaseAnalyzer check; getAnalyzersForModuleAndConfig(fileName, tokens, m, analysisConfig, moduleScope)) - { - if (check.getName() == messageCheckName) - { - foreach (ref autofix; autofixes) - autofix.resolveAutoFixFromCheck(check, m, tokens, formattingConfig); - return; - } - } - - throw new Exception("Cannot find analyzer " ~ messageCheckName - ~ " to resolve autofix with."); -} - void improveAutoFixWhitespace(scope const(char)[] code, AutoFix.CodeReplacement[] replacements) { import std.ascii : isWhite; @@ -1142,237 +905,3 @@ version (unittest) } } } - -MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleName, const StaticAnalysisConfig config) -{ - MessageSet set = new MessageSet; - BaseAnalyzerDmd[] visitors; - - if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTCodegen)(config)) - visitors ~= new ObjectConstCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTCodegen)(config)) - visitors ~= new EnumArrayVisitor!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(DeleteCheck!ASTCodegen)(config)) - visitors ~= new DeleteCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTCodegen)(config)) - visitors ~= new FinalAttributeChecker!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTCodegen)(config)) - visitors ~= new ImportSortednessCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTCodegen)(config)) - visitors ~= new IncorrectInfiniteRangeCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTCodegen)(config)) - visitors ~= new RedundantAttributesCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTCodegen)(config)) - visitors ~= new LengthSubtractionCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTCodegen)(config)) - visitors ~= new AliasSyntaxCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTCodegen)(config)) - visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(ConstructorCheck!ASTCodegen)(config)) - visitors ~= new ConstructorCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTCodegen)(config)) - visitors ~= new AssertWithoutMessageCheck!ASTCodegen( - fileName, - config.assert_without_msg == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(LocalImportCheck!ASTCodegen)(config)) - visitors ~= new LocalImportCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTCodegen)(config)) - visitors ~= new OpEqualsWithoutToHashCheck!ASTCodegen( - fileName, - config.opequals_tohash_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(TrustTooMuchCheck!ASTCodegen)(config)) - visitors ~= new TrustTooMuchCheck!ASTCodegen( - fileName, - config.trust_too_much == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTCodegen)(config)) - visitors ~= new AutoRefAssignmentCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTCodegen)(config)) - visitors ~= new LogicPrecedenceCheck!ASTCodegen( - fileName, - config.logical_precedence_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(UnusedLabelCheck!ASTCodegen)(config)) - visitors ~= new UnusedLabelCheck!ASTCodegen( - fileName, - config.unused_label_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config)) - visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName); - - if (moduleName.shouldRunDmd!(PokemonExceptionCheck!ASTCodegen)(config)) - visitors ~= new PokemonExceptionCheck!ASTCodegen( - fileName, - config.exception_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTCodegen)(config)) - visitors ~= new BackwardsRangeCheck!ASTCodegen( - fileName, - config.backwards_range_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(ProperlyDocumentedPublicFunctions!ASTCodegen)(config)) - visitors ~= new ProperlyDocumentedPublicFunctions!ASTCodegen( - fileName, - config.properly_documented_public_functions == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(RedundantParenCheck!ASTCodegen)(config)) - visitors ~= new RedundantParenCheck!ASTCodegen( - fileName, - config.redundant_parens_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(StaticIfElse!ASTCodegen)(config)) - visitors ~= new StaticIfElse!ASTCodegen( - fileName, - config.static_if_else_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(UselessAssertCheck!ASTCodegen)(config)) - visitors ~= new UselessAssertCheck!ASTCodegen( - fileName, - config.useless_assert_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config)) - visitors ~= new AsmStyleCheck!ASTCodegen( - fileName, - config.asm_style_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(RedundantStorageClassCheck!ASTCodegen)(config)) - visitors ~= new RedundantStorageClassCheck!ASTCodegen( - fileName, - config.redundant_storage_classes == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(NumberStyleCheck!ASTCodegen)(config)) - visitors ~= new NumberStyleCheck!ASTCodegen( - fileName, - config.number_style_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(IfElseSameCheck!ASTCodegen)(config)) - visitors ~= new IfElseSameCheck!ASTCodegen( - fileName, - config.if_else_same_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(CyclomaticComplexityCheck!ASTCodegen)(config)) - visitors ~= new CyclomaticComplexityCheck!ASTCodegen( - fileName, - config.cyclomatic_complexity == Check.skipTests && !ut, - config.max_cyclomatic_complexity.to!int - ); - - if (moduleName.shouldRunDmd!(LabelVarNameCheck!ASTCodegen)(config)) - visitors ~= new LabelVarNameCheck!ASTCodegen( - fileName, - config.label_var_same_name_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(LambdaReturnCheck!ASTCodegen)(config)) - visitors ~= new LambdaReturnCheck!ASTCodegen( - fileName, - config.lambda_return_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(AlwaysCurlyCheck!ASTCodegen)(config)) - visitors ~= new AlwaysCurlyCheck!ASTCodegen( - fileName, - config.always_curly_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(StyleChecker!ASTCodegen)(config)) - visitors ~= new StyleChecker!ASTCodegen( - fileName, - config.style_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(AutoFunctionChecker!ASTCodegen)(config)) - visitors ~= new AutoFunctionChecker!ASTCodegen( - fileName, - config.auto_function_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(UnusedParameterCheck!ASTCodegen)(config)) - visitors ~= new UnusedParameterCheck!ASTCodegen( - fileName, - config.unused_parameter_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(UnusedVariableCheck!ASTCodegen)(config)) - visitors ~= new UnusedVariableCheck!ASTCodegen( - fileName, - config.unused_variable_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(UnmodifiedFinder!ASTCodegen)(config)) - visitors ~= new UnmodifiedFinder!ASTCodegen( - fileName, - config.could_be_immutable_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(BodyOnDisabledFuncsCheck!ASTCodegen)(config)) - visitors ~= new BodyOnDisabledFuncsCheck!ASTCodegen( - fileName, - config.body_on_disabled_func_check == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(UselessInitializerChecker!ASTCodegen)(config)) - visitors ~= new UselessInitializerChecker!ASTCodegen( - fileName, - config.useless_initializer == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!(HasPublicExampleCheck!ASTCodegen)(config)) - visitors ~= new HasPublicExampleCheck!ASTCodegen( - fileName, - config.has_public_example == Check.skipTests && !ut - ); - - if (moduleName.shouldRunDmd!LineLengthCheck(config)) - visitors ~= new LineLengthCheck( - fileName, - config.long_line_check == Check.skipTests && !ut, - config.max_line_length - ); - - if (moduleName.shouldRunDmd!(UnusedResultChecker!ASTCodegen)(config)) - visitors ~= new UnusedResultChecker!ASTCodegen( - fileName, - config.unused_result == Check.skipTests && !ut - ); - - foreach (visitor; visitors) - { - m.accept(visitor); - - foreach (message; visitor.messages) - set.insert(message); - } - - return set; -} diff --git a/src/dscanner/analysis/rundmd.d b/src/dscanner/analysis/rundmd.d new file mode 100644 index 00000000..a0a56fc6 --- /dev/null +++ b/src/dscanner/analysis/rundmd.d @@ -0,0 +1,369 @@ +module dscanner.analysis.rundmd; + +import std.algorithm : any, canFind, filter, map; +import std.conv : to; + +import dmd.astcodegen; + +import dscanner.analysis.config : Check, StaticAnalysisConfig; +import dscanner.analysis.base : BaseAnalyzerDmd, MessageSet; + +import dscanner.analysis.alias_syntax_check : AliasSyntaxCheck; +import dscanner.analysis.always_curly : AlwaysCurlyCheck; +import dscanner.analysis.asm_style : AsmStyleCheck; +import dscanner.analysis.assert_without_msg : AssertWithoutMessageCheck; +import dscanner.analysis.auto_function : AutoFunctionChecker; +import dscanner.analysis.auto_ref_assignment : AutoRefAssignmentCheck; +import dscanner.analysis.body_on_disabled_funcs : BodyOnDisabledFuncsCheck; +import dscanner.analysis.builtin_property_names : BuiltinPropertyNameCheck; +import dscanner.analysis.constructors : ConstructorCheck; +import dscanner.analysis.cyclomatic_complexity : CyclomaticComplexityCheck; +import dscanner.analysis.del : DeleteCheck; +import dscanner.analysis.enumarrayliteral : EnumArrayVisitor; +import dscanner.analysis.explicitly_annotated_unittests : ExplicitlyAnnotatedUnittestCheck; +import dscanner.analysis.final_attribute : FinalAttributeChecker; +import dscanner.analysis.has_public_example : HasPublicExampleCheck; +import dscanner.analysis.ifelsesame : IfElseSameCheck; +import dscanner.analysis.imports_sortedness : ImportSortednessCheck; +import dscanner.analysis.incorrect_infinite_range : IncorrectInfiniteRangeCheck; +import dscanner.analysis.label_var_same_name_check : LabelVarNameCheck; +import dscanner.analysis.lambda_return_check : LambdaReturnCheck; +import dscanner.analysis.length_subtraction : LengthSubtractionCheck; +import dscanner.analysis.line_length : LineLengthCheck; +import dscanner.analysis.local_imports : LocalImportCheck; +import dscanner.analysis.logic_precedence : LogicPrecedenceCheck; +import dscanner.analysis.numbers : NumberStyleCheck; +import dscanner.analysis.objectconst : ObjectConstCheck; +import dscanner.analysis.opequals_without_tohash : OpEqualsWithoutToHashCheck; +import dscanner.analysis.pokemon : PokemonExceptionCheck; +import dscanner.analysis.properly_documented_public_functions : ProperlyDocumentedPublicFunctions; +import dscanner.analysis.range : BackwardsRangeCheck; +import dscanner.analysis.redundant_attributes : RedundantAttributesCheck; +import dscanner.analysis.redundant_parens : RedundantParenCheck; +import dscanner.analysis.redundant_storage_class : RedundantStorageClassCheck; +import dscanner.analysis.static_if_else : StaticIfElse; +import dscanner.analysis.style : StyleChecker; +import dscanner.analysis.trust_too_much : TrustTooMuchCheck; +import dscanner.analysis.unmodified : UnmodifiedFinder; +import dscanner.analysis.unused_label : UnusedLabelCheck; +import dscanner.analysis.unused_parameter : UnusedParameterCheck; +import dscanner.analysis.unused_result : UnusedResultChecker; +import dscanner.analysis.unused_variable : UnusedVariableCheck; +import dscanner.analysis.useless_assert : UselessAssertCheck; +import dscanner.analysis.useless_initializer : UselessInitializerChecker; + +version (unittest) + enum ut = true; +else + enum ut = false; + +MessageSet analyzeDmd(string fileName, ASTCodegen.Module m, const char[] moduleName, const StaticAnalysisConfig config) +{ + MessageSet set = new MessageSet; + BaseAnalyzerDmd[] visitors; + + if (moduleName.shouldRunDmd!(ObjectConstCheck!ASTCodegen)(config)) + visitors ~= new ObjectConstCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(EnumArrayVisitor!ASTCodegen)(config)) + visitors ~= new EnumArrayVisitor!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(DeleteCheck!ASTCodegen)(config)) + visitors ~= new DeleteCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(FinalAttributeChecker!ASTCodegen)(config)) + visitors ~= new FinalAttributeChecker!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(ImportSortednessCheck!ASTCodegen)(config)) + visitors ~= new ImportSortednessCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(IncorrectInfiniteRangeCheck!ASTCodegen)(config)) + visitors ~= new IncorrectInfiniteRangeCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(RedundantAttributesCheck!ASTCodegen)(config)) + visitors ~= new RedundantAttributesCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(LengthSubtractionCheck!ASTCodegen)(config)) + visitors ~= new LengthSubtractionCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(AliasSyntaxCheck!ASTCodegen)(config)) + visitors ~= new AliasSyntaxCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(ExplicitlyAnnotatedUnittestCheck!ASTCodegen)(config)) + visitors ~= new ExplicitlyAnnotatedUnittestCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(ConstructorCheck!ASTCodegen)(config)) + visitors ~= new ConstructorCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(AssertWithoutMessageCheck!ASTCodegen)(config)) + visitors ~= new AssertWithoutMessageCheck!ASTCodegen( + fileName, + config.assert_without_msg == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(LocalImportCheck!ASTCodegen)(config)) + visitors ~= new LocalImportCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(OpEqualsWithoutToHashCheck!ASTCodegen)(config)) + visitors ~= new OpEqualsWithoutToHashCheck!ASTCodegen( + fileName, + config.opequals_tohash_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(TrustTooMuchCheck!ASTCodegen)(config)) + visitors ~= new TrustTooMuchCheck!ASTCodegen( + fileName, + config.trust_too_much == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(AutoRefAssignmentCheck!ASTCodegen)(config)) + visitors ~= new AutoRefAssignmentCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(LogicPrecedenceCheck!ASTCodegen)(config)) + visitors ~= new LogicPrecedenceCheck!ASTCodegen( + fileName, + config.logical_precedence_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(UnusedLabelCheck!ASTCodegen)(config)) + visitors ~= new UnusedLabelCheck!ASTCodegen( + fileName, + config.unused_label_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(BuiltinPropertyNameCheck!ASTCodegen)(config)) + visitors ~= new BuiltinPropertyNameCheck!ASTCodegen(fileName); + + if (moduleName.shouldRunDmd!(PokemonExceptionCheck!ASTCodegen)(config)) + visitors ~= new PokemonExceptionCheck!ASTCodegen( + fileName, + config.exception_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(BackwardsRangeCheck!ASTCodegen)(config)) + visitors ~= new BackwardsRangeCheck!ASTCodegen( + fileName, + config.backwards_range_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(ProperlyDocumentedPublicFunctions!ASTCodegen)(config)) + visitors ~= new ProperlyDocumentedPublicFunctions!ASTCodegen( + fileName, + config.properly_documented_public_functions == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(RedundantParenCheck!ASTCodegen)(config)) + visitors ~= new RedundantParenCheck!ASTCodegen( + fileName, + config.redundant_parens_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(StaticIfElse!ASTCodegen)(config)) + visitors ~= new StaticIfElse!ASTCodegen( + fileName, + config.static_if_else_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(UselessAssertCheck!ASTCodegen)(config)) + visitors ~= new UselessAssertCheck!ASTCodegen( + fileName, + config.useless_assert_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(config)) + visitors ~= new AsmStyleCheck!ASTCodegen( + fileName, + config.asm_style_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(RedundantStorageClassCheck!ASTCodegen)(config)) + visitors ~= new RedundantStorageClassCheck!ASTCodegen( + fileName, + config.redundant_storage_classes == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(NumberStyleCheck!ASTCodegen)(config)) + visitors ~= new NumberStyleCheck!ASTCodegen( + fileName, + config.number_style_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(IfElseSameCheck!ASTCodegen)(config)) + visitors ~= new IfElseSameCheck!ASTCodegen( + fileName, + config.if_else_same_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(CyclomaticComplexityCheck!ASTCodegen)(config)) + visitors ~= new CyclomaticComplexityCheck!ASTCodegen( + fileName, + config.cyclomatic_complexity == Check.skipTests && !ut, + config.max_cyclomatic_complexity.to!int + ); + + if (moduleName.shouldRunDmd!(LabelVarNameCheck!ASTCodegen)(config)) + visitors ~= new LabelVarNameCheck!ASTCodegen( + fileName, + config.label_var_same_name_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(LambdaReturnCheck!ASTCodegen)(config)) + visitors ~= new LambdaReturnCheck!ASTCodegen( + fileName, + config.lambda_return_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(AlwaysCurlyCheck!ASTCodegen)(config)) + visitors ~= new AlwaysCurlyCheck!ASTCodegen( + fileName, + config.always_curly_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(StyleChecker!ASTCodegen)(config)) + visitors ~= new StyleChecker!ASTCodegen( + fileName, + config.style_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(AutoFunctionChecker!ASTCodegen)(config)) + visitors ~= new AutoFunctionChecker!ASTCodegen( + fileName, + config.auto_function_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(UnusedParameterCheck!ASTCodegen)(config)) + visitors ~= new UnusedParameterCheck!ASTCodegen( + fileName, + config.unused_parameter_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(UnusedVariableCheck!ASTCodegen)(config)) + visitors ~= new UnusedVariableCheck!ASTCodegen( + fileName, + config.unused_variable_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(UnmodifiedFinder!ASTCodegen)(config)) + visitors ~= new UnmodifiedFinder!ASTCodegen( + fileName, + config.could_be_immutable_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(BodyOnDisabledFuncsCheck!ASTCodegen)(config)) + visitors ~= new BodyOnDisabledFuncsCheck!ASTCodegen( + fileName, + config.body_on_disabled_func_check == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(UselessInitializerChecker!ASTCodegen)(config)) + visitors ~= new UselessInitializerChecker!ASTCodegen( + fileName, + config.useless_initializer == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!(HasPublicExampleCheck!ASTCodegen)(config)) + visitors ~= new HasPublicExampleCheck!ASTCodegen( + fileName, + config.has_public_example == Check.skipTests && !ut + ); + + if (moduleName.shouldRunDmd!LineLengthCheck(config)) + visitors ~= new LineLengthCheck( + fileName, + config.long_line_check == Check.skipTests && !ut, + config.max_line_length + ); + + if (moduleName.shouldRunDmd!(UnusedResultChecker!ASTCodegen)(config)) + visitors ~= new UnusedResultChecker!ASTCodegen( + fileName, + config.unused_result == Check.skipTests && !ut + ); + + foreach (visitor; visitors) + { + m.accept(visitor); + + foreach (message; visitor.messages) + set.insert(message); + } + + return set; +} + +/** + * 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 shouldRunDmd(check : BaseAnalyzerDmd)(const char[] moduleName, const ref StaticAnalysisConfig config) +{ + enum string a = check.name; + + 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) + { + import std.array : split; + + 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 moduleName.shouldRunDmd!(AsmStyleCheck!ASTCodegen)(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")); +} diff --git a/src/dscanner/analysis/unused.d b/src/dscanner/analysis/unused.d deleted file mode 100644 index 26d53d14..00000000 --- a/src/dscanner/analysis/unused.d +++ /dev/null @@ -1,461 +0,0 @@ -// Copyright Brian Schott (Hackerpilot) 2014-2015. -// Distributed under the Boost Software License, Version 1.0. -// (See accompanying file LICENSE_1_0.txt or copy at -// http://www.boost.org/LICENSE_1_0.txt) -module dscanner.analysis.unused; - -import dparse.ast; -import dparse.lexer; -import dscanner.analysis.base; -import std.container; -import std.regex : Regex, regex, matchAll; -import dsymbol.scope_ : Scope; -import std.algorithm : all; - -/** - * Checks for unused variables. - */ -abstract class UnusedIdentifierCheck : BaseAnalyzer -{ - alias visit = BaseAnalyzer.visit; - - /** - */ - this(BaseAnalyzerArguments args) - { - super(args); - re = regex("[\\p{Alphabetic}_][\\w_]*"); - } - - override void visit(const Module mod) - { - pushScope(); - mod.accept(this); - popScope(); - } - - override void visit(const Declaration declaration) - { - if (!isOverride) - foreach (attribute; declaration.attributes) - isOverride = isOverride || (attribute.attribute == tok!"override"); - declaration.accept(this); - isOverride = false; - } - - override void visit(const FunctionDeclaration functionDec) - { - pushScope(); - if (functionDec.functionBody - && (functionDec.functionBody.specifiedFunctionBody - || functionDec.functionBody.shortenedFunctionBody)) - { - immutable bool ias = inAggregateScope; - inAggregateScope = false; - if (!isOverride) - functionDec.parameters.accept(this); - functionDec.functionBody.accept(this); - inAggregateScope = ias; - } - popScope(); - } - - mixin PartsUseVariables!AliasInitializer; - mixin PartsUseVariables!ArgumentList; - mixin PartsUseVariables!AssertExpression; - mixin PartsUseVariables!ClassDeclaration; - mixin PartsUseVariables!FunctionBody; - mixin PartsUseVariables!FunctionCallExpression; - mixin PartsUseVariables!FunctionDeclaration; - mixin PartsUseVariables!IndexExpression; - mixin PartsUseVariables!Initializer; - mixin PartsUseVariables!InterfaceDeclaration; - mixin PartsUseVariables!NewExpression; - mixin PartsUseVariables!StaticIfCondition; - mixin PartsUseVariables!StructDeclaration; - mixin PartsUseVariables!TemplateArgumentList; - mixin PartsUseVariables!ThrowExpression; - mixin PartsUseVariables!CastExpression; - - override void dynamicDispatch(const ExpressionNode n) - { - interestDepth++; - super.dynamicDispatch(n); - interestDepth--; - } - - override void visit(const SwitchStatement switchStatement) - { - if (switchStatement.expression !is null) - { - interestDepth++; - switchStatement.expression.accept(this); - interestDepth--; - } - switchStatement.accept(this); - } - - override void visit(const WhileStatement whileStatement) - { - if (whileStatement.condition.expression !is null) - { - interestDepth++; - whileStatement.condition.expression.accept(this); - interestDepth--; - } - if (whileStatement.declarationOrStatement !is null) - whileStatement.declarationOrStatement.accept(this); - } - - override void visit(const DoStatement doStatement) - { - if (doStatement.expression !is null) - { - interestDepth++; - doStatement.expression.accept(this); - interestDepth--; - } - if (doStatement.statementNoCaseNoDefault !is null) - doStatement.statementNoCaseNoDefault.accept(this); - } - - override void visit(const ForStatement forStatement) - { - if (forStatement.initialization !is null) - forStatement.initialization.accept(this); - if (forStatement.test !is null) - { - interestDepth++; - forStatement.test.accept(this); - interestDepth--; - } - if (forStatement.increment !is null) - { - interestDepth++; - forStatement.increment.accept(this); - interestDepth--; - } - if (forStatement.declarationOrStatement !is null) - forStatement.declarationOrStatement.accept(this); - } - - override void visit(const IfStatement ifStatement) - { - if (ifStatement.condition.expression !is null) - { - interestDepth++; - ifStatement.condition.expression.accept(this); - interestDepth--; - } - if (ifStatement.thenStatement !is null) - ifStatement.thenStatement.accept(this); - if (ifStatement.elseStatement !is null) - ifStatement.elseStatement.accept(this); - } - - override void visit(const ForeachStatement foreachStatement) - { - if (foreachStatement.low !is null) - { - interestDepth++; - foreachStatement.low.accept(this); - interestDepth--; - } - if (foreachStatement.high !is null) - { - interestDepth++; - foreachStatement.high.accept(this); - interestDepth--; - } - foreachStatement.accept(this); - } - - override void visit(const AssignExpression assignExp) - { - interestDepth++; - assignExp.accept(this); - interestDepth--; - } - - override void visit(const TemplateDeclaration templateDeclaration) - { - immutable inAgg = inAggregateScope; - inAggregateScope = true; - templateDeclaration.accept(this); - inAggregateScope = inAgg; - } - - override void visit(const IdentifierOrTemplateChain chain) - { - if (interestDepth > 0 && chain.identifiersOrTemplateInstances[0].identifier != tok!"") - variableUsed(chain.identifiersOrTemplateInstances[0].identifier.text); - chain.accept(this); - } - - override void visit(const TemplateSingleArgument single) - { - if (single.token != tok!"") - variableUsed(single.token.text); - } - - override void visit(const UnaryExpression unary) - { - const bool interesting = unary.prefix == tok!"*" || unary.unaryExpression !is null; - interestDepth += interesting; - unary.accept(this); - interestDepth -= interesting; - } - - override void visit(const MixinExpression mix) - { - interestDepth++; - mixinDepth++; - mix.accept(this); - mixinDepth--; - interestDepth--; - } - - override void visit(const PrimaryExpression primary) - { - if (interestDepth > 0) - { - const IdentifierOrTemplateInstance idt = primary.identifierOrTemplateInstance; - - if (idt !is null) - { - if (idt.identifier != tok!"") - variableUsed(idt.identifier.text); - else if (idt.templateInstance && idt.templateInstance.identifier != tok!"") - variableUsed(idt.templateInstance.identifier.text); - } - if ((mixinDepth > 0 && primary.primary == tok!"stringLiteral") - || primary.primary == tok!"wstringLiteral" - || primary.primary == tok!"dstringLiteral") - { - foreach (part; matchAll(primary.primary.text, re)) - { - void checkTree(in size_t treeIndex) - { - auto uu = UnUsed(part.hit); - auto r = tree[treeIndex].equalRange(&uu); - if (!r.empty) - r.front.uncertain = true; - } - checkTree(tree.length - 1); - if (tree.length >= 2) - checkTree(tree.length - 2); - } - } - } - primary.accept(this); - } - - override void visit(const ReturnStatement retStatement) - { - if (retStatement.expression !is null) - { - interestDepth++; - visit(retStatement.expression); - interestDepth--; - } - } - - override void visit(const BlockStatement blockStatement) - { - immutable bool sb = inAggregateScope; - inAggregateScope = false; - if (blockStatementIntroducesScope) - pushScope(); - blockStatement.accept(this); - if (blockStatementIntroducesScope) - popScope(); - inAggregateScope = sb; - } - - override void visit(const Type2 tp) - { - if (tp.typeIdentifierPart && - tp.typeIdentifierPart.identifierOrTemplateInstance) - { - const IdentifierOrTemplateInstance idt = tp.typeIdentifierPart.identifierOrTemplateInstance; - if (idt.identifier != tok!"") - variableUsed(idt.identifier.text); - else if (idt.templateInstance) - { - const TemplateInstance ti = idt.templateInstance; - if (ti.identifier != tok!"") - variableUsed(idt.templateInstance.identifier.text); - if (ti.templateArguments && ti.templateArguments.templateSingleArgument) - variableUsed(ti.templateArguments.templateSingleArgument.token.text); - } - } - tp.accept(this); - } - - override void visit(const WithStatement withStatetement) - { - interestDepth++; - if (withStatetement.expression) - withStatetement.expression.accept(this); - interestDepth--; - if (withStatetement.declarationOrStatement) - withStatetement.declarationOrStatement.accept(this); - } - - override void visit(const StructBody structBody) - { - immutable bool sb = inAggregateScope; - inAggregateScope = true; - foreach (dec; structBody.declarations) - visit(dec); - inAggregateScope = sb; - } - - override void visit(const ConditionalStatement conditionalStatement) - { - immutable bool cs = blockStatementIntroducesScope; - blockStatementIntroducesScope = false; - conditionalStatement.accept(this); - blockStatementIntroducesScope = cs; - } - - override void visit(const AsmPrimaryExp primary) - { - if (primary.token != tok!"") - variableUsed(primary.token.text); - if (primary.identifierChain !is null) - variableUsed(primary.identifierChain.identifiers[0].text); - } - - override void visit(const TraitsExpression) - { - // issue #266: Ignore unused variables inside of `__traits` expressions - } - - override void visit(const TypeofExpression) - { - // issue #270: Ignore unused variables inside of `typeof` expressions - } - - abstract protected void popScope(); - - protected uint interestDepth; - - protected Tree[] tree; - - protected void variableDeclared(string name, Token token, bool isRef) - { - if (inAggregateScope || name.all!(a => a == '_')) - return; - tree[$ - 1].insert(new UnUsed(name, token, isRef)); - } - - protected void pushScope() - { - tree ~= new Tree; - } - -private: - - struct UnUsed - { - string name; - Token token; - bool isRef; - bool uncertain; - } - - alias Tree = RedBlackTree!(UnUsed*, "a.name < b.name"); - - mixin template PartsUseVariables(NodeType) - { - override void visit(const NodeType node) - { - interestDepth++; - node.accept(this); - interestDepth--; - } - } - - void variableUsed(string name) - { - size_t treeIndex = tree.length - 1; - auto uu = UnUsed(name); - while (true) - { - if (tree[treeIndex].removeKey(&uu) != 0 || treeIndex == 0) - break; - treeIndex--; - } - } - - Regex!char re; - - bool inAggregateScope; - - uint mixinDepth; - - bool isOverride; - - bool blockStatementIntroducesScope = true; -} - -/// Base class for unused parameter/variables checks -abstract class UnusedStorageCheck : UnusedIdentifierCheck -{ - alias visit = UnusedIdentifierCheck.visit; - - /** - Ignore declarations which are allowed to be unused, e.g. inside of a - speculative compilation: __traits(compiles, { S s = 0; }) - **/ - uint ignoreDeclarations = 0; - - /// Kind of declaration for error messages e.g. "Variable" - const string publicType; - - /// Kind of declaration for error reports e.g. "unused_variable" - const string reportType; - - /** - * Params: - * args = commonly shared analyzer arguments - * publicType = declaration kind used in error messages, e.g. "Variable"s - * reportType = declaration kind used in error reports, e.g. "unused_variable" - */ - this(BaseAnalyzerArguments args, string publicType = null, string reportType = null) - { - super(args); - this.publicType = publicType; - this.reportType = reportType; - } - - override void visit(const TraitsExpression traitsExp) - { - // issue #788: Enum values might be used inside of `__traits` expressions, e.g.: - // enum name = "abc"; - // __traits(hasMember, S, name); - ignoreDeclarations++; - if (traitsExp.templateArgumentList) - traitsExp.templateArgumentList.accept(this); - ignoreDeclarations--; - } - - override final protected void popScope() - { - if (!ignoreDeclarations) - { - foreach (uu; tree[$ - 1]) - { - if (!uu.isRef && tree.length > 1) - { - if (uu.uncertain) - continue; - immutable string errorMessage = publicType ~ ' ' ~ uu.name ~ " is never used."; - addErrorMessage(uu.token, "dscanner.suspicious." ~ reportType, errorMessage); - } - } - } - tree = tree[0 .. $ - 1]; - } -} diff --git a/src/dscanner/main.d b/src/dscanner/main.d index 2d8dd264..e486adb1 100644 --- a/src/dscanner/main.d +++ b/src/dscanner/main.d @@ -31,6 +31,7 @@ import dscanner.outliner; import dscanner.symbol_finder; import dscanner.analysis.run; import dscanner.analysis.config; +import dscanner.analysis.autofix; import dscanner.dscanner_version; import dscanner.utils;