<!DOCTYPE html>
<html>

<head>
  <base href="." />
  <title>Typescript Compiler Playground</title>
  <link rel="stylesheet" href="styles.css">
  <script src="https://unpkg.com/systemjs@0.19.31/dist/system.js"></script>
  <script src="config.js"></script>
  <script>
  System.import('app')
    .catch(console.error.bind(console));
</script>
</head>

<body>

TypeScript version: <pre id="tsVersion"></pre>
Errors: <pre id="errors"></pre>

<div class="files">

<!-- Input files -->
<pre fileName="/cases.ts" class="input row1"></pre>
<pre fileName="/helper.ts" class="input row2">
</pre>

<!-- Output files -->
<pre fileName="/cases.js" class="output row1"></pre>
<pre fileName="/helper.js" class="output row2"></pre>

</div>
</body>
</html>
System.config({
  //use typescript for compilation
  transpiler: 'typescript',
  //typescript compiler options
  typescriptOptions: {
  },
  paths: {
    'npm:': 'https://unpkg.com/'
  },
  //map tells the System loader where to look for things
  map: {
    'app': './src',
    'typescript': 'npm:typescript@latest/lib/typescript.js'
  },
  //packages defines our app package
  packages: {
    app: {
      main: './main.ts',
      defaultExtension: 'ts'
    },
  },
  meta: {
    typescript: {
      format: 'global',
      exports: 'ts'
    },
  }
});
import * as ts from 'typescript';
import {compile} from './util';

const fileData = {
  '/cases.ts': `
/*importDecl*/
import {x} from './helper';

/*classStmt*/
class A {
  /*staticPropDecl*/
  static prop = 10;
  /*propDecl*/
  prop = 10;
}

/*varStmt*/
const y = x;
/*exportDecl*/
export const z = 10;
`,
  '/helper.ts': `
export const x = 10;
`
};

compile(fileData, {before: [transformComments] });

function transformComments(context: ts.TransformationContext) {
  return (sourceFile: ts.SourceFile): ts.SourceFile => {
    return visitNode(sourceFile);

    function visitNode<T extends ts.Node>(node: T) {
      const leadingCommentRanges = ts.getLeadingCommentRanges(sourceFile.text, node.pos);
      if (leadingCommentRanges) {
        const synthComments = synthesizeCommentRanges(sourceFile, leadingCommentRanges);
        synthComments.forEach(c => c.text = `<${node.kind}>${c.text}`);
        ts.setSyntheticLeadingComments(node, synthComments);
        ts.setEmitFlags(node, ts.NoComments);
      }
      return ts.visitEachChild(node, visitNode, context);
    }
  };
}
    
/**
 * Converts `ts.CommentRange`s into `ts.SynthesizedComment`s
 * @param sourceFile
 * @param parsedComments
 */
function synthesizeCommentRanges(
    sourceFile: ts.SourceFile, parsedComments: ts.CommentRange[]): ts.SynthesizedComment[] {
  const synthesizedComments: ts.SynthesizedComment[] = [];
  parsedComments.forEach(({kind, pos, end, hasTrailingNewLine}, commentIdx) => {
    let commentText = sourceFile.text.substring(pos, end).trim();
    if (kind === ts.SyntaxKind.MultiLineCommentTrivia) {
      commentText = commentText.replace(/(^\/\*)|(\*\/$)/g, '');
    } else if (kind === ts.SyntaxKind.SingleLineCommentTrivia) {
      if (commentText.startsWith('///')) {
        // tripple-slash comments are typescript specific, ignore them in the output.
        return;
      }
      commentText = commentText.replace(/(^\/\/)/g, '');
    }
    synthesizedComments.push({kind, text: commentText, hasTrailingNewLine, pos: -1, end: -1});
  });
  return synthesizedComments;
}    
import * as ts from 'typescript';

