1package org.chromium.devtools.jsdoc;
2
3import com.google.javascript.jscomp.Compiler;
4import com.google.javascript.jscomp.NodeTraversal;
5import com.google.javascript.jscomp.parsing.Config;
6import com.google.javascript.jscomp.parsing.Config.LanguageMode;
7import com.google.javascript.jscomp.parsing.ParserRunner;
8import com.google.javascript.rhino.ErrorReporter;
9import com.google.javascript.rhino.Node;
10
11import org.chromium.devtools.jsdoc.checks.ContextTrackingValidationCheck;
12
13import java.io.FileNotFoundException;
14import java.io.IOException;
15import java.nio.ByteBuffer;
16import java.nio.charset.StandardCharsets;
17import java.nio.file.FileSystems;
18import java.nio.file.Files;
19import java.util.ArrayList;
20import java.util.List;
21import java.util.concurrent.Callable;
22
23public 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