e5285a540bd2e9e855115c807e4d9fa4807665a0
[JavaForFun] /
1 package de.example.plugins.custom.javascript;
2 /*
3  * SonarQube JavaScript Plugin
4  * Copyright (C) 2011-2016 SonarSource SA
5  * mailto:contact AT sonarsource DOT com
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU Lesser General Public
9  * License as published by the Free Software Foundation; either
10  * version 3 of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
15  * Lesser General Public License for more details.
16  *
17  * You should have received a copy of the GNU Lesser General Public License
18  * along with this program; if not, write to the Free Software Foundation,
19  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
20  */
21
22 import java.io.File;
23 import java.io.InterruptedIOException;
24 import java.nio.charset.Charset;
25 import java.util.ArrayList;
26 import java.util.List;
27
28 import javax.annotation.Nullable;
29
30 import org.sonar.api.batch.fs.FilePredicate;
31 import org.sonar.api.batch.fs.FileSystem;
32 import org.sonar.api.batch.fs.InputFile;
33 import org.sonar.api.batch.fs.InputFile.Type;
34 import org.sonar.api.batch.fs.TextRange;
35 import org.sonar.api.batch.rule.CheckFactory;
36 import org.sonar.api.batch.sensor.Sensor;
37 import org.sonar.api.batch.sensor.SensorContext;
38 import org.sonar.api.batch.sensor.SensorDescriptor;
39 import org.sonar.api.batch.sensor.issue.NewIssue;
40 import org.sonar.api.batch.sensor.issue.NewIssueLocation;
41 import org.sonar.api.batch.sensor.symbol.NewSymbolTable;
42 import org.sonar.api.config.Settings;
43 import org.sonar.api.issue.NoSonarFilter;
44 import org.sonar.api.measures.FileLinesContextFactory;
45 import org.sonar.api.rule.RuleKey;
46 import org.sonar.api.utils.log.Logger;
47 import org.sonar.api.utils.log.Loggers;
48 import org.sonar.javascript.checks.ParsingErrorCheck;
49 import org.sonar.javascript.highlighter.HighlightSymbolTableBuilder;
50 import org.sonar.javascript.parser.JavaScriptParserBuilder;
51 import org.sonar.javascript.tree.visitors.CharsetAwareVisitor;
52 import org.sonar.javascript.visitors.JavaScriptVisitorContext;
53 import org.sonar.plugins.javascript.JavaScriptLanguage;
54 import org.sonar.plugins.javascript.api.CustomJavaScriptRulesDefinition;
55 import org.sonar.plugins.javascript.api.JavaScriptCheck;
56 import org.sonar.plugins.javascript.api.tree.ScriptTree;
57 import org.sonar.plugins.javascript.api.tree.Tree;
58 import org.sonar.plugins.javascript.api.visitors.FileIssue;
59 import org.sonar.plugins.javascript.api.visitors.Issue;
60 import org.sonar.plugins.javascript.api.visitors.IssueLocation;
61 import org.sonar.plugins.javascript.api.visitors.LineIssue;
62 import org.sonar.plugins.javascript.api.visitors.PreciseIssue;
63 import org.sonar.plugins.javascript.api.visitors.TreeVisitor;
64 import org.sonar.plugins.javascript.api.visitors.TreeVisitorContext;
65 import org.sonar.squidbridge.api.AnalysisException;
66
67 import com.google.common.annotations.VisibleForTesting;
68 import com.google.common.base.Preconditions;
69 import com.google.common.base.Throwables;
70 import com.google.common.collect.Lists;
71 import com.sonar.sslr.api.RecognitionException;
72 import com.sonar.sslr.api.typed.ActionParser;
73
74 import de.example.custom.javascript.checks.CheckList;
75 import de.example.plugins.custom.javascript.minify.MinificationAssessor;
76
77 public class CustomSensor implements Sensor {
78
79   private static final Logger LOG = Loggers.get(CustomSensor.class);
80
81   private final JavaScriptChecks checks;
82   private final FileSystem fileSystem;
83   private final FilePredicate mainFilePredicate;
84   private final Settings settings;
85   private final ActionParser<Tree> parser;
86   // parsingErrorRuleKey equals null if ParsingErrorCheck is not activated
87   private RuleKey parsingErrorRuleKey = null;
88
89   public CustomSensor(
90     CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem, NoSonarFilter noSonarFilter, Settings settings) {
91     this(checkFactory, fileSystem, settings, null);
92   }
93
94   public CustomSensor(
95     CheckFactory checkFactory, FileSystem fileSystem,
96     Settings settings, @Nullable CustomJavaScriptRulesDefinition[] customRulesDefinition
97   ) {
98
99     this.checks = JavaScriptChecks.createJavaScriptCheck(checkFactory)
100       .addChecks(CheckList.REPOSITORY_KEY, CheckList.getChecks())
101       .addCustomChecks(customRulesDefinition);
102     this.fileSystem = fileSystem;
103     this.mainFilePredicate = fileSystem.predicates().and(
104       fileSystem.predicates().hasType(InputFile.Type.MAIN),
105       fileSystem.predicates().hasLanguage(JavaScriptLanguage.KEY));
106     this.settings = settings;
107     this.parser = JavaScriptParserBuilder.createParser(getEncoding());
108   }
109
110   @VisibleForTesting
111   protected void analyseFiles(SensorContext context, List<TreeVisitor> treeVisitors, Iterable<InputFile> inputFiles) {
112       for (InputFile inputFile : inputFiles) {
113         if (!isExcluded(inputFile.file())) {
114           analyse(context, inputFile, treeVisitors);
115         }
116       }
117   }
118   
119   private Charset getEncoding() {
120     return fileSystem.encoding();
121   }
122
123
124   private void analyse(SensorContext sensorContext, InputFile inputFile, List<TreeVisitor> visitors) {
125     ScriptTree scriptTree;
126
127     try {
128       scriptTree = (ScriptTree) parser.parse(new java.io.File(inputFile.absolutePath()));
129       scanFile(sensorContext, inputFile, visitors, scriptTree);
130
131     } catch (RecognitionException e) {
132       checkInterrupted(e);
133       LOG.error("Unable to parse file: " + inputFile.absolutePath());
134       LOG.error(e.getMessage());
135       processRecognitionException(e, sensorContext, inputFile);
136
137     } catch (Exception e) {
138       checkInterrupted(e);
139       throw new AnalysisException("Unable to analyse file: " + inputFile.absolutePath(), e);
140     }
141
142   }
143
144   private static void checkInterrupted(Exception e) {
145     Throwable cause = Throwables.getRootCause(e);
146     if (cause instanceof InterruptedException || cause instanceof InterruptedIOException) {
147       throw new AnalysisException("Analysis cancelled", e);
148     }
149   }
150
151   private void processRecognitionException(RecognitionException e, SensorContext sensorContext, InputFile inputFile) {
152     if (parsingErrorRuleKey != null) {
153       NewIssue newIssue = sensorContext.newIssue();
154
155       NewIssueLocation primaryLocation = newIssue.newLocation()
156         .message(e.getMessage())
157         .on(inputFile)
158         .at(inputFile.selectLine(e.getLine()));
159
160       newIssue
161         .forRule(parsingErrorRuleKey)
162         .at(primaryLocation)
163         .save();
164     }
165   }
166
167   private void scanFile(SensorContext sensorContext, InputFile inputFile, List<TreeVisitor> visitors, ScriptTree scriptTree) {
168     JavaScriptVisitorContext context = new JavaScriptVisitorContext(scriptTree, inputFile.file(), settings);
169
170     highlightSymbols(sensorContext.newSymbolTable().onFile(inputFile), context);
171
172     List<Issue> fileIssues = new ArrayList<>();
173
174     for (TreeVisitor visitor : visitors) {
175       if (visitor instanceof CharsetAwareVisitor) {
176         ((CharsetAwareVisitor) visitor).setCharset(fileSystem.encoding());
177       }
178
179       if (visitor instanceof JavaScriptCheck) {
180         fileIssues.addAll(((JavaScriptCheck) visitor).scanFile(context));
181
182       } else {
183         visitor.scanTree(context);
184       }
185
186     }
187
188     saveFileIssues(sensorContext, fileIssues, inputFile);
189   }
190
191
192   private static void highlightSymbols(NewSymbolTable newSymbolTable, TreeVisitorContext context) {
193     HighlightSymbolTableBuilder.build(newSymbolTable, context);
194   }
195
196   private void saveFileIssues(SensorContext sensorContext, List<Issue> fileIssues, InputFile inputFile) {
197     for (Issue issue : fileIssues) {
198       RuleKey ruleKey = ruleKey(issue.check());
199       if (issue instanceof FileIssue) {
200         saveFileIssue(sensorContext, inputFile, ruleKey, (FileIssue) issue);
201
202       } else if (issue instanceof LineIssue) {
203         saveLineIssue(sensorContext, inputFile, ruleKey, (LineIssue) issue);
204
205       } else {
206         savePreciseIssue(sensorContext, inputFile, ruleKey, (PreciseIssue)issue);
207       }
208     }
209   }
210
211   private static void savePreciseIssue(SensorContext sensorContext, InputFile inputFile, RuleKey ruleKey, PreciseIssue issue) {
212     NewIssue newIssue = sensorContext.newIssue();
213
214     newIssue
215       .forRule(ruleKey)
216       .at(newLocation(inputFile, newIssue, issue.primaryLocation()));
217
218     if (issue.cost() != null) {
219       newIssue.gap(issue.cost());
220     }
221
222     for (IssueLocation secondary : issue.secondaryLocations()) {
223       newIssue.addLocation(newLocation(inputFile, newIssue, secondary));
224     }
225     newIssue.save();
226   }
227
228
229   private static NewIssueLocation newLocation(InputFile inputFile, NewIssue issue, IssueLocation location) {
230     TextRange range = inputFile.newRange(
231       location.startLine(), location.startLineOffset(), location.endLine(), location.endLineOffset());
232
233     NewIssueLocation newLocation = issue.newLocation()
234       .on(inputFile)
235       .at(range);
236
237     if (location.message() != null) {
238       newLocation.message(location.message());
239     }
240     return newLocation;
241   }
242
243
244   private RuleKey ruleKey(JavaScriptCheck check) {
245     Preconditions.checkNotNull(check);
246     RuleKey ruleKey = checks.ruleKeyFor(check);
247     if (ruleKey == null) {
248       throw new IllegalStateException("No rule key found for a rule");
249     }
250     return ruleKey;
251   }
252
253   public boolean isExcluded(File file) {
254     boolean isMinified = new MinificationAssessor(getEncoding()).isMinified(file);
255     if (isMinified) {
256       LOG.debug("File [" + file.getAbsolutePath() + "] looks like a minified file and will not be analyzed");
257     }
258     return isMinified;
259   }
260
261   @Override
262   public void describe(SensorDescriptor descriptor) {
263     descriptor
264       .onlyOnLanguage(JavaScriptLanguage.KEY)
265       .name("Custom JavaScript Sensor")
266       .onlyOnFileType(Type.MAIN);
267   }
268
269   @Override
270   public void execute(SensorContext context) {
271     List<TreeVisitor> treeVisitors = Lists.newArrayList();
272     treeVisitors.addAll(checks.visitorChecks());
273
274     for (TreeVisitor check : treeVisitors) {
275       if (check instanceof ParsingErrorCheck) {
276         parsingErrorRuleKey = checks.ruleKeyFor((JavaScriptCheck) check);
277         break;
278       }
279     }
280
281     analyseFiles(context, treeVisitors, fileSystem.inputFiles(mainFilePredicate));
282
283   }
284
285
286   private static void saveLineIssue(SensorContext sensorContext, InputFile inputFile, RuleKey ruleKey, LineIssue issue) {
287     NewIssue newIssue = sensorContext.newIssue();
288
289     NewIssueLocation primaryLocation = newIssue.newLocation()
290       .message(issue.message())
291       .on(inputFile)
292       .at(inputFile.selectLine(issue.line()));
293
294     saveIssue(newIssue, primaryLocation, ruleKey, issue);
295   }
296
297   private static void saveFileIssue(SensorContext sensorContext, InputFile inputFile, RuleKey ruleKey, FileIssue issue) {
298     NewIssue newIssue = sensorContext.newIssue();
299
300     NewIssueLocation primaryLocation = newIssue.newLocation()
301       .message(issue.message())
302       .on(inputFile);
303
304     saveIssue(newIssue, primaryLocation, ruleKey, issue);
305   }
306
307   private static void saveIssue(NewIssue newIssue, NewIssueLocation primaryLocation, RuleKey ruleKey, Issue issue) {
308     newIssue
309       .forRule(ruleKey)
310       .at(primaryLocation);
311
312     if (issue.cost() != null) {
313       newIssue.gap(issue.cost());
314     }
315
316     newIssue.save();
317   }
318
319 }