1 /* 2 *Copyright (C) 2018 Laurent Tréguier 3 * 4 *This file is part of DLS. 5 * 6 *DLS is free software: you can redistribute it and/or modify 7 *it under the terms of the GNU General Public License as published by 8 *the Free Software Foundation, either version 3 of the License, or 9 *(at your option) any later version. 10 * 11 *DLS is distributed in the hope that it will be useful, 12 *but WITHOUT ANY WARRANTY; without even the implied warranty of 13 *MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 *GNU General Public License for more details. 15 * 16 *You should have received a copy of the GNU General Public License 17 *along with DLS. If not, see <http://www.gnu.org/licenses/>. 18 * 19 */ 20 21 module dls.tools.analysis_tool; 22 23 import dls.tools.tool : Tool; 24 25 private immutable diagnosticSource = "D-Scanner"; 26 27 class AnalysisTool : Tool 28 { 29 import dls.protocol.definitions : Diagnostic; 30 import dls.util.uri : Uri; 31 import dscanner.analysis.config : StaticAnalysisConfig; 32 33 private static AnalysisTool _instance; 34 35 static void initialize() 36 { 37 _instance = new AnalysisTool(); 38 } 39 40 static void shutdown() 41 { 42 destroy(_instance); 43 } 44 45 @property static AnalysisTool instance() 46 { 47 return _instance; 48 } 49 50 private StaticAnalysisConfig[string] _analysisConfigs; 51 52 void addAnalysisConfigPath(Uri uri) 53 { 54 import dscanner.analysis.config : defaultStaticAnalysisConfig; 55 56 _analysisConfigs[uri.path] = defaultStaticAnalysisConfig(); 57 updateAnalysisConfigPath(uri); 58 } 59 60 void removeAnalysisConfigPath(Uri uri) 61 { 62 if (uri.path in _analysisConfigs) 63 { 64 _analysisConfigs.remove(uri.path); 65 } 66 } 67 68 void updateAnalysisConfigPath(Uri uri) 69 { 70 import dls.protocol.interfaces : PublishDiagnosticsParams; 71 import dls.protocol.jsonrpc : send; 72 import dls.protocol.messages.methods : TextDocument; 73 import dls.util.document : Document; 74 import dls.util.logger : logger; 75 import dscanner.analysis.config : defaultStaticAnalysisConfig; 76 import inifiled : readINIFile; 77 import std.file : exists; 78 import std.path : buildNormalizedPath; 79 80 auto configPath = buildNormalizedPath(uri.path, configuration.analysis.configFile); 81 82 if (configPath.exists()) 83 { 84 logger.infof("Updating config from file %s", configPath); 85 auto conf = uri.path in _analysisConfigs ? _analysisConfigs[uri.path] 86 : defaultStaticAnalysisConfig(); 87 readINIFile(conf, configPath); 88 _analysisConfigs[uri.path] = conf; 89 90 foreach (documentUri; Document.uris) 91 { 92 send(TextDocument.publishDiagnostics, 93 new PublishDiagnosticsParams(documentUri, scan(documentUri))); 94 } 95 } 96 } 97 98 Diagnostic[] scan(Uri uri) 99 { 100 import dls.protocol.definitions : DiagnosticSeverity; 101 import dls.tools.symbol_tool : SymbolTool; 102 import dls.util.document : Document; 103 import dls.util.logger : logger; 104 import dparse.lexer : LexerConfig, StringBehavior, StringCache, 105 getTokensForParser; 106 import dparse.parser : parseModule; 107 import dparse.rollback_allocator : RollbackAllocator; 108 import dscanner.analysis.run : analyze; 109 import std.array : appender; 110 import std.json : JSONValue; 111 import std.typecons : Nullable, nullable; 112 113 logger.infof("Scanning document %s", uri.path); 114 115 auto stringCache = StringCache(StringCache.defaultBucketCount); 116 auto tokens = getTokensForParser(Document.get(uri).toString(), 117 LexerConfig(uri.path, StringBehavior.source), &stringCache); 118 RollbackAllocator ra; 119 auto document = Document.get(uri); 120 auto diagnostics = appender!(Diagnostic[]); 121 122 const syntaxProblemhandler = (string path, size_t line, size_t column, 123 string msg, bool isError) { 124 diagnostics ~= new Diagnostic(document.wordRangeAtLineAndByte(line - 1, column - 1), msg, (isError 125 ? DiagnosticSeverity.error : DiagnosticSeverity.warning).nullable, 126 Nullable!JSONValue.init, diagnosticSource.nullable); 127 }; 128 129 const mod = parseModule(tokens, uri.path, &ra, syntaxProblemhandler); 130 const analysisResults = analyze(uri.path, mod, getConfig(uri), 131 SymbolTool.instance.cache, tokens, true); 132 133 foreach (result; analysisResults) 134 { 135 diagnostics ~= new Diagnostic(document.wordRangeAtLineAndByte(result.line - 1, result.column - 1), 136 result.message, DiagnosticSeverity.warning.nullable, 137 JSONValue(result.key).nullable, diagnosticSource.nullable); 138 } 139 140 return diagnostics.data; 141 } 142 143 private StaticAnalysisConfig getConfig(Uri uri) 144 { 145 import dls.tools.symbol_tool : SymbolTool; 146 import dscanner.analysis.config : defaultStaticAnalysisConfig; 147 148 const configUri = SymbolTool.instance.getWorkspace(uri); 149 const configPath = configUri is null ? "" : configUri.path; 150 return (configPath in _analysisConfigs) ? _analysisConfigs[configPath] 151 : defaultStaticAnalysisConfig(); 152 } 153 }