From c007f361a363465437eca94f81e4dfe0152b562f Mon Sep 17 00:00:00 2001 From: Anish Visaria Date: Sun, 22 Sep 2019 12:32:03 -0400 Subject: [PATCH] Optimize files size computing (#128) - Iterate through each mapping rather than the source and only look at the source to determine the number of columns contributing to each file's mapped bytes. - Add new error types `InvalidMappingLine` (when source map references a line outside source lines) and `InvalidMappingColumn` (when source map reference a column beyond source column at the particular line) --- src/app-error.ts | 29 +++++++ src/explore.ts | 94 +++++++++++----------- src/index.ts | 9 +++ tests/__snapshots__/app-error.test.ts.snap | 10 +++ tests/app-error.test.ts | 7 ++ 5 files changed, 101 insertions(+), 48 deletions(-) diff --git a/src/app-error.ts b/src/app-error.ts index 898b953..4de28f6 100644 --- a/src/app-error.ts +++ b/src/app-error.ts @@ -36,6 +36,19 @@ interface UnmappedBytesErrorContext { unmappedBytes: number; } +interface InvalidMappingLineErrorContext { + code: 'InvalidMappingLine'; + generatedLine: number; + maxLine: number; +} + +interface InvalidMappingColumnErrorContext { + code: 'InvalidMappingColumn'; + generatedLine: number; + generatedColumn: number; + maxColumn: number; +} + interface CannotOpenTempFileErrorContext { code: 'CannotOpenTempFile'; error: Buffer; @@ -46,6 +59,8 @@ export type ErrorContext = | CommonErrorContext | OneSourceSourceMapErrorContext | UnmappedBytesErrorContext + | InvalidMappingLineErrorContext + | InvalidMappingColumnErrorContext | CannotOpenTempFileErrorContext; export function getErrorMessage(context: ErrorContext): string { @@ -71,6 +86,20 @@ See ${SOURCE_MAP_INFO_URL}`; return `Unable to map ${unmappedBytes}/${totalBytes} bytes (${bytesString}%)`; } + case 'InvalidMappingLine': { + const { generatedLine, maxLine } = context; + + return `Your source map refers to generated line ${generatedLine}, but the source only contains ${maxLine} line(s). +Check that you are using the correct source map.`; + } + + case 'InvalidMappingColumn': { + const { generatedLine, generatedColumn, maxColumn } = context; + + return `Your source map refers to generated column ${generatedColumn} on line ${generatedLine}, but the source only contains ${maxColumn} column(s) on that line. +Check that you are using the correct source map.`; + } + case 'CannotSaveFile': return 'Unable to save HTML to file'; diff --git a/src/explore.ts b/src/explore.ts index fdf5948..e451147 100644 --- a/src/explore.ts +++ b/src/explore.ts @@ -21,7 +21,7 @@ export async function exploreBundle( const sourceMapData = await loadSourceMap(code, map); - const sizes = computeGeneratedFileSizes(sourceMapData); + const sizes = computeFileSizeMapOptimized(sourceMapData); const files = adjustSourcePaths(sizes.files, options); @@ -86,65 +86,63 @@ async function loadSourceMap(codeFile: File, sourceMapFile?: File): Promise { + // Lines are 1-based + const line = lines[generatedLine - 1]; + if (line === null) { + throw new AppError({ + code: 'InvalidMappingLine', + generatedLine: generatedLine, + maxLine: lines.length, + }); + } - totalBytes += numChars; + // Columns are 0-based + if (generatedColumn >= line.length) { + throw new AppError({ + code: 'InvalidMappingColumn', + generatedLine: generatedLine, + generatedColumn: generatedColumn, + maxColumn: line.length, + }); + } - if (source === null) { - unmappedBytes += numChars; + let mappingLength = 0; + if (lastGeneratedColumn !== null) { + if (lastGeneratedColumn >= line.length) { + throw new AppError({ + code: 'InvalidMappingColumn', + generatedLine: generatedLine, + generatedColumn: lastGeneratedColumn, + maxColumn: line.length, + }); + } + mappingLength = lastGeneratedColumn - generatedColumn + 1; } else { - files[source] = (files[source] || 0) + numChars; + mappingLength = line.length - generatedColumn; } - } + files[source] = (files[source] || 0) + mappingLength; + mappedBytes += mappingLength; + }); + + // Don't count newlines as original version didn't count newlines + const totalBytes = codeFileContent.length - lines.length + 1; return { files, - unmappedBytes, + unmappedBytes: totalBytes - mappedBytes, totalBytes, }; } -interface Span { - source: string | null; - numChars: number; -} - -function computeSpans(sourceMapData: SourceMapData): Span[] { - const { consumer, codeFileContent } = sourceMapData; - - const lines = codeFileContent.split('\n'); - const spans: Span[] = []; - let numChars = 0; - - let lastSource: string | null | undefined = undefined; // not a string, not null - - for (let line = 1; line <= lines.length; line++) { - const lineText = lines[line - 1]; - const numCols = lineText.length; - - for (let column = 0; column < numCols; column++, numChars++) { - const { source } = consumer.originalPositionFor({ line, column }); - - if (source !== lastSource) { - lastSource = source; - spans.push({ source, numChars: 1 }); - } else { - spans[spans.length - 1].numChars += 1; - } - } - } - - return spans; -} - export function adjustSourcePaths(fileSizeMap: FileSizeMap, options: ExploreOptions): FileSizeMap { if (!options.noRoot) { const prefix = getCommonPathPrefix(Object.keys(fileSizeMap)); diff --git a/src/index.ts b/src/index.ts index 414c8a7..fef656a 100644 --- a/src/index.ts +++ b/src/index.ts @@ -20,6 +20,8 @@ export type ErrorCode = | 'NoSourceMap' | 'OneSourceSourceMap' | 'UnmappedBytes' + | 'InvalidMappingLine' + | 'InvalidMappingColumn' | 'CannotSaveFile' | 'CannotCreateTempFile' | 'CannotOpenTempFile'; @@ -71,3 +73,10 @@ export interface ExploreErrorResult { } export type BundlesAndFileTokens = (Bundle | string)[] | Bundle | string; + +// TODO: Remove when https://github.com/mozilla/source-map/pull/374 is merged +declare module 'source-map' { + export interface MappingItem { + lastGeneratedColumn: number | null; + } +} diff --git a/tests/__snapshots__/app-error.test.ts.snap b/tests/__snapshots__/app-error.test.ts.snap index 6931cf2..9b3ce09 100644 --- a/tests/__snapshots__/app-error.test.ts.snap +++ b/tests/__snapshots__/app-error.test.ts.snap @@ -12,6 +12,16 @@ exports['app-error getErrorMessage should create message for \'CannotSaveFile\' Unable to save HTML to file ` +exports['app-error getErrorMessage should create message for \'InvalidMappingColumn\' 1'] = ` +Your source map refers to generated column 81 on line 60, but the source only contains 80 column(s) on that line. +Check that you are using the correct source map. +` + +exports['app-error getErrorMessage should create message for \'InvalidMappingLine\' 1'] = ` +Your source map refers to generated line 60, but the source only contains 57 line(s). +Check that you are using the correct source map. +` + exports['app-error getErrorMessage should create message for \'NoBundles\' 1'] = ` No file(s) provided ` diff --git a/tests/app-error.test.ts b/tests/app-error.test.ts index 5adc95e..c7c340b 100644 --- a/tests/app-error.test.ts +++ b/tests/app-error.test.ts @@ -9,6 +9,13 @@ describe('app-error', function() { { code: 'NoSourceMap' }, { code: 'OneSourceSourceMap', filename: 'foo.min.js' }, { code: 'UnmappedBytes', totalBytes: 100, unmappedBytes: 70 }, + { code: 'InvalidMappingLine', generatedLine: 60, maxLine: 57 }, + { + code: 'InvalidMappingColumn', + generatedLine: 60, + generatedColumn: 81, + maxColumn: 80, + }, { code: 'CannotSaveFile' }, { code: 'CannotCreateTempFile' }, {