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;
+  }
+}