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

Support preexisisting inline source maps. #364

Merged
merged 1 commit into from
Feb 13, 2017

Conversation

LucasSloan
Copy link
Contributor

If tsickle is passed sources that include inline source maps (eg ts 'sources' produced by ngc), compose those source maps with the source maps produced by running tsickle.


return this.getCanonicalFileName(path.resolve(fileDir, sourceFileName));
sourceFile.text = sm.removeInlineSourceMap(sourceFile.text);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not generally safe to mutate the TypeScript AST. We've run into very hard to track bugs because of it before. Can you extract the source map on load?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into the initial load.

@@ -125,6 +126,7 @@ export class TsickleCompilerHost implements ts.CompilerHost {
}

const sourceFile = this.runConfiguration.oldProgram.getSourceFile(fileName);
this.stripAndStoreExistingSourceMap(sourceFile);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this need to happen on every getSourceFile? Shouldn't we only do this when loading the file from disk the very first time?

Also: how often does getSourceFile get called? We shove megabytes through this, running a regexp over and over again on all inputs could get quite costly.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved into the first load.

return getInlineSourceMapCount(source) > 0;
}

export function getInlineSourceMapCount(source: string): number {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is only used in tests, is it really important to expose this and test it separately?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It isn't part of the public api and I'd like to keep all the source map functions together.

}

export function extractInlineSourceMap(source: string): string {
const result = INLINE_SOURCE_MAP_REGEX.exec(source)!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we've hard issues before where source code would contain:

// #sourceMapURL...
more().code();
// #sourceMap...

I.e., you should take the last source map, not the first.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something, but this still returns the first matching //# sourceMap, and the clobbering code below still replaces the first one, where it should be the last one, doesn't it?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You were right! I was reusing the same regex object, which moves the pointer in the string forward each time, rendering my test that this was happening invalid.

const match = source.match(INLINE_SOURCE_MAP_REGEX);
if (match) {
// The INLINE_SOURCE_MAP_REGEX has a capture group, but we only want to count real matches
return match.length / 2;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are you sure this really counts the matches?

> m = 'a a a a a a a'.match(/(a)/m).length
2

Maybe you're missing a g?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was missing a g, thanks!

}`);

const closurizeSources = new Map<string, string>();
{
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

drop the extra block?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

`//# sourceMappingURL=data:application/json;base64,${encodedSourceMap}`);
} else {
return `${source}
//# sourceMappingURL=data:application/json;base64,${encodedSourceMap}`;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this would be clearer to read if you just used \n in the string.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That is better, yes.

@LucasSloan LucasSloan force-pushed the preexisting-inline-source-maps branch from e1db360 to ea90d59 Compare February 11, 2017 02:53
import * as ts from 'typescript';

import {convertDecorators} from './decorator-annotator';
import {processES5} from './es5processor';
import {ModulesManifest} from './modules_manifest';
import * as sm from './source_map_utils';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Go rule around short names is that the more local and contextual the name (e.g. the iterator for a "for" loop), the more likely you are to know the name in context (e.g. "i"). For a global name like this that appears across the file, a longer name like sourceMapUtils is more appropriate.

(The JS/TS style rule is to just always use long names.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough, but why is typescript imported as ts?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hah, that's a good point! I blame @mprobst

@@ -96,13 +96,16 @@ export class TsickleCompilerHost implements ts.CompilerHost {
/** externs.js files produced by tsickle, if any. */
public externs: {[fileName: string]: string} = {};

private sourceFileToPreexistingSourceMapMap = new Map<ts.SourceFile, SourceMapGenerator>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop one "map" suffix here, because it's implied in the type. (E.g. the other maps below don't have Map at the end.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


import {expect} from 'chai';

import * as sm from '../src/source_map_utils';
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same comment here about module name, but the short name is okay-er here because it's a short file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.


export function sourceMapTextToGenerator(sourceMapText: string): SourceMapGenerator {
const sourceMapJson: any = sourceMapText;
return SourceMapGenerator.fromSourceMap(this.sourceMapTextToConsumer(sourceMapJson));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's the idea behind this function and the one above it? They seem to just wrap another function?

I think the use of this here accidentally compiles but it shouldn't.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They wrap another function (and do the cast to any). When they were inlined, it was getting hard to read. And the this is intentional - we're calling another function on the same object.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Which object? I think this function is a top-level function, not a method (?)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wow, my bad. I thought this was still where I had originally written it, despite you calling attention to it. Fixed.


export function getInlineSourceMapCount(source: string): number {
const match = source.match(INLINE_SOURCE_MAP_REGEX);
if (match) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: I think it'd be more idiomatic to write return match ? match.length : 0;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

return source;
}

export function setInlineSourceMap(source: string, sourceMap: string): string {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you add some docs and mention that this clobbers pre-existing maps?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

export function extractInlineSourceMap(source: string): string {
const result = INLINE_SOURCE_MAP_REGEX.exec(source)!;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I might be missing something, but this still returns the first matching //# sourceMap, and the clobbering code below still replaces the first one, where it should be the last one, doesn't it?

@mprobst
Copy link
Contributor

mprobst commented Feb 13, 2017 via email

@LucasSloan LucasSloan force-pushed the preexisting-inline-source-maps branch from ea90d59 to 08d31e3 Compare February 13, 2017 21:05
@@ -0,0 +1,77 @@
import {SourceMapConsumer, SourceMapGenerator} from 'source-map';

function getInlineSourceMapRegex(): RegExp {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document why this is not using a constant.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

const inlineSourceMapRegex = getInlineSourceMapRegex();
let previousResult: RegExpExecArray|null = null;
let result: RegExpExecArray|null = null;
do {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comment why this is looping

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

}

export function removeInlineSourceMap(source: string): string {
if (containsInlineSourceMap(source)) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you double check if you need the if() here? and below

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not needed here, needed below.

If tsickle is passed sources that include inline source maps (eg ts 'sources' produced by ngc), compose those source maps with the source maps produced by running tsickle.
@LucasSloan LucasSloan force-pushed the preexisting-inline-source-maps branch from 08d31e3 to c5460b2 Compare February 13, 2017 23:15
@LucasSloan LucasSloan merged commit 7bc348c into master Feb 13, 2017
drjanitor pushed a commit to drjanitor/tsickle that referenced this pull request Feb 14, 2017
If tsickle is passed sources that include inline source maps (eg ts 'sources' produced by ngc), compose those source maps with the source maps produced by running tsickle.
@mprobst mprobst deleted the preexisting-inline-source-maps branch March 16, 2017 12:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants