// @flow import type {SourceLocation} from '@parcel/types'; import type {FileSystem} from '@parcel/fs'; import SourceMap from '@parcel/source-map'; import path from 'path'; import {normalizeSeparators, isAbsolute} from './path'; export const SOURCEMAP_RE: RegExp = /(?:\/\*|\/\/)\s*[@#]\s*sourceMappingURL\s*=\s*([^\s*]+)(?:\s*\*\/)?\s*$/; const DATA_URL_RE = /^data:[^;]+(?:;charset=[^;]+)?;base64,(.*)/; export const SOURCEMAP_EXTENSIONS: Set = new Set([ 'css', 'es', 'es6', 'js', 'jsx', 'mjs', 'ts', 'tsx', ]); export function matchSourceMappingURL( contents: string, ): null | RegExp$matchResult { return contents.match(SOURCEMAP_RE); } export async function loadSourceMapUrl( fs: FileSystem, filename: string, contents: string, ): Promise { let match = matchSourceMappingURL(contents); if (match) { let url = match[1].trim(); let dataURLMatch = url.match(DATA_URL_RE); let mapFilePath; if (dataURLMatch) { mapFilePath = filename; } else { mapFilePath = url.replace(/^file:\/\//, ''); mapFilePath = isAbsolute(mapFilePath) ? mapFilePath : path.join(path.dirname(filename), mapFilePath); } return { url, filename: mapFilePath, map: JSON.parse( dataURLMatch ? Buffer.from(dataURLMatch[1], 'base64').toString() : await fs.readFile(mapFilePath, 'utf8'), ), }; } } export async function loadSourceMap( filename: string, contents: string, options: {fs: FileSystem, projectRoot: string, ...}, ): Promise { let foundMap = await loadSourceMapUrl(options.fs, filename, contents); if (foundMap) { let mapSourceRoot = path.dirname(filename); if ( foundMap.map.sourceRoot && !normalizeSeparators(foundMap.map.sourceRoot).startsWith('/') ) { mapSourceRoot = path.join(mapSourceRoot, foundMap.map.sourceRoot); } let sourcemapInstance = new SourceMap(options.projectRoot); sourcemapInstance.addVLQMap({ ...foundMap.map, sources: foundMap.map.sources.map(s => { return path.join(mapSourceRoot, s); }), }); return sourcemapInstance; } } export function remapSourceLocation( loc: SourceLocation, originalMap: SourceMap, ): SourceLocation { let { filePath, start: {line: startLine, column: startCol}, end: {line: endLine, column: endCol}, } = loc; let lineDiff = endLine - startLine; let colDiff = endCol - startCol; let start = originalMap.findClosestMapping(startLine, startCol - 1); let end = originalMap.findClosestMapping(endLine, endCol - 1); if (start?.original) { if (start.source) { filePath = start.source; } ({line: startLine, column: startCol} = start.original); startCol++; // source map columns are 0-based } if (end?.original) { ({line: endLine, column: endCol} = end.original); endCol++; // source map columns are 0-based if (endLine < startLine) { endLine = startLine; endCol = startCol; } else if (endLine === startLine && endCol < startCol && lineDiff === 0) { endCol = startCol + colDiff; } else if (endLine === startLine && startCol === endCol && lineDiff === 0) { // Prevent 0-length ranges endCol = startCol + 1; } } else { endLine = startLine; endCol = startCol; } return { filePath, start: { line: startLine, column: startCol, }, end: { line: endLine, column: endCol, }, }; }