Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Report alignment issue when inputSourceMap from TypeScript #108

Closed
lukescott opened this issue May 22, 2017 · 15 comments
Closed

Report alignment issue when inputSourceMap from TypeScript #108

lukescott opened this issue May 22, 2017 · 15 comments

Comments

@lukescott
Copy link

Here is a jest transformer I'm using:

const tsc = require("typescript")
const tsConfig = require("./tsconfig.json") || {}
const babel = require('babel-core')
const jestPreset = require('babel-preset-jest')
const babelPluginIstanbul = require('babel-plugin-istanbul').default

const createTransformer = (options) => {
	options = Object.assign({}, options, {
		plugins: (options && options.plugins) || [],
		presets: ((options && options.presets) || []).concat([jestPreset]),
		retainLines: true
	});
	delete options.cacheDirectory;
	delete options.filename;
	return {
		canInstrument: true,
		process(src, filename, config, transformOptions) {
			let inputSourceMap
			if (filename.endsWith(".ts") || filename.endsWith(".tsx")) {
				let compilerOptions = Object.assign({},
					tsConfig.compilerOptions, {
						sourceMap: true
					}
				)
				let transpileOut = tsc.transpileModule(src, {
					fileName: filename,
					compilerOptions
				});
				src = transpileOut.outputText
				inputSourceMap = JSON.parse(transpileOut.sourceMapText)
			}
			const theseOptions = Object.assign({
				filename,
				//inlineSourceMap,
			}, options);
			if (transformOptions && transformOptions.instrument) {
				theseOptions.auxiliaryCommentBefore = ' istanbul ignore next ';
				// Copied from jest-runtime transform.js
				theseOptions.plugins = theseOptions.plugins.concat([
					[
						babelPluginIstanbul, {
							inputSourceMap,
							cwd: config.rootDir,
							exclude: []
						}
					]
				]);
			}
			return babel.transform(src, theseOptions).code
		}
	};
};

module.exports = createTransformer();
module.exports.createTransformer = createTransformer;

The highlights in the coverage report is misaligned.

May be related to babel/babel#5408

Apparently babel also takes inputSourceMap as an option, but only takes inputSourceMap into account after plugins have run. I'm assuming that's why this plugin has that option as well. But no matter what I do I get the same misalignment.

I've also tried having TypeScript pass the sourcemap inline, but that still does the same thing.

@lukescott lukescott changed the title Report alignment issue when input source map from TypeScript Report alignment issue when inputSourceMap from TypeScript May 22, 2017
@bcoe bcoe added the bug label May 23, 2017
@lukescott
Copy link
Author

lukescott commented May 23, 2017

Repo to reproduce https://github.com/lukescott/ts-babel-source-map-test

preprocessor1.js (TSC -> Babel - wrong)
transformer1

preprocessor2.js (Babel - correct)
transformer2

preprocessor3.js (TypeScript, then Babel thru jest)
transformer3

Change preprocessor in package.json to test each case.

For some reason the first and third are not showing the yellow highlight, but on my code it shows the highlight on the first case, but in the wrong spot. Third case never shows the yellow highlight.

@bcoe
Copy link
Member

bcoe commented May 24, 2017

@MichaReiser originally implemented the inputSourceMap logic here:

https://github.com/istanbuljs/istanbuljs/tree/master/packages/istanbul-lib-instrument

Perhaps he can speak to what might be getting lost in translation -- since it sounds like he's had a setup working appropriately.

I appreciate the work you've put into a reproduction, and if you feel like helping dig further I would happily accept a patch against the istanbuljs/istanbuljs mono-repo.

@MichaReiser
Copy link

MichaReiser commented May 24, 2017

My setup differs slightly. I used TypeScript -> Webpack transpiling with babel.

@lukescott
Can you please upload the source maps of the transpilation pipelines (just for your demo file, istanbul). Then the source maps can be analyzed. If the mapping error already occurs in these source maps, then it is not an error caused by istanbul.

Edit: To me it seems as the input source map is not considered at all when using together with babel

@lukescott
Copy link
Author

lukescott commented May 24, 2017

@MichaReiser
Copy link

@lukescott Can you give me an example how to setup and test your project?
Which commands to I have to run to get the output? Where is the output stored?

I have cloned your repo and ran npm install. I can execute jest but don't think that it generates any coverage information?

I need to debug lib-istanbul-source-map. I don't remember all details of the implementation...

@lukescott
Copy link
Author

lukescott commented May 25, 2017

I run jest --no-cache --coverage. By default the first case runs. Output goes to ./coverage. You can change this in package.json.

Here is a complete standalone script (hope this helps):

var tsc = require("typescript");
var babel = require('babel-core');
var babelPluginIstanbul = require('babel-plugin-istanbul').default;

var filename = "./src/foo/foo.ts";

var tsConfig = {
	fileName: filename,
	compilerOptions: {
		module: "es2015",
		target: "esnext",
		jsx: "preserve",
		baseUrl: ".",
		moduleResolution: "node",
		paths: {
			"*": [
				"src/*"
			]
		},
		sourceMap: true
	}
};

