Merge pull request #3982 from Microsoft/sourceMapsInTranspile

add transpileModule function that can return emitted source map
This commit is contained in:
Vladimir Matveev 2015-07-23 17:25:30 -07:00
commit e1fa3c5515
12 changed files with 165 additions and 66 deletions

View file

@ -509,7 +509,6 @@ namespace ts {
Option_noEmit_cannot_be_specified_with_option_out_or_outDir: { code: 5040, category: DiagnosticCategory.Error, key: "Option 'noEmit' cannot be specified with option 'out' or 'outDir'." },
Option_noEmit_cannot_be_specified_with_option_declaration: { code: 5041, category: DiagnosticCategory.Error, key: "Option 'noEmit' cannot be specified with option 'declaration'." },
Option_project_cannot_be_mixed_with_source_files_on_a_command_line: { code: 5042, category: DiagnosticCategory.Error, key: "Option 'project' cannot be mixed with source files on a command line." },
Option_sourceMap_cannot_be_specified_with_option_isolatedModules: { code: 5043, category: DiagnosticCategory.Error, key: "Option 'sourceMap' cannot be specified with option 'isolatedModules'." },
Option_declaration_cannot_be_specified_with_option_isolatedModules: { code: 5044, category: DiagnosticCategory.Error, key: "Option 'declaration' cannot be specified with option 'isolatedModules'." },
Option_noEmitOnError_cannot_be_specified_with_option_isolatedModules: { code: 5045, category: DiagnosticCategory.Error, key: "Option 'noEmitOnError' cannot be specified with option 'isolatedModules'." },
Option_out_cannot_be_specified_with_option_isolatedModules: { code: 5046, category: DiagnosticCategory.Error, key: "Option 'out' cannot be specified with option 'isolatedModules'." },

View file

@ -2025,10 +2025,6 @@
"category": "Error",
"code": 5042
},
"Option 'sourceMap' cannot be specified with option 'isolatedModules'.": {
"category": "Error",
"code": 5043
},
"Option 'declaration' cannot be specified with option 'isolatedModules'.": {
"category": "Error",
"code": 5044

View file

@ -602,10 +602,6 @@ namespace ts {
function verifyCompilerOptions() {
if (options.isolatedModules) {
if (options.sourceMap) {
diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_sourceMap_cannot_be_specified_with_option_isolatedModules));
}
if (options.declaration) {
diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_declaration_cannot_be_specified_with_option_isolatedModules));
}

View file

