| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, |
| * software distributed under the License is distributed on an |
| * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| * KIND, either express or implied. See the License for the |
| * specific language governing permissions and limitations |
| * under the License. |
| */ |
| package org.netbeans.modules.javascript2.editor.parser; |
| |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import javax.swing.event.ChangeListener; |
| import org.netbeans.api.annotations.common.CheckForNull; |
| import org.netbeans.api.annotations.common.NonNull; |
| import org.netbeans.api.lexer.Language; |
| import org.netbeans.api.lexer.Token; |
| import org.netbeans.api.lexer.TokenSequence; |
| import org.netbeans.modules.csl.spi.GsfUtilities; |
| import org.netbeans.modules.javascript2.lexer.api.JsTokenId; |
| import org.netbeans.modules.javascript2.lexer.api.LexUtilities; |
| import org.netbeans.modules.parsing.api.Snapshot; |
| import org.netbeans.modules.parsing.api.Task; |
| import org.netbeans.modules.parsing.spi.ParseException; |
| import org.netbeans.modules.parsing.spi.Parser; |
| import org.netbeans.modules.parsing.spi.SourceModificationEvent; |
| import org.openide.filesystems.FileObject; |
| import org.openide.util.ChangeSupport; |
| |
| /** |
| * |
| * @author Petr Hejl |
| */ |
| public abstract class SanitizingParser<R extends BaseParserResult> extends Parser { |
| |
| private static final Logger LOGGER = Logger.getLogger(SanitizingParser.class.getName()); |
| |
| public static final boolean PARSE_BIG_FILES = Boolean.getBoolean("nb.js.parse.big.files"); //NOI18N |
| public static final long MAX_FILE_SIZE_TO_PARSE = Integer.getInteger("nb.js.big.file.size", 1024 * 1024); //NOI18N |
| private static final long MAX_MINIMIZE_FILE_SIZE_TO_PARSE = Integer.getInteger("nb.js.big.minimize.file.size", 0); //NOI18N |
| /** |
| * This is count of closing curly brackets that follows at the end of a json file. |
| * If the file has sequence of MAX_RICHT_CURLY_BRACKETS, then it's not parse due stack overflow. |
| */ |
| private static final int MAX_RIGHT_CURLY_BRACKETS = 30; |
| |
| private final Language<JsTokenId> language; |
| private final ChangeSupport listeners; |
| |
| private R lastResult = null; |
| |
| public SanitizingParser(Language<JsTokenId> language) { |
| this.language = language; |
| this.listeners = new ChangeSupport(this); |
| } |
| |
| @Override |
| public final void parse(Snapshot snapshot, Task task, SourceModificationEvent event) throws ParseException { |
| if (LOGGER.isLoggable(Level.FINEST)) { |
| LOGGER.log(Level.FINEST, snapshot.getText().toString()); |
| } |
| try { |
| JsErrorManager errorManager = new JsErrorManager(snapshot, language); |
| lastResult = parseSource(snapshot, event, getSanitizeStrategy(), errorManager); |
| lastResult.setErrors(errorManager.getErrors()); |
| } catch (Exception ex) { |
| LOGGER.log (Level.INFO, "Exception during parsing", ex); |
| // TODO create empty result |
| lastResult = createErrorResult(snapshot); |
| } |
| } |
| |
| protected abstract String getDefaultScriptName(); |
| |
| @CheckForNull |
| protected abstract R parseSource(Context context, JsErrorManager errorManager) throws Exception; |
| |
| protected abstract String getMimeType(); |
| |
| @NonNull |
| protected abstract R createErrorResult(@NonNull Snapshot snapshot); |
| |
| protected Sanitize getSanitizeStrategy() { |
| return Sanitize.NONE; |
| } |
| |
| final R parseSource(Snapshot snapshot, SourceModificationEvent event, |
| Sanitize sanitizing, JsErrorManager errorManager) throws Exception { |
| |
| FileObject fo = snapshot.getSource().getFileObject(); |
| long startTime = System.nanoTime(); |
| String scriptName; |
| if (fo != null) { |
| scriptName = snapshot.getSource().getFileObject().getNameExt(); |
| } else { |
| scriptName = getDefaultScriptName(); |
| } |
| if (!isParsable(snapshot)) { |
| return createErrorResult(snapshot); |
| } |
| int caretOffset = GsfUtilities.getLastKnownCaretOffset(snapshot, event); |
| |
| Context context = new Context(scriptName, snapshot, caretOffset, language); |
| R result = parseContext(context, sanitizing, errorManager); |
| if (!result.success() && context.isModule()) { |
| // module may be broken completely by broken/unfinished export |
| // try to at least parse it as normal source |
| context.isModule = false; |
| result = parseContext(context, sanitizing, errorManager, false); |
| } |
| |
| if (LOGGER.isLoggable(Level.FINE)) { |
| LOGGER.log(Level.FINE, "Parsing took: {0} ms; source: {1}", |
| new Object[]{(System.nanoTime() - startTime) / 1000000, scriptName}); |
| } |
| return result; |
| } |
| |
| /** |
| * This method try to analyze the text and says whether the snapshot should be file |
| * @param snapshot |
| * @return whether the snapshot should be parsed |
| */ |
| private boolean isParsable (Snapshot snapshot) { |
| FileObject fo = snapshot.getSource().getFileObject(); |
| boolean isEmbeded = !getMimeType().equals(snapshot.getMimePath().getPath()); |
| Long size; |
| CharSequence text = snapshot.getText(); |
| String scriptName; |
| scriptName = (fo != null) ? snapshot.getSource().getFileObject().getNameExt() : getDefaultScriptName(); |
| size = (fo != null && !isEmbeded) ? fo.getSize() : (long)text.length(); |
| |
| if (!PARSE_BIG_FILES) { |
| if (size > MAX_FILE_SIZE_TO_PARSE) { |
| if (LOGGER.isLoggable(Level.FINE)) { |
| LOGGER.log(Level.FINE, "The file {0} was not parsed because the size is too big.", scriptName); |
| } |
| return false; |
| } |
| |
| if (size > MAX_MINIMIZE_FILE_SIZE_TO_PARSE && !(snapshot.getMimeType().equals(JsTokenId.JSON_MIME_TYPE) || snapshot.getMimeType().equals(JsTokenId.PACKAGE_JSON_MIME_TYPE)||snapshot.getMimeType().equals(JsTokenId.BOWER_JSON_MIME_TYPE))) { |
| // try to find only for the file that has size bigger then 1/3 of the max size |
| boolean isMinified = false; |
| TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(snapshot, 0, language); |
| if (ts != null) { |
| int countedLines = 0; |
| int countChars = 0; |
| while (!isMinified && ts.moveNext() && countedLines < 5) { |
| LexUtilities.findNext(ts, Arrays.asList(JsTokenId.DOC_COMMENT, JsTokenId.BLOCK_COMMENT, JsTokenId.LINE_COMMENT, JsTokenId.EOL, JsTokenId.WHITESPACE)); |
| int offset = ts.offset(); |
| LexUtilities.findNextToken(ts, Arrays.asList(JsTokenId.EOL)); |
| countChars += (ts.offset() - offset); |
| countedLines++; |
| } |
| if (countedLines > 0 && (countChars / countedLines) > 200) { |
| if (LOGGER.isLoggable(Level.FINE)) { |
| LOGGER.log(Level.FINE, "The file {0} was not parsed because it is minimized and the size is too big.", scriptName); |
| } |
| return false; |
| } |
| } |
| } else if (snapshot.getMimeType().equals(JsTokenId.JSON_MIME_TYPE)) { |
| int index = text.length() - 1; |
| if (index < 0) { |
| // NETBEANS-2881 |
| return false; |
| } |
| char ch = text.charAt(index); |
| while (index > 0 && ch != '}') { |
| index--; |
| ch = text.charAt(index); |
| } |
| int count = 0; |
| while (index > 0 && ch == '}' && count <= MAX_RIGHT_CURLY_BRACKETS) { |
| index--; |
| count++; |
| ch = text.charAt(index); |
| |
| } |
| if (count >= MAX_RIGHT_CURLY_BRACKETS) { // See issue 247274 |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| R parseContext(Context context, Sanitize sanitizing, |
| JsErrorManager errorManager) throws Exception { |
| return parseContext(context, sanitizing, errorManager, true); |
| } |
| |
| private R parseContext(Context context, Sanitize sanitizing, |
| JsErrorManager errorManager, boolean copyErrors) throws Exception { |
| |
| boolean sanitized = false; |
| if ((sanitizing != Sanitize.NONE) && (sanitizing != Sanitize.NEVER)) { |
| boolean ok = sanitizeSource(context, sanitizing, errorManager); |
| |
| if (ok) { |
| sanitized = true; |
| assert context.getSanitizedSource() != null; |
| } else { |
| // Try next trick |
| return parseContext(context, sanitizing.next(), errorManager, false); |
| } |
| } |
| |
| JsErrorManager current = new JsErrorManager(context.getSnapshot(), language); |
| R r = parseSource(context, current); |
| |
| if (copyErrors) { |
| errorManager.fillErrors(current); |
| } |
| |
| if (sanitizing != Sanitize.NEVER) { |
| if (!sanitized) { |
| if (current.getMissingCurlyError() != null) { |
| return parseContext(context, Sanitize.MISSING_CURLY, errorManager, false); |
| } |
| if (current.getMissingSemicolonError() != null) { |
| return parseContext(context, Sanitize.MISSING_SEMICOLON, errorManager, false); |
| } |
| } |
| // TODO not very clever check |
| if (r == null || !current.isEmpty()) { |
| return parseContext(context, sanitizing.next(), errorManager, false); |
| } |
| } |
| return r != null ? |
| r : |
| createErrorResult(context.getSnapshot()); |
| } |
| |
| private boolean sanitizeSource(Context context, Sanitize sanitizing, JsErrorManager errorManager) { |
| if (sanitizing == Sanitize.MISSING_CURLY) { |
| org.netbeans.modules.csl.api.Error error = errorManager.getMissingCurlyError(); |
| if (error != null) { |
| int offset = error.getStartPosition(); |
| return sanitizeBrackets(sanitizing, context, offset, '{', '}'); // NOI18N |
| } |
| } else if (sanitizing == Sanitize.MISSING_SEMICOLON) { |
| org.netbeans.modules.csl.api.Error error = errorManager.getMissingSemicolonError(); |
| if (error != null) { |
| String source = context.getOriginalSource(); |
| |
| boolean ok = false; |
| StringBuilder builder = new StringBuilder(source); |
| if (error.getStartPosition() >= source.length()) { |
| builder.append(';'); // NOI18N |
| ok = true; |
| } else { |
| int replaceOffset = error.getStartPosition(); |
| if (replaceOffset >= 0 && Character.isWhitespace(replaceOffset)) { |
| builder.delete(replaceOffset, replaceOffset + 1); |
| builder.insert(replaceOffset, ';'); // NOI18N |
| ok = true; |
| } |
| } |
| |
| if (ok) { |
| context.setSanitizedSource(builder.toString()); |
| context.setSanitization(sanitizing); |
| return true; |
| } |
| } |
| } else if (sanitizing == Sanitize.SYNTAX_ERROR_CURRENT) { |
| List<? extends org.netbeans.modules.csl.api.Error> errors = errorManager.getErrors(); |
| if (!errors.isEmpty()) { |
| org.netbeans.modules.csl.api.Error error = errors.get(0); |
| int offset = error.getStartPosition(); |
| TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence( |
| context.getSnapshot(), 0, language); |
| if (ts != null) { |
| ts.move(offset); |
| if (ts.moveNext()) { |
| int start = ts.offset(); |
| if (start >= 0 && ts.moveNext()) { |
| int end = ts.offset(); |
| ts.movePrevious(); |
| while(ts.movePrevious() && ts.token().id() == JsTokenId.WHITESPACE) { |
| } |
| if (ts.token().id() == JsTokenId.OPERATOR_DOT) { |
| start = ts.offset(); |
| } |
| StringBuilder builder = new StringBuilder(context.getOriginalSource()); |
| erase(builder, start, end); |
| context.setSanitizedSource(builder.toString()); |
| context.setSanitization(sanitizing); |
| return true; |
| } |
| } |
| } |
| } |
| } else if (sanitizing == Sanitize.SYNTAX_ERROR_PREVIOUS) { |
| List<? extends org.netbeans.modules.csl.api.Error> errors = errorManager.getErrors(); |
| if (!errors.isEmpty()) { |
| org.netbeans.modules.csl.api.Error error = errors.get(0); |
| int offset = error.getStartPosition(); |
| return sanitizePrevious(sanitizing, context, offset, new TokenCondition() { |
| |
| @Override |
| public boolean found(JsTokenId id) { |
| return id != JsTokenId.WHITESPACE |
| && id != JsTokenId.EOL |
| && id != JsTokenId.DOC_COMMENT |
| && id != JsTokenId.LINE_COMMENT |
| && id != JsTokenId.BLOCK_COMMENT; |
| } |
| }); |
| } |
| } else if (sanitizing == Sanitize.MISSING_PAREN) { |
| List<? extends org.netbeans.modules.csl.api.Error> errors = errorManager.getErrors(); |
| if (!errors.isEmpty()) { |
| org.netbeans.modules.csl.api.Error error = errors.get(0); |
| int offset = error.getStartPosition(); |
| return sanitizeBrackets(sanitizing, context, offset, '(', ')'); // NOI18N |
| } |
| } else if (sanitizing == Sanitize.ERROR_DOT) { |
| List<? extends org.netbeans.modules.csl.api.Error> errors = errorManager.getErrors(); |
| if (!errors.isEmpty()) { |
| org.netbeans.modules.csl.api.Error error = errors.get(0); |
| int offset = error.getStartPosition(); |
| return sanitizePrevious(sanitizing, context, offset, new TokenCondition() { |
| |
| @Override |
| public boolean found(JsTokenId id) { |
| return id == JsTokenId.OPERATOR_DOT; |
| } |
| }); |
| } |
| } else if (sanitizing == Sanitize.ERROR_LINE) { |
| List<? extends org.netbeans.modules.csl.api.Error> errors = errorManager.getErrors(); |
| if (!errors.isEmpty()) { |
| org.netbeans.modules.csl.api.Error error = errors.get(0); |
| int offset = error.getStartPosition(); |
| return sanitizeLine(sanitizing, context, offset); |
| } |
| } else if (sanitizing == Sanitize.EDITED_LINE) { |
| int offset = context.getCaretOffset(); |
| return sanitizeLine(sanitizing, context, offset); |
| } else if (sanitizing == Sanitize.PREVIOUS_LINES) { |
| StringBuilder result = new StringBuilder(context.getOriginalSource()); |
| for (org.netbeans.modules.csl.api.Error error : errorManager.getErrors()) { |
| TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence( |
| context.getSnapshot(), error.getStartPosition(), language); |
| if (ts != null) { |
| ts.move(error.getStartPosition()); |
| if (ts.movePrevious()) { |
| LexUtilities.findPreviousIncluding(ts, Collections.singletonList(JsTokenId.EOL)); |
| if (!sanitizeLine(context.getOriginalSource(), result, ts.offset())) { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } else { |
| return false; |
| } |
| } |
| context.setSanitizedSource(result.toString()); |
| context.setSanitization(sanitizing); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean sanitizePrevious(Sanitize sanitizing, Context context, int offset, TokenCondition condition) { |
| TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence( |
| context.getSnapshot(), 0, language); |
| if (ts != null) { |
| ts.move(offset); |
| int start = -1; |
| while (ts.movePrevious()) { |
| if (condition.found(ts.token().id())) { |
| start = ts.offset(); |
| break; |
| } |
| } |
| if (start >= 0) { |
| int end = offset; |
| if (ts.moveNext()) { |
| end = ts.offset(); |
| } |
| StringBuilder builder = new StringBuilder(context.getOriginalSource()); |
| erase(builder, start, end); |
| context.setSanitizedSource(builder.toString()); |
| context.setSanitization(sanitizing); |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private boolean sanitizeLine(Sanitize sanitizing, Context context, int offset) { |
| if (offset > -1) { |
| String source = context.getOriginalSource(); |
| |
| StringBuilder builder = new StringBuilder(source); |
| if (!sanitizeLine(source, builder, offset)) { |
| return false; |
| } |
| context.setSanitizedSource(builder.toString()); |
| context.setSanitization(sanitizing); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean sanitizeLine(String source, StringBuilder result, int offset) { |
| if (offset > -1 && !source.isEmpty()) { |
| int start = offset > 0 ? offset - 1 : offset; |
| int end = start + 1; |
| // fix until new line or } |
| boolean incPosition = false; |
| char c = source.charAt(start); |
| while (start > 0 && c != '\n' && c != '\r' && c != '{' && c != '}') { // NOI18N |
| c = source.charAt(--start); |
| if (start <= 0) { |
| incPosition = false; |
| } else { |
| incPosition = true; |
| } |
| } |
| if (incPosition) { |
| start++; |
| } |
| boolean decPosition = false; |
| if (end < source.length()) { |
| c = source.charAt(end); |
| while (end < source.length() && c != '\n' && c != '\r' && c != '{' && c != '}') { // NOI18N |
| c = source.charAt(end++); |
| if (end >= source.length()) { |
| decPosition = false; |
| } else { |
| decPosition = true; |
| } |
| } |
| } |
| if (decPosition) { |
| end--; |
| } |
| |
| erase(result, start, end); |
| return true; |
| } |
| return false; |
| } |
| |
| private boolean sanitizeBrackets(Sanitize sanitizing, Context context, int offset, |
| char left, char right) { |
| String source = context.getOriginalSource(); |
| int balance = 0; |
| for (int i = 0; i < source.length(); i++) { |
| char current = source.charAt(i); |
| if (current == left) { |
| balance++; |
| } else if (current == right) { |
| balance--; |
| } |
| } |
| if (balance != 0) { |
| StringBuilder builder = new StringBuilder(source); |
| if (balance < 0) { |
| while (balance < 0) { |
| int index = builder.lastIndexOf(Character.toString(right)); |
| if (index < 0) { |
| break; |
| } |
| erase(builder, index, index + 1); |
| balance++; |
| } |
| } else if (balance > 0) { |
| if (offset >= source.length()) { |
| while (balance > 0) { |
| builder.append(right); |
| balance--; |
| } |
| } else { |
| while (balance > 0 && offset - balance >= 0) { |
| // we try to insert them if there are enough whitespaces |
| char current = source.charAt(offset - balance); |
| if (Character.isWhitespace(current)) { |
| builder.replace(offset - balance, |
| offset - balance + 1, Character.toString(right)); |
| balance--; |
| } else { |
| return false; |
| } |
| } |
| if (balance > 0) { |
| return false; |
| } |
| } |
| } |
| context.setSanitizedSource(builder.toString()); |
| context.setSanitization(sanitizing); |
| return true; |
| } |
| return false; |
| } |
| |
| @Override |
| public final Result getResult(Task task) throws ParseException { |
| return lastResult; |
| } |
| |
| @Override |
| public final void addChangeListener(@NonNull final ChangeListener changeListener) { |
| LOGGER.log(Level.FINE, "Adding changeListener: {0}", changeListener); //NOI18N) |
| listeners.addChangeListener(changeListener); |
| } |
| |
| @Override |
| public final void removeChangeListener(@NonNull final ChangeListener changeListener) { |
| LOGGER.log(Level.FINE, "Removing changeListener: {0}", changeListener); //NOI18N) |
| listeners.removeChangeListener(changeListener); |
| } |
| |
| protected final void fireChange() { |
| listeners.fireChange(); |
| } |
| |
| private static void erase(StringBuilder builder, int start, int end) { |
| builder.delete(start, end); |
| for (int i = start; i < end; i++) { |
| builder.insert(i, ' '); |
| } |
| } |
| |
| /** |
| * Parsing context |
| */ |
| protected static final class Context { |
| |
| private static final List<JsTokenId> IMPORT_EXPORT = new ArrayList<>(2); |
| |
| static { |
| Collections.addAll(IMPORT_EXPORT, JsTokenId.KEYWORD_IMPORT, JsTokenId.KEYWORD_EXPORT); |
| } |
| |
| private final String name; |
| |
| private final Snapshot snapshot; |
| |
| private final int caretOffset; |
| |
| private final Language<JsTokenId> language; |
| |
| private String source; |
| |
| private String sanitizedSource; |
| |
| private Sanitize sanitization; |
| |
| private Boolean isModule = null; |
| |
| Context(String name, Snapshot snapshot, int caretOffset, Language<JsTokenId> language) { |
| this.name = name; |
| this.snapshot = snapshot; |
| this.caretOffset = caretOffset; |
| this.language = language; |
| } |
| |
| public String getName() { |
| return name; |
| } |
| |
| public Snapshot getSnapshot() { |
| return snapshot; |
| } |
| |
| public int getCaretOffset() { |
| return snapshot.getEmbeddedOffset(caretOffset); |
| } |
| |
| public String getSource() { |
| if (sanitizedSource != null) { |
| return sanitizedSource; |
| } |
| return getOriginalSource(); |
| } |
| |
| public String getOriginalSource() { |
| if (source == null) { |
| source = snapshot.getText().toString(); |
| } |
| return source; |
| } |
| |
| public String getSanitizedSource() { |
| return sanitizedSource; |
| } |
| |
| public void setSanitizedSource(String sanitizedSource) { |
| this.sanitizedSource = sanitizedSource; |
| } |
| |
| public Sanitize getSanitization() { |
| return sanitization; |
| } |
| |
| public void setSanitization(Sanitize sanitization) { |
| this.sanitization = sanitization; |
| } |
| |
| public boolean isModule() { |
| if (isModule == null) { |
| isModule = isModule(snapshot, language); |
| } |
| return isModule; |
| } |
| |
| private static boolean isModule(Snapshot snapshot, Language<JsTokenId> language) { |
| if (BaseParserResult.isEmbedded(snapshot)) { |
| return isModule(snapshot, language, 0, Integer.MAX_VALUE); |
| } else { |
| TokenSequence<? extends JsTokenId> seq = LexUtilities.getJsPositionedSequence(snapshot, 0); |
| if (seq == null) { |
| return false; |
| } else { |
| Token<? extends JsTokenId> token = LexUtilities.findNextToken(seq, IMPORT_EXPORT); |
| if (token != null && IMPORT_EXPORT.contains(token.id())) { |
| return true; |
| } |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isModule(Snapshot snapshot, Language<JsTokenId> language, int offset, int max) { |
| assert BaseParserResult.isEmbedded(snapshot); |
| TokenSequence<? extends JsTokenId> seq = LexUtilities.getNextJsTokenSequence( |
| snapshot, offset, Integer.MAX_VALUE, language); |
| if (seq != null) { |
| Token<? extends JsTokenId> token = LexUtilities.findNextToken(seq, IMPORT_EXPORT); |
| if (token != null) { |
| if (IMPORT_EXPORT.contains(token.id())) { |
| return true; |
| } else { |
| return isModule(snapshot, language, seq.offset() + token.length(), max); |
| } |
| } |
| } |
| return false; |
| } |
| } |
| |
| /** Attempts to sanitize the input buffer */ |
| public static enum Sanitize { |
| /** Only parse the current file accurately, don't try heuristics */ |
| NEVER { |
| |
| @Override |
| public Sanitize next() { |
| return NEVER; |
| } |
| }, |
| |
| /** Perform no sanitization */ |
| NONE { |
| |
| @Override |
| public Sanitize next() { |
| return MISSING_CURLY; |
| } |
| }, |
| |
| /** Attempt to fix missing } */ |
| MISSING_CURLY { |
| |
| @Override |
| public Sanitize next() { |
| return MISSING_SEMICOLON; |
| } |
| }, |
| |
| /** Attempt to fix missing } */ |
| MISSING_SEMICOLON { |
| |
| @Override |
| public Sanitize next() { |
| return SYNTAX_ERROR_CURRENT; |
| } |
| }, |
| |
| /** Remove current error token */ |
| SYNTAX_ERROR_CURRENT { |
| |
| @Override |
| public Sanitize next() { |
| return SYNTAX_ERROR_PREVIOUS; |
| } |
| }, |
| |
| /** Remove token before error */ |
| SYNTAX_ERROR_PREVIOUS { |
| |
| @Override |
| public Sanitize next() { |
| return MISSING_PAREN; |
| } |
| }, |
| |
| MISSING_PAREN { |
| |
| @Override |
| public Sanitize next() { |
| return EDITED_DOT; |
| } |
| }, |
| |
| /** Try to remove the trailing . or :: at the caret line */ |
| EDITED_DOT { |
| |
| @Override |
| public Sanitize next() { |
| return ERROR_DOT; |
| } |
| }, |
| |
| /** |
| * Try to remove the trailing . at the error position, or the prior |
| * line. |
| */ |
| ERROR_DOT { |
| |
| @Override |
| public Sanitize next() { |
| return ERROR_LINE; |
| } |
| }, |
| |
| /** Try to cut out the error line */ |
| ERROR_LINE { |
| |
| @Override |
| public Sanitize next() { |
| return EDITED_LINE; |
| } |
| }, |
| |
| /** Try to cut out the current edited line, if known */ |
| EDITED_LINE { |
| |
| @Override |
| public Sanitize next() { |
| return PREVIOUS_LINES; |
| } |
| }, |
| |
| PREVIOUS_LINES { |
| |
| @Override |
| public Sanitize next() { |
| return NEVER; |
| } |
| }; |
| |
| |
| public abstract Sanitize next(); |
| } |
| |
| private abstract static class TokenCondition { |
| |
| public abstract boolean found(JsTokenId id); |
| |
| } |
| |
| } |