summaryrefslogtreecommitdiffstats
path: root/devtools/client/debugger/src/workers/parser/utils/inferClassName.js
blob: b0c3ae78352214e38d6ec9dee4136d1bf48104a1 (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
/* 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
import * as t from "@babel/types";
import type { SimplePath } from "./simple-path";

// the function class is inferred from a call like
// createClass or extend
function fromCallExpression(callExpression: SimplePath) {
  const allowlist = ["extend", "createClass"];
  const { callee } = callExpression.node;
  if (!callee) {
    return null;
  }

  const name = t.isMemberExpression(callee)
    ? callee.property.name
    : callee.name;

  if (!allowlist.includes(name)) {
    return null;
  }

  const variable = callExpression.findParent(p =>
    t.isVariableDeclarator(p.node)
  );
  if (variable) {
    return variable.node.id.name;
  }

  const assignment = callExpression.findParent(p =>
    t.isAssignmentExpression(p.node)
  );

  if (!assignment) {
    return null;
  }

  const { left } = assignment.node;

  if (left.name) {
    return name;
  }

  if (t.isMemberExpression(left)) {
    return left.property.name;
  }

  return null;
}

// the function class is inferred from a prototype assignment
// e.g. TodoClass.prototype.render = function() {}
function fromPrototype(assignment) {
  const { left } = assignment.node;
  if (!left) {
    return null;
  }

  if (
    t.isMemberExpression(left) &&
    left.object &&
    t.isMemberExpression(left.object) &&
    left.object.property.identifier === "prototype"
  ) {
    return left.object.object.name;
  }

  return null;
}

// infer class finds an appropriate class for functions
// that are defined inside of a class like thing.
// e.g. `class Foo`, `TodoClass.prototype.foo`,
//      `Todo = createClass({ foo: () => {}})`
export function inferClassName(path: SimplePath): string | null {
  const classDeclaration = path.findParent(p => t.isClassDeclaration(p.node));
  if (classDeclaration) {
    return classDeclaration.node.id.name;
  }

  const callExpression = path.findParent(p => t.isCallExpression(p.node));
  if (callExpression) {
    return fromCallExpression(callExpression);
  }

  const assignment = path.findParent(p => t.isAssignmentExpression(p.node));
  if (assignment) {
    return fromPrototype(assignment);
  }

  return null;
}