summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/utils/pause/frames/displayName.js
blob: dbd53d53cd7a25e0c3301295142626a22da1474e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
/* 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 <http://mozilla.org/MPL/2.0/>. */

// @flow

// eslint-disable-next-line max-len
import type { Frame } from "../../../types";

// Decodes an anonymous naming scheme that
// spider monkey implements based on "Naming Anonymous JavaScript Functions"
// http://johnjbarton.github.io/nonymous/index.html
const objectProperty = /([\w\d\$]+)$/;
const arrayProperty = /\[(.*?)\]$/;
const functionProperty = /([\w\d]+)[\/\.<]*?$/;
const annonymousProperty = /([\w\d]+)\(\^\)$/;

export function simplifyDisplayName(displayName: string | void): string | void {
  // if the display name has a space it has already been mapped
  if (!displayName || /\s/.exec(displayName)) {
    return displayName;
  }

  const scenarios = [
    objectProperty,
    arrayProperty,
    functionProperty,
    annonymousProperty,
  ];

  for (const reg of scenarios) {
    const match = reg.exec(displayName);
    if (match) {
      return match[1];
    }
  }

  return displayName;
}

const displayNameMap = {
  Babel: {
    tryCatch: "Async",
  },
  Backbone: {
    "extend/child": "Create Class",
    ".create": "Create Model",
  },
  jQuery: {
    "jQuery.event.dispatch": "Dispatch Event",
  },
  React: {
    // eslint-disable-next-line max-len
    "ReactCompositeComponent._renderValidatedComponentWithoutOwnerOrContext/renderedElement<":
      "Render",
    _renderValidatedComponentWithoutOwnerOrContext: "Render",
  },
  VueJS: {
    "renderMixin/Vue.prototype._render": "Render",
  },
  Webpack: {
    // eslint-disable-next-line camelcase
    __webpack_require__: "Bootstrap",
  },
};

function mapDisplayNames(frame, library) {
  const { displayName } = frame;
  return displayNameMap[library]?.[displayName] || displayName;
}

function getFrameDisplayName(frame: Frame): string {
  const {
    displayName,
    originalDisplayName,
    userDisplayName,
    name,
  } = (frame: any);
  return originalDisplayName || userDisplayName || displayName || name;
}

type formatDisplayNameParams = {
  shouldMapDisplayName: boolean,
};
export function formatDisplayName(
  frame: Frame,
  { shouldMapDisplayName = true }: formatDisplayNameParams = {},
  l10n: typeof L10N
): string {
  const { library } = frame;
  let displayName = getFrameDisplayName(frame);
  if (library && shouldMapDisplayName) {
    displayName = mapDisplayNames(frame, library);
  }

  return simplifyDisplayName(displayName) || l10n.getStr("anonymousFunction");
}

export function formatCopyName(frame: Frame, l10n: typeof L10N): string {
  const displayName = formatDisplayName(frame, undefined, l10n);
  if (!frame.source) {
    throw new Error("no frame source");
  }
  const fileName = frame.source.url || frame.source.id;
  const frameLocation = frame.location.line;

  return `${displayName} (${fileName}#${frameLocation})`;
}