From b2f84385b1005824d79775c5542e375615ec7a9b Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Mon, 9 Apr 2018 17:32:03 -0400 Subject: [PATCH 1/2] [scopes] refactor mapScopes --- src/actions/pause/mapScopes.js | 398 +----------------- .../mapScopes/buildGeneratedBindingList.js | 87 ++++ .../pause/mapScopes/buildMappedScopes.js | 82 ++++ .../pause/mapScopes/findGeneratedBinding.js | 106 +++++ .../pause/mapScopes/generateClientScope.js | 68 +++ src/utils/pause/mapScopes/index.js | 5 + src/utils/pause/mapScopes/isReliableScope.js | 27 ++ src/utils/pause/mapScopes/types.js | 7 + 8 files changed, 402 insertions(+), 378 deletions(-) create mode 100644 src/utils/pause/mapScopes/buildGeneratedBindingList.js create mode 100644 src/utils/pause/mapScopes/buildMappedScopes.js create mode 100644 src/utils/pause/mapScopes/findGeneratedBinding.js create mode 100644 src/utils/pause/mapScopes/generateClientScope.js create mode 100644 src/utils/pause/mapScopes/index.js create mode 100644 src/utils/pause/mapScopes/isReliableScope.js create mode 100644 src/utils/pause/mapScopes/types.js diff --git a/src/actions/pause/mapScopes.js b/src/actions/pause/mapScopes.js index d2ce560cb5..6c03408bf5 100644 --- a/src/actions/pause/mapScopes.js +++ b/src/actions/pause/mapScopes.js @@ -4,7 +4,6 @@ // @flow -import { has } from "lodash"; import { getSource } from "../../selectors"; import { loadSourceText } from "../sources/loadSourceText"; import { @@ -15,10 +14,7 @@ import { } from "../../workers/parser"; import type { RenderableScope } from "../../utils/pause/scopes/getScope"; import { PROMISE } from "../utils/middleware/promise"; -import { locColumn } from "../../utils/pause/mapScopes/locColumn"; - -// eslint-disable-next-line max-len -import { findGeneratedBindingFromPosition } from "../../utils/pause/mapScopes/findGeneratedBindingFromPosition"; +import { locColumn, buildMappedScopes } from "../../utils/pause/mapScopes"; import { features } from "../../utils/prefs"; import { log } from "../../utils/log"; @@ -35,12 +31,6 @@ import type { ThunkArgs } from "../types"; export type OriginalScope = RenderableScope; -export type GeneratedBindingLocation = { - name: string, - loc: BindingLocation, - desc: BindingContents | null -}; - export function mapScopes(scopes: Promise, frame: Frame) { return async function({ dispatch, getState, client, sourceMaps }: ThunkArgs) { const generatedSourceRecord = getSource( @@ -56,378 +46,30 @@ export function mapScopes(scopes: Promise, frame: Frame) { !sourceRecord.isPrettyPrinted && !isGeneratedId(frame.location.sourceId); - await dispatch({ - type: "MAP_SCOPES", - frame, - [PROMISE]: (async function() { - if (!shouldMapScopes) { - return null; - } - - await dispatch(loadSourceText(sourceRecord)); - - try { - return await buildMappedScopes( - sourceRecord.toJS(), - frame, - await scopes, - sourceMaps, - client - ); - } catch (e) { - log(e); - return null; - } - })() - }); - }; -} - -async function buildMappedScopes( - source: Source, - frame: Frame, - scopes: Scope, - sourceMaps: any, - client: any -): Promise { - const originalAstScopes = await getScopes(frame.location); - const generatedAstScopes = await getScopes(frame.generatedLocation); - - if (!originalAstScopes || !generatedAstScopes) { - return null; - } - - const generatedAstBindings = buildGeneratedBindingList( - scopes, - generatedAstScopes, - frame.this - ); - - const expressionLookup = {}; - const mappedOriginalScopes = []; - - for (const item of originalAstScopes) { - const generatedBindings = {}; - - for (const name of Object.keys(item.bindings)) { - const binding = item.bindings[name]; - - const result = await findGeneratedBinding( - sourceMaps, - client, - source, - name, - binding, - generatedAstBindings - ); - - if (result) { - generatedBindings[name] = result.grip; - - if ( - binding.refs.length !== 0 && - // These are assigned depth-first, so we don't want shadowed - // bindings in parent scopes overwriting the expression. - !Object.prototype.hasOwnProperty.call(expressionLookup, name) - ) { - expressionLookup[name] = result.expression; - } - } - } - - mappedOriginalScopes.push({ - ...item, - generatedBindings - }); - } - - const mappedGeneratedScopes = generateClientScope( - scopes, - mappedOriginalScopes - ); - - return isReliableScope(mappedGeneratedScopes) - ? { mappings: expressionLookup, scope: mappedGeneratedScopes } - : null; -} - -/** - * Consider a scope and its parents reliable if the vast majority of its - * bindings were successfully mapped to generated scope bindings. - */ -function isReliableScope(scope: OriginalScope): boolean { - let totalBindings = 0; - let unknownBindings = 0; - - for (let s = scope; s; s = s.parent) { - const vars = (s.bindings && s.bindings.variables) || {}; - for (const key of Object.keys(vars)) { - const binding = vars[key]; - - totalBindings += 1; - if ( - binding.value && - typeof binding.value === "object" && - (binding.value.type === "unscoped" || binding.value.type === "unmapped") - ) { - unknownBindings += 1; - } - } - } - - // As determined by fair dice roll. - return totalBindings === 0 || unknownBindings / totalBindings < 0.9; -} - -function generateClientScope( - scopes: Scope, - originalScopes: Array -): OriginalScope { - // Pull the root object scope and root lexical scope to reuse them in - // our mapped scopes. This assumes that file file being processed is - // a CommonJS or ES6 module, which might not be ideal. Potentially - // should add some logic to try to detect those cases? - let globalLexicalScope: ?OriginalScope = null; - for (let s = scopes; s.parent; s = s.parent) { - // $FlowIgnore - Flow doesn't like casting 'parent'. - globalLexicalScope = s; - } - if (!globalLexicalScope) { - throw new Error("Assertion failure - there should always be a scope"); - } - - // Build a structure similar to the client's linked scope object using - // the original AST scopes, but pulling in the generated bindings - // linked to each scope. - const result = originalScopes - .slice(0, -2) - .reverse() - .reduce((acc, orig, i): OriginalScope => { - const { - // The 'this' binding data we have is handled independently, so - // the binding data is not included here. - // eslint-disable-next-line no-unused-vars - this: _this, - ...variables - } = orig.generatedBindings; - - return { - // Flow doesn't like casting 'parent'. - parent: (acc: any), - actor: `originalActor${i}`, - type: orig.type, - bindings: { - arguments: [], - variables - }, - ...(orig.type === "function" - ? { - function: { - displayName: orig.displayName - } - } - : null), - ...(orig.type === "block" - ? { - block: { - displayName: orig.displayName - } - } - : null) - }; - }, globalLexicalScope); - - // The rendering logic in getScope 'this' bindings only runs on the current - // selected frame scope, so we pluck out the 'this' binding that was mapped, - // and put it in a special location - const thisScope = originalScopes.find(scope => scope.bindings.this); - if (thisScope) { - result.bindings.this = thisScope.generatedBindings.this || null; - } - - return result; -} - -async function findGeneratedBinding( - sourceMaps: any, - client: any, - source: Source, - name: string, - originalBinding: BindingData, - generatedAstBindings: Array -): Promise { - // If there are no references to the implicits, then we have no way to - // even attempt to map it back to the original since there is no location - // data to use. Bail out instead of just showing it as unmapped. - if ( - originalBinding.type === "implicit" && - !originalBinding.refs.some(item => item.type === "ref") - ) { - return null; - } - - const { refs } = originalBinding; - - const genContent = await refs.reduce(async (acc, pos) => { - const result = await acc; - if (result) { - return result; + if (!shouldMapScopes) { + return null; } - return await findGeneratedBindingFromPosition( - sourceMaps, - client, - source, - pos, - name, - originalBinding.type, - generatedAstBindings - ); - }, null); + await dispatch(loadSourceText(sourceRecord)); + const originalAstScopes = await getScopes(frame.location); + const generatedAstScopes = await getScopes(frame.generatedLocation); - if (genContent && genContent.desc) { - return { - grip: genContent.desc, - expression: genContent.expression - }; - } else if (genContent) { - // If there is no descriptor for 'this', then this is not the top-level - // 'this' that the server gave us a binding for, and we can just ignore it. - if (name === "this") { + if (!originalAstScopes || !generatedAstScopes) { return null; } - // If the location is found but the descriptor is not, then it - // means that the server scope information didn't match the scope - // information from the DevTools parsed scopes. - return { - grip: { - configurable: false, - enumerable: true, - writable: false, - value: { - type: "unscoped", - unscoped: true, - - // HACK: Until support for "unscoped" lands in devtools-reps, - // this will make these show as (unavailable). - missingArguments: true - } - }, - expression: null - }; - } - - // If no location mapping is found, then the map is bad, or - // the map is okay but it original location is inside - // of some scope, but the generated location is outside, leading - // us to search for bindings that don't technically exist. - return { - grip: { - configurable: false, - enumerable: true, - writable: false, - value: { - type: "unmapped", - unmapped: true, - - // HACK: Until support for "unmapped" lands in devtools-reps, - // this will make these show as (unavailable). - missingArguments: true - } - }, - expression: null + dispatch({ + type: "MAP_SCOPES", + frame, + [PROMISE]: buildMappedScopes( + sourceRecord.toJS(), + frame, + originalAstScopes, + generatedAstScopes, + await scopes, + sourceMaps, + client + ) + }); }; } - -function buildGeneratedBindingList( - scopes: Scope, - generatedAstScopes: SourceScope[], - thisBinding: ?BindingContents -): Array { - // The server's binding data doesn't include general 'this' binding - // information, so we manually inject the one 'this' binding we have into - // the normal binding data we are working with. - const frameThisOwner = generatedAstScopes.find( - generated => "this" in generated.bindings - ); - - const clientScopes = []; - for (let s = scopes; s; s = s.parent) { - const bindings = s.bindings - ? Object.assign({}, ...s.bindings.arguments, s.bindings.variables) - : {}; - - clientScopes.push(bindings); - } - - const generatedMainScopes = generatedAstScopes.slice(0, -2); - const generatedGlobalScopes = generatedAstScopes.slice(-2); - - const clientMainScopes = clientScopes.slice(0, generatedMainScopes.length); - const clientGlobalScopes = clientScopes.slice(generatedMainScopes.length); - - // Map the main parsed script body using the nesting hierarchy of the - // generated and client scopes. - const generatedBindings = generatedMainScopes.reduce((acc, generated, i) => { - const bindings = clientMainScopes[i]; - - if (generated === frameThisOwner && thisBinding) { - bindings.this = { - value: thisBinding - }; - } - - for (const name of Object.keys(generated.bindings)) { - const { refs } = generated.bindings[name]; - for (const loc of refs) { - acc.push({ - name, - loc, - desc: bindings[name] || null - }); - } - } - return acc; - }, []); - - // Bindings in the global/lexical global of the generated code may or - // may not be the real global if the generated code is running inside - // of an evaled context. To handle this, we just look up the client scope - // hierarchy to find the closest binding with that name. - for (const generated of generatedGlobalScopes) { - for (const name of Object.keys(generated.bindings)) { - const { refs } = generated.bindings[name]; - for (const loc of refs) { - const bindings = clientGlobalScopes.find(b => has(b, name)); - - if (bindings) { - generatedBindings.push({ - name, - loc, - desc: bindings[name] - }); - } - } - } - } - - // Sort so we can binary-search. - return generatedBindings.sort((a, b) => { - const aStart = a.loc.start; - const bStart = a.loc.start; - - if (aStart.line === bStart.line) { - return locColumn(aStart) - locColumn(bStart); - } - return aStart.line - bStart.line; - }); -} diff --git a/src/utils/pause/mapScopes/buildGeneratedBindingList.js b/src/utils/pause/mapScopes/buildGeneratedBindingList.js new file mode 100644 index 0000000000..23ec6bc712 --- /dev/null +++ b/src/utils/pause/mapScopes/buildGeneratedBindingList.js @@ -0,0 +1,87 @@ +// @flow +import { has } from "lodash"; +import { locColumn } from "./locColumn"; + +export function buildGeneratedBindingList( + scopes: Scope, + generatedAstScopes: SourceScope[], + thisBinding: ?BindingContents +): Array { + // The server's binding data doesn't include general 'this' binding + // information, so we manually inject the one 'this' binding we have into + // the normal binding data we are working with. + const frameThisOwner = generatedAstScopes.find( + generated => "this" in generated.bindings + ); + + const clientScopes = []; + for (let s = scopes; s; s = s.parent) { + const bindings = s.bindings + ? Object.assign({}, ...s.bindings.arguments, s.bindings.variables) + : {}; + + clientScopes.push(bindings); + } + + const generatedMainScopes = generatedAstScopes.slice(0, -2); + const generatedGlobalScopes = generatedAstScopes.slice(-2); + + const clientMainScopes = clientScopes.slice(0, generatedMainScopes.length); + const clientGlobalScopes = clientScopes.slice(generatedMainScopes.length); + + // Map the main parsed script body using the nesting hierarchy of the + // generated and client scopes. + const generatedBindings = generatedMainScopes.reduce((acc, generated, i) => { + const bindings = clientMainScopes[i]; + + if (generated === frameThisOwner && thisBinding) { + bindings.this = { + value: thisBinding + }; + } + + for (const name of Object.keys(generated.bindings)) { + const { refs } = generated.bindings[name]; + for (const loc of refs) { + acc.push({ + name, + loc, + desc: bindings[name] || null + }); + } + } + return acc; + }, []); + + // Bindings in the global/lexical global of the generated code may or + // may not be the real global if the generated code is running inside + // of an evaled context. To handle this, we just look up the client scope + // hierarchy to find the closest binding with that name. + for (const generated of generatedGlobalScopes) { + for (const name of Object.keys(generated.bindings)) { + const { refs } = generated.bindings[name]; + for (const loc of refs) { + const bindings = clientGlobalScopes.find(b => has(b, name)); + + if (bindings) { + generatedBindings.push({ + name, + loc, + desc: bindings[name] + }); + } + } + } + } + + // Sort so we can binary-search. + return generatedBindings.sort((a, b) => { + const aStart = a.loc.start; + const bStart = a.loc.start; + + if (aStart.line === bStart.line) { + return locColumn(aStart) - locColumn(bStart); + } + return aStart.line - bStart.line; + }); +} diff --git a/src/utils/pause/mapScopes/buildMappedScopes.js b/src/utils/pause/mapScopes/buildMappedScopes.js new file mode 100644 index 0000000000..d21dbb288b --- /dev/null +++ b/src/utils/pause/mapScopes/buildMappedScopes.js @@ -0,0 +1,82 @@ +// @flow + +import { isReliableScope } from "./isReliableScope"; +import { buildGeneratedBindingList } from "./buildGeneratedBindingList"; +import { findGeneratedBinding } from "./findGeneratedBinding"; +import { generateClientScope } from "./generateClientScope"; + +import type { + Frame, + Scope, + Source, + BindingContents, + ScopeBindings +} from "../../../types"; + +export async function buildMappedScopes( + source: Source, + frame: Frame, + originalAstScopes: Scope, + generatedAstScopes: Scope, + scopes: Scope, + sourceMaps: any, + client: any +): Promise { + const generatedAstBindings = buildGeneratedBindingList( + scopes, + generatedAstScopes, + frame.this + ); + + const expressionLookup = {}; + const mappedOriginalScopes = []; + + for (const item of originalAstScopes) { + const generatedBindings = {}; + + for (const name of Object.keys(item.bindings)) { + const binding = item.bindings[name]; + + const result = await findGeneratedBinding( + sourceMaps, + client, + source, + name, + binding, + generatedAstBindings + ); + + if (result) { + generatedBindings[name] = result.grip; + + if ( + binding.refs.length !== 0 && + // These are assigned depth-first, so we don't want shadowed + // bindings in parent scopes overwriting the expression. + !Object.prototype.hasOwnProperty.call(expressionLookup, name) + ) { + expressionLookup[name] = result.expression; + } + } + } + + mappedOriginalScopes.push({ + ...item, + generatedBindings + }); + } + + const mappedGeneratedScopes = generateClientScope( + scopes, + mappedOriginalScopes + ); + + return isReliableScope(mappedGeneratedScopes) + ? { mappings: expressionLookup, scope: mappedGeneratedScopes } + : null; +} diff --git a/src/utils/pause/mapScopes/findGeneratedBinding.js b/src/utils/pause/mapScopes/findGeneratedBinding.js new file mode 100644 index 0000000000..e80a043a8a --- /dev/null +++ b/src/utils/pause/mapScopes/findGeneratedBinding.js @@ -0,0 +1,106 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at . */ + +// @flow + +// eslint-disable-next-line max-len +import { findGeneratedBindingFromPosition } from "./findGeneratedBindingFromPosition"; + +import type { GeneratedBindingLocation } from "./types"; +import type { BindingData } from "../../workers/parser"; + +export async function findGeneratedBinding( + sourceMaps: any, + client: any, + source: Source, + name: string, + originalBinding: BindingData, + generatedAstBindings: Array +): Promise { + // If there are no references to the implicits, then we have no way to + // even attempt to map it back to the original since there is no location + // data to use. Bail out instead of just showing it as unmapped. + if ( + originalBinding.type === "implicit" && + !originalBinding.refs.some(item => item.type === "ref") + ) { + return null; + } + + const { refs } = originalBinding; + + const genContent = await refs.reduce(async (acc, pos) => { + const result = await acc; + if (result) { + return result; + } + + return await findGeneratedBindingFromPosition( + sourceMaps, + client, + source, + pos, + name, + originalBinding.type, + generatedAstBindings + ); + }, null); + + if (genContent && genContent.desc) { + return { + grip: genContent.desc, + expression: genContent.expression + }; + } else if (genContent) { + // If there is no descriptor for 'this', then this is not the top-level + // 'this' that the server gave us a binding for, and we can just ignore it. + if (name === "this") { + return null; + } + + // If the location is found but the descriptor is not, then it + // means that the server scope information didn't match the scope + // information from the DevTools parsed scopes. + return { + grip: { + configurable: false, + enumerable: true, + writable: false, + value: { + type: "unscoped", + unscoped: true, + + // HACK: Until support for "unscoped" lands in devtools-reps, + // this will make these show as (unavailable). + missingArguments: true + } + }, + expression: null + }; + } + + // If no location mapping is found, then the map is bad, or + // the map is okay but it original location is inside + // of some scope, but the generated location is outside, leading + // us to search for bindings that don't technically exist. + return { + grip: { + configurable: false, + enumerable: true, + writable: false, + value: { + type: "unmapped", + unmapped: true, + + // HACK: Until support for "unmapped" lands in devtools-reps, + // this will make these show as (unavailable). + missingArguments: true + } + }, + expression: null + }; +} diff --git a/src/utils/pause/mapScopes/generateClientScope.js b/src/utils/pause/mapScopes/generateClientScope.js new file mode 100644 index 0000000000..20021fc0a6 --- /dev/null +++ b/src/utils/pause/mapScopes/generateClientScope.js @@ -0,0 +1,68 @@ +export function generateClientScope( + scopes: Scope, + originalScopes: Array +): OriginalScope { + // Pull the root object scope and root lexical scope to reuse them in + // our mapped scopes. This assumes that file file being processed is + // a CommonJS or ES6 module, which might not be ideal. Potentially + // should add some logic to try to detect those cases? + let globalLexicalScope: ?OriginalScope = null; + for (let s = scopes; s.parent; s = s.parent) { + // $FlowIgnore - Flow doesn't like casting 'parent'. + globalLexicalScope = s; + } + if (!globalLexicalScope) { + throw new Error("Assertion failure - there should always be a scope"); + } + + // Build a structure similar to the client's linked scope object using + // the original AST scopes, but pulling in the generated bindings + // linked to each scope. + const result = originalScopes + .slice(0, -2) + .reverse() + .reduce((acc, orig, i): OriginalScope => { + const { + // The 'this' binding data we have is handled independently, so + // the binding data is not included here. + // eslint-disable-next-line no-unused-vars + this: _this, + ...variables + } = orig.generatedBindings; + + return { + // Flow doesn't like casting 'parent'. + parent: (acc: any), + actor: `originalActor${i}`, + type: orig.type, + bindings: { + arguments: [], + variables + }, + ...(orig.type === "function" + ? { + function: { + displayName: orig.displayName + } + } + : null), + ...(orig.type === "block" + ? { + block: { + displayName: orig.displayName + } + } + : null) + }; + }, globalLexicalScope); + + // The rendering logic in getScope 'this' bindings only runs on the current + // selected frame scope, so we pluck out the 'this' binding that was mapped, + // and put it in a special location + const thisScope = originalScopes.find(scope => scope.bindings.this); + if (thisScope) { + result.bindings.this = thisScope.generatedBindings.this || null; + } + + return result; +} diff --git a/src/utils/pause/mapScopes/index.js b/src/utils/pause/mapScopes/index.js new file mode 100644 index 0000000000..b39cf9ac9a --- /dev/null +++ b/src/utils/pause/mapScopes/index.js @@ -0,0 +1,5 @@ +// @flow + +export * from "./findGeneratedBinding"; +export * from "./locColumn"; +export * from "./buildMappedScopes"; diff --git a/src/utils/pause/mapScopes/isReliableScope.js b/src/utils/pause/mapScopes/isReliableScope.js new file mode 100644 index 0000000000..0318abd867 --- /dev/null +++ b/src/utils/pause/mapScopes/isReliableScope.js @@ -0,0 +1,27 @@ +/** + * Consider a scope and its parents reliable if the vast majority of its + * bindings were successfully mapped to generated scope bindings. + */ +export function isReliableScope(scope: OriginalScope): boolean { + let totalBindings = 0; + let unknownBindings = 0; + + for (let s = scope; s; s = s.parent) { + const vars = (s.bindings && s.bindings.variables) || {}; + for (const key of Object.keys(vars)) { + const binding = vars[key]; + + totalBindings += 1; + if ( + binding.value && + typeof binding.value === "object" && + (binding.value.type === "unscoped" || binding.value.type === "unmapped") + ) { + unknownBindings += 1; + } + } + } + + // As determined by fair dice roll. + return totalBindings === 0 || unknownBindings / totalBindings < 0.9; +} diff --git a/src/utils/pause/mapScopes/types.js b/src/utils/pause/mapScopes/types.js new file mode 100644 index 0000000000..7a447f9f85 --- /dev/null +++ b/src/utils/pause/mapScopes/types.js @@ -0,0 +1,7 @@ +// @flow + +export type GeneratedBindingLocation = { + name: string, + loc: BindingLocation, + desc: BindingContents | null +}; From 3c86ad43da5781e488460702c5f8912d2aeff65a Mon Sep 17 00:00:00 2001 From: Jason Laster Date: Mon, 9 Apr 2018 19:21:20 -0400 Subject: [PATCH 2/2] batch getting generated locations --- .../pause/mapScopes/buildMappedScopes.js | 34 ++++++++++++++++-- .../pause/mapScopes/findGeneratedBinding.js | 8 ++--- .../findGeneratedBindingFromPosition.js | 35 +++++++++++-------- 3 files changed, 55 insertions(+), 22 deletions(-) diff --git a/src/utils/pause/mapScopes/buildMappedScopes.js b/src/utils/pause/mapScopes/buildMappedScopes.js index d21dbb288b..0cc8e05e3c 100644 --- a/src/utils/pause/mapScopes/buildMappedScopes.js +++ b/src/utils/pause/mapScopes/buildMappedScopes.js @@ -4,7 +4,7 @@ import { isReliableScope } from "./isReliableScope"; import { buildGeneratedBindingList } from "./buildGeneratedBindingList"; import { findGeneratedBinding } from "./findGeneratedBinding"; import { generateClientScope } from "./generateClientScope"; - +import { flatMap, uniqBy } from "lodash"; import type { Frame, Scope, @@ -36,6 +36,12 @@ export async function buildMappedScopes( const expressionLookup = {}; const mappedOriginalScopes = []; + const generatedLocations = await getGeneratedPositions( + originalAstScopes, + source, + sourceMaps + ); + for (const item of originalAstScopes) { const generatedBindings = {}; @@ -43,9 +49,8 @@ export async function buildMappedScopes( const binding = item.bindings[name]; const result = await findGeneratedBinding( - sourceMaps, + generatedLocations, client, - source, name, binding, generatedAstBindings @@ -80,3 +85,26 @@ export async function buildMappedScopes( ? { mappings: expressionLookup, scope: mappedGeneratedScopes } : null; } + +async function getGeneratedPositions(originalAstScopes, source, sourceMaps) { + const scopes = Object.values(originalAstScopes); + const bindings = flatMap(scopes, ({ bindings }) => Object.values(bindings)); + const refs = flatMap(bindings, ({ refs }) => refs); + + const locations = refs.reduce((positions, ref) => { + positions.push(ref.start); + positions.push(ref.end); + if (ref.declaration) { + positions.push(ref.declaration.start); + positions.push(ref.declaration.end); + } + return positions; + }, []); + + const originalLocations = uniqBy(locations, loc => + Object.values(loc).join("-") + ); + + let sourcesMap = { [source.id]: source.url }; + return sourceMaps.batchGeneratedLocations(originalLocations, sourcesMap); +} diff --git a/src/utils/pause/mapScopes/findGeneratedBinding.js b/src/utils/pause/mapScopes/findGeneratedBinding.js index e80a043a8a..97d746d042 100644 --- a/src/utils/pause/mapScopes/findGeneratedBinding.js +++ b/src/utils/pause/mapScopes/findGeneratedBinding.js @@ -10,10 +10,9 @@ import { findGeneratedBindingFromPosition } from "./findGeneratedBindingFromPosi import type { GeneratedBindingLocation } from "./types"; import type { BindingData } from "../../workers/parser"; -export async function findGeneratedBinding( - sourceMaps: any, +export async function i( + generatedLocations: any, client: any, - source: Source, name: string, originalBinding: BindingData, generatedAstBindings: Array @@ -40,9 +39,8 @@ export async function findGeneratedBinding( } return await findGeneratedBindingFromPosition( - sourceMaps, + generatedLocations, client, - source, pos, name, originalBinding.type, diff --git a/src/utils/pause/mapScopes/findGeneratedBindingFromPosition.js b/src/utils/pause/mapScopes/findGeneratedBindingFromPosition.js index 5a765a347a..a289104aec 100644 --- a/src/utils/pause/mapScopes/findGeneratedBindingFromPosition.js +++ b/src/utils/pause/mapScopes/findGeneratedBindingFromPosition.js @@ -25,15 +25,14 @@ type GeneratedDescriptor = { }; export async function findGeneratedBindingFromPosition( - sourceMaps: any, + generatedLocations: any, client: any, - source: Source, pos: BindingLocation, name: string, type: BindingType, generatedAstBindings: Array ): Promise { - const range = await getGeneratedLocationRange(pos, source, sourceMaps); + const range = getGeneratedLocationRange(pos, generatedLocations); if (range) { const result = await findGeneratedReference(type, generatedAstBindings, { @@ -52,10 +51,9 @@ export async function findGeneratedBindingFromPosition( // If the imported name itself does not map to a useful range, fall back // to resolving the bindinding using the location of the overall // import declaration. - importRange = await getGeneratedLocationRange( + importRange = getGeneratedLocationRange( pos.declaration, - source, - sourceMaps + generatedLocations ); if (!importRange) { @@ -99,7 +97,7 @@ async function findGeneratedReference( return type === "import" ? await mapImportReferenceToDescriptor(val, mapped) - : await mapBindingReferenceToDescriptor(val, mapped); + : mapBindingReferenceToDescriptor(val, mapped); }, null); } @@ -130,7 +128,7 @@ async function findGeneratedImportDeclaration( * Given a generated binding, and a range over the generated code, statically * check if the given binding matches the range. */ -async function mapBindingReferenceToDescriptor( +function mapBindingReferenceToDescriptor( binding: GeneratedBindingLocation, mapped: { type: BindingLocationType, @@ -341,22 +339,31 @@ function mappingContains(mapped, item) { ); } -async function getGeneratedLocationRange( +function getGeneratedLocation(pos, generatedLocations) { + const { sourceId, line, column } = pos; + return generatedLocations[sourceId][line][column]; +} + +function getGeneratedLocationRange( pos: { start: Location, end: Location }, - source: Source, - sourceMaps: any + generatedLocations: any ): Promise<{ start: Location, end: Location } | null> { - const start = await sourceMaps.getGeneratedLocation(pos.start, source); - const end = await sourceMaps.getGeneratedLocation(pos.end, source); + // console.log(`> getting locations ${pos.start.line} ${pos.end.line}`); + const start = getGeneratedLocation(pos.start, generatedLocations); + const end = getGeneratedLocation(pos.end, generatedLocations); // Since the map takes the closest location, sometimes mapping a // binding's location can point at the start of a binding listed after // it, so we need to make sure it maps to a location that actually has // a size in order to avoid picking up the wrong descriptor. - if (isEqual(start, end)) { + if ( + start.column == end.column && + start.line == end.line && + start.sourceId == end.sourceId + ) { return null; }