File size: 5,004 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 |
import { dirname, resolve } from 'path';
import { readFile, readFileSync, Promise } from 'sander';
import codec from '@jridgewell/sourcemap-codec';
import getMap from './utils/getMap.js';
export default function Node ({ file, content }) {
this.file = file ? resolve( file ) : null;
this.content = content || null; // sometimes exists in sourcesContent, sometimes doesn't
if ( !this.file && this.content === null ) {
throw new Error( 'A source must specify either file or content' );
}
// these get filled in later
this.map = null;
this.mappings = null;
this.sources = null;
this.isOriginalSource = null;
this._stats = {
decodingTime: 0,
encodingTime: 0,
tracingTime: 0,
untraceable: 0
};
}
Node.prototype = {
load ( sourcesContentByPath, sourceMapByPath ) {
return getContent( this, sourcesContentByPath ).then( content => {
this.content = sourcesContentByPath[ this.file ] = content;
return getMap( this, sourceMapByPath ).then( map => {
if ( !map ) return null;
this.map = map;
let decodingStart = process.hrtime();
this.mappings = codec.decode( map.mappings );
let decodingTime = process.hrtime( decodingStart );
this._stats.decodingTime = 1e9 * decodingTime[0] + decodingTime[1];
const sourcesContent = map.sourcesContent || [];
const sourceRoot = resolve( dirname( this.file ), map.sourceRoot || '' );
this.sources = map.sources.map( ( source, i ) => {
return new Node({
file: source ? resolve( sourceRoot, source ) : null,
content: sourcesContent[i]
});
});
const promises = this.sources.map( node => node.load( sourcesContentByPath, sourceMapByPath ) );
return Promise.all( promises );
});
});
},
loadSync ( sourcesContentByPath, sourceMapByPath ) {
if ( !this.content ) {
if ( !sourcesContentByPath[ this.file ] ) {
sourcesContentByPath[ this.file ] = readFileSync( this.file, { encoding: 'utf-8' });
}
this.content = sourcesContentByPath[ this.file ];
}
const map = getMap( this, sourceMapByPath, true );
let sourcesContent;
if ( !map ) {
this.isOriginalSource = true;
} else {
this.map = map;
this.mappings = codec.decode( map.mappings );
sourcesContent = map.sourcesContent || [];
const sourceRoot = resolve( dirname( this.file ), map.sourceRoot || '' );
this.sources = map.sources.map( ( source, i ) => {
const node = new Node({
file: resolve( sourceRoot, source ),
content: sourcesContent[i]
});
node.loadSync( sourcesContentByPath, sourceMapByPath );
return node;
});
}
},
/**
* Traces a segment back to its origin
* @param {number} lineIndex - the zero-based line index of the
segment as found in `this`
* @param {number} columnIndex - the zero-based column index of the
segment as found in `this`
* @param {string || null} - if specified, the name that should be
(eventually) returned, as it is closest to the generated code
* @returns {object}
@property {string} source - the filepath of the source
@property {number} line - the one-based line index
@property {number} column - the zero-based column index
@property {string || null} name - the name corresponding
to the segment being traced
*/
trace ( lineIndex, columnIndex, name ) {
// If this node doesn't have a source map, we have
// to assume it is the original source
if ( this.isOriginalSource ) {
return {
source: this.file,
line: lineIndex + 1,
column: columnIndex || 0,
name: name
};
}
// Otherwise, we need to figure out what this position in
// the intermediate file corresponds to in *its* source
const segments = this.mappings[ lineIndex ];
if ( !segments || segments.length === 0 ) {
return null;
}
if ( columnIndex != null ) {
let len = segments.length;
let i;
for ( i = 0; i < len; i += 1 ) {
let generatedCodeColumn = segments[i][0];
if ( generatedCodeColumn > columnIndex ) {
break;
}
if ( generatedCodeColumn === columnIndex ) {
if ( segments[i].length < 4 ) return null;
let sourceFileIndex = segments[i][1];
let sourceCodeLine = segments[i][2];
let sourceCodeColumn = segments[i][3];
let nameIndex = segments[i][4];
let parent = this.sources[ sourceFileIndex ];
return parent.trace( sourceCodeLine, sourceCodeColumn, this.map.names[ nameIndex ] || name );
}
}
}
// fall back to a line mapping
let sourceFileIndex = segments[0][1];
let sourceCodeLine = segments[0][2];
let nameIndex = segments[0][4];
let parent = this.sources[ sourceFileIndex ];
return parent.trace( sourceCodeLine, null, this.map.names[ nameIndex ] || name );
}
};
function getContent ( node, sourcesContentByPath ) {
if ( node.file in sourcesContentByPath ) {
node.content = sourcesContentByPath[ node.file ];
}
if ( !node.content ) {
return readFile( node.file, { encoding: 'utf-8' });
}
return Promise.resolve( node.content );
}
|