Skip to content

Commit decf4d4

Browse files
aslushnikovCommit bot
authored and
Commit bot
committed
DevTools: improve identifier extraction in SourceMapNamesResolver
Apart from extracting identifier names from SourceMap.name entries, this patch attempts to extract variable names from a mapped source. BUG=none R=dgozman, pfeldman_ooo Review URL: https://codereview.chromium.org/1887913002 Cr-Commit-Position: refs/heads/master@{#387533}
1 parent e2ca0ad commit decf4d4

File tree

6 files changed

+180
-64
lines changed

6 files changed

+180
-64
lines changed

Diff for: front_end/common/Text.js

+10
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,16 @@ WebInspector.TextCursor.prototype = {
128128
return this._offset;
129129
},
130130

131+
/**
132+
* @param {number} offset
133+
*/
134+
resetTo: function(offset)
135+
{
136+
this._offset = offset;
137+
this._lineNumber = this._lineEndings.lowerBound(offset);
138+
this._columnNumber = this._lineNumber ? this._offset - this._lineEndings[this._lineNumber - 1] - 1 : this._offset;
139+
},
140+
131141
/**
132142
* @return {number}
133143
*/

Diff for: front_end/es_tree/ESTreeWalker.js

+8-3
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,16 @@
55
/**
66
* @constructor
77
* @param {function(!ESTree.Node)} beforeVisit
8-
* @param {function(!ESTree.Node)} afterVisit
8+
* @param {function(!ESTree.Node)=} afterVisit
99
*/
1010
WebInspector.ESTreeWalker = function(beforeVisit, afterVisit)
1111
{
1212
this._beforeVisit = beforeVisit;
13-
this._afterVisit = afterVisit;
13+
this._afterVisit = afterVisit || new Function();
1414
}
1515

16+
WebInspector.ESTreeWalker.SkipSubtree = {};
17+
1618
WebInspector.ESTreeWalker.prototype = {
1719
/**
1820
* @param {!ESTree.Node} ast
@@ -32,7 +34,10 @@ WebInspector.ESTreeWalker.prototype = {
3234
return;
3335
node.parent = parent;
3436

35-
this._beforeVisit.call(null, node);
37+
if (this._beforeVisit.call(null, node) === WebInspector.ESTreeWalker.SkipSubtree) {
38+
this._afterVisit.call(null, node);
39+
return;
40+
}
3641

3742
var walkOrder = WebInspector.ESTreeWalker._walkOrder[node.type];
3843
if (!walkOrder) {

Diff for: front_end/externs.js

+2
Original file line numberDiff line numberDiff line change
@@ -683,6 +683,8 @@ ESTree.Node = function()
683683
this.name;
684684
/** @type {(?ESTree.Node|undefined)} */
685685
this.id;
686+
/** @type {(number|undefined)} */
687+
this.length;
686688
}
687689

