<commit_before>/// <reference path='../node_modules/typescript/bin/typescript.d.ts' />
import base = require('./base');
import ts = require('typescript');
import ts2dart = require('./main');

type CallHandler = (c: ts.CallExpression, context: ts.Expression) => void;
type PropertyHandler = (c: ts.PropertyAccessExpression) => void;

const FACADE_DEBUG = false;

export class FacadeConverter extends base.TranspilerBase {
  private tc: ts.TypeChecker;
  private candidateProperties: {[propertyName: string]: boolean} = {};
  private candidateTypes: {[typeName: string]: boolean} = {};

  constructor(transpiler: ts2dart.Transpiler) {
    super(transpiler);
    this.extractPropertyNames(this.callHandlers, this.candidateProperties);
    this.extractPropertyNames(this.propertyHandlers, this.candidateProperties);
    this.extractPropertyNames(this.TS_TO_DART_TYPENAMES, this.candidateTypes);
  }

  private extractPropertyNames(m: ts.Map<ts.Map<any>>, candidates: {[k: string]: boolean}) {
    for (var fileName in m) {
      Object.keys(m[fileName])
          .filter((k) => m[fileName].hasOwnProperty(k))
          .map((propName) => propName.substring(propName.lastIndexOf('.') + 1))
          .forEach((propName) => candidates[propName] = true);
    }
  }

  setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; }

  maybeHandleCall(c: ts.CallExpression): boolean {
    if (!this.tc) return false;

    var symbol: ts.Symbol;
    var context: ts.Expression;
    var ident: string;

    if (c.expression.kind === ts.SyntaxKind.Identifier) {
      // Function call.
      ident = base.ident(c.expression);
      if (!this.candidateProperties.hasOwnProperty(ident)) return false;
      symbol = this.tc.getSymbolAtLocation(c.expression);
      if (FACADE_DEBUG) console.log('s:', symbol);

      if (!symbol) {
        this.reportMissingType(c, ident);
        return false;
      }

      context = null;
    } else if (c.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
      // Method call.
      var pa = <ts.PropertyAccessExpression>c.expression;
      ident = base.ident(pa.name);
      if (!this.candidateProperties.hasOwnProperty(ident)) return false;

      symbol = this.tc.getSymbolAtLocation(pa);
      if (FACADE_DEBUG) console.log('s:', symbol);

      // Error will be reported by PropertyAccess handling below.
      if (!symbol) return false;

      context = pa.expression;
    } else {
      // Not a call we recognize.
      return false;
    }

    var handler = this.getHandler(symbol, this.callHandlers);
    return handler && !handler(c, context);
  }

  handlePropertyAccess(pa: ts.PropertyAccessExpression): boolean {
    if (!this.tc) return;
    var ident = pa.name.text;
    if (!this.candidateProperties.hasOwnProperty(ident)) return false;
    var symbol = this.tc.getSymbolAtLocation(pa.name);
    if (!symbol) {
      this.reportMissingType(pa, ident);
      return false;
    }

    var handler = this.getHandler(symbol, this.propertyHandlers);
    return handler && !handler(pa);
  }

  /**
   * Searches for type references that require extra imports and emits the imports as necessary.
   */
  emitExtraImports(sourceFile: ts.SourceFile) {
    var hasXmlHttpRequest = (n: ts.Node): boolean => {
      if (n.kind === ts.SyntaxKind.TypeReference) {
        var type = base.ident((<ts.TypeReferenceNode>n).typeName);
        if (type === 'XMLHttpRequest') {
          return true;
        }
      }
      return n.getChildren(sourceFile).some(hasXmlHttpRequest);
    };

    if (hasXmlHttpRequest(sourceFile)) {
      this.emit('import "dart:html";');
    }
  }

  visitTypeName(typeName: ts.EntityName) {
    if (typeName.kind !== ts.SyntaxKind.Identifier) {
      this.visit(typeName);
      return;
    }
    var ident = base.ident(typeName);
    if (this.candidateTypes.hasOwnProperty(ident) && this.tc) {
      var symbol = this.tc.getSymbolAtLocation(typeName);
      if (!symbol) {
        this.reportMissingType(typeName, ident);
        return;
      }
      let fileAndName = this.getFileAndName(symbol);
      if (fileAndName) {
        var fileSubs = this.TS_TO_DART_TYPENAMES[fileAndName.fileName];
        if (fileSubs && fileSubs.hasOwnProperty(fileAndName.qname)) {
          this.emit(fileSubs[fileAndName.qname]);
          return;
        }
      }
    }
    this.emit(ident);
  }

  private getHandler<T>(symbol: ts.Symbol, m: ts.Map<ts.Map<T>>): T {
    var {fileName, qname} = this.getFileAndName(symbol);
    var fileSubs = m[fileName];
    if (!fileSubs) return null;
    return fileSubs[qname];
  }

  private getFileAndName(symbol: ts.Symbol): {fileName: string, qname: string} {
    while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbol(symbol);
    let decl = symbol.valueDeclaration;
    if (!decl) {
      // In the case of a pure declaration with no assignment, there is no value declared.
      // Just grab the first declaration, hoping it is declared once.
      decl = symbol.declarations[0];
    }

    var fileName = decl.getSourceFile().fileName;
    fileName = this.getRelativeFileName(fileName);
    fileName = fileName.replace(/(\.d)?\.ts$/, '');

    if (FACADE_DEBUG) console.log('fn:', fileName);
    var qname = this.tc.getFullyQualifiedName(symbol);
    // Some Qualified Names include their file name. Might be a bug in TypeScript,
    // for the time being just special case.
    if (symbol.flags & ts.SymbolFlags.Function || symbol.flags & ts.SymbolFlags.Variable ||
        symbol.flags & ts.SymbolFlags.Class) {
      qname = symbol.getName();
    }
    if (FACADE_DEBUG) console.log('qn:', qname);
    return {fileName, qname};
  }

  private isNamedType(node: ts.Node, fileName: string, qname: string): boolean {
    var symbol = this.tc.getTypeAtLocation(node).getSymbol();
    if (!symbol) return false;
    var actual = this.getFileAndName(symbol);
    if (fileName === 'lib' && !(actual.fileName === 'lib' || actual.fileName === 'lib.es6')) {
      return false;
    } else {
      if (fileName !== actual.fileName) return false;<commit_msg>feat: defensive check that a symbol has some declaration.<commit_after>/// <reference path='../node_modules/typescript/bin/typescript.d.ts' />
import base = require('./base');
import ts = require('typescript');
import ts2dart = require('./main');

type CallHandler = (c: ts.CallExpression, context: ts.Expression) => void;
type PropertyHandler = (c: ts.PropertyAccessExpression) => void;

const FACADE_DEBUG = false;

export class FacadeConverter extends base.TranspilerBase {
  private tc: ts.TypeChecker;
  private candidateProperties: {[propertyName: string]: boolean} = {};
  private candidateTypes: {[typeName: string]: boolean} = {};

  constructor(transpiler: ts2dart.Transpiler) {
    super(transpiler);
    this.extractPropertyNames(this.callHandlers, this.candidateProperties);
    this.extractPropertyNames(this.propertyHandlers, this.candidateProperties);
    this.extractPropertyNames(this.TS_TO_DART_TYPENAMES, this.candidateTypes);
  }

  private extractPropertyNames(m: ts.Map<ts.Map<any>>, candidates: {[k: string]: boolean}) {
    for (var fileName in m) {
      Object.keys(m[fileName])
          .filter((k) => m[fileName].hasOwnProperty(k))
          .map((propName) => propName.substring(propName.lastIndexOf('.') + 1))
          .forEach((propName) => candidates[propName] = true);
    }
  }

  setTypeChecker(tc: ts.TypeChecker) { this.tc = tc; }

  maybeHandleCall(c: ts.CallExpression): boolean {
    if (!this.tc) return false;

    var symbol: ts.Symbol;
    var context: ts.Expression;
    var ident: string;

    if (c.expression.kind === ts.SyntaxKind.Identifier) {
      // Function call.
      ident = base.ident(c.expression);
      if (!this.candidateProperties.hasOwnProperty(ident)) return false;
      symbol = this.tc.getSymbolAtLocation(c.expression);
      if (FACADE_DEBUG) console.log('s:', symbol);

      if (!symbol) {
        this.reportMissingType(c, ident);
        return false;
      }

      context = null;
    } else if (c.expression.kind === ts.SyntaxKind.PropertyAccessExpression) {
      // Method call.
      var pa = <ts.PropertyAccessExpression>c.expression;
      ident = base.ident(pa.name);
      if (!this.candidateProperties.hasOwnProperty(ident)) return false;

      symbol = this.tc.getSymbolAtLocation(pa);
      if (FACADE_DEBUG) console.log('s:', symbol);

      // Error will be reported by PropertyAccess handling below.
      if (!symbol) return false;

      context = pa.expression;
    } else {
      // Not a call we recognize.
      return false;
    }

    var handler = this.getHandler(c, symbol, this.callHandlers);
    return handler && !handler(c, context);
  }

  handlePropertyAccess(pa: ts.PropertyAccessExpression): boolean {
    if (!this.tc) return;
    var ident = pa.name.text;
    if (!this.candidateProperties.hasOwnProperty(ident)) return false;
    var symbol = this.tc.getSymbolAtLocation(pa.name);
    if (!symbol) {
      this.reportMissingType(pa, ident);
      return false;
    }

    var handler = this.getHandler(pa, symbol, this.propertyHandlers);
    return handler && !handler(pa);
  }

  /**
   * Searches for type references that require extra imports and emits the imports as necessary.
   */
  emitExtraImports(sourceFile: ts.SourceFile) {
    var hasXmlHttpRequest = (n: ts.Node): boolean => {
      if (n.kind === ts.SyntaxKind.TypeReference) {
        var type = base.ident((<ts.TypeReferenceNode>n).typeName);
        if (type === 'XMLHttpRequest') {
          return true;
        }
      }
      return n.getChildren(sourceFile).some(hasXmlHttpRequest);
    };

    if (hasXmlHttpRequest(sourceFile)) {
      this.emit('import "dart:html";');
    }
  }

  visitTypeName(typeName: ts.EntityName) {
    if (typeName.kind !== ts.SyntaxKind.Identifier) {
      this.visit(typeName);
      return;
    }
    var ident = base.ident(typeName);
    if (this.candidateTypes.hasOwnProperty(ident) && this.tc) {
      var symbol = this.tc.getSymbolAtLocation(typeName);
      if (!symbol) {
        this.reportMissingType(typeName, ident);
        return;
      }
      let fileAndName = this.getFileAndName(typeName, symbol);
      if (fileAndName) {
        var fileSubs = this.TS_TO_DART_TYPENAMES[fileAndName.fileName];
        if (fileSubs && fileSubs.hasOwnProperty(fileAndName.qname)) {
          this.emit(fileSubs[fileAndName.qname]);
          return;
        }
      }
    }
    this.emit(ident);
  }

  private getHandler<T>(n: ts.Node, symbol: ts.Symbol, m: ts.Map<ts.Map<T>>): T {
    var {fileName, qname} = this.getFileAndName(n, symbol);
    var fileSubs = m[fileName];
    if (!fileSubs) return null;
    return fileSubs[qname];
  }

  private getFileAndName(n: ts.Node, symbol: ts.Symbol): {fileName: string, qname: string} {
    while (symbol.flags & ts.SymbolFlags.Alias) symbol = this.tc.getAliasedSymbol(symbol);
    let decl = symbol.valueDeclaration;
    if (!decl) {
      // In the case of a pure declaration with no assignment, there is no value declared.
      // Just grab the first declaration, hoping it is declared once.
      if (!symbol.declarations || symbol.declarations.length === 0) {
        this.reportError(n, 'no declarations for symbol ' + symbol.name);
        return;
      }
      decl = symbol.declarations[0];
    }

    var fileName = decl.getSourceFile().fileName;
    fileName = this.getRelativeFileName(fileName);
    fileName = fileName.replace(/(\.d)?\.ts$/, '');

    if (FACADE_DEBUG) console.log('fn:', fileName);
    var qname = this.tc.getFullyQualifiedName(symbol);
    // Some Qualified Names include their file name. Might be a bug in TypeScript,
    // for the time being just special case.
    if (symbol.flags & ts.SymbolFlags.Function || symbol.flags & ts.SymbolFlags.Variable ||
        symbol.flags & ts.SymbolFlags.Class) {
      qname = symbol.getName();
    }
    if (FACADE_DEBUG) console.log('qn:', qname);
    return {fileName, qname};
  }

  private isNamedType(node: ts.Node, fileName: string, qname: string): boolean {
    var symbol = this.tc.getTypeAtLocation(node).getSymbol();
    if (!symbol) return false;
    var actual = this.getFileAndName(node, symbol);
    if (fileName === 'lib' && !(actual.fileName === 'lib' || actual.fileName === 'lib.es6')) {
      return false;
    } else {
      if (fileName !== actual.fileName) return false;
{'fraction_non_alphanumeric': 0.13472087770248467, 'fraction_numerical': 0.0008873830267828332, 'mean_word_length': 3.1924247548190734, 'pattern_counts': {'":': 0, '<': 19, '<?xml version=': 0, '>': 31, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3675261', 'n_tokens_mistral': 3817, 'n_tokens_neox': 3813, 'n_words': 865}