Skip to content

Commit 8655cdb

Browse files
committed
refactor: extract parts of babel plugin logic
1 parent ebe3729 commit 8655cdb

File tree

5 files changed

+96
-123
lines changed

5 files changed

+96
-123
lines changed
Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
11
export const CORE_PACKAGE_NAME = '@ottrelite/core';
22
export const TRACE_COMPONENT_LIFECYCLE_HOOK_NAME = 'useComponentRenderTracing';
33
export const TRACE_COMPONENT_LIFECYCLE_HOC_NAME = 'withComponentRenderTracing';
4-
export const UNKNOWN_COMPONENT_NAME_PLACEHOLDER = 'unknown';

packages/instrumentation-react/src/index.ts

Lines changed: 4 additions & 122 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
import type { NodePath, PluginObj, PluginPass } from '@babel/core';
1+
import type { NodePath, PluginObj } from '@babel/core';
22
import { generate } from '@babel/generator';
33
import * as t from '@babel/types';
4-
import type { TracingAPI } from '@ottrelite/core';
54

65
import {
76
CORE_PACKAGE_NAME,
87
TRACE_COMPONENT_LIFECYCLE_HOC_NAME,
98
TRACE_COMPONENT_LIFECYCLE_HOOK_NAME,
10-
UNKNOWN_COMPONENT_NAME_PLACEHOLDER,
119
} from './constants';
12-
import { isEnvBoolVarTrue, uniqueVarName } from './utils';
10+
import { getLocationDescription } from './utils/getLocationDescription';
11+
import { isEnvBoolVarTrue, uniqueVarName } from './utils/misc';
12+
import { parseDirective } from './utils/parseDirective';
1313

1414
const LOG_DEBUG = isEnvBoolVarTrue('DEBUG');
1515
const PRINT_GENERATED_CODE = isEnvBoolVarTrue('PRINT_GENERATED_CODE');
@@ -20,124 +20,6 @@ type AugmentedProgramNode = NodePath<t.Program> & {
2020
__hasFunctionComponent: boolean;
2121
};
2222