var babelConfig = {
	filename: filename,
	retainLines: true,
	inputSourceMap: inputSourceMap,
	sourceMap: true,
	auxiliaryCommentBefore: " istanbul ignore next ",
	presets: ["latest"],
	plugins: [
		"transform-class-properties",
		"transform-object-rest-spread",
		[babelPluginIstanbul, {
			cwd: ".",
			exclude: [],
			// inputSourceMap: inputSourceMap, // seems to do same thing as above
		}]
	]
};


var src = 'import bar from "../bar/bar"\
\
export default bar({\
	input(state, value = "") {\
		if (!value) {\
			return null\
		}\
		return state\
	},\
})';
var transpileOut = tsc.transpileModule(src, tsConfig);
var inputSourceMap = JSON.parse(transpileOut.sourceMapText);
var babelOut = babel.transform(transpileOut.outputText, babelConfig);

console.log("tsc out:", transpileOut.sourceMapText);
console.log("babel out:", JSON.stringify(babelOut.map));

https://github.com/lukescott/ts-babel-source-map-test/blob/master/snippet.js

@lukescott
Copy link
Author

lukescott commented May 26, 2017

Ok, so doing a bit of debugging, this seems to be where inputSource Map ends:

https://github.com/istanbuljs/istanbuljs/blob/df85ba642e1329a44e699bfcbf08ddf060414394/packages/istanbul-lib-instrument/src/source-coverage.js#L93

It looks like it should end up here some how:

https://github.com/istanbuljs/istanbuljs/blob/189519dd2656ba304712b5a69900317bedf8d6e8/packages/istanbul-lib-source-maps/lib/map-store.js#L103

But transformCoverage is never called. The only place transformCoverage is called is istanbul-api, which doesn't seem to be included anywhere.

So it would seem inputSourceMap is not being used at all.

@lukescott
Copy link
Author

Ok, I finally figured it out:

https://github.com/facebook/jest/blob/9b157c3a7c325c3971b2aabbe4c8ab4ce0b0c56d/packages/jest-cli/src/reporters/CoverageReporter.js#L81

Jest only calls transformCoverage if mapCoverage is true (false by default):

https://facebook.github.io/jest/docs/configuration.html#mapcoverage-boolean

Don't understand why this is even an option. If there is no inputSourceMap, how is it "resource intensive"? Why is this even an option?

@lukescott
Copy link
Author

lukescott commented May 27, 2017

Not sure why this is, but if you return {code: "", map:...} to Jest instead of just .code it breaks, but in a different way. Not really sure how all this is supposed to work 🤕 .

@MichaReiser
Copy link

Don't understand why this is even an option. If there is no inputSourceMap, how is it "resource intensive"? Why is this even an option?

In case there exists source map and you don't want to reconfigure your whole build to omit these

Not sure why this is, but if you return {code: "", map:...} to Jest instead of just .code it breaks, but in a different way. Not really sure how all this is supposed to work 🤕 .

Probably because the mappings of the source maps point to invalid positions?

If I were you. Try to debug it first without Jest. Then it is clear if it is an issue of istanbul or jest.

@bcoe
Copy link
Member

bcoe commented May 27, 2017

@lukescott when you enabled map-coverage were you able to eventually get appropriate output?

@lukescott
Copy link
Author

lukescott commented May 27, 2017

I'm just passing inputSourceMap into babel (which is picked up by babel-plugin-istanbul) and then enabling mapCoverage in Jest. Jest calls transformCoverage when that option is enabled. Istanbul only uses inputSourceMap if transformCoverage is called.

I think it would work the same way if you did inlineSourceMap: true on tsc and sourceMap: "inline" on babel as istanbul will pick up on the inline source map. As long as mapCoverage is enabled in Jest, it should translate correctly.

It just wasn't clear that the option needed to be enabled, especially since a jest transformer has a canInstrument: true flag.

It looks like most other test runners have you call them with nyc, which calls transformCoverage for you.

@bcoe
Copy link
Member

bcoe commented May 27, 2017

@lukescott (CC: @DmitriiAbramov, @cpojer) is there anywhere we could document this better on istanbul's end, or any changes that could be made to Jest or babel-plugin-istanbul that would have made this less confusing?

If you have the time, would happily accept any updates you can make to documentation, or default settings, that would have helped you avoid this confusing behavior.

@MichaReiser
Copy link

I think it would work the same way if you did inlineSourceMap: true on tsc and sourceMap: "inline" on babel as istanbul will pick up on the inline source map. As long as mapCoverage is enabled in Jest, it should translate correctly.

This is true. The input source map is only used if there are no inline source maps (it is a fallback) with the intent, to make the right thing by default.

@bcoe
Copy link
Member

bcoe commented Jul 22, 2017

@lukescott just doing some issue cleanup -- sounds like you did ultimately land in a good place with this bug, i.e., got everything working.

@bcoe bcoe closed this as completed Jul 22, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants