<!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.