| /* |
| * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * - Neither the name of Oracle nor the names of its |
| * contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, |
| * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| // Usage: jjs --language=es6 staticchecker.js -- <file> |
| // or jjs --language=es6 staticchecker.js -- <directory> |
| // default argument is the current directory |
| |
| if (arguments.length == 0) { |
| arguments[0] = "."; |
| } |
| |
| const File = Java.type("java.io.File"); |
| const file = new File(arguments[0]); |
| if (!file.exists()) { |
| print(arguments[0] + " is neither a file nor a directory"); |
| exit(1); |
| } |
| |
| // A simple static checker for javascript best practices. |
| // static checks performed are: |
| // |
| // * __proto__ magic property is bad (non-standard) |
| // * 'with' statements are bad |
| // * 'eval' calls are bad |
| // * 'delete foo' (scope variable delete) is bad |
| // * assignment to standard globals is bad (eg. Object = "hello") |
| // * assignment to property on standard prototype is bad (eg. String.prototype.foo = 45) |
| // * exception swallow (empty catch block in try-catch statements) |
| |
| const Files = Java.type("java.nio.file.Files"); |
| const EmptyStatementTree = Java.type("jdk.nashorn.api.tree.EmptyStatementTree"); |
| const IdentifierTree = Java.type("jdk.nashorn.api.tree.IdentifierTree"); |
| const MemberSelectTree = Java.type("jdk.nashorn.api.tree.MemberSelectTree"); |
| const Parser = Java.type("jdk.nashorn.api.tree.Parser"); |
| const SimpleTreeVisitor = Java.type("jdk.nashorn.api.tree.SimpleTreeVisitorES6"); |
| const Tree = Java.type("jdk.nashorn.api.tree.Tree"); |
| |
| const parser = Parser.create("-scripting", "--language=es6"); |
| |
| // capture standard global upfront |
| const globals = new Set(); |
| for (let name of Object.getOwnPropertyNames(this)) { |
| globals.add(name); |
| } |
| |
| const checkFile = function(file) { |
| print("Parsing " + file); |
| const ast = parser.parse(file, print); |
| if (!ast) { |
| print("FAILED to parse: " + file); |
| return; |
| } |
| |
| const checker = new (Java.extend(SimpleTreeVisitor)) { |
| lineMap: null, |
| |
| printWarning(node, msg) { |
| var pos = node.startPosition; |
| var line = this.lineMap.getLineNumber(pos); |
| var column = this.lineMap.getColumnNumber(pos); |
| print(`WARNING: ${msg} in ${file} @ ${line}:${column}`); |
| }, |
| |
| printWithWarning(node) { |
| this.printWarning(node, "'with' usage"); |
| }, |
| |
| printProtoWarning(node) { |
| this.printWarning(node, "__proto__ usage"); |
| }, |
| |
| printScopeDeleteWarning(node, varName) { |
| this.printWarning(node, `delete ${varName}`); |
| }, |
| |
| hasOnlyEmptyStats(stats) { |
| const itr = stats.iterator(); |
| while (itr.hasNext()) { |
| if (! (itr.next() instanceof EmptyStatementTree)) { |
| return false; |
| } |
| } |
| |
| return true; |
| }, |
| |
| checkProto(node, name) { |
| if (name == "__proto__") { |
| this.printProtoWarning(node); |
| } |
| }, |
| |
| checkAssignment(lhs) { |
| if (lhs instanceof IdentifierTree && globals.has(lhs.name)) { |
| this.printWarning(lhs, `assignment to standard global "${lhs.name}"`); |
| } else if (lhs instanceof MemberSelectTree) { |
| const expr = lhs.expression; |
| if (expr instanceof MemberSelectTree && |
| expr.expression instanceof IdentifierTree && |
| globals.has(expr.expression.name) && |
| "prototype" == expr.identifier) { |
| this.printWarning(lhs, |
| `property set "${expr.expression.name}.prototype.${lhs.identifier}"`); |
| } |
| } |
| }, |
| |
| visitAssignment(node, extra) { |
| this.checkAssignment(node.variable); |
| Java.super(checker).visitAssignment(node, extra); |
| }, |
| |
| visitCatch(node, extra) { |
| var stats = node.block.statements; |
| if (stats.empty || this.hasOnlyEmptyStats(stats)) { |
| this.printWarning(node, "exception swallow"); |
| } |
| Java.super(checker).visitCatch(node, extra); |
| }, |
| |
| visitCompilationUnit(node, extra) { |
| this.lineMap = node.lineMap; |
| Java.super(checker).visitCompilationUnit(node, extra); |
| }, |
| |
| visitFunctionCall(node, extra) { |
| var func = node.functionSelect; |
| if (func instanceof IdentifierTree && func.name == "eval") { |
| this.printWarning(node, "eval call found"); |
| } |
| Java.super(checker).visitFunctionCall(node, extra); |
| }, |
| |
| visitIdentifier(node, extra) { |
| this.checkProto(node, node.name); |
| Java.super(checker).visitIdentifier(node, extra); |
| }, |
| |
| visitMemberSelect(node, extra) { |
| this.checkProto(node, node.identifier); |
| Java.super(checker).visitMemberSelect(node, extra); |
| }, |
| |
| visitProperty(node, extra) { |
| this.checkProto(node, node.key); |
| Java.super(checker).visitProperty(node, extra); |
| }, |
| |
| visitUnary(node, extra) { |
| if (node.kind == Tree.Kind.DELETE && |
| node.expression instanceof IdentifierTree) { |
| this.printScopeDeleteWarning(node, node.expression.name); |
| } |
| Java.super(checker).visitUnary(node, extra); |
| }, |
| |
| visitWith(node, extra) { |
| this.printWithWarning(node); |
| Java.super(checker).visitWith(node, extra); |
| } |
| }; |
| |
| try { |
| ast.accept(checker, null); |
| } catch (e) { |
| print(e); |
| if (e.printStackTrace) e.printStackTrace(); |
| if (e.stack) print(e.stack); |
| } |
| } |
| |
| if (file.isDirectory()) { |
| Files.walk(file.toPath()) |
| .filter(function(p) Files.isRegularFile(p)) |
| .filter(function(p) p.toFile().name.endsWith('.js')) |
| .forEach(checkFile); |
| } else { |
| checkFile(file); |
| } |