Skip to content
This repository was archived by the owner on Jan 11, 2023. It is now read-only.

More advanced original source scopes mapping.#4521

Merged
jasonLaster merged 2 commits intofirefox-devtools:masterfrom
yurydelendik:advanced-scopes
Nov 17, 2017
Merged

More advanced original source scopes mapping.#4521
jasonLaster merged 2 commits intofirefox-devtools:masterfrom
yurydelendik:advanced-scopes

Conversation

@yurydelendik
Copy link
Contributor

Associated Issue: Display synthesized scopes (e.g. ES6) instead of transpiled

Test case:

  1. Open https://yurydelendik.github.io/old-man-sandbox/sorted-es6/test.html in debugger
  2. Set breakpoint at webpack/sorted.js line 9 (return na < nb ? -1 : na > nb ? 1 : 0;)
  3. Reload the page

@yurydelendik yurydelendik force-pushed the advanced-scopes branch 2 times, most recently from 3a11f5c to 69d857a Compare October 27, 2017 23:42
Copy link
Contributor

@jasonLaster jasonLaster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The next step is to add some unit tests, which will help us refactor more confidently and not regress w/ this complicated logic.

I think that these utils might be happier in the mappings library where they can take advantage of the other utils to get good input: i.e.

describe("synthesizeScopes", () => {

  it("basic", () => {
     const fixture = getFixture("sum")
     const mappings ..
     const scopes ...

    // we'll need to fake the frame and pause packets, but we don't have a good mechanism to do that in dbg.html either

    expect(synthesizeScopes(scope, frame, pause)).toMatchSnapshot();
  })

})

@yurydelendik yurydelendik force-pushed the advanced-scopes branch 4 times, most recently from 2d67e7a to f89a920 Compare November 1, 2017 22:22
@yurydelendik yurydelendik changed the title [WIP] More advanced original source scopes mapping. More advanced original source scopes mapping. Nov 1, 2017
Copy link
Contributor

@jasonLaster jasonLaster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left some style comments…


await dispatch(loadSourceText(generatedSource));
originalSources.forEach(async source => {
await dispatch(loadSourceText(source));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this necessary? It could be a very expensive operation

: L10N.getStr("anonymous");
} else {
title = L10N.getStr("scopes.block");
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

perhaps a small helper for getting the scope title

function getScopeTitle(...) {
  if (type === "function") {
    // FIXME Use original function name here
    const lastGeneratedScope = generatedScopes[generatedScopes.length - 1];
    const isLastGeneratedScopeFn =
      lastGeneratedScope && lastGeneratedScope.type === "function";
    title =
      isLastGeneratedScopeFn && lastGeneratedScope.function.displayName
        ? simplifyDisplayName(lastGeneratedScope.function.displayName)
        : L10N.getStr("anonymous");
  } else {
    title = L10N.getStr("scopes.block");
  }
}

scopeIndex,
lastScopeIndex: syntheticScopes.scopes.length - 1,
generatedScopes,
foundGeneratedNames: Object.create(null),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style nit: foundGeneratedNames: {}

path: `${key}/${generatedName}`,
contents: { value: { type: "undefined" } }
});
});
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we move this to a small helper:

function findOriginalBindings(...) {
   let vars = [];
   bindingsNames.forEach(name => {
     // Find binding name in the original source bindings
     const generatedScope = generatedScopes.find(
       gs => gs.sourceBindings && name in gs.sourceBindings
     );
     if (!generatedScope || !generatedScope.sourceBindings) {
       return;
     }
     // .. and map it to the generated name
     const generatedName = generatedScope.sourceBindings[name];
     // Skip if we already use the generated name
     if (generatedName && !foundGeneratedNames[generatedName]) {
       if (generatedScope.bindings.variables[generatedName]) {
         vars.push({
           name,
           generatedName,
           path: `${key}/${generatedName}`,
           contents: generatedScope.bindings.variables[generatedName]
         });
         foundGeneratedNames[generatedName] = true;
         return;
       }
 
       const arg = generatedScope.bindings.arguments.find(
         arg_ => arg_[generatedName]
       );
       if (arg) {
         vars.push({
           name,
           generatedName,
           path: `${key}/${generatedName}`,
           contents: arg[generatedName]
         });
         foundGeneratedNames[generatedName] = true;
         return;
       }
     }
 
     vars.push({
       name,
       generatedName,
       path: `${key}/${generatedName}`,
       contents: { value: { type: "undefined" } }
     });
   });
}

});
}
return acc;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

synthesize scope is complicated enough where it takes some time to see what it is trying to do. I think it would be nice to extract the "real" work so that the code flow is more readable.

