From 46b5e29fb3e6e51981f66b8545578b5214b08ed2 Mon Sep 17 00:00:00 2001 From: Jakub Valtar Date: Tue, 19 Sep 2017 22:52:49 +0200 Subject: [PATCH] Gather error checking code in PDEX.ErrorChecker --- java/src/processing/mode/java/pdex/PDEX.java | 165 ++++++++++++++---- .../mode/java/pdex/PreprocessedSketch.java | 21 ++- .../mode/java/pdex/PreprocessingService.java | 11 +- .../mode/java/pdex/SourceUtils.java | 22 +-- 4 files changed, 150 insertions(+), 69 deletions(-) diff --git a/java/src/processing/mode/java/pdex/PDEX.java b/java/src/processing/mode/java/pdex/PDEX.java index 47c32d4b0..aa673afaa 100644 --- a/java/src/processing/mode/java/pdex/PDEX.java +++ b/java/src/processing/mode/java/pdex/PDEX.java @@ -48,6 +48,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.function.Consumer; import java.util.function.Predicate; +import java.util.regex.Matcher; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -1083,32 +1084,18 @@ public class PDEX { IProblem[] iproblems = ps.compilationUnit.getProblems(); - { // Handle missing brace problems - IProblem missingBraceProblem = Arrays.stream(iproblems) - .filter(ErrorChecker::isMissingBraceProblem) - .findFirst() - // Ignore if it is at the end of file - .filter(p -> p.getSourceEnd() + 1 < ps.javaCode.length()) - // Ignore if the tab number does not match our detected tab number - .filter(p -> ps.missingBraceProblems.isEmpty() || - ps.missingBraceProblems.get(0).getTabIndex() == - ps.mapJavaToSketch(p.getSourceStart(), p.getSourceEnd()+1).tabIndex - ) - .orElse(null); - - // If there is missing brace ignore all other problems - if (missingBraceProblem != null) { - // Prefer ECJ problem, shows location more accurately - iproblems = new IProblem[]{missingBraceProblem}; - } else if (!ps.missingBraceProblems.isEmpty()) { - // Fallback to manual detection - problems.addAll(ps.missingBraceProblems); - } + { // Check for curly quotes + List curlyQuoteProblems = checkForCurlyQuotes(ps); + problems.addAll(curlyQuoteProblems); } - AtomicReference searchClassPath = new AtomicReference<>(null); + if (problems.isEmpty()) { // Check for missing braces + List missingBraceProblems = checkForMissingBraces(ps); + problems.addAll(missingBraceProblems); + } if (problems.isEmpty()) { + AtomicReference searchClassPath = new AtomicReference<>(null); List cuProblems = Arrays.stream(iproblems) // Filter Warnings if they are not enabled .filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled)) @@ -1121,17 +1108,10 @@ public class PDEX { .contains("Syntax error, insert \":: IdentifierOrNew\"")) // Transform into our Problems .map(iproblem -> { - int start = iproblem.getSourceStart(); - int stop = iproblem.getSourceEnd() + 1; // make it exclusive - SketchInterval in = ps.mapJavaToSketch(start, stop); - if (in == SketchInterval.BEFORE_START) return null; - String badCode = ps.pdeCode.substring(in.startPdeOffset, in.stopPdeOffset); - int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset); - JavaProblem p = JavaProblem.fromIProblem(iproblem, in.tabIndex, line, badCode); - p.setPDEOffsets(in.startTabOffset, in.stopTabOffset); + JavaProblem p = convertIProblem(iproblem, ps); // Handle import suggestions - if (JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) { + if (p != null && JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) { ClassPath cp = searchClassPath.updateAndGet(prev -> prev != null ? prev : new ClassPathFactory().createFromPaths(ps.searchClassPathArray)); String[] s = suggCache.computeIfAbsent(iproblem.getArguments()[0], @@ -1161,6 +1141,16 @@ public class PDEX { TimeUnit.MILLISECONDS); } + static private JavaProblem convertIProblem(IProblem iproblem, PreprocessedSketch ps) { + SketchInterval in = ps.mapJavaToSketch(iproblem); + if (in == SketchInterval.BEFORE_START) return null; + String badCode = ps.pdeCode.substring(in.startPdeOffset, in.stopPdeOffset); + int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset); + JavaProblem p = JavaProblem.fromIProblem(iproblem, in.tabIndex, line, badCode); + p.setPDEOffsets(in.startTabOffset, in.stopTabOffset); + return p; + } + static private boolean isUndefinedTypeProblem(IProblem iproblem) { int id = iproblem.getID(); @@ -1186,6 +1176,119 @@ public class PDEX { } + private static final Pattern CURLY_QUOTE_REGEX = + Pattern.compile("([“”‘’])", Pattern.UNICODE_CHARACTER_CLASS); + + static private List checkForCurlyQuotes(PreprocessedSketch ps) { + List problems = new ArrayList<>(0); + + // Go through the scrubbed code and look for curly quotes (they should not be any) + Matcher matcher = CURLY_QUOTE_REGEX.matcher(ps.scrubbedPdeCode); + while (matcher.find()) { + int pdeOffset = matcher.start(); + String q = matcher.group(); + + int tabIndex = ps.pdeOffsetToTabIndex(pdeOffset); + int tabOffset = ps.pdeOffsetToTabOffset(tabIndex, pdeOffset); + int tabLine = ps.tabOffsetToTabLine(tabIndex, tabOffset); + + String message = Language.interpolate("editor.status.bad_curly_quote", q); + JavaProblem problem = new JavaProblem(message, JavaProblem.ERROR, tabIndex, tabLine, 10); + problem.setPDEOffsets(tabOffset, tabOffset+1); + + problems.add(problem); + } + + + // Go through iproblems and look for problems involving curly quotes + List problems2 = new ArrayList<>(0); + IProblem[] iproblems = ps.compilationUnit.getProblems(); + + for (IProblem iproblem : iproblems) { + switch (iproblem.getID()) { + case IProblem.ParsingErrorDeleteToken: + case IProblem.ParsingErrorDeleteTokens: + case IProblem.ParsingErrorInvalidToken: + case IProblem.ParsingErrorReplaceTokens: + case IProblem.UnterminatedString: + SketchInterval in = ps.mapJavaToSketch(iproblem); + if (in == SketchInterval.BEFORE_START) continue; + String badCode = ps.pdeCode.substring(in.startPdeOffset, in.stopPdeOffset); + matcher.reset(badCode); + while (matcher.find()) { + int offset = matcher.start(); + String q = matcher.group(); + int tabStart = in.startTabOffset + offset; + int tabStop = tabStart + 1; + // Prevent duplicate problems + if (problems.stream().noneMatch(p -> p.getStartOffset() == tabStart)) { + int line = ps.tabOffsetToTabLine(in.tabIndex, tabStart); + String message; + if (iproblem.getID() == IProblem.UnterminatedString) { + message = Language.interpolate("editor.status.unterm_string_curly", q); + } else { + message = Language.interpolate("editor.status.bad_curly_quote", q); + } + JavaProblem p = new JavaProblem(message, JavaProblem.ERROR, in.tabIndex, line, 10); + p.setPDEOffsets(tabStart, tabStop); + problems2.add(p); + } + } + } + } + + problems.addAll(problems2); + + return problems; + } + + + static private List checkForMissingBraces(PreprocessedSketch ps) { + List problems = new ArrayList<>(0); + for (int tabIndex = 0; tabIndex < ps.tabStartOffsets.length; tabIndex++) { + int tabStartOffset = ps.tabStartOffsets[tabIndex]; + int tabEndOffset = (tabIndex < ps.tabStartOffsets.length - 1) ? + ps.tabStartOffsets[tabIndex + 1] : ps.scrubbedPdeCode.length(); + int[] braceResult = SourceUtils.checkForMissingBraces(ps.scrubbedPdeCode, tabStartOffset, tabEndOffset); + if (braceResult[0] != 0) { + JavaProblem problem = + new JavaProblem(braceResult[0] < 0 + ? Language.interpolate("editor.status.missing.left_curly_bracket") + : Language.interpolate("editor.status.missing.right_curly_bracket"), + JavaProblem.ERROR, tabIndex, braceResult[1], 8); + problem.setPDEOffsets(braceResult[3], braceResult[3] + 1); + problems.add(problem); + } + } + + if (problems.isEmpty()) { + return problems; + } + + int problemTabIndex = problems.get(0).getTabIndex(); + + IProblem missingBraceProblem = Arrays.stream(ps.compilationUnit.getProblems()) + .filter(ErrorChecker::isMissingBraceProblem) + // Ignore if it is at the end of file + .filter(p -> p.getSourceEnd() + 1 < ps.javaCode.length()) + // Ignore if the tab number does not match our detected tab number + .filter(p -> problemTabIndex == ps.mapJavaToSketch(p).tabIndex) + .findFirst() + .orElse(null); + + // Prefer ECJ problem, shows location more accurately + if (missingBraceProblem != null) { + JavaProblem p = convertIProblem(missingBraceProblem, ps); + if (p != null) { + problems.clear(); + problems.add(p); + } + } + + return problems; + } + + static public String[] getImportSuggestions(ClassPath cp, String className) { RegExpResourceFilter regf = new RegExpResourceFilter( Pattern.compile(".*"), diff --git a/java/src/processing/mode/java/pdex/PreprocessedSketch.java b/java/src/processing/mode/java/pdex/PreprocessedSketch.java index 50838eff8..daaf22ec1 100644 --- a/java/src/processing/mode/java/pdex/PreprocessedSketch.java +++ b/java/src/processing/mode/java/pdex/PreprocessedSketch.java @@ -2,6 +2,7 @@ package processing.mode.java.pdex; import com.google.classpath.ClassPath; +import org.eclipse.jdt.core.compiler.IProblem; import org.eclipse.jdt.core.dom.ASTNode; import org.eclipse.jdt.core.dom.CompilationUnit; @@ -11,7 +12,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; -import processing.app.Problem; import processing.app.Sketch; import processing.core.PApplet; import processing.mode.java.pdex.TextTransform.OffsetMapper; @@ -30,13 +30,12 @@ public class PreprocessedSketch { public final int[] tabStartOffsets; + public final String scrubbedPdeCode; public final String pdeCode; public final String javaCode; public final OffsetMapper offsetMapper; - public final List missingBraceProblems; - public final boolean hasSyntaxErrors; public final boolean hasCompilationErrors; @@ -84,6 +83,12 @@ public class PreprocessedSketch { } + public SketchInterval mapJavaToSketch(IProblem iproblem) { + return mapJavaToSketch(iproblem.getSourceStart(), + iproblem.getSourceEnd() + 1); // make it exclusive + } + + public SketchInterval mapJavaToSketch(int startJavaOffset, int stopJavaOffset) { int length = stopJavaOffset - startJavaOffset; int startPdeOffset = javaOffsetToPdeOffset(startJavaOffset); @@ -120,7 +125,7 @@ public class PreprocessedSketch { } - private int pdeOffsetToTabIndex(int pdeOffset) { + public int pdeOffsetToTabIndex(int pdeOffset) { pdeOffset = Math.max(0, pdeOffset); int tab = Arrays.binarySearch(tabStartOffsets, pdeOffset); if (tab < 0) { @@ -130,7 +135,7 @@ public class PreprocessedSketch { } - private int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) { + public int pdeOffsetToTabOffset(int tabIndex, int pdeOffset) { int tabStartOffset = tabStartOffsets[clipTabIndex(tabIndex)]; return pdeOffset - tabStartOffset; } @@ -210,13 +215,12 @@ public class PreprocessedSketch { public int[] tabStartOffsets = new int[0]; + public String scrubbedPdeCode; public String pdeCode; public String javaCode; public OffsetMapper offsetMapper; - public final List missingBraceProblems = new ArrayList<>(0); - public boolean hasSyntaxErrors; public boolean hasCompilationErrors; @@ -246,13 +250,12 @@ public class PreprocessedSketch { tabStartOffsets = b.tabStartOffsets; + scrubbedPdeCode = b.scrubbedPdeCode; pdeCode = b.pdeCode; javaCode = b.javaCode; offsetMapper = b.offsetMapper != null ? b.offsetMapper : OffsetMapper.EMPTY_MAPPER; - missingBraceProblems = Collections.unmodifiableList(b.missingBraceProblems); - hasSyntaxErrors = b.hasSyntaxErrors; hasCompilationErrors = b.hasCompilationErrors; diff --git a/java/src/processing/mode/java/pdex/PreprocessingService.java b/java/src/processing/mode/java/pdex/PreprocessingService.java index 5e9d3f0bf..86990342b 100644 --- a/java/src/processing/mode/java/pdex/PreprocessingService.java +++ b/java/src/processing/mode/java/pdex/PreprocessingService.java @@ -312,6 +312,8 @@ public class PreprocessingService { SourceUtils.scrubCommentsAndStrings(workBuffer); + result.scrubbedPdeCode = workBuffer.toString(); + Mode sketchMode = PdePreprocessor.parseMode(workBuffer); // Prepare transforms to convert pde code into parsable code @@ -394,15 +396,6 @@ public class PreprocessingService { } } - { // Check for missing braces - List missingBraceProblems = - SourceUtils.checkForMissingBraces(workBuffer, result.tabStartOffsets); - if (!missingBraceProblems.isEmpty()) { - result.missingBraceProblems.addAll(missingBraceProblems); - result.hasSyntaxErrors = true; - } - } - // Transform code to parsable state String parsableStage = toParsable.apply(); OffsetMapper parsableMapper = toParsable.getMapper(); diff --git a/java/src/processing/mode/java/pdex/SourceUtils.java b/java/src/processing/mode/java/pdex/SourceUtils.java index 8e9968f99..bb29cda84 100644 --- a/java/src/processing/mode/java/pdex/SourceUtils.java +++ b/java/src/processing/mode/java/pdex/SourceUtils.java @@ -331,27 +331,9 @@ public class SourceUtils { } - static public List checkForMissingBraces(StringBuilder p, int[] tabStartOffsets) { - List problems = new ArrayList<>(0); - for (int tabIndex = 0; tabIndex < tabStartOffsets.length; tabIndex++) { - int tabStartOffset = tabStartOffsets[tabIndex]; - int tabEndOffset = (tabIndex < tabStartOffsets.length - 1) ? - tabStartOffsets[tabIndex + 1] : p.length(); - int[] braceResult = checkForMissingBraces(p, tabStartOffset, tabEndOffset); - if (braceResult[0] != 0) { - JavaProblem problem = - new JavaProblem(braceResult[0] < 0 - ? "Found one too many } characters without { to match it." - : "Found one too many { characters without } to match it.", - JavaProblem.ERROR, tabIndex, braceResult[1], 8); - problem.setPDEOffsets(braceResult[3], braceResult[3] + 1); - problems.add(problem); - } - } - return problems; - } - + // TODO: move this to a better place when JavaBuild starts using JDT and we + // don't need to check errors at two different places [jv 2017-09-19] /** * Checks a single code fragment (such as a tab) for non-matching braces. * Broken out to allow easy use in JavaBuild.