688690
/**

Diff for: front_end/formatter_worker/FormatterWorker.js

+20-20
Original file line numberDiff line numberDiff line change
@@ -131,49 +131,49 @@ WebInspector.evaluatableJavaScriptSubstring = function(content)
131131
*/
132132
WebInspector.javaScriptIdentifiers = function(content)
133133
{
134-
var root = acorn.parse(content, {});
134+
var root = acorn.parse(content, { ranges: false, ecmaVersion: 6 });
135+
135136
/** @type {!Array<!ESTree.Node>} */
136137
var identifiers = [];
137-
var functionDeclarationCounter = 0;
138-
var walker = new WebInspector.ESTreeWalker(beforeVisit, afterVisit);
138+
var walker = new WebInspector.ESTreeWalker(beforeVisit);
139139

140140
/**
141141
* @param {!ESTree.Node} node
142142
* @return {boolean}
143143
*/
144144
function isFunction(node)
145145
{
146-
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression";
146+
return node.type === "FunctionDeclaration" || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
147147
}
148148

149149
/**
150150
* @param {!ESTree.Node} node
151151
*/
152152
function beforeVisit(node)
153153
{
154-
if (isFunction(node))
155-
functionDeclarationCounter++;
154+
if (isFunction(node)) {
155+
if (node.id)
156+
identifiers.push(node.id);
157+
return WebInspector.ESTreeWalker.SkipSubtree;
158+
}
156159

157-
if (functionDeclarationCounter > 1)
160+
if (node.type !== "Identifier")
158161
return;
159162

160-
if (isFunction(node) && node.params)
161-
identifiers.pushAll(node.params);
162-
163-
if (node.type === "VariableDeclarator")
164-
identifiers.push(/** @type {!ESTree.Node} */(node.id));
163+
if (node.parent && node.parent.type === "MemberExpression" && node.parent.property === node && !node.parent.computed)
164+
return;
165+
identifiers.push(node);
165166
}
166167

167-
/**
168-
* @param {!ESTree.Node} node
169-
*/
170-
function afterVisit(node)
171-
{
172-
if (isFunction(node))
173-
functionDeclarationCounter--;
168+
if (!root || root.type !== "Program" || root.body.length !== 1 || !isFunction(root.body[0])) {
169+
postMessage([]);
170+
return;
174171
}
175172

176-
walker.walk(root);
173+
var functionNode = root.body[0];
174+
for (var param of functionNode.params)
175+
walker.walk(param);
176+
walker.walk(functionNode.body);
177177
var reduced = identifiers.map(id => ({name: id.name, offset: id.start}));
178178
postMessage(reduced);
179179
}

Diff for: front_end/sdk/DebuggerModel.js

+8-1
Original file line numberDiff line numberDiff line change
@@ -1038,7 +1038,6 @@ WebInspector.DebuggerModel.CallFrame.fromPayloadArray = function(debuggerModel,
10381038
}
10391039

10401040
WebInspector.DebuggerModel.CallFrame.prototype = {
1041-
10421041
/**
10431042
* @return {!WebInspector.Script}
10441043
*/
@@ -1205,6 +1204,14 @@ WebInspector.DebuggerModel.Scope = function(callFrame, ordinal)
12051204
}
12061205

12071206
WebInspector.DebuggerModel.Scope.prototype = {
1207+
/**
1208+
* @return {!WebInspector.DebuggerModel.CallFrame}
1209+
*/
1210+
callFrame: function()
1211+
{
1212+
return this._callFrame;
1213+
},
1214+
12081215
/**
12091216
* @return {string}
12101217
*/

Diff for: front_end/sources/SourceMapNamesResolver.js

+132-40
Original file line numberDiff line numberDiff line change
@@ -5,53 +5,49 @@
55
WebInspector.SourceMapNamesResolver = {};
66

77
WebInspector.SourceMapNamesResolver._cachedMapSymbol = Symbol("cache");
8-
WebInspector.SourceMapNamesResolver._cachedPromiseSymbol = Symbol("cachePromise");
8+
WebInspector.SourceMapNamesResolver._cachedIdentifiersSymbol = Symbol("cachedIdentifiers");
99

1010
/**
11-
* @param {!WebInspector.DebuggerModel.Scope} scope
12-
* @return {!Promise.<!Map<string, string>>}
11+
* @constructor
12+
* @param {string} name
13+
* @param {number} lineNumber
14+
* @param {number} columnNumber
1315
*/
14-
WebInspector.SourceMapNamesResolver._resolveScope = function(scope)
16+
WebInspector.SourceMapNamesResolver.Identifier = function(name, lineNumber, columnNumber)
1517
{
16-
var cachedMap = scope[WebInspector.SourceMapNamesResolver._cachedMapSymbol];
17-
if (cachedMap)
18-
return Promise.resolve(cachedMap);
19-
20-
var cachedPromise = scope[WebInspector.SourceMapNamesResolver._cachedPromiseSymbol];
21-
if (cachedPromise)
22-
return cachedPromise;
18+
this.name = name;
19+
this.lineNumber = lineNumber;
20+
this.columnNumber = columnNumber;
21+
}
2322

23+
/**
24+
* @param {!WebInspector.DebuggerModel.Scope} scope
25+
* @return {!Promise<!Array<!WebInspector.SourceMapNamesResolver.Identifier>>}
26+
*/
27+
WebInspector.SourceMapNamesResolver._scopeIdentifiers = function(scope)
28+
{
2429
var startLocation = scope.startLocation();
2530
var endLocation = scope.endLocation();
2631

2732
if (scope.type() === DebuggerAgent.ScopeType.Global || !startLocation || !endLocation || !startLocation.script().sourceMapURL || (startLocation.script() !== endLocation.script()))
28-
return Promise.resolve(new Map());
33+
return Promise.resolve(/** @type {!Array<!WebInspector.SourceMapNamesResolver.Identifier>}*/([]));
2934

3035
var script = startLocation.script();
31-
var sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script);
32-
if (!sourceMap)
33-
return Promise.resolve(new Map());
34-
35-
var promise = script.requestContent().then(onContent);
36-
scope[WebInspector.SourceMapNamesResolver._cachedPromiseSymbol] = promise;
37-
return promise;
36+
return script.requestContent().then(onContent);
3837

3938
/**
4039
* @param {?string} content
41-
* @return {!Promise<!Map<string, string>>}
40+
* @return {!Promise<!Array<!WebInspector.SourceMapNamesResolver.Identifier>>}
4241
*/
4342
function onContent(content)
4443
{
4544
if (!content)
46-
return Promise.resolve(new Map());
47-
48-
var startLocation = scope.startLocation();
49-
var endLocation = scope.endLocation();
50-
var textRange = new WebInspector.TextRange(startLocation.lineNumber, startLocation.columnNumber, endLocation.lineNumber, endLocation.columnNumber);
45+
return Promise.resolve(/** @type {!Array<!WebInspector.SourceMapNamesResolver.Identifier>}*/([]));
5146

5247
var text = new WebInspector.Text(content);
53-
var scopeText = text.extract(textRange);
54-
var scopeStart = text.toSourceRange(textRange).offset;
48+
var scopeRange = new WebInspector.TextRange(startLocation.lineNumber, startLocation.columnNumber, endLocation.lineNumber, endLocation.columnNumber)
49+
var scopeText = text.extract(scopeRange);
50+
var scopeStart = text.toSourceRange(scopeRange).offset;
5551
var prefix = "function fui";
5652

5753
return WebInspector.SourceMapNamesResolverWorker._instance().javaScriptIdentifiers(prefix + scopeText)
@@ -63,28 +59,124 @@ WebInspector.SourceMapNamesResolver._resolveScope = function(scope)
6359
* @param {number} scopeStart
6460
* @param {string} prefix
6561
* @param {!Array<!{name: string, offset: number}>} identifiers
66-
* @return {!Map<string, string>}
62+
* @return {!Array<!WebInspector.SourceMapNamesResolver.Identifier>}
6763
*/
6864
function onIdentifiers(text, scopeStart, prefix, identifiers)
6965
{
70-
var namesMapping = new Map();
71-
var lineEndings = text.lineEndings();
72-
66+
var result = [];
67+
var cursor = new WebInspector.TextCursor(text.lineEndings());
68+
var promises = [];
7369
for (var i = 0; i < identifiers.length; ++i) {
7470
var id = identifiers[i];
71+
if (id.offset < prefix.length)
72+
continue;
7573
var start = scopeStart + id.offset - prefix.length;
74+
cursor.resetTo(start);
75+
result.push(new WebInspector.SourceMapNamesResolver.Identifier(id.name, cursor.lineNumber(), cursor.columnNumber()));
76+
}
77+
return result;
78+
}
79+
}
80+
81+
/**
82+
* @param {!WebInspector.DebuggerModel.Scope} scope
83+
* @return {!Promise.<!Map<string, string>>}
84+
*/
85+
WebInspector.SourceMapNamesResolver._resolveScope = function(scope)
86+
{
87+
var identifiersPromise = scope[WebInspector.SourceMapNamesResolver._cachedIdentifiersSymbol];
88+
if (identifiersPromise)
89+
return identifiersPromise;
90+
91+
var script = scope.callFrame().script;
92+
var sourceMap = WebInspector.debuggerWorkspaceBinding.sourceMapForScript(script);
93+
if (!sourceMap)
94+
return Promise.resolve(new Map());
7695

77-
var lineNumber = lineEndings.lowerBound(start);
78-
var columnNumber = start - (lineNumber === 0 ? 0 : (lineEndings[lineNumber - 1] + 1));
79-
var entry = sourceMap.findEntry(lineNumber, columnNumber);
80-
if (entry)
96+
/** @type {!Map<string, !WebInspector.Text>} */
97+
var textCache = new Map();
98+
identifiersPromise = WebInspector.SourceMapNamesResolver._scopeIdentifiers(scope).then(onIdentifiers);
99+
scope[WebInspector.SourceMapNamesResolver._cachedIdentifiersSymbol] = identifiersPromise;
100+
return identifiersPromise;
101+
102+
/**
103+
* @param {!Array<!WebInspector.SourceMapNamesResolver.Identifier>} identifiers
104+
* @return {!Promise<!Map<string, string>>}
105+
*/
106+
function onIdentifiers(identifiers)
107+
{
108+
var namesMapping = new Map();
109+
// Extract as much as possible from SourceMap.
110+
for (var i = 0; i < identifiers.length; ++i) {
111+
var id = identifiers[i];
112+
var entry = sourceMap.findEntry(id.lineNumber, id.columnNumber);
113+
if (entry && entry.name)
81114
namesMapping.set(id.name, entry.name);
82115
}
83116

84-
scope[WebInspector.SourceMapNamesResolver._cachedMapSymbol] = namesMapping;
85-
delete scope[WebInspector.SourceMapNamesResolver._cachedPromiseSymbol];
86-
WebInspector.SourceMapNamesResolver._scopeResolvedForTest();
87-
return namesMapping;
117+
// Resolve missing identifier names from sourcemap ranges.
118+
var promises = [];
119+
for (var i = 0; i < identifiers.length; ++i) {
120+
var id = identifiers[i];
121+
if (namesMapping.has(id.name))
122+
continue;
123+
var promise = resolveSourceName(id).then(onSourceNameResolved.bind(null, namesMapping, id));
124+
promises.push(promise);
125+
}
126+
return Promise.all(promises)
127+
.then(() => WebInspector.SourceMapNamesResolver._scopeResolvedForTest())
128+
.then(() => namesMapping)
129+
}
130+
131+
/**
132+
* @param {!Map<string, string>} namesMapping
133+
* @param {!WebInspector.SourceMapNamesResolver.Identifier} id
134+
* @param {?string} sourceName
135+
*/
136+
function onSourceNameResolved(namesMapping, id, sourceName)
137+
{
138+
if (!sourceName)
139+
return;
140+
namesMapping.set(id.name, sourceName);
141+
}
142+
143+
/**
144+
* @param {!WebInspector.SourceMapNamesResolver.Identifier} id
145+
* @return {!Promise<?string>}
146+
*/
147+
function resolveSourceName(id)
148+
{
149+
var startEntry = sourceMap.findEntry(id.lineNumber, id.columnNumber);
150+
var endEntry = sourceMap.findEntry(id.lineNumber, id.columnNumber + id.name.length);
151+
if (!startEntry || !endEntry || !startEntry.sourceURL || startEntry.sourceURL !== endEntry.sourceURL
152+
|| !startEntry.sourceLineNumber || !startEntry.sourceColumnNumber
153+
|| !endEntry.sourceLineNumber || !endEntry.sourceColumnNumber)
154+
return Promise.resolve(/** @type {?string} */(null));
155+
var sourceTextRange = new WebInspector.TextRange(startEntry.sourceLineNumber, startEntry.sourceColumnNumber, endEntry.sourceLineNumber, endEntry.sourceColumnNumber);
156+
var uiSourceCode = WebInspector.networkMapping.uiSourceCodeForScriptURL(startEntry.sourceURL, script);
157+
if (!uiSourceCode)
158+
return Promise.resolve(/** @type {?string} */(null));
159+
160+
return uiSourceCode.requestContent()
161+
.then(onSourceContent.bind(null, sourceTextRange));
162+
}
163+
164+
/**
165+
* @param {!WebInspector.TextRange} sourceTextRange
166+
* @param {?string} content
167+
* @return {?string}
168+
*/
169+
function onSourceContent(sourceTextRange, content)
170+
{
171+
if (!content)
172+
return null;
173+
var text = textCache.get(content);
174+
if (!text) {
175+
text = new WebInspector.Text(content);
176+
textCache.set(content, text);
177+
}
178+
var originalIdentifier = text.extract(sourceTextRange).trim();
179+
return /[a-zA-Z0-9_$]+/.test(originalIdentifier) ? originalIdentifier : null;
88180
}
89181
}
90182

@@ -151,7 +243,7 @@ WebInspector.SourceMapNamesResolver.resolveExpression = function(callFrame, orig
151243
if (reverseMapping.has(originalText))
152244
return Promise.resolve(reverseMapping.get(originalText) || "");
153245

154-
return WebInspector.SourceMapNamesResolver._resolveExpression(callFrame, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber)
246+
return WebInspector.SourceMapNamesResolver._resolveExpression(callFrame, uiSourceCode, lineNumber, startColumnNumber, endColumnNumber);
155247
}
156248
}
157249

0 commit comments

Comments
 (0)