12
13
import {parse} from '@babel/parser';
14
import LRU from 'lru-cache';
15
-import {SourceMapConsumer} from 'source-map-js';
15
import {getHookName} from '../astUtils';
16
import {areSourceMapsAppliedToErrors} from '../ErrorTester';
17
import {__DEBUG__} from 'react-devtools-shared/src/constants';
21
withAsyncPerfMeasurements,
22
withSyncPerfMeasurements,
23
} from 'react-devtools-shared/src/PerformanceLoggingUtils';
24
+import SourceMapConsumer from '../SourceMapConsumer';
25
26
+import type {SourceMapConsumerType} from '../SourceMapConsumer';
27
import type {
28
HooksList,
29
LocationKeyToHookSourceAndMetadata,
30
} from './loadSourceAndMetadata';
31
import type {HookSource} from 'react-debug-tools/src/ReactDebugHooks';
32
import type {HookNames, LRUCache} from 'react-devtools-shared/src/types';
32
-import type {SourceConsumer} from '../astUtils';
33
34
type AST = mixed;
35
53
// Column number in original source code.
54
originalSourceColumnNumber: number | null,
55
56
- // APIs from source-map for parsing source maps (if detected).
57
- sourceConsumer: SourceConsumer | null,
56
+ // Alternate APIs from source-map for parsing source maps (if detected).
57
+ sourceMapConsumer: SourceMapConsumerType | null,
58
|};
59
60
type LocationKeyToHookParsedMetadata = Map<string, HookParsedMetadata>;
61
62
type CachedRuntimeCodeMetadata = {|
63
- sourceConsumer: SourceConsumer | null,
63
metadataConsumer: SourceMapMetadataConsumer | null,
64
+ sourceMapConsumer: SourceMapConsumerType | null,
65
|};
66
67
const runtimeURLToMetadataCache: LRUCache<
68
string,
69
CachedRuntimeCodeMetadata,
70
-> = new LRU({
71
- max: 50,
72
- dispose: (runtimeSourceURL: string, metadata: CachedRuntimeCodeMetadata) => {
73
- if (__DEBUG__) {
74
- console.log(
75
- `runtimeURLToMetadataCache.dispose() Evicting cached metadata for "${runtimeSourceURL}"`,
76
- );
77
- }
78
-
79
- const sourceConsumer = metadata.sourceConsumer;
80
- if (sourceConsumer !== null) {
81
- sourceConsumer.destroy();
82
- }
83
- },
84
-});
70
+> = new LRU({max: 50});
71
72
type CachedSourceCodeMetadata = {|
73
originalSourceAST: AST,
206
originalSourceURL: null,
207
originalSourceLineNumber: null,
208
originalSourceColumnNumber: null,
223
- sourceConsumer: null,
209
+ sourceMapConsumer: null,
210
};
211
212
locationKeyToHookParsedMetadata.set(locationKey, hookParsedMetadata);
227
-
228
- const runtimeSourceURL = hookSourceAndMetadata.runtimeSourceURL;
229
-
230
- // If we've already loaded the source map info for this file,
231
- // we can skip reloading it (and more importantly, re-parsing it).
232
- const runtimeMetadata = runtimeURLToMetadataCache.get(runtimeSourceURL);
233
- if (runtimeMetadata != null) {
234
- if (__DEBUG__) {
235
- console.groupCollapsed(
236
- `parseHookNames() Found cached runtime metadata for file "${runtimeSourceURL}"`,
237
- );
238
- console.log(runtimeMetadata);
239
- console.groupEnd();
240
- }
241
- hookParsedMetadata.sourceConsumer = runtimeMetadata.sourceConsumer;
242
- hookParsedMetadata.metadataConsumer = runtimeMetadata.metadataConsumer;
243
- }
213
},
214
);
215
249
throw Error('Hook source code location not found.');
250
}
251
283
- const {metadataConsumer, sourceConsumer} = hookParsedMetadata;
252
+ const {metadataConsumer, sourceMapConsumer} = hookParsedMetadata;
253
const runtimeSourceCode = ((hookSourceAndMetadata.runtimeSourceCode: any): string);
254
let hasHookMap = false;
255
let originalSourceURL;
256
let originalSourceCode;
257
let originalSourceColumnNumber;
258
let originalSourceLineNumber;
290
- if (areSourceMapsAppliedToErrors() || sourceConsumer == null) {
259
+ if (areSourceMapsAppliedToErrors() || sourceMapConsumer === null) {
260
// Either the current environment automatically applies source maps to errors,
261
// or the current code had no source map to begin with.
262
// Either way, we don't need to convert the Error stack frame locations.
268
// Namespace them?
269
originalSourceURL = hookSourceAndMetadata.runtimeSourceURL;
270
} else {
302
- // Parse and extract the AST from the source map.
303
- // Now that the source map has been loaded,
304
- // extract the original source for later.
305
- // TODO (named hooks) Refactor this read, github.com/facebook/react/pull/22181
306
- const {column, line, source} = withSyncPerfMeasurements(
307
- 'sourceConsumer.originalPositionFor()',
308
- () =>
309
- sourceConsumer.originalPositionFor({
310
- line: lineNumber,
311
-
312
- // Column numbers are represented differently between tools/engines.
313
- // Error.prototype.stack columns are 1-based (like most IDEs) but ASTs are 0-based.
314
- // For more info see https://github.com/facebook/react/issues/21792#issuecomment-873171991
315
- column: columnNumber - 1,
316
- }),
317
- );
318
-
319
- if (source == null) {
320
- // TODO (named hooks) maybe fall back to the runtime source instead of throwing?
321
- throw new Error(
322
- 'Could not map hook runtime location to original source location',
323
- );
324
- }
271
+ const {
272
+ column,
273
+ line,
274
+ sourceContent,
275
+ sourceURL,
276
+ } = sourceMapConsumer.originalPositionFor({
277
+ columnNumber,
278
+ lineNumber,
279
+ });
280
281
originalSourceColumnNumber = column;
282
originalSourceLineNumber = line;
328
- // TODO (named hooks) maybe canonicalize this URL somehow?
329
- // It can be relative if the source map specifies it that way,
330
- // but we use it as a cache key across different source maps and there can be collisions.
331
- originalSourceURL = (source: string);
332
- originalSourceCode = withSyncPerfMeasurements(
333
- 'sourceConsumer.sourceContentFor()',
334
- () => (sourceConsumer.sourceContentFor(source, true): string),
335
- );
283
+ originalSourceCode = sourceContent;
284
+ originalSourceURL = sourceURL;
285
+ }
286
337
- if (__DEBUG__) {
338
- console.groupCollapsed(
339
- `parseSourceAST() Extracted source code from source map for "${originalSourceURL}"`,
340
- );
341
- console.log(originalSourceCode);
342
- console.groupEnd();
343
- }
287
+ hookParsedMetadata.originalSourceCode = originalSourceCode;
288
+ hookParsedMetadata.originalSourceURL = originalSourceURL;
289
+ hookParsedMetadata.originalSourceLineNumber = originalSourceLineNumber;
290
+ hookParsedMetadata.originalSourceColumnNumber = originalSourceColumnNumber;
291
345
- if (
346
- metadataConsumer != null &&
347
- metadataConsumer.hasHookMap(originalSourceURL)
348
- ) {
349
- hasHookMap = true;
350
- }
292
+ if (
293
+ metadataConsumer != null &&
294
+ metadataConsumer.hasHookMap(originalSourceURL)
295
+ ) {
296
+ hasHookMap = true;
297
}
298
299
if (__DEBUG__) {
302
);
303
}
304
359
- hookParsedMetadata.originalSourceCode = originalSourceCode;
360
- hookParsedMetadata.originalSourceURL = originalSourceURL;
361
- hookParsedMetadata.originalSourceLineNumber = originalSourceLineNumber;
362
- hookParsedMetadata.originalSourceColumnNumber = originalSourceColumnNumber;
363
-
305
if (hasHookMap) {
306
if (__DEBUG__) {
307
console.log(
388
throw Error(`Expected to find HookParsedMetadata for "${locationKey}"`);
389
}
390
450
- const sourceMapJSON = hookSourceAndMetadata.sourceMapJSON;
451
- if (sourceMapJSON != null) {
452
- hookParsedMetadata.metadataConsumer = withSyncPerfMeasurements(
453
- 'new SourceMapMetadataConsumer(sourceMapJSON)',
454
- () => new SourceMapMetadataConsumer(sourceMapJSON),
455
- );
456
- hookParsedMetadata.sourceConsumer = withSyncPerfMeasurements(
457
- 'new SourceMapConsumer(sourceMapJSON)',
458
- () => new SourceMapConsumer(sourceMapJSON),
459
- );
391
+ const {runtimeSourceURL, sourceMapJSON} = hookSourceAndMetadata;
392
+
393
+ // If we've already loaded the source map info for this file,
394
+ // we can skip reloading it (and more importantly, re-parsing it).
395
+ const runtimeMetadata = runtimeURLToMetadataCache.get(runtimeSourceURL);
396
+ if (runtimeMetadata != null) {
397
+ if (__DEBUG__) {
398
+ console.groupCollapsed(
399
+ `parseHookNames() Found cached runtime metadata for file "${runtimeSourceURL}"`,
400
+ );
401
+ console.log(runtimeMetadata);
402
+ console.groupEnd();
403
+ }
404
461
- const runtimeSourceURL = hookSourceAndMetadata.runtimeSourceURL;
405
+ hookParsedMetadata.metadataConsumer = runtimeMetadata.metadataConsumer;
406
+ hookParsedMetadata.sourceMapConsumer =
407
+ runtimeMetadata.sourceMapConsumer;
408
+ } else {
409
+ if (sourceMapJSON != null) {
410
+ const sourceMapConsumer = withSyncPerfMeasurements(
411
+ 'new SourceMapConsumer(sourceMapJSON)',
412
+ () => SourceMapConsumer(sourceMapJSON),
413
+ );
414
463
- // Only set once to avoid triggering eviction/cleanup code.
464
- if (!runtimeURLToMetadataCache.has(runtimeSourceURL)) {
465
- if (__DEBUG__) {
466
- console.log(
467
- `parseSourceMaps() Caching runtime metadata for "${runtimeSourceURL}"`,
468
- );
469
- }
415
+ const metadataConsumer = withSyncPerfMeasurements(
416
+ 'new SourceMapMetadataConsumer(sourceMapJSON)',
417
+ () => new SourceMapMetadataConsumer(sourceMapJSON),
418
+ );
419
+
420
+ hookParsedMetadata.metadataConsumer = metadataConsumer;
421
+ hookParsedMetadata.sourceMapConsumer = sourceMapConsumer;
422
423
+ // Only set once to avoid triggering eviction/cleanup code.
424
runtimeURLToMetadataCache.set(runtimeSourceURL, {
472
- metadataConsumer: hookParsedMetadata.metadataConsumer,
473
- sourceConsumer: hookParsedMetadata.sourceConsumer,
425
+ metadataConsumer: metadataConsumer,
426
+ sourceMapConsumer: sourceMapConsumer,
427
});
428
}
429
}