import { SourceMapConsumer , MappedPosition as MappedPositionSM } from 'source-map' ; import * as path from 'path' ; import * as sourceMapUtils from './sourceMapUtils' ; import * as utils from '../utils' ; import { logger } from 'vscode-debugadapter' ; import { IPathMapping } from '../debugAdapterInterfaces' ; export type MappedPosition = MappedPositionSM ; /**
 * A pair of the original path in the sourcemap, and the full absolute path as inferred
 */ export interface ISourcePathDetails { originalPath : string ; inferredPath : string ; startPosition : MappedPosition ; } export class SourceMap { private _generatedPath : string ; private _sources : string [ ] ; private _smc : SourceMapConsumer ; private _authoredPathCaseMap = new Map < string , string > ( ) ; private _allSourcePathDetails : ISourcePathDetails [ ] ; private _originalSources : string [ ] ; private _originalSourceRoot : string ; /**
     * Returns list of ISourcePathDetails for all sources in this sourcemap, sorted by their
     * positions within the sourcemap.
     */ public get allSourcePathDetails ( ) : ISourcePathDetails [ ] { if ( ! this . _allSourcePathDetails ) { this . _allSourcePathDetails = this . _sources . map ( ( inferredPath , i ) => { const originalSource = this . _originalSources [ i ] ; const originalPath = this . _originalSourceRoot ? sourceMapUtils . getFullSourceEntry ( this . _originalSourceRoot , originalSource ) : originalSource ; return < ISourcePathDetails > { inferredPath , originalPath , startPosition : this . generatedPositionFor ( inferredPath , 0 , 0 ) } ; } ) . sort ( ( a , b ) => { if ( ! a . startPosition ) { logger . log ( `Could not map start position for: ${ a . inferredPath }` ) ; return - 1 ; } else if ( ! b . startPosition ) { logger . log ( `Could not map start position for: ${ b . inferredPath }` ) ; return 1 ; } if ( a . startPosition . line === b . startPosition . line ) { return a . startPosition . column - b . startPosition . column ; } else { return a . startPosition . line - b . startPosition . line ; } } ) ; } return this . _allSourcePathDetails ; } /**
     * generatedPath: an absolute local path or a URL
     * json: sourcemap contents as string
     */ public constructor ( generatedPath , json , pathMapping ? , sourceMapPathOverrides ? , isVSClient = false ) { this . _generatedPath = generatedPath ; const sm = JSON . parse ( json ) ; logger . log ( `SourceMap: creating for ${ generatedPath }` ) ; logger . log ( `SourceMap: sourceRoot: ${ sm . sourceRoot }` ) ; if ( sm . sourceRoot && sm . sourceRoot . toLowerCase ( ) === '/source/' ) { logger . log ( 'Warning: if you are using gulp-sourcemaps < 2.0 directly or indirectly, you may need to set sourceRoot manually in your build config, if your files are not actually under a directory called /source' ) ; } logger . log ( `SourceMap: sources: ${ JSON . stringify ( sm . sources ) }` ) ; if ( pathMapping ) { logger . log ( `SourceMap: pathMapping: ${ JSON . stringify ( pathMapping ) }` ) ; } const computedSourceRoot = sourceMapUtils . getComputedSourceRoot ( sm . sourceRoot , this . _generatedPath , pathMapping ) ; this . _originalSourceRoot = sm . sourceRoot ; this . _originalSources = sm . sources ; sm . sourceRoot = null ; this . _sources = sm . sources . map ( sourcePath => { if ( sourceMapPathOverrides ) { const fullSourceEntry = sourceMapUtils . getFullSourceEntry ( this . _originalSourceRoot , sourcePath ) ; const mappedFullSourceEntry = sourceMapUtils . applySourceMapPathOverrides ( fullSourceEntry , sourceMapPathOverrides , isVSClient ) ; if ( fullSourceEntry !== mappedFullSourceEntry ) { return utils . canonicalizeUrl ( mappedFullSourceEntry ) ; } } if ( sourcePath . startsWith ( 'file://' ) ) { return utils . canonicalizeUrl ( sourcePath ) ; } if ( ! path . isAbsolute ( sourcePath ) ) { sourcePath = utils . properResolve ( computedSourceRoot , sourcePath ) ; } return utils . canonicalizeUrl ( sourcePath ) ; } ) ; sm . sources = this . _sources . map ( sourceAbsPath => { const lowerCaseSourceAbsPath = sourceAbsPath . toLowerCase ( ) ; this . _authoredPathCaseMap . set ( lowerCaseSourceAbsPath , sourceAbsPath ) ; return utils . pathToFileURL ( lowerCaseSourceAbsPath , true ) ; } ) ; this . _smc = new SourceMapConsumer ( sm ) ; } public get authoredSources ( ) : string [ ] { return this . _sources ; } public generatedPath ( ) { return this . _generatedPath ; } public doesOriginateFrom ( absPath ) { return this . authoredSources . some ( path => path === absPath ) ; } public authoredPositionFor ( line , column ) { line ++ ; const lookupArgs = { line , column , bias : ( < any > SourceMapConsumer ) . GREATEST_LOWER_BOUND } ; let position = this . _smc . originalPositionFor ( lookupArgs ) ; if ( ! position . source ) { lookupArgs . bias = ( < any > SourceMapConsumer ) . LEAST_UPPER_BOUND ; position = this . _smc . originalPositionFor ( lookupArgs ) ; } if ( position . source ) { position . source = utils . canonicalizeUrl ( position . source ) ; position . source = this . _authoredPathCaseMap . get ( position . source ) || position . source ; position . line -- ; return position ; } else { return null ; } } public generatedPositionFor ( source , line , column ) { line ++ ; source = utils . pathToFileURL ( source . toLowerCase ( ) , true ) ; const lookupArgs = { line , column , source , bias : ( < any > SourceMapConsumer ) . LEAST_UPPER_BOUND } ; let position = this . _smc . generatedPositionFor ( lookupArgs ) ; if ( position . line === null ) { lookupArgs . bias = ( < any > SourceMapConsumer ) . GREATEST_LOWER_BOUND ; position = this . _smc . generatedPositionFor ( lookupArgs ) ; } if ( position . line === null ) { return null ; } else { return { line : position . line - 1 , column : position . column , source : this . _generatedPath } ; } } public sourceContentFor ( authoredSourcePath ) { authoredSourcePath = utils . pathToFileURL ( authoredSourcePath , true ) ; return ( < any > this . _smc ) . sourceContentFor ( authoredSourcePath , true ) ; } }