1 module dls.tools.analysis_tool; 2 3 import dls.tools.tool : Tool; 4 5 private immutable diagnosticSource = "D-Scanner"; 6 7 class AnalysisTool : Tool 8 { 9 import dls.protocol.definitions : Diagnostic; 10 import dls.util.uri : Uri; 11 import dscanner.analysis.config : StaticAnalysisConfig, 12 defaultStaticAnalysisConfig; 13 import dls.util.logger : logger; 14 import std.path : buildNormalizedPath; 15 16 private StaticAnalysisConfig[string] _analysisConfigs; 17 18 void addAnalysisConfigPath(Uri uri) 19 { 20 _analysisConfigs[uri.path] = defaultStaticAnalysisConfig(); 21 updateAnalysisConfigPath(uri); 22 } 23 24 void removeAnalysisConfigPath(Uri uri) 25 { 26 if (uri.path in _analysisConfigs) 27 { 28 _analysisConfigs.remove(uri.path); 29 } 30 } 31 32 void updateAnalysisConfigPath(Uri uri) 33 { 34 import dls.protocol.interfaces : PublishDiagnosticsParams; 35 import dls.protocol.jsonrpc : send; 36 import dls.util.document : Document; 37 import inifiled : readINIFile; 38 import std.file : exists; 39 40 auto configPath = buildNormalizedPath(uri.path, _configuration.analysis.configFile); 41 42 if (configPath.exists()) 43 { 44 logger.infof("Updating config from file %s", configPath); 45 auto conf = uri.path in _analysisConfigs ? _analysisConfigs[uri.path] 46 : defaultStaticAnalysisConfig(); 47 readINIFile(conf, configPath); 48 _analysisConfigs[uri.path] = conf; 49 50 foreach (documentUri; Document.uris) 51 { 52 send("textDocument/publishDiagnostics", 53 new PublishDiagnosticsParams(documentUri, scan(documentUri))); 54 } 55 } 56 } 57 58 Diagnostic[] scan(Uri uri) 59 { 60 import dls.protocol.definitions : DiagnosticSeverity; 61 import dls.tools.tools : Tools; 62 import dls.util.document : Document; 63 import dparse.lexer : LexerConfig, StringBehavior, StringCache, 64 getTokensForParser; 65 import dparse.parser : parseModule; 66 import dparse.rollback_allocator : RollbackAllocator; 67 import dscanner.analysis.run : analyze; 68 import std.array : appender; 69 import std.json : JSONValue; 70 import std.typecons : Nullable, nullable; 71 72 logger.infof("Scanning document %s", uri.path); 73 74 auto stringCache = StringCache(StringCache.defaultBucketCount); 75 auto tokens = getTokensForParser(Document[uri].toString(), 76 LexerConfig(uri.path, StringBehavior.source), &stringCache); 77 RollbackAllocator ra; 78 auto document = Document[uri]; 79 auto diagnostics = appender!(Diagnostic[]); 80 81 const syntaxProblemhandler = (string path, size_t line, size_t column, 82 string msg, bool isError) { 83 diagnostics ~= new Diagnostic(document.wordRangeAtLineAndByte(line - 1, column - 1), msg, (isError 84 ? DiagnosticSeverity.error : DiagnosticSeverity.warning).nullable, 85 Nullable!JSONValue.init, diagnosticSource.nullable); 86 }; 87 88 const mod = parseModule(tokens, uri.path, &ra, syntaxProblemhandler); 89 const analysisResults = analyze(uri.path, mod, getConfig(uri), 90 *Tools.symbolTool.getWorkspaceCache(uri), tokens, true); 91 92 foreach (result; analysisResults) 93 { 94 diagnostics ~= new Diagnostic(document.wordRangeAtLineAndByte(result.line - 1, result.column - 1), 95 result.message, DiagnosticSeverity.warning.nullable, 96 JSONValue(result.key).nullable, diagnosticSource.nullable); 97 } 98 99 return diagnostics.data; 100 } 101 102 private StaticAnalysisConfig getConfig(Uri uri) 103 { 104 import std.algorithm : startsWith; 105 import std.array : array; 106 import std.path : pathSplitter; 107 108 string[] configPathParts; 109 110 foreach (path, config; _analysisConfigs) 111 { 112 auto splitter = pathSplitter(path); 113 114 if (pathSplitter(uri.path).startsWith(splitter)) 115 { 116 auto pathParts = splitter.array; 117 118 if (pathParts.length > configPathParts.length) 119 { 120 configPathParts = pathParts; 121 } 122 } 123 } 124 125 const configPath = buildNormalizedPath(configPathParts); 126 return (configPath in _analysisConfigs) ? _analysisConfigs[configPath] 127 : defaultStaticAnalysisConfig(); 128 } 129 }