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 }