Add support for external file references in source maps#15935
Add support for external file references in source maps#15935rbuckton merged 1 commit intomicrosoft:mastermicrosoft/TypeScript:masterfrom chuckjaz:external-file-source-mapchuckjaz/TypeScript:external-file-source-mapCopy head branch name to clipboard
Conversation
|
@rbuckton can you please review this change? |
|
@rbuckton I wanted to add tests for this but it was unclear to me where they should go. Recommendations? |
rbuckton
left a comment
There was a problem hiding this comment.
A few minor comments, but generally this looks good.
| /** | ||
| * Create an external source map source file reference | ||
| */ | ||
| export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number) { |
There was a problem hiding this comment.
This should have an explicit return type annotation, to associate it with the definition of SourceMapSource in types.ts.
There was a problem hiding this comment.
Leaving the result untyped avoids a catch-22 caused by https://github.com/Microsoft/TypeScript/blob/v2.3.4/src/services/types.ts#L70-L72.
I added return type and as any which I don't really like but there is no good way I found around this. Including services/types.ts (e.g. when building tsserer.js) requires this literal have getLineAndCharacterOfPosition() but not include services/types.ts (e.g. when building tsc.js) requires it not have it. This is caused indirectly because SourceMapSource needs to extend SourceFileLIke (which I changed to be more clear by explicitly extending the interface).
There was a problem hiding this comment.
Out of curiosity, why must it extend SourceFileLike? Could you not use a union in the places where this matters in either sourcemap.ts or utilities.ts?
There was a problem hiding this comment.
sourcemap.ts calls getLineAndCharacterOfPosition() from scanner.ts with a SourceMapSource which then requires SourceFileSource to be SourceFileLike.
I don't want to infect scanner.ts with SourceMapSource as SourceFileLike is intended to be the minimal specification of a content and cached lines. The fact it is extended to include getLineAndCharacterOfPosition() in services/types.ts seems troubling and unnecessary.
There was a problem hiding this comment.
@chuckjaz The constructor for a SourceFile is actually created by the objectAllocator. This allows us to keep nodes relatively light for the commandline compiler, but also allows us to create richer nodes for the language service.
You could consider adding a constructor factory for SourceMapSource to the object allocator, and hook in the getLineAndCharacterOfPosition method with a separate constructor in services.
There was a problem hiding this comment.
To make this clean I believe there are three options:
- Remove the extension of
SourceFileLikeinservice/types.tsand fix all calls to the methodgetLineAndCharacterofPosition()to call the functiongetLineAndCharacterOfPosition(). - Introduce a new sub-type of
SourceFileLIkeinservice/types.ts, such asServiceSource, that extendsSourceFileLIkeand move all references toSourceFileLIkeinservicesto useServiceSource. - Introduce a new type in
compiler/types.tscalled something likeSourceWithLinesthatSourceFileLikeextends from and change all other references toSourceFileLikeincompilertoSourceWithLines.
I like the first one best. It is faster than using a method and I could not find any cases in service that overrides getCharacterAndLineOfPosition in a source file that would required either (2) or (3).
There was a problem hiding this comment.
@rbuckton Sorry. I was writing my last response before I saw yours. I will update the PR to your recommendation.
There was a problem hiding this comment.
Please note that this method is also used in src/harness/typeWriter.ts. Please also note that while SourceFileLike and SourceFileLike#getLineAndCharacterOfPosition are public, the computeLineAndCharacterOfPosition function is internal.
I think my comment above stands as the best approach at this point.
| const emitNode = node.emitNode; | ||
| const emitFlags = emitNode && emitNode.flags; | ||
| const { pos, end } = emitNode && emitNode.sourceMapRange || node; | ||
| let source = emitNode && emitNode.sourceMapRange && emitNode.sourceMapRange.source; |
There was a problem hiding this comment.
To cut down on lookups, consider changing the above to:
const range = emitNode && emitNode.sourceMapRange;
const { pos, end } = range || node;
let source = range && range.source;a96b88b to
ba8adea
Compare
rbuckton
left a comment
There was a problem hiding this comment.
Just a few more minor comments.
| export interface SourceMapSource { | ||
| fileName: string; | ||
| text: string; | ||
| lineMap: number[]; |
There was a problem hiding this comment.
line maps are internal only.
| function SourceMapSource(this: SourceMapSource, fileName: string, text: string, skipTrivia?: (pos: number) => number) { | ||
| this.fileName = fileName; | ||
| this.text = text; | ||
| if (skipTrivia) this.skipTrivia = skipTrivia; |
There was a problem hiding this comment.
You lost the default for skipTrivia with this change. Previously you had pos => pos. Was this intended?
| } | ||
|
|
||
| export interface SourceMapSource { | ||
| /* @internal */ getLineAndCharacterOfPosition(pos: number): LineAndCharacter; |
There was a problem hiding this comment.
getLineAndCharacterOfPosition is generally public.
There was a problem hiding this comment.
This was intentional to prevent getLineAndCharacterOfPosition from being written to typescript.d.ts as part of SourceMapSource and making supplying a getLineAndCharacerOfPosition() required when creating a SourceMapRange for calls to setSourceMapRange().
There was a problem hiding this comment.
source is optional on SourceMapRange and a SourceMapSource should always be created by calling createSourceMapSource in factory.ts. Marking it internal here means that SourceMapSource can't be used as a SourceFileLike in the public API, only in the compiler.
There was a problem hiding this comment.
SourceFileLike isn't really used in the public API anyways so maybe this doesn't matter.
There was a problem hiding this comment.
Removing /* @internal */ since removing it then it will essentially force calling createSoruceMapSource() which is, as you point out, a good thing.
ba8adea to
34e8f38
Compare
34e8f38 to
ce1d1c8
Compare
|
This is a great change! Thanks for the contribution! |
A proposed implementation of #15934 that allows transformers to generate source map references to external files for nodes that are added to the AST that represent code from a different file and, potentially, a different source language.
Fixes #15934