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 }