blob: d6b60ce8d583b311152e4b786cc00e147e8842ca [file] [log] [blame]
import * as ts from 'typescript';
import {Checker} from '../../checker';
import {Fix} from '../../failure';
import {Fixer} from '../../util/fixer';
import {Config} from '../../util/pattern_config';
import {shouldExamineNode} from '../ast_tools';
/**
* A patternEngine is the logic that handles a specific PatternKind.
*/
export abstract class PatternEngine {
private readonly whitelistedPrefixes: string[] = [];
private readonly whitelistedRegExps: RegExp[] = [];
private readonly whitelistMemoizer: Map<string, boolean> = new Map();
constructor(
protected readonly config: Config, protected readonly fixer?: Fixer) {
if (config.whitelistEntries) {
for (const e of config.whitelistEntries) {
if (e.prefix) {
this.whitelistedPrefixes =
this.whitelistedPrefixes.concat(...e.prefix);
}
if (e.regexp) {
this.whitelistedRegExps = this.whitelistedRegExps.concat(
...e.regexp.map(r => new RegExp(r)));
}
}
}
}
/**
* `register` will be called by the ConformanceRule to tell Tsetse the
* PatternEngine will handle matching. Implementations should use
*`checkAndFilterResults` as a wrapper for `check`.
**/
abstract register(checker: Checker): void;
/**
* `check` is the PatternEngine subclass-specific matching logic. Overwrite
* with what the engine looks for, i.e., AST matching. The whitelisting logic
* and fix generation are handled in `checkAndFilterResults`.
*/
abstract check(tc: ts.TypeChecker, n: ts.Node): ts.Node|undefined;
/**
* A wrapper for `check` that handles aspects of the analysis that are not
* engine-specific, and which defers to the subclass-specific logic
* afterwards.
*/
checkAndFilterResults(c: Checker, n: ts.Node) {
if (!shouldExamineNode(n) || n.getSourceFile().isDeclarationFile) {
return;
}
const matchedNode = this.check(c.typeChecker, n);
if (matchedNode && !this.isWhitelisted(matchedNode)) {
const fix: Fix|undefined =
this.fixer ? this.fixer.getFixForFlaggedNode(matchedNode) : undefined;
c.addFailureAtNode(matchedNode, this.config.errorMessage, fix);
}
}
isWhitelisted(n: ts.Node): boolean {
const name: string = n.getSourceFile().fileName;
if (this.whitelistMemoizer.has(name)) {
return this.whitelistMemoizer.get(name)!;
}
for (const p of this.whitelistedPrefixes) {
if (name.indexOf(p) == 0) {
this.whitelistMemoizer.set(name, true);
return true;
}
}
for (const re of this.whitelistedRegExps) {
if (re.test(name)) {
this.whitelistMemoizer.set(name, true);
return true;
}
}
this.whitelistMemoizer.set(name, false);
return false;
}
}