@bazel/typescript
Version:
TypeScript rules for Bazel
194 lines (193 loc) • 7.64 kB
JavaScript
"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;