23-
function parseDirective(
24-
path: NodePath<t.Function | t.Class>,
25-
traceDirective: t.Directive,
26-
locationDescription: string
27-
) {
28-
// extract the component name from "use trace ..."
29-
const maybeMatches = traceDirective.value.value
30-
.match(/use trace\s+(\S+)(?:\s+(\S+))?$/)
31-
?.filter((x) => x !== undefined);
32-
33-
let componentName: string | undefined;
34-
let apiToUse: TracingAPI = 'dev';
35-
36-
if (maybeMatches) {
37-
// case 1: 'use trace <api/name>'; give priority to api (if valid) over name
38-
if (maybeMatches.length === 2) {
39-
const apiName = maybeMatches[1]?.trim();
40-
41-
if (apiName === 'dev' || apiName === 'otel') {
42-
apiToUse = apiName;
43-
} else {
44-
componentName = apiName;
45-
}
46-
}
47-
48-
// case 2: 'use trace <api> <name>'
49-
if (maybeMatches.length === 3) {
50-
const apiName = maybeMatches[1]?.trim();
51-
componentName = maybeMatches[2]?.trim();
52-
53-
if (apiName === 'dev' || apiName === 'otel') {
54-
apiToUse = apiName;
55-
} else {
56-
throw new Error(
57-
`[Ottrelite] Invalid tracing API specified: "${apiName}". Supported APIs are "dev" and "otel". Localization: ${locationDescription}.`
58-
);
59-
}
60-
}
61-
}
62-
63-
let isFunctionComponent = true;
64-
65-
if ('id' in path.node && path.node.id?.name) {
66-
// function or class declaration
67-
componentName ??= path.node.id.name;
68-
} else if (
69-
path.parent.type === 'VariableDeclarator' &&
70-
path.parent.id.type === 'Identifier'
71-
) {
72-
// variable assignment
73-
componentName ??= path.parent.id.name;
74-
} else if (
75-
path.parent.type === 'ObjectProperty' &&
76-
path.parent.key.type === 'Identifier'
77-
) {
78-
// object property assignment
79-
componentName ??= path.parent.key.name;
80-
} else if (
81-
path.parent.type === 'ClassMethod' &&
82-
path.parent.key.type === 'Identifier'
83-
) {
84-
// class method
85-
isFunctionComponent = false;
86-
componentName ??= path.parent.key.name;
87-
} else if (path.parent.type === 'ExportDefaultDeclaration') {
88-
// anonymous export default
89-
throw new Error(
90-
`[Ottrelite] Identifier is an anonymous default export, which is not supported! The tracer must be given a unique name. Localization: ${locationDescription}.`
91-
);
92-
}
93-
94-
// /**
95-
// * important notice: the above works for ES6, but if the code passed to Babel is already ES5, then it will look like the following:
96-
// *
97-
// * return _createClass(ClassComponentName, [{
98-
// * key: "render",
99-
// * value: function render() {
100-
// * ...
101-
// * },
102-
// * ...
103-
// * ]);
104-
// *
105-
// * to handle this properly, we need to check if there exists a _createClass parent
106-
// */
107-
// const maybeClassCreationExpression = path.findParent(
108-
// (p) =>
109-
// t.isCallExpression(p.node) &&
110-
// t.isIdentifier(p.node.callee, { name: '_createClass' })
111-
// );
112-
// if (maybeClassCreationExpression) {
113-
// isFunctionComponent = false;
114-
// // so far, the componentName would be 'render'
115-
// try {
116-
// componentName = (
117-
// (maybeClassCreationExpression.node as t.CallExpression)
118-
// .arguments[0] as t.Identifier
119-
// ).name;
120-
// } catch {
121-
// // it's not critical if a more precise name is impossible to extract
122-
// }
123-
// }
124-
125-
return {
126-
componentName: componentName ?? UNKNOWN_COMPONENT_NAME_PLACEHOLDER,
127-
isFunctionComponent,
128-
apiToUse,
129-
};
130-
}
131-
132-
function getLocationDescription(
133-
path: NodePath | t.ClassMethod,
134-
pluginPass: PluginPass
135-
) {
136-
const loc =
137-
path.type === 'ClassMethod' ? (path as t.ClassMethod).loc : path.node.loc;
138-
return `${pluginPass.filename}:${loc?.start.line}:${loc?.start.column} through ${loc?.end.line}:${loc?.end.column}`;
139-
}
140-
14123
export default function ottrelitePlugin(): PluginObj {
14224
return {
14325
visitor: {
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import { NodePath, PluginPass } from '@babel/core';
2+
import * as t from '@babel/types';
3+
4+
export function getLocationDescription(
5+
path: NodePath | t.ClassMethod,
6+
pluginPass: PluginPass
7+
) {
8+
const loc =
9+
path.type === 'ClassMethod' ? (path as t.ClassMethod).loc : path.node.loc;
10+
return `${pluginPass.filename}:${loc?.start.line}:${loc?.start.column} through ${loc?.end.line}:${loc?.end.column}`;
11+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { NodePath } from '@babel/core';
2+
import * as t from '@babel/types';
3+
import type { TracingAPI } from '@ottrelite/core';
4+
5+
export function parseDirective(
6+
path: NodePath<t.Function | t.Class>,
7+
traceDirective: t.Directive,
8+
locationDescription: string
9+
) {
10+
// extract the component name from "use trace ..."
11+
const maybeMatches = traceDirective.value.value
12+
.match(/use trace\s+(\S+)(?:\s+(\S+))?$/)
13+
?.filter((x) => x !== undefined);
14+
15+
let componentName: string | undefined;
16+
let apiToUse: TracingAPI = 'dev';
17+
18+
if (maybeMatches) {
19+
// case 1: 'use trace <api/name>'; give priority to api (if valid) over name
20+
if (maybeMatches.length === 2) {
21+
const apiName = maybeMatches[1]?.trim();
22+
23+
if (apiName === 'dev' || apiName === 'otel') {
24+
apiToUse = apiName;
25+
} else {
26+
componentName = apiName;
27+
}
28+
}
29+
30+
// case 2: 'use trace <api> <name>'
31+
if (maybeMatches.length === 3) {
32+
const apiName = maybeMatches[1]?.trim();
33+
componentName = maybeMatches[2]?.trim();
34+
35+
if (apiName === 'dev' || apiName === 'otel') {
36+
apiToUse = apiName;
37+
} else {
38+
throw new Error(
39+
`[Ottrelite] Invalid tracing API specified: "${apiName}". Supported APIs are "dev" and "otel". Localization: ${locationDescription}.`
40+
);
41+
}
42+
}
43+
}
44+
45+
let isFunctionComponent = true;
46+
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'
53+
) {
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}.`
73+
);
74+
}
75+
76+
return {
77+
componentName: componentName ?? 'unknown',
78+
isFunctionComponent,
79+
apiToUse,
80+
};
81+
}

0 commit comments

Comments
 (0)