1 package org.chromium.devtools.jsdoc; 2 3 import com.google.javascript.jscomp.Compiler; 4 import com.google.javascript.jscomp.NodeTraversal; 5 import com.google.javascript.jscomp.parsing.Config; 6 import com.google.javascript.jscomp.parsing.Config.LanguageMode; 7 import com.google.javascript.jscomp.parsing.ParserRunner; 8 import com.google.javascript.rhino.ErrorReporter; 9 import com.google.javascript.rhino.Node; 10 11 import org.chromium.devtools.jsdoc.checks.ContextTrackingValidationCheck; 12 13 import java.io.FileNotFoundException; 14 import java.io.IOException; 15 import java.nio.ByteBuffer; 16 import java.nio.charset.StandardCharsets; 17 import java.nio.file.FileSystems; 18 import java.nio.file.Files; 19 import java.util.ArrayList; 20 import java.util.List; 21 import java.util.concurrent.Callable; 22 23 public class FileCheckerCallable implements Callable<ValidatorContext> { 24 25 private final String fileName; 26 27 public FileCheckerCallable(String fileName) { 28 this.fileName = fileName; 29 } 30 31 @Override 32 public ValidatorContext call() { 33 try { 34 ValidatorContext context = new ValidatorContext(readScriptText(), fileName); 35 ValidationCheckDispatcher dispatcher = new ValidationCheckDispatcher(context); 36 dispatcher.registerCheck(new ContextTrackingValidationCheck()); 37 NodeTraversal.traverse(new Compiler(), parseScript(context), dispatcher); 38 return context; 39 } catch (FileNotFoundException e) { 40 logError("File not found: " + fileName); 41 } catch (IOException e) { 42 logError("Failed to read file " + fileName); 43 } 44 return null; 45 } 46 47 private String readScriptText() throws IOException { 48 byte[] encoded = Files.readAllBytes(FileSystems.getDefault().getPath(fileName)); 49 String text = StandardCharsets.UTF_8.decode(ByteBuffer.wrap(encoded)).toString(); 50 return text; 51 } 52 53 private static Node parseScript(final ValidatorContext context) { 54 Config config = ParserRunner.createConfig(true, LanguageMode.ECMASCRIPT5_STRICT, true); 55 ErrorReporter errorReporter = new ErrorReporter() { 56 @Override 57 public void warning(String message, String sourceName, int line, int lineOffset) { 58 // Ignore. 59 } 60 61 @Override 62 public void error(String message, String sourceName, int line, int lineOffset) { 63 logError("at " + sourceName + ":" + line + ":" + lineOffset); 64 } 65 }; 66 try { 67 return ParserRunner.parse( 68 context.sourceFile, context.sourceFile.getCode(), config, errorReporter).ast; 69 } catch (IOException e) { 70 // Does not happen with preloaded files. 71 return null; 72 } 73 } 74 75 private static void logError(String message) { 76 System.err.println("ERROR: " + message); 77 } 78 79 private static class ValidationCheckDispatcher extends DoDidVisitorAdapter { 80 private final List<ValidationCheck> checks = new ArrayList<>(2); 81 private final ValidatorContext context; 82 83 public ValidationCheckDispatcher(ValidatorContext context) { 84 this.context = context; 85 } 86 87 public void registerCheck(ValidationCheck check) { 88 check.setContext(context); 89 checks.add(check); 90 } 91 92 @Override 93 public void doVisit(Node node) { 94 for (DoDidNodeVisitor visitor : checks) { 95 visitor.doVisit(node); 96 } 97 } 98 99 @Override 100 public void didVisit(Node node) { 101 for (ValidationCheck check : checks) { 102 check.didVisit(node); 103 } 104 } 105 } 106 } 107