function synthesizeScope(
   acc: SynthesizeScopeContext,
   syntheticScope: SyntheticScope,
   index: number
  ): SynthesizeScopeContext {

  let bindings = findOriginalBindings(...)
  
  if (isLast) {
    bindings = [...bindings, ...findUnusedBindings(...)]
  }
  
  if (isFirst) {
    bindings = [...bindings, ...getSpecialVariables(...)]
    // ...
  }
  
  if (bindings.length > 0) {
    const title = getScopeTitle(...);
    result.push({
      name: title,
      path: key,
      contents: vars
    });
  }

  return acc
}

s = s.parent;
}

const { result } = syntheticScopes.scopes.reduce(synthesizeScope, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the fact that synthesizeScope is mutating makes synthesize a bit more complicated... perhaps, we could have it return the bindings...

  const { result } = syntheticScopes.scopes.reduce((bloby, syntheticScope, index) => {
    const bindings = getBindingsFromScope(bloby,..) 
    // it's tempting to update foundGeneratedNames here too so that `getBindingsFromScope` is pure, 
    // but not sure if that is needed... 
    if (bindings.length) {
      const title = getScopeTitle(...);
      blob.result.push({
        name: title,
        path: key,
        contents: vars
      });
    }
  }, blobOStuff)

});
}
return acc;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be nice to create the synthetic scope bindings in the map-bindings space. This would be closer to the data structure we'd get from source-maps

  • we can add the special vars in the debugger side

@yurydelendik yurydelendik force-pushed the advanced-scopes branch 4 times, most recently from 4ebcba5 to 0578470 Compare November 8, 2017 22:57
Copy link
Contributor

@jasonLaster jasonLaster left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is looking really good

return generatedScopes;
},
async getOriginalSourceScopes(location) {
return getScopes(location);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets first load the source here...

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How can we track if we already loaded the source?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can call await loadSourceText and if it is loaded it will come back quickly

);

if (sourceRecord.get("isWasm")) {
if (generatedSourceRecord.get("isWasm")) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we move this down below add_scopes. can we move this to a different PR? b/c wasm is different and scary for some :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ryanjduffy
Copy link
Contributor

ryanjduffy commented Nov 10, 2017

I ran a quick test with this and wanted to verify what I'm seeing.

ES6 Source:

function test () {
	let a = 1;
	if (a) {
	  const b = 2;
	}
}

document.addEventListener('click', test);

Transpiled source:

'use strict';

function test() {
	var a = 1;
	if (a) {
		var b = 2;
	}
}

document.addEventListener('click', test);

//# sourceMappingURL=scopes-compiled.js.map

When pausing on line 4, const b = 2;, both a and b show within the test scope and only <this> within the if block.

I expected <this> to be restricted to function scope and b to be within the block scope.

Thoughts?

@jasonLaster
Copy link
Contributor

jasonLaster commented Nov 10, 2017

@ryanjduffy could you "remix" the dbg example: http://dbg.glitch.me/ or... if it is using babel, publish the example somewhere else...

maybe devtools-examples.

@yurydelendik
Copy link
Contributor Author

maybe devtools-examples.

I would rather to see it as a complete example online with source maps. I wonder if babel spits out names.

@jasonLaster
Copy link
Contributor

i'll look for a codepen that has babel

@yurydelendik
Copy link
Contributor Author

yurydelendik commented Nov 10, 2017

I expected to be restricted to function scope and b to be within the block scope.

Based on debugger.html logic at https://github.com/devtools-html/debugger.html/blob/60c59811df6e82ab7831d8faab4cee23ec48bd63/src/utils/scopes.js#L173-L179 we place <this> on the selected scope.

@ryanjduffy
Copy link
Contributor

Here's a live version of the sample: https://ryanjduffy.github.io/debugger-examples/scopes/

@yurydelendik
Copy link
Contributor Author

(The devtools/client/shared/source-map/worker.js needs to be updated to address the issue above, works in debugger.html now)

@yurydelendik
Copy link
Contributor Author

Here's a live version of the sample: https://ryanjduffy.github.io/debugger-examples/scopes/

@ryanjduffy the names section is empty at https://ryanjduffy.github.io/debugger-examples/scopes/scopes-compiled.js.map , what did you use to generate the .map file?

@ryanjduffy
Copy link
Contributor

Babel cli

return L10N.getStr("scopes.block");
}

export function translateScope(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does the name translate mean anything special?

would getScope, which is parallel to getScopes work?

@jasonLaster jasonLaster force-pushed the advanced-scopes branch 4 times, most recently from c89600c to 5ff01ad Compare November 17, 2017 22:41
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants