From b72a39564bdf0d7de94e527b6e6c783564317cb1 Mon Sep 17 00:00:00 2001 From: Jakub Valtar Date: Fri, 16 Dec 2016 04:36:23 +0100 Subject: [PATCH] Detect missing braces in tabs, ignore other problems until fixed --- .../mode/java/pdex/JavaProblem.java | 15 ++- java/src/processing/mode/java/pdex/PDEX.java | 108 ++++++++++++------ .../mode/java/pdex/PreprocessedSketch.java | 7 ++ .../mode/java/pdex/PreprocessingService.java | 11 +- .../mode/java/pdex/SourceUtils.java | 41 +++++++ 5 files changed, 145 insertions(+), 37 deletions(-) diff --git a/java/src/processing/mode/java/pdex/JavaProblem.java b/java/src/processing/mode/java/pdex/JavaProblem.java index bb6a7fb58..bfe39b007 100644 --- a/java/src/processing/mode/java/pdex/JavaProblem.java +++ b/java/src/processing/mode/java/pdex/JavaProblem.java @@ -60,21 +60,28 @@ public class JavaProblem implements Problem { public static final int ERROR = 1, WARNING = 2; + public JavaProblem(String message, int type, int tabIndex, int lineNumber) { + this.message = message; + this.type = type; + this.tabIndex = tabIndex; + this.lineNumber = lineNumber; + } + /** * * @param iProblem - The IProblem which is being wrapped * @param tabIndex - The tab number to which the error belongs to * @param lineNumber - Line number(pde code) of the error */ - public JavaProblem(IProblem iProblem, int tabIndex, int lineNumber) { + public static JavaProblem fromIProblem(IProblem iProblem, int tabIndex, int lineNumber) { + int type = 0; if(iProblem.isError()) { type = ERROR; } else if (iProblem.isWarning()) { type = WARNING; } - this.tabIndex = tabIndex; - this.lineNumber = lineNumber; - this.message = ErrorMessageSimplifier.getSimplifiedErrorMessage(iProblem); + String message = ErrorMessageSimplifier.getSimplifiedErrorMessage(iProblem); + return new JavaProblem(message, type, tabIndex, lineNumber); } public void setPDEOffsets(int startOffset, int stopOffset){ diff --git a/java/src/processing/mode/java/pdex/PDEX.java b/java/src/processing/mode/java/pdex/PDEX.java index 37b0d9b8e..b6a7e0ffb 100644 --- a/java/src/processing/mode/java/pdex/PDEX.java +++ b/java/src/processing/mode/java/pdex/PDEX.java @@ -1079,40 +1079,69 @@ public class PDEX { Map suggCache = JavaMode.importSuggestEnabled ? new HashMap<>() : Collections.emptyMap(); - // Process problems + final List problems = new ArrayList<>(); + IProblem[] iproblems = ps.compilationUnit.getProblems(); - final List problems = Arrays.stream(iproblems) - // Filter Warnings if they are not enabled - .filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled)) - // Hide a useless error which is produced when a line ends with - // an identifier without a semicolon. "Missing a semicolon" is - // also produced and is preferred over this one. - // (Syntax error, insert ":: IdentifierOrNew" to complete Expression) - // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780 - .filter(iproblem -> !iproblem.getMessage() - .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; - int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset); - JavaProblem p = new JavaProblem(iproblem, in.tabIndex, line); - p.setPDEOffsets(in.startTabOffset, in.stopTabOffset); - // Handle import suggestions - if (JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) { - ClassPath cp = ps.searchClassPath; - String[] s = suggCache.computeIfAbsent(iproblem.getArguments()[0], - name -> getImportSuggestions(cp, name)); - p.setImportSuggestions(s); - } + { // 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); - return p; - }) - .filter(Objects::nonNull) - .collect(Collectors.toList()); + // 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); + } + } + + if (problems.isEmpty()) { + List cuProblems = Arrays.stream(iproblems) + // Filter Warnings if they are not enabled + .filter(iproblem -> !(iproblem.isWarning() && !JavaMode.warningsEnabled)) + // Hide a useless error which is produced when a line ends with + // an identifier without a semicolon. "Missing a semicolon" is + // also produced and is preferred over this one. + // (Syntax error, insert ":: IdentifierOrNew" to complete Expression) + // See: https://bugs.eclipse.org/bugs/show_bug.cgi?id=405780 + .filter(iproblem -> !iproblem.getMessage() + .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; + int line = ps.tabOffsetToTabLine(in.tabIndex, in.startTabOffset); + JavaProblem p = JavaProblem.fromIProblem(iproblem, in.tabIndex, line); + p.setPDEOffsets(in.startTabOffset, in.stopTabOffset); + + // Handle import suggestions + if (JavaMode.importSuggestEnabled && isUndefinedTypeProblem(iproblem)) { + ClassPath cp = ps.searchClassPath; + String[] s = suggCache.computeIfAbsent(iproblem.getArguments()[0], + name -> getImportSuggestions(cp, name)); + p.setImportSuggestions(s); + } + + return p; + }) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + problems.addAll(cuProblems); + } if (scheduledUiUpdate != null) { scheduledUiUpdate.cancel(true); @@ -1129,13 +1158,28 @@ public class PDEX { } - private boolean isUndefinedTypeProblem(IProblem iproblem) { + static private boolean isUndefinedTypeProblem(IProblem iproblem) { int id = iproblem.getID(); return id == IProblem.UndefinedType || id == IProblem.UndefinedName || id == IProblem.UnresolvedVariable; } + static private boolean isMissingBraceProblem(IProblem iproblem) { + switch (iproblem.getID()) { + case IProblem.ParsingErrorInsertToComplete: { + char brace = iproblem.getArguments()[0].charAt(0); + return brace == '{' || brace == '}'; + } + case IProblem.ParsingErrorInsertTokenAfter: { + char brace = iproblem.getArguments()[1].charAt(0); + return brace == '{' || brace == '}'; + } + default: + return false; + } + } + public static String[] getImportSuggestions(ClassPath cp, String className) { RegExpResourceFilter regf = new RegExpResourceFilter( diff --git a/java/src/processing/mode/java/pdex/PreprocessedSketch.java b/java/src/processing/mode/java/pdex/PreprocessedSketch.java index 9f4fd2005..53fa7402f 100644 --- a/java/src/processing/mode/java/pdex/PreprocessedSketch.java +++ b/java/src/processing/mode/java/pdex/PreprocessedSketch.java @@ -11,6 +11,7 @@ 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; @@ -34,6 +35,8 @@ public class PreprocessedSketch { public final OffsetMapper offsetMapper; + public final List missingBraceProblems; + public final boolean hasSyntaxErrors; public final boolean hasCompilationErrors; @@ -212,6 +215,8 @@ public class PreprocessedSketch { public OffsetMapper offsetMapper; + public final List missingBraceProblems = new ArrayList<>(0); + public boolean hasSyntaxErrors; public boolean hasCompilationErrors; @@ -246,6 +251,8 @@ public class PreprocessedSketch { 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 8a3005256..6ac0981ef 100644 --- a/java/src/processing/mode/java/pdex/PreprocessingService.java +++ b/java/src/processing/mode/java/pdex/PreprocessingService.java @@ -392,6 +392,15 @@ 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(); @@ -414,7 +423,7 @@ public class PreprocessingService { makeAST(parser, compilableStageChars, COMPILER_OPTIONS); // Get syntax problems from compilable AST - result.hasSyntaxErrors = Arrays.stream(compilableCU.getProblems()) + result.hasSyntaxErrors |= Arrays.stream(compilableCU.getProblems()) .anyMatch(IProblem::isError); // Generate bindings after getting problems - avoids diff --git a/java/src/processing/mode/java/pdex/SourceUtils.java b/java/src/processing/mode/java/pdex/SourceUtils.java index 531c8a3ed..cddfcff1c 100644 --- a/java/src/processing/mode/java/pdex/SourceUtils.java +++ b/java/src/processing/mode/java/pdex/SourceUtils.java @@ -323,4 +323,45 @@ public class SourceUtils { } + static public List checkForMissingBraces(StringBuilder p, int[] tabStartOffsets) { + List problems = new ArrayList<>(0); + tabLoop: for (int tabIndex = 0; tabIndex < tabStartOffsets.length; tabIndex++) { + int tabStartOffset = tabStartOffsets[tabIndex]; + int tabEndOffset = (tabIndex < tabStartOffsets.length - 1) ? + tabStartOffsets[tabIndex + 1] : p.length(); + int depth = 0; + int lineNumber = 0; + for (int i = tabStartOffset; i < tabEndOffset; i++) { + char ch = p.charAt(i); + switch (ch) { + case '{': + depth++; + break; + case '}': + depth--; + break; + case '\n': + lineNumber++; + break; + } + if (depth < 0) { + JavaProblem problem = + new JavaProblem("Found one too many } characters without { to match it.", + JavaProblem.ERROR, tabIndex, lineNumber); + problem.setPDEOffsets(i - tabStartOffset, i - tabStartOffset + 1); + problems.add(problem); + continue tabLoop; + } + } + if (depth > 0) { + JavaProblem problem = + new JavaProblem("Found one too many { characters without } to match it.", + JavaProblem.ERROR, tabIndex, lineNumber - 1); + problem.setPDEOffsets(tabEndOffset - tabStartOffset - 2, tabEndOffset - tabStartOffset - 1); + problems.add(problem); + } + } + return problems; + } + }