export function compile(fileData: {[fileName: string]: string}, transformers: ts.CustomTransformers) {
  window.tsVersion.textContent = ts.version;
  const errorEl = window.errors;

  const options: ts.CompilerOptions = {}; 
  const files = readFilesFromDom(fileData);
  try {
    const program = createInMemoryProgram(options, files);
    errorEl.textContent = checkProgram(program);
    program.emit(/*targetSourceFile*/ undefined, /*writeFile*/ undefined, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ undefined, transformers);
  } catch (e) {
    errorEl.textContent = e.message;
  }
}

export interface File {
  read(): string;
  write(data: string): void;
}

export interface Files {
   [fileName: string]: File;
}

export function readFilesFromDom(initData: {[fileName: string]: string}): Files {
  const files: Files = {
    'lib.d.ts': new ReadOnlyFile('')
  };
  const fileElements = [].slice.call(document.querySelectorAll('[fileName]'));
  fileElements.forEach((fileEl) => {
    const fileName = fileEl.getAttribute('fileName');
    files[fileName] = {
      read: () => fileEl.textContent.trim(),
      write: (data: string) => fileEl.textContent = data; 
    };
    fileEl.textContent = initData[fileName] || '';
  });
  return files;
}

class ReadOnlyFile implements File {
  constructor(public data: string) {}
  read() { return this.data; }
  write() {}
}

export function createInMemoryProgram(options: ts.CompilerOptions, files: Files): ts.Program {
  const host = new InMemoryCompilerHost(files);
  return ts.createProgram(Object.keys(files).filter(f => f.endsWith('.ts')), options, host);
}

class InMemoryCompilerHost  implements ts.CompilerHost {
  constructor(public files: Files) {}
  getSourceFile(fileName: string, languageVersion: ScriptTarget, onError?: (message: string) => void): SourceFile {
    if (!(fileName in this.files)) {
      throw new Error(`No file ${fileName}`);
    }
    const content = this.files[fileName].read();
    return ts.createSourceFile(fileName, content, languageVersion, /* setParentNodes */ true);
  }
  fileExists(fileName: string): boolean {
    return fileName in this.files;
  }
  readFile(fileName: string): string {
    if (!(fileName in this.files)) {
      throw new Error(`No file ${fileName}`);
    }
    return this.files[fileName].read();
  }
  
  getDirectories(path: string): string[] { return []; }
  writeFile(fileName: string, data: string, writeByteOrderMark: boolean) {
    if (!(fileName in this.files)) {
      throw new Error(`No file ${fileName}`);
    }
    this.files[fileName].write(data);
  }

  getCurrentDirectory(): string { return '/'; }
  getCanonicalFileName(fileName: string): string { return fileName; }
  useCaseSensitiveFileNames(): boolean { return false; }
  getNewLine(): string { return '\n'; }
  getDefaultLibFileName(options: ts.CompilerOptions): string { return 'lib.d.ts'; }
}

export function checkProgram(program: ts.Program) {
  const diags = [
    ...program.getOptionsDiagnostics(),
    ...program.getSyntacticDiagnostics(),
    ...program.getSemanticDiagnostics()
  ];
  return diags.length ? formatDiagnostics(diags) : 'None';
}

function formatDiagnostics(diags: ts.Diagnostic[]): string {
  return diags
      .map((d) => {
        let res = ts.DiagnosticCategory[d.category];
        if (d.file) {
          res += ' at ' + d.file.fileName + ':';
          if (d.start) {
            const {line, character} = d.file.getLineAndCharacterOfPosition(d.start);
            res += (line + 1) + ':' + (character + 1) + ':';
          }
        }
        res += ' ' + ts.flattenDiagnosticMessageText(d.messageText, '\n');
        return res;
      })
      .join('\n');
}

 
[filename]:before {
  content: attr(filename);
  display: block;
  font-weight: bold;
}

.files {
  display: grid;
}

.input {
  grid-column: 1
}

.output {
  grid-column: 2
}

.row1 {
  grid-row: 1
}

.row2 {
  grid-row: 2
}

.row3 {
  grid-row: 3
}

.row4 {
  grid-row: 4
}
# Typescript Compiler Error Reproduction

This Plunker runs the TypeScript Compiler programmatically
to reproduce bugs.

The reproduction data is contained in `main.ts`.

The input/output/errors of the TypeScript Compiler are shown
in the DOM.