@ -1760,18 +1760,31 @@ namespace ts {
sourceFile.version = version;
sourceFile.scriptSnapshot = scriptSnapshot;
}
export interface TranspileOptions {
compilerOptions?: CompilerOptions;
fileName?: string;
reportDiagnostics?: boolean;
moduleName?: string;
}
export interface TranspileOutput {
outputText: string;
diagnostics?: Diagnostic[];
sourceMapText?: string;
}
/*
* This function will compile source text from 'input' argument using specified compiler options.
* If not options are provided - it will use a set of default compiler options.
* Extra compiler options that will unconditionally be used bu this function are:
* Extra compiler options that will unconditionally be used by this function are:
* - isolatedModules = true
* - allowNonTsExtensions = true
* - noLib = true
* - noResolve = true
*/
export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string {
let options = compilerOptions ? clone(compilerOptions) : getDefaultCompilerOptions();
*/
export function transpileModule(input: string, transpileOptions?: TranspileOptions): TranspileOutput {
let options = transpileOptions.compilerOptions ? clone(transpileOptions.compilerOptions) : getDefaultCompilerOptions();
options.isolatedModules = true;
@ -1787,23 +1800,30 @@ namespace ts {
options.noResolve = true;
// Parse
let inputFileName = fileName || "module.ts";
let inputFileName = transpileOptions.fileName || "module.ts";
let sourceFile = createSourceFile(inputFileName, input, options.target);
if (moduleName) {
sourceFile.moduleName = moduleName;
if (transpileOptions.moduleName) {
sourceFile.moduleName = transpileOptions.moduleName;
}
let newLine = getNewLineCharacter(options);
// Output
let outputText: string;
let sourceMapText: string;
// Create a compilerHost object to allow the compiler to read and write files
let compilerHost: CompilerHost = {
getSourceFile: (fileName, target) => fileName === inputFileName ? sourceFile : undefined,
writeFile: (name, text, writeByteOrderMark) => {
Debug.assert(outputText === undefined, "Unexpected multiple outputs for the file: " + name);
outputText = text;
if (fileExtensionIs(name, ".map")) {
Debug.assert(sourceMapText === undefined, `Unexpected multiple source map outputs for the file '${name}'`);
sourceMapText = text;
}
else {
Debug.assert(outputText === undefined, "Unexpected multiple outputs for the file: " + name);
outputText = text;
}
},
getDefaultLibFileName: () => "lib.d.ts",
useCaseSensitiveFileNames: () => false,
@ -1813,16 +1833,29 @@ namespace ts {
};
let program = createProgram([inputFileName], options, compilerHost);
addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile));
addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics());
let diagnostics: Diagnostic[];
if (transpileOptions.reportDiagnostics) {
diagnostics = [];
addRange(/*to*/ diagnostics, /*from*/ program.getSyntacticDiagnostics(sourceFile));
addRange(/*to*/ diagnostics, /*from*/ program.getOptionsDiagnostics());
}
// Emit
program.emit();
Debug.assert(outputText !== undefined, "Output generation failed");
return outputText;
return { outputText, diagnostics, sourceMapText };
}
/*
* This is a shortcut function for transpileModule - it accepts transpileOptions as parameters and returns only outputText part of the result.
*/
export function transpile(input: string, compilerOptions?: CompilerOptions, fileName?: string, diagnostics?: Diagnostic[], moduleName?: string): string {
let output = transpileModule(input, { compilerOptions, fileName, reportDiagnostics: !!diagnostics, moduleName });
// addRange correctly handles cases when wither 'from' or 'to' argument is missing
addRange(diagnostics, output.diagnostics);
return output.outputText;
}
export function createLanguageServiceSourceFile(fileName: string, scriptSnapshot: IScriptSnapshot, scriptTarget: ScriptTarget, version: string, setNodeParents: boolean): SourceFile {

View file

@ -1,7 +0,0 @@
error TS5043: Option 'sourceMap' cannot be specified with option 'isolatedModules'.
!!! error TS5043: Option 'sourceMap' cannot be specified with option 'isolatedModules'.
==== tests/cases/compiler/isolatedModulesSourceMap.ts (0 errors) ====
export var x;

View file

@ -1,7 +1,7 @@
//// [isolatedModulesSourceMap.ts]
export var x;
export var x = 1;
//// [isolatedModulesSourceMap.js]
export var x;
export var x = 1;
//# sourceMappingURL=isolatedModulesSourceMap.js.map

View file

@ -1,2 +1,2 @@
//// [isolatedModulesSourceMap.js.map]
{"version":3,"file":"isolatedModulesSourceMap.js","sourceRoot":"","sources":["isolatedModulesSourceMap.ts"],"names":[],"mappings":"AACA,WAAW,CAAC,CAAC"}
{"version":3,"file":"isolatedModulesSourceMap.js","sourceRoot":"","sources":["isolatedModulesSourceMap.ts"],"names":[],"mappings":"AACA,WAAW,CAAC,GAAG,CAAC,CAAC"}

View file

@ -8,20 +8,26 @@ sources: isolatedModulesSourceMap.ts
emittedFile:tests/cases/compiler/isolatedModulesSourceMap.js
sourceFile:isolatedModulesSourceMap.ts
-------------------------------------------------------------------
>>>export var x;
>>>export var x = 1;
1 >
2 >^^^^^^^^^^^
3 > ^
4 > ^
5 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^->
4 > ^^^
5 > ^
6 > ^
7 > ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^->
1 >
>
2 >export var
3 > x
4 > ;
4 > =
5 > 1
6 > ;
1 >Emitted(1, 1) Source(2, 1) + SourceIndex(0)
2 >Emitted(1, 12) Source(2, 12) + SourceIndex(0)
3 >Emitted(1, 13) Source(2, 13) + SourceIndex(0)
4 >Emitted(1, 14) Source(2, 14) + SourceIndex(0)
4 >Emitted(1, 16) Source(2, 16) + SourceIndex(0)
5 >Emitted(1, 17) Source(2, 17) + SourceIndex(0)
6 >Emitted(1, 18) Source(2, 18) + SourceIndex(0)
---
>>>//# sourceMappingURL=isolatedModulesSourceMap.js.map

View file

@ -0,0 +1,5 @@
=== tests/cases/compiler/isolatedModulesSourceMap.ts ===
export var x = 1;
>x : Symbol(x, Decl(isolatedModulesSourceMap.ts, 1, 10))

View file

@ -0,0 +1,6 @@
=== tests/cases/compiler/isolatedModulesSourceMap.ts ===
export var x = 1;
>x : number
>1 : number

View file

@ -3,4 +3,4 @@
// @target: es6
// @filename: file1.ts
export var x;
export var x = 1;

View file

@ -2,58 +2,120 @@
module ts {
describe("Transpile", () => {
function runTest(input: string, compilerOptions: ts.CompilerOptions = {}, fileName?: string, moduleName?: string, expectedOutput?: string, expectedDiagnosticCodes: number[] = []): void {
let diagnostics: Diagnostic[] = [];
let result = transpile(input, compilerOptions, fileName || "file.ts", diagnostics, moduleName);
interface TranspileTestSettings {
options?: TranspileOptions;
expectedOutput?: string;
expectedDiagnosticCodes?: number[];
}
function checkDiagnostics(diagnostics: Diagnostic[], expectedDiagnosticCodes?: number[]) {
if(!expectedDiagnosticCodes) {
return;
}
for (let i = 0; i < expectedDiagnosticCodes.length; i++) {
assert.equal(expectedDiagnosticCodes[i], diagnostics[i] && diagnostics[i].code, `Could not find expeced diagnostic.`);
}
assert.equal(diagnostics.length, expectedDiagnosticCodes.length, "Resuting diagnostics count does not match expected");
if (expectedOutput !== undefined) {
assert.equal(result, expectedOutput);
}
assert.equal(diagnostics.length, expectedDiagnosticCodes.length, "Resuting diagnostics count does not match expected");
}
function test(input: string, testSettings: TranspileTestSettings): void {
let diagnostics: Diagnostic[] = [];
let transpileOptions: TranspileOptions = testSettings.options || {};
let transpileResult = transpile(input, transpileOptions.compilerOptions, transpileOptions.fileName, diagnostics, transpileOptions.moduleName);
transpileOptions.reportDiagnostics = true;
let transpileModuleResult = transpileModule(input, transpileOptions);
checkDiagnostics(diagnostics, testSettings.expectedDiagnosticCodes);
checkDiagnostics(transpileModuleResult.diagnostics, testSettings.expectedDiagnosticCodes);
if (testSettings.expectedOutput !== undefined) {
assert.equal(transpileResult, testSettings.expectedOutput);
assert.equal(transpileModuleResult.outputText, testSettings.expectedOutput);
}
// check source maps
if (!transpileOptions.compilerOptions) {
transpileOptions.compilerOptions = {};
}
if (!transpileOptions.fileName) {
transpileOptions.fileName = "file.ts";
}
transpileOptions.compilerOptions.sourceMap = true;
let transpileModuleResultWithSourceMap = transpileModule(input, transpileOptions);
assert.isTrue(transpileModuleResultWithSourceMap.sourceMapText !== undefined);
let expectedSourceMapFileName = removeFileExtension(transpileOptions.fileName) + ".js.map";
let expectedSourceMappingUrlLine = `//# sourceMappingURL=${expectedSourceMapFileName}`;
if (testSettings.expectedOutput !== undefined) {
assert.equal(transpileModuleResultWithSourceMap.outputText, testSettings.expectedOutput + expectedSourceMappingUrlLine);
}
else {
// expected output is not set, just verify that output text has sourceMappingURL as a last line
let output = transpileModuleResultWithSourceMap.outputText;
assert.isTrue(output.length >= expectedSourceMappingUrlLine.length);
if (output.length === expectedSourceMappingUrlLine.length) {
assert.equal(output, expectedSourceMappingUrlLine);
}
else {
let suffix = getNewLineCharacter(transpileOptions.compilerOptions) + expectedSourceMappingUrlLine
assert.isTrue(output.indexOf(suffix, output.length - suffix.length) !== -1);
}
}
}
it("Generates correct compilerOptions diagnostics", () => {
// Expecting 5047: "Option 'isolatedModules' can only be used when either option'--module' is provided or option 'target' is 'ES6' or higher."
runTest(`var x = 0;`, {}, /*fileName*/ undefined, /*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/ [5047]);
test(`var x = 0;`, { expectedDiagnosticCodes: [5047] });
});
it("Generates no diagnostics with valid inputs", () => {
// No errors
runTest(`var x = 0;`, { module: ModuleKind.CommonJS }, /*fileName*/ undefined, /*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/ []);
test(`var x = 0;`, { options: { compilerOptions: { module: ModuleKind.CommonJS } } });
});
it("Generates no diagnostics for missing file references", () => {
runTest(`/// <reference path="file2.ts" />
var x = 0;`,
{ module: ModuleKind.CommonJS }, /*fileName*/ undefined, /*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/ []);
test(`/// <reference path="file2.ts" />
var x = 0;`,
{ options: { compilerOptions: { module: ModuleKind.CommonJS } } });
});
it("Generates no diagnostics for missing module imports", () => {
runTest(`import {a} from "module2";`,
{ module: ModuleKind.CommonJS }, /*fileName*/ undefined,/*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/ []);
test(`import {a} from "module2";`,
{ options: { compilerOptions: { module: ModuleKind.CommonJS } } });
});
it("Generates expected syntactic diagnostics", () => {
runTest(`a b`,
{ module: ModuleKind.CommonJS }, /*fileName*/ undefined, /*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/ [1005]); /// 1005: ';' Expected
test(`a b`,
{ options: { compilerOptions: { module: ModuleKind.CommonJS } }, expectedDiagnosticCodes: [1005] }); /// 1005: ';' Expected
});
it("Does not generate semantic diagnostics", () => {
runTest(`var x: string = 0;`,
{ module: ModuleKind.CommonJS }, /*fileName*/ undefined, /*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/ []);
test(`var x: string = 0;`,
{ options: { compilerOptions: { module: ModuleKind.CommonJS } } });
});
it("Generates module output", () => {
runTest(`var x = 0;`, { module: ModuleKind.AMD }, /*fileName*/ undefined, /*moduleName*/undefined, `define(["require", "exports"], function (require, exports) {\r\n var x = 0;\r\n});\r\n`);
test(`var x = 0;`,
{
options: { compilerOptions: { module: ModuleKind.AMD } },
expectedOutput: `define(["require", "exports"], function (require, exports) {\r\n var x = 0;\r\n});\r\n`
});
});
it("Uses correct newLine character", () => {
runTest(`var x = 0;`, { module: ModuleKind.CommonJS, newLine: NewLineKind.LineFeed }, /*fileName*/ undefined, /*moduleName*/undefined, `var x = 0;\n`, /*expectedDiagnosticCodes*/ []);
test(`var x = 0;`,
{
options: { compilerOptions: { module: ModuleKind.CommonJS, newLine: NewLineKind.LineFeed } },
expectedOutput: `var x = 0;\n`
});
});
it("Sets module name", () => {
@ -66,12 +128,15 @@ var x = 0;`,
` }\n` +
` }\n` +
`});\n`;
runTest("var x = 1;", { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, /*fileName*/ undefined, "NamedModule", output)
test("var x = 1;",
{
options: { compilerOptions: { module: ModuleKind.System, newLine: NewLineKind.LineFeed }, moduleName: "NamedModule" },
expectedOutput: output
})
});
it("No extra errors for file without extension", () => {
runTest(`var x = 0;`, { module: ModuleKind.CommonJS }, "file", /*moduleName*/undefined, /*expectedOutput*/ undefined, /*expectedDiagnosticCodes*/[]);
test(`var x = 0;`, { options: { compilerOptions: { module: ModuleKind.CommonJS }, fileName: "file" } });
});
});
}