Skip to content

Commit 37577ff

Browse files
committed
feat: instrumentation react to infer component name from attributes: name or componentName
1 parent 4000666 commit 37577ff

File tree

3 files changed

+64
-27
lines changed

3 files changed

+64
-27
lines changed

docs/docs/docs/instrumentation-react/class-components.mdx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,11 @@ export class TracedClassComponent extends Component<ExternallyDrivenComponentPro
7676

7777
### What will be the name of the tracer?
7878

79+
The following is an in-order (by priority) list of how the name of the tracer is determined:
80+
7981
| Declaration style | Name |
8082
| --------------------------------------------------- | ---------------------------------------- |
8183
| _Explicit name from the string after `"use trace"`_ | The passed string |
84+
| component with `displayName` or `name` | `displayName` or `name` property |
8285
| named class | Name of the class |
8386
| anonymous class | Name of the assignment target identifier |

docs/docs/docs/instrumentation-react/function-components.mdx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,12 @@ export const MyComponent = memo(function MyComponent(){
6565
6666
### What will be the name of the tracer?
6767
68-
| Declaration style | Name |
68+
The following is an in-order (by priority) list of how the name of the tracer is determined:
69+
70+
| Declaration style / context | Name |
6971
| --------------------------------------------------- | ---------------------------------------- |
7072
| _Explicit name from the string after `"use trace"`_ | The passed string |
73+
| component with `displayName` or `name` | `displayName` or `name` property |
7174
| named function | Name of the function |
7275
| arrow function | Name of the assignment target identifier |
7376
| anonymous function | Name of the assignment target identifier |

packages/instrumentation-react/src/utils/parseDirective.ts

Lines changed: 57 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export function parseDirective(
77
traceDirective: t.Directive,
88
locationDescription: string
99
) {
10-
// extract the component name from "use trace ..."
10+
// I. Extract the component name from "use trace ..."
1111
const maybeMatches = traceDirective.value.value
1212
.match(/use trace\s+(\S+)(?:\s+(\S+))?$/)
1313
?.filter((x) => x !== undefined);
@@ -44,33 +44,64 @@ export function parseDirective(
4444

4545
let isFunctionComponent = true;
4646

47-
if ('id' in path.node && path.node.id?.name) {
48-
// function or class declaration
49-
componentName ??= path.node.id.name;
50-
} else if (
51-
path.parent.type === 'VariableDeclarator' &&
52-
path.parent.id.type === 'Identifier'
47+
// II. Attempt to read displayName or name, if componentName is still undefined
48+
if (
49+
!componentName &&
50+
(path.node.type === 'FunctionDeclaration' ||
51+
path.node.type === 'FunctionExpression' ||
52+
path.node.type === 'ArrowFunctionExpression')
5353
) {
54-
// variable assignment
55-
componentName ??= path.parent.id.name;
56-
} else if (
57-
path.parent.type === 'ObjectProperty' &&
58-
path.parent.key.type === 'Identifier'
59-
) {
60-
// object property assignment
61-
componentName ??= path.parent.key.name;
62-
} else if (
63-
path.parent.type === 'ClassMethod' &&
64-
path.parent.key.type === 'Identifier'
65-
) {
66-
// class method
67-
isFunctionComponent = false;
68-
componentName ??= path.parent.key.name;
69-
} else if (path.parent.type === 'ExportDefaultDeclaration') {
70-
// anonymous export default
71-
throw new Error(
72-
`[Ottrelite] Identifier is an anonymous default export, which is not supported! The tracer must be given a unique name. Localization: ${locationDescription}.`
54+
// look for .displayName or .name assignments in parent scope
55+
const parent = path.findParent(
56+
(p) => p.isProgram() || p.isBlockStatement()
7357
);
58+
if (parent) {
59+
parent.traverse({
60+
AssignmentExpression(assignPath) {
61+
if (
62+
assignPath.node.left.type === 'MemberExpression' &&
63+
assignPath.node.left.property.type === 'Identifier' &&
64+
(assignPath.node.left.property.name === 'displayName' ||
65+
assignPath.node.left.property.name === 'name') &&
66+
assignPath.node.right.type === 'StringLiteral'
67+
) {
68+
componentName = assignPath.node.right.value;
69+
}
70+
},
71+
});
72+
}
73+
}
74+
75+
// III. Infer the name from context, if componentName is still undefined
76+
if (!componentName) {
77+
if ('id' in path.node && path.node.id?.name) {
78+
// function or class declaration
79+
componentName ??= path.node.id.name;
80+
} else if (
81+
path.parent.type === 'VariableDeclarator' &&
82+
path.parent.id.type === 'Identifier'
83+
) {
84+
// variable assignment
85+
componentName ??= path.parent.id.name;
86+
} else if (
87+
path.parent.type === 'ObjectProperty' &&
88+
path.parent.key.type === 'Identifier'
89+
) {
90+
// object property assignment
91+
componentName ??= path.parent.key.name;
92+
} else if (
93+
path.parent.type === 'ClassMethod' &&
94+
path.parent.key.type === 'Identifier'
95+
) {
96+
// class method
97+
isFunctionComponent = false;
98+
componentName ??= path.parent.key.name;
99+
} else if (path.parent.type === 'ExportDefaultDeclaration') {
100+
// anonymous export default
101+
throw new Error(
102+
`[Ottrelite] Identifier is an anonymous default export, which is not supported! The tracer must be given a unique name. Localization: ${locationDescription}.`
103+
);
104+
}
74105
}
75106

76107
return {

0 commit comments

Comments
 (0)