1 package de.example.plugins.custom.javascript;
3 * SonarQube JavaScript Plugin
4 * Copyright (C) 2011-2016 SonarSource SA
5 * mailto:contact AT sonarsource DOT com
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.
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.
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.
23 import java.io.InterruptedIOException;
24 import java.nio.charset.Charset;
25 import java.util.ArrayList;
26 import java.util.List;
28 import javax.annotation.Nullable;
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;
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;
74 import de.example.custom.javascript.checks.CheckList;
75 import de.example.plugins.custom.javascript.minify.MinificationAssessor;
77 public class CustomSensor implements Sensor {
79 private static final Logger LOG = Loggers.get(CustomSensor.class);
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;
90 CheckFactory checkFactory, FileLinesContextFactory fileLinesContextFactory, FileSystem fileSystem, NoSonarFilter noSonarFilter, Settings settings) {
91 this(checkFactory, fileSystem, settings, null);
95 CheckFactory checkFactory, FileSystem fileSystem,
96 Settings settings, @Nullable CustomJavaScriptRulesDefinition[] customRulesDefinition
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());
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);
119 private Charset getEncoding() {
120 return fileSystem.encoding();
124 private void analyse(SensorContext sensorContext, InputFile inputFile, List<TreeVisitor> visitors) {
125 ScriptTree scriptTree;
128 scriptTree = (ScriptTree) parser.parse(new java.io.File(inputFile.absolutePath()));
129 scanFile(sensorContext, inputFile, visitors, scriptTree);
131 } catch (RecognitionException e) {
133 LOG.error("Unable to parse file: " + inputFile.absolutePath());
134 LOG.error(e.getMessage());
135 processRecognitionException(e, sensorContext, inputFile);
137 } catch (Exception e) {
139 throw new AnalysisException("Unable to analyse file: " + inputFile.absolutePath(), e);
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);
151 private void processRecognitionException(RecognitionException e, SensorContext sensorContext, InputFile inputFile) {
152 if (parsingErrorRuleKey != null) {
153 NewIssue newIssue = sensorContext.newIssue();
155 NewIssueLocation primaryLocation = newIssue.newLocation()
156 .message(e.getMessage())
158 .at(inputFile.selectLine(e.getLine()));
161 .forRule(parsingErrorRuleKey)
167 private void scanFile(SensorContext sensorContext, InputFile inputFile, List<TreeVisitor> visitors, ScriptTree scriptTree) {
168 JavaScriptVisitorContext context = new JavaScriptVisitorContext(scriptTree, inputFile.file(), settings);
170 highlightSymbols(sensorContext.newSymbolTable().onFile(inputFile), context);
172 List<Issue> fileIssues = new ArrayList<>();
174 for (TreeVisitor visitor : visitors) {
175 if (visitor instanceof CharsetAwareVisitor) {
176 ((CharsetAwareVisitor) visitor).setCharset(fileSystem.encoding());
179 if (visitor instanceof JavaScriptCheck) {
180 fileIssues.addAll(((JavaScriptCheck) visitor).scanFile(context));
183 visitor.scanTree(context);
188 saveFileIssues(sensorContext, fileIssues, inputFile);
192 private static void highlightSymbols(NewSymbolTable newSymbolTable, TreeVisitorContext context) {
193 HighlightSymbolTableBuilder.build(newSymbolTable, context);
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);
202 } else if (issue instanceof LineIssue) {
203 saveLineIssue(sensorContext, inputFile, ruleKey, (LineIssue) issue);
206 savePreciseIssue(sensorContext, inputFile, ruleKey, (PreciseIssue)issue);
211 private static void savePreciseIssue(SensorContext sensorContext, InputFile inputFile, RuleKey ruleKey, PreciseIssue issue) {
212 NewIssue newIssue = sensorContext.newIssue();
216 .at(newLocation(inputFile, newIssue, issue.primaryLocation()));
218 if (issue.cost() != null) {
219 newIssue.gap(issue.cost());
222 for (IssueLocation secondary : issue.secondaryLocations()) {
223 newIssue.addLocation(newLocation(inputFile, newIssue, secondary));
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());
233 NewIssueLocation newLocation = issue.newLocation()
237 if (location.message() != null) {
238 newLocation.message(location.message());
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");
253 public boolean isExcluded(File file) {
254 boolean isMinified = new MinificationAssessor(getEncoding()).isMinified(file);
256 LOG.debug("File [" + file.getAbsolutePath() + "] looks like a minified file and will not be analyzed");
262 public void describe(SensorDescriptor descriptor) {
264 .onlyOnLanguage(JavaScriptLanguage.KEY)
265 .name("Custom JavaScript Sensor")
266 .onlyOnFileType(Type.MAIN);
270 public void execute(SensorContext context) {
271 List<TreeVisitor> treeVisitors = Lists.newArrayList();
272 treeVisitors.addAll(checks.visitorChecks());
274 for (TreeVisitor check : treeVisitors) {
275 if (check instanceof ParsingErrorCheck) {
276 parsingErrorRuleKey = checks.ruleKeyFor((JavaScriptCheck) check);
281 analyseFiles(context, treeVisitors, fileSystem.inputFiles(mainFilePredicate));
286 private static void saveLineIssue(SensorContext sensorContext, InputFile inputFile, RuleKey ruleKey, LineIssue issue) {
287 NewIssue newIssue = sensorContext.newIssue();
289 NewIssueLocation primaryLocation = newIssue.newLocation()
290 .message(issue.message())
292 .at(inputFile.selectLine(issue.line()));
294 saveIssue(newIssue, primaryLocation, ruleKey, issue);
297 private static void saveFileIssue(SensorContext sensorContext, InputFile inputFile, RuleKey ruleKey, FileIssue issue) {
298 NewIssue newIssue = sensorContext.newIssue();
300 NewIssueLocation primaryLocation = newIssue.newLocation()
301 .message(issue.message())
304 saveIssue(newIssue, primaryLocation, ruleKey, issue);
307 private static void saveIssue(NewIssue newIssue, NewIssueLocation primaryLocation, RuleKey ruleKey, Issue issue) {
310 .at(primaryLocation);
312 if (issue.cost() != null) {
313 newIssue.gap(issue.cost());