Add a BANNED_NAME engine to Tsetse's ConformancePatternRule.
PiperOrigin-RevId: 252610371
diff --git a/internal/tsetse/rules/conformance_pattern_rule.ts b/internal/tsetse/rules/conformance_pattern_rule.ts
index b683355..add5286 100644
--- a/internal/tsetse/rules/conformance_pattern_rule.ts
+++ b/internal/tsetse/rules/conformance_pattern_rule.ts
@@ -3,6 +3,7 @@
import {AbstractRule} from '../rule';
import {Fixer} from '../util/fixer';
import {Config, MatchedNodeTypes, PatternKind} from '../util/pattern_config';
+import {NameEngine} from '../util/pattern_engines/name_engine';
import {PatternEngine} from '../util/pattern_engines/pattern_engine';
import {PropertyNonConstantWriteEngine} from '../util/pattern_engines/property_non_constant_write_engine';
import {PropertyWriteEngine} from '../util/pattern_engines/property_write_engine';
@@ -36,6 +37,10 @@
config as Config<PatternKind.BANNED_PROPERTY_NON_CONSTANT_WRITE>,
fixer);
break;
+ case PatternKind.BANNED_NAME:
+ engine =
+ new NameEngine(config as Config<PatternKind.BANNED_NAME>, fixer);
+ break;
default:
throw new Error('Config type not recognized, or not implemented yet.');
}
diff --git a/internal/tsetse/tests/ban_conformance_pattern/name_test.ts b/internal/tsetse/tests/ban_conformance_pattern/name_test.ts
new file mode 100644
index 0000000..3165d38
--- /dev/null
+++ b/internal/tsetse/tests/ban_conformance_pattern/name_test.ts
@@ -0,0 +1,45 @@
+import 'jasmine';
+import {ConformancePatternRule, PatternKind} from '../../rules/conformance_pattern_rule';
+import {compileAndCheck, customMatchers} from '../../util/testing/test_support';
+
+describe('BANNED_NAME', () => {
+ it('matches simple example of globals', () => {
+ const rule = new ConformancePatternRule({
+ errorMessage: 'no Infinity',
+ kind: PatternKind.BANNED_NAME,
+ values: ['Infinity']
+ });
+ const source = [
+ `Infinity; 1+1;`,
+ ].join('\n');
+ const results = compileAndCheck(rule, source);
+
+ expect(results.length).toBe(1);
+ expect(results[0]).toBeFailureMatching({
+ matchedCode: `Infinity`,
+ errorMessage: 'no Infinity'
+ });
+ });
+
+ it('matches namespaced globals', () => {
+ const rule = new ConformancePatternRule({
+ errorMessage: 'no blob url',
+ kind: PatternKind.BANNED_NAME,
+ values: ['URL.createObjectURL']
+ });
+ const source = [
+ `URL.createObjectURL({});`,
+ ].join('\n');
+ const results = compileAndCheck(rule, source);
+
+ expect(results.length).toBe(1);
+ expect(results[0]).toBeFailureMatching({
+ matchedCode: `createObjectURL`,
+ errorMessage: 'no blob url'
+ });
+ });
+});
+
+beforeEach(() => {
+ jasmine.addMatchers(customMatchers);
+});
diff --git a/internal/tsetse/util/match_symbol.ts b/internal/tsetse/util/match_symbol.ts
index 5df53b1..c60195b 100644
--- a/internal/tsetse/util/match_symbol.ts
+++ b/internal/tsetse/util/match_symbol.ts
@@ -11,6 +11,10 @@
/**
* This class matches symbols given a "foo.bar.baz" name, where none of the
* steps are instances of classes.
+ *
+ * Note that this isn't smart about subclasses and types: to write a check, we
+ * strongly suggest finding the expected symbol in externs to find the object
+ * name on which the symbol was initially defined.
*/
export class AbsoluteMatcher {
/**
diff --git a/internal/tsetse/util/pattern_config.ts b/internal/tsetse/util/pattern_config.ts
index c8c0927..9ec75f7 100644
--- a/internal/tsetse/util/pattern_config.ts
+++ b/internal/tsetse/util/pattern_config.ts
@@ -6,6 +6,7 @@
* https://github.com/google/closure-compiler/wiki/JS-Conformance-Framework)
*/
export enum PatternKind {
+ BANNED_NAME = 'banned-name',
BANNED_PROPERTY_WRITE = 'banned-property-write',
BANNED_PROPERTY_NON_CONSTANT_WRITE = 'banned-property-non-constant-write'
}
@@ -87,4 +88,5 @@
[PatternKind.BANNED_PROPERTY_NON_CONSTANT_WRITE]: ts.BinaryExpression&{
left: ts.PropertyAccessExpression;
};
+ [PatternKind.BANNED_NAME]: ts.Identifier;
}
diff --git a/internal/tsetse/util/pattern_engines/name_engine.ts b/internal/tsetse/util/pattern_engines/name_engine.ts
new file mode 100644
index 0000000..332c6c6
--- /dev/null
+++ b/internal/tsetse/util/pattern_engines/name_engine.ts
@@ -0,0 +1,43 @@
+import * as ts from 'typescript';
+import {Checker} from '../../checker';
+import {ErrorCode} from '../../error_code';
+import {debugLog, shouldExamineNode} from '../ast_tools';
+import {Fixer} from '../fixer';
+import {AbsoluteMatcher} from '../match_symbol';
+import {Config, MatchedNodeTypes, PatternKind} from '../pattern_config';
+import {PatternEngine} from './pattern_engine';
+
+export class NameEngine extends PatternEngine<PatternKind.BANNED_NAME> {
+ private readonly matcher: AbsoluteMatcher;
+ constructor(
+ config: Config<PatternKind.BANNED_NAME>,
+ fixer?: Fixer<MatchedNodeTypes[PatternKind.BANNED_NAME]>) {
+ super(config, fixer);
+ // TODO: Support more than one single value here, or even build a
+ // multi-pattern engine. This would help for performance.
+ if (this.config.values.length !== 1) {
+ throw new Error(`BANNED_NAME expects one value, got(${
+ this.config.values.join(',')})`);
+ }
+ this.matcher = new AbsoluteMatcher(this.config.values[0]);
+ }
+
+ register(checker: Checker) {
+ checker.on(
+ ts.SyntaxKind.Identifier, this.checkAndFilterResults.bind(this),
+ ErrorCode.CONFORMANCE_PATTERN);
+ }
+
+ check(tc: ts.TypeChecker, n: MatchedNodeTypes[PatternKind.BANNED_NAME]):
+ MatchedNodeTypes[PatternKind.BANNED_NAME]|undefined {
+ if (!shouldExamineNode(n) || n.getSourceFile().isDeclarationFile) {
+ return;
+ }
+ debugLog(`inspecting ${n.getText().trim()}`);
+ if (!this.matcher.matches(n, tc)) {
+ debugLog('Not the right global name.');
+ return;
+ }
+ return n;
+ }
+}