UNPKG

@bazel/typescript

Version:
194 lines (193 loc) 7.64 kB
"use strict"; /** * @fileoverview Checker contains all the information we need to perform source * file AST traversals and report errors. */ Object.defineProperty(exports, "__esModule", { value: true }); const ts = require("typescript"); const failure_1 = require("./failure"); /** * Tsetse rules use on() and addFailureAtNode() for rule implementations. * Rules can get a ts.TypeChecker from checker.typeChecker so typed rules are * possible. Compiler uses execute() to run the Tsetse check. */ class Checker { constructor(program) { /** Node to handlers mapping for all enabled rules. */ this.nodeHandlersMap = new Map(); /** * Mapping from identifier name to handlers for all rules inspecting property * names. */ this.namedIdentifierHandlersMap = new Map(); /** * Mapping from property name to handlers for all rules inspecting property * accesses expressions. */ this.namedPropertyAccessHandlersMap = new Map(); /** * Mapping from string literal value to handlers for all rules inspecting * string literals. */ this.stringLiteralElementAccessHandlersMap = new Map(); this.failures = []; // currentCode will be set before invoking any handler functions so the value // initialized here is never used. this.currentCode = 0; // Avoid the cost for each rule to create a new TypeChecker. this.typeChecker = program.getTypeChecker(); } /** * This doesn't run any checks yet. Instead, it registers `handlerFunction` on * `nodeKind` node in `nodeHandlersMap` map. After all rules register their * handlers, the source file AST will be traversed. */ on(nodeKind, handlerFunction, code) { const newHandler = { handlerFunction, code }; const registeredHandlers = this.nodeHandlersMap.get(nodeKind); if (registeredHandlers === undefined) { this.nodeHandlersMap.set(nodeKind, [newHandler]); } else { registeredHandlers.push(newHandler); } } /** * Similar to `on`, but registers handlers on more specific node type, i.e., * identifiers. */ onNamedIdentifier(identifierName, handlerFunction, code) { const newHandler = { handlerFunction, code }; const registeredHandlers = this.namedIdentifierHandlersMap.get(identifierName); if (registeredHandlers === undefined) { this.namedIdentifierHandlersMap.set(identifierName, [newHandler]); } else { registeredHandlers.push(newHandler); } } /** * Similar to `on`, but registers handlers on more specific node type, i.e., * property access expressions. */ onNamedPropertyAccess(propertyName, handlerFunction, code) { const newHandler = { handlerFunction, code }; const registeredHandlers = this.namedPropertyAccessHandlersMap.get(propertyName); if (registeredHandlers === undefined) { this.namedPropertyAccessHandlersMap.set(propertyName, [newHandler]); } else { registeredHandlers.push(newHandler); } } /** * Similar to `on`, but registers handlers on more specific node type, i.e., * element access expressions with string literals as keys. */ onStringLiteralElementAccess(key, handlerFunction, code) { const newHandler = { handlerFunction, code }; const registeredHandlers = this.stringLiteralElementAccessHandlersMap.get(key); if (registeredHandlers === undefined) { this.stringLiteralElementAccessHandlersMap.set(key, [newHandler]); } else { registeredHandlers.push(newHandler); } } /** * Add a failure with a span. */ addFailure(start, end, failureText, fix) { if (!this.currentSourceFile) { throw new Error('Source file not defined'); } if (start >= end || end > this.currentSourceFile.end || start < 0) { // Since only addFailureAtNode() is exposed for now this shouldn't happen. throw new Error(`Invalid start and end position: [${start}, ${end}]` + ` in file ${this.currentSourceFile.fileName}.`); } const failure = new failure_1.Failure(this.currentSourceFile, start, end, failureText, this.currentCode, fix); this.failures.push(failure); } addFailureAtNode(node, failureText, fix) { // node.getStart() takes a sourceFile as argument whereas node.getEnd() // doesn't need it. this.addFailure(node.getStart(this.currentSourceFile), node.getEnd(), failureText, fix); } /** Dispatch general handlers registered via `on` */ dispatchNodeHandlers(node) { const handlers = this.nodeHandlersMap.get(node.kind); if (handlers === undefined) return; for (const handler of handlers) { this.currentCode = handler.code; handler.handlerFunction(this, node); } } /** Dispatch identifier handlers registered via `onNamedIdentifier` */ dispatchNamedIdentifierHandlers(id) { const handlers = this.namedIdentifierHandlersMap.get(id.text); if (handlers === undefined) return; for (const handler of handlers) { this.currentCode = handler.code; handler.handlerFunction(this, id); } } /** * Dispatch property access handlers registered via `onNamedPropertyAccess` */ dispatchNamedPropertyAccessHandlers(prop) { const handlers = this.namedPropertyAccessHandlersMap.get(prop.name.text); if (handlers === undefined) return; for (const handler of handlers) { this.currentCode = handler.code; handler.handlerFunction(this, prop); } } /** * Dispatch string literal handlers registered via * `onStringLiteralElementAccess`. */ dispatchStringLiteralElementAccessHandlers(elem) { const ty = this.typeChecker.getTypeAtLocation(elem.argumentExpression); if (!ty.isStringLiteral()) return; const handlers = this.stringLiteralElementAccessHandlersMap.get(ty.value); if (handlers === undefined) return; for (const handler of handlers) { this.currentCode = handler.code; handler.handlerFunction(this, elem); } } /** * Walk `sourceFile`, invoking registered handlers with Checker as the first * argument and current node as the second argument. Return failures if there * are any. */ execute(sourceFile) { const thisChecker = this; this.currentSourceFile = sourceFile; this.failures = []; run(sourceFile); return this.failures; function run(node) { // Dispatch handlers registered via `on` thisChecker.dispatchNodeHandlers(node); // Dispatch handlers for named identifiers and properties if (ts.isIdentifier(node)) { thisChecker.dispatchNamedIdentifierHandlers(node); } else if (ts.isPropertyAccessExpression(node)) { thisChecker.dispatchNamedPropertyAccessHandlers(node); } else if (ts.isElementAccessExpression(node)) { thisChecker.dispatchStringLiteralElementAccessHandlers(node); } ts.forEachChild(node, run); } } } exports.Checker = Checker;