4848import java .util .concurrent .atomic .AtomicReference ;
4949import java .util .function .Consumer ;
5050import java .util .function .Predicate ;
51+ import java .util .regex .Matcher ;
5152import java .util .regex .Pattern ;
5253import java .util .stream .Collectors ;
5354
@@ -1083,32 +1084,18 @@ private void handleSketchProblems(PreprocessedSketch ps) {
10831084
10841085 IProblem [] iproblems = ps .compilationUnit .getProblems ();
10851086
1086- { // Handle missing brace problems
1087- IProblem missingBraceProblem = Arrays .stream (iproblems )
1088- .filter (ErrorChecker ::isMissingBraceProblem )
1089- .findFirst ()
1090- // Ignore if it is at the end of file
1091- .filter (p -> p .getSourceEnd () + 1 < ps .javaCode .length ())
1092- // Ignore if the tab number does not match our detected tab number
1093- .filter (p -> ps .missingBraceProblems .isEmpty () ||
1094- ps .missingBraceProblems .get (0 ).getTabIndex () ==
1095- ps .mapJavaToSketch (p .getSourceStart (), p .getSourceEnd ()+1 ).tabIndex
1096- )
1097- .orElse (null );
1098-
1099- // If there is missing brace ignore all other problems
1100- if (missingBraceProblem != null ) {
1101- // Prefer ECJ problem, shows location more accurately
1102- iproblems = new IProblem []{missingBraceProblem };
1103- } else if (!ps .missingBraceProblems .isEmpty ()) {
1104- // Fallback to manual detection
1105- problems .addAll (ps .missingBraceProblems );
1106- }
1087+ { // Check for curly quotes
1088+ List <JavaProblem > curlyQuoteProblems = checkForCurlyQuotes (ps );
1089+ problems .addAll (curlyQuoteProblems );
11071090 }
11081091
1109- AtomicReference <ClassPath > searchClassPath = new AtomicReference <>(null );
1092+ if (problems .isEmpty ()) { // Check for missing braces
1093+ List <JavaProblem > missingBraceProblems = checkForMissingBraces (ps );
1094+ problems .addAll (missingBraceProblems );
1095+ }
11101096
11111097 if (problems .isEmpty ()) {
1098+ AtomicReference <ClassPath > searchClassPath = new AtomicReference <>(null );
11121099 List <Problem > cuProblems = Arrays .stream (iproblems )
11131100 // Filter Warnings if they are not enabled
11141101 .filter (iproblem -> !(iproblem .isWarning () && !JavaMode .warningsEnabled ))
@@ -1121,19 +1108,10 @@ private void handleSketchProblems(PreprocessedSketch ps) {
11211108 .contains ("Syntax error, insert \" :: IdentifierOrNew\" " ))
11221109 // Transform into our Problems
11231110 .map (iproblem -> {
1124- int start = iproblem .getSourceStart ();
1125- int stop = iproblem .getSourceEnd () + 1 ; // make it exclusive
1126- SketchInterval in = ps .mapJavaToSketch (start , stop );
1127- if (in == SketchInterval .BEFORE_START ) return null ;
1128- int line = ps .tabOffsetToTabLine (in .tabIndex , in .startTabOffset );
1129- ps .sketch .updateSketchCodes (); // seems to be needed
1130- String badCode = ps .sketch .getCode (in .tabIndex ).getProgram ()
1131- .substring (in .startTabOffset , in .stopTabOffset );
1132- JavaProblem p = JavaProblem .fromIProblem (iproblem , in .tabIndex , line , badCode );
1133- p .setPDEOffsets (in .startTabOffset , in .stopTabOffset );
1111+ JavaProblem p = convertIProblem (iproblem , ps );
11341112
11351113 // Handle import suggestions
1136- if (JavaMode .importSuggestEnabled && isUndefinedTypeProblem (iproblem )) {
1114+ if (p != null && JavaMode .importSuggestEnabled && isUndefinedTypeProblem (iproblem )) {
11371115 ClassPath cp = searchClassPath .updateAndGet (prev -> prev != null ?
11381116 prev : new ClassPathFactory ().createFromPaths (ps .searchClassPathArray ));
11391117 String [] s = suggCache .computeIfAbsent (iproblem .getArguments ()[0 ],
@@ -1163,6 +1141,16 @@ private void handleSketchProblems(PreprocessedSketch ps) {
11631141 TimeUnit .MILLISECONDS );
11641142 }
11651143
1144+ static private JavaProblem convertIProblem (IProblem iproblem , PreprocessedSketch ps ) {
1145+ SketchInterval in = ps .mapJavaToSketch (iproblem );
1146+ if (in == SketchInterval .BEFORE_START ) return null ;
1147+ String badCode = ps .getPdeCode (in );
1148+ int line = ps .tabOffsetToTabLine (in .tabIndex , in .startTabOffset );
1149+ JavaProblem p = JavaProblem .fromIProblem (iproblem , in .tabIndex , line , badCode );
1150+ p .setPDEOffsets (in .startTabOffset , in .stopTabOffset );
1151+ return p ;
1152+ }
1153+
11661154
11671155 static private boolean isUndefinedTypeProblem (IProblem iproblem ) {
11681156 int id = iproblem .getID ();
@@ -1188,6 +1176,119 @@ static private boolean isMissingBraceProblem(IProblem iproblem) {
11881176 }
11891177
11901178
1179+ private static final Pattern CURLY_QUOTE_REGEX =
1180+ Pattern .compile ("([“”‘’])" , Pattern .UNICODE_CHARACTER_CLASS );
1181+
1182+ static private List <JavaProblem > checkForCurlyQuotes (PreprocessedSketch ps ) {
1183+ List <JavaProblem > problems = new ArrayList <>(0 );
1184+
1185+ // Go through the scrubbed code and look for curly quotes (they should not be any)
1186+ Matcher matcher = CURLY_QUOTE_REGEX .matcher (ps .scrubbedPdeCode );
1187+ while (matcher .find ()) {
1188+ int pdeOffset = matcher .start ();
1189+ String q = matcher .group ();
1190+
1191+ int tabIndex = ps .pdeOffsetToTabIndex (pdeOffset );
1192+ int tabOffset = ps .pdeOffsetToTabOffset (tabIndex , pdeOffset );
1193+ int tabLine = ps .tabOffsetToTabLine (tabIndex , tabOffset );
1194+
1195+ String message = Language .interpolate ("editor.status.bad_curly_quote" , q );
1196+ JavaProblem problem = new JavaProblem (message , JavaProblem .ERROR , tabIndex , tabLine );
1197+ problem .setPDEOffsets (tabOffset , tabOffset +1 );
1198+
1199+ problems .add (problem );
1200+ }
1201+
1202+
1203+ // Go through iproblems and look for problems involving curly quotes
1204+ List <JavaProblem > problems2 = new ArrayList <>(0 );
1205+ IProblem [] iproblems = ps .compilationUnit .getProblems ();
1206+
1207+ for (IProblem iproblem : iproblems ) {
1208+ switch (iproblem .getID ()) {
1209+ case IProblem .ParsingErrorDeleteToken :
1210+ case IProblem .ParsingErrorDeleteTokens :
1211+ case IProblem .ParsingErrorInvalidToken :
1212+ case IProblem .ParsingErrorReplaceTokens :
1213+ case IProblem .UnterminatedString :
1214+ SketchInterval in = ps .mapJavaToSketch (iproblem );
1215+ if (in == SketchInterval .BEFORE_START ) continue ;
1216+ String badCode = ps .getPdeCode (in );
1217+ matcher .reset (badCode );
1218+ while (matcher .find ()) {
1219+ int offset = matcher .start ();
1220+ String q = matcher .group ();
1221+ int tabStart = in .startTabOffset + offset ;
1222+ int tabStop = tabStart + 1 ;
1223+ // Prevent duplicate problems
1224+ if (problems .stream ().noneMatch (p -> p .getStartOffset () == tabStart )) {
1225+ int line = ps .tabOffsetToTabLine (in .tabIndex , tabStart );
1226+ String message ;
1227+ if (iproblem .getID () == IProblem .UnterminatedString ) {
1228+ message = Language .interpolate ("editor.status.unterm_string_curly" , q );
1229+ } else {
1230+ message = Language .interpolate ("editor.status.bad_curly_quote" , q );
1231+ }
1232+ JavaProblem p = new JavaProblem (message , JavaProblem .ERROR , in .tabIndex , line );
1233+ p .setPDEOffsets (tabStart , tabStop );
1234+ problems2 .add (p );
1235+ }
1236+ }
1237+ }
1238+ }
1239+
1240+ problems .addAll (problems2 );
1241+
1242+ return problems ;
1243+ }
1244+
1245+
1246+ static private List <JavaProblem > checkForMissingBraces (PreprocessedSketch ps ) {
1247+ List <JavaProblem > problems = new ArrayList <>(0 );
1248+ for (int tabIndex = 0 ; tabIndex < ps .tabStartOffsets .length ; tabIndex ++) {
1249+ int tabStartOffset = ps .tabStartOffsets [tabIndex ];
1250+ int tabEndOffset = (tabIndex < ps .tabStartOffsets .length - 1 ) ?
1251+ ps .tabStartOffsets [tabIndex + 1 ] : ps .scrubbedPdeCode .length ();
1252+ int [] braceResult = SourceUtils .checkForMissingBraces (ps .scrubbedPdeCode , tabStartOffset , tabEndOffset );
1253+ if (braceResult [0 ] != 0 ) {
1254+ JavaProblem problem =
1255+ new JavaProblem (braceResult [0 ] < 0
1256+ ? Language .interpolate ("editor.status.missing.left_curly_bracket" )
1257+ : Language .interpolate ("editor.status.missing.right_curly_bracket" ),
1258+ JavaProblem .ERROR , tabIndex , braceResult [1 ]);
1259+ problem .setPDEOffsets (braceResult [3 ], braceResult [3 ] + 1 );
1260+ problems .add (problem );
1261+ }
1262+ }
1263+
1264+ if (problems .isEmpty ()) {
1265+ return problems ;
1266+ }
1267+
1268+ int problemTabIndex = problems .get (0 ).getTabIndex ();
1269+
1270+ IProblem missingBraceProblem = Arrays .stream (ps .compilationUnit .getProblems ())
1271+ .filter (ErrorChecker ::isMissingBraceProblem )
1272+ // Ignore if it is at the end of file
1273+ .filter (p -> p .getSourceEnd () + 1 < ps .javaCode .length ())
1274+ // Ignore if the tab number does not match our detected tab number
1275+ .filter (p -> problemTabIndex == ps .mapJavaToSketch (p ).tabIndex )
1276+ .findFirst ()
1277+ .orElse (null );
1278+
1279+ // Prefer ECJ problem, shows location more accurately
1280+ if (missingBraceProblem != null ) {
1281+ JavaProblem p = convertIProblem (missingBraceProblem , ps );
1282+ if (p != null ) {
1283+ problems .clear ();
1284+ problems .add (p );
1285+ }
1286+ }
1287+
1288+ return problems ;
1289+ }
1290+
1291+
11911292 static public String [] getImportSuggestions (ClassPath cp , String className ) {
11921293 RegExpResourceFilter regf = new RegExpResourceFilter (
11931294 Pattern .compile (".*" ),
0 commit comments