mirror of
https://github.com/Sevichecc/Urara-Blog.git
synced 2025-05-03 22:39:30 +08:00
175 lines
4.9 KiB
Text
175 lines
4.9 KiB
Text
import { dirname, resolve } from 'path';
|
|
import { readFile, readFileSync, Promise } from 'sander';
|
|
import { decode } from '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 = 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 = 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 );
|
|
}
|