@flisk/analyze-tracking 0.7.2 → 0.7.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. package/README.md +4 -0
  2. package/bin/cli.js +30 -2
  3. package/package.json +12 -8
  4. package/src/analyze/go/astTraversal.js +121 -0
  5. package/src/analyze/go/constants.js +20 -0
  6. package/src/analyze/go/eventDeduplicator.js +47 -0
  7. package/src/analyze/go/eventExtractor.js +156 -0
  8. package/src/analyze/go/goAstParser/constants.js +39 -0
  9. package/src/analyze/go/goAstParser/expressionParser.js +281 -0
  10. package/src/analyze/go/goAstParser/index.js +52 -0
  11. package/src/analyze/go/goAstParser/statementParser.js +387 -0
  12. package/src/analyze/go/goAstParser/tokenizer.js +196 -0
  13. package/src/analyze/go/goAstParser/typeParser.js +202 -0
  14. package/src/analyze/go/goAstParser/utils.js +99 -0
  15. package/src/analyze/go/index.js +55 -0
  16. package/src/analyze/go/propertyExtractor.js +670 -0
  17. package/src/analyze/go/trackingDetector.js +71 -0
  18. package/src/analyze/go/trackingExtractor.js +54 -0
  19. package/src/analyze/go/typeContext.js +88 -0
  20. package/src/analyze/go/utils.js +215 -0
  21. package/src/analyze/index.js +11 -6
  22. package/src/analyze/javascript/constants.js +115 -0
  23. package/src/analyze/javascript/detectors/analytics-source.js +119 -0
  24. package/src/analyze/javascript/detectors/index.js +10 -0
  25. package/src/analyze/javascript/extractors/event-extractor.js +179 -0
  26. package/src/analyze/javascript/extractors/index.js +13 -0
  27. package/src/analyze/javascript/extractors/property-extractor.js +172 -0
  28. package/src/analyze/javascript/index.js +38 -0
  29. package/src/analyze/javascript/parser.js +126 -0
  30. package/src/analyze/javascript/utils/function-finder.js +123 -0
  31. package/src/analyze/python/index.js +111 -0
  32. package/src/analyze/python/pythonTrackingAnalyzer.py +814 -0
  33. package/src/analyze/ruby/detectors.js +46 -0
  34. package/src/analyze/ruby/extractors.js +258 -0
  35. package/src/analyze/ruby/index.js +51 -0
  36. package/src/analyze/ruby/traversal.js +123 -0
  37. package/src/analyze/ruby/types.js +30 -0
  38. package/src/analyze/ruby/visitor.js +66 -0
  39. package/src/analyze/typescript/constants.js +109 -0
  40. package/src/analyze/typescript/detectors/analytics-source.js +125 -0
  41. package/src/analyze/typescript/detectors/index.js +10 -0
  42. package/src/analyze/typescript/extractors/event-extractor.js +269 -0
  43. package/src/analyze/typescript/extractors/index.js +14 -0
  44. package/src/analyze/typescript/extractors/property-extractor.js +427 -0
  45. package/src/analyze/typescript/index.js +48 -0
  46. package/src/analyze/typescript/parser.js +131 -0
  47. package/src/analyze/typescript/utils/function-finder.js +139 -0
  48. package/src/analyze/typescript/utils/type-resolver.js +208 -0
  49. package/src/generateDescriptions/index.js +81 -0
  50. package/src/generateDescriptions/llmUtils.js +33 -0
  51. package/src/generateDescriptions/promptUtils.js +62 -0
  52. package/src/generateDescriptions/schemaUtils.js +61 -0
  53. package/src/index.js +13 -4
  54. package/src/{fileProcessor.js → utils/fileProcessor.js} +5 -0
  55. package/src/{repoDetails.js → utils/repoDetails.js} +5 -0
  56. package/src/utils/yamlGenerator.js +47 -0
  57. package/src/analyze/analyzeGoFile.js +0 -1164
  58. package/src/analyze/analyzeJsFile.js +0 -87
  59. package/src/analyze/analyzePythonFile.js +0 -42
  60. package/src/analyze/analyzeRubyFile.js +0 -419
  61. package/src/analyze/analyzeTsFile.js +0 -192
  62. package/src/analyze/go2json.js +0 -1069
  63. package/src/analyze/helpers.js +0 -656
  64. package/src/analyze/pythonTrackingAnalyzer.py +0 -541
  65. package/src/generateDescriptions.js +0 -196
  66. package/src/yamlGenerator.js +0 -23
@@ -0,0 +1,179 @@
1
+ /**
2
+ * @fileoverview Event extraction logic for different analytics providers
3
+ * @module analyze/javascript/extractors/event-extractor
4
+ */
5
+
6
+ const { NODE_TYPES } = require('../constants');
7
+ const { extractProperties } = require('./property-extractor');
8
+
9
+ /**
10
+ * Event data structure
11
+ * @typedef {Object} EventData
12
+ * @property {string|null} eventName - The event name
13
+ * @property {Object|null} propertiesNode - AST node containing event properties
14
+ */
15
+
16
+ /**
17
+ * Provider-specific extraction strategies
18
+ */
19
+ const EXTRACTION_STRATEGIES = {
20
+ googleanalytics: extractGoogleAnalyticsEvent,
21
+ snowplow: extractSnowplowEvent,
22
+ mparticle: extractMparticleEvent,
23
+ default: extractDefaultEvent
24
+ };
25
+
26
+ /**
27
+ * Extracts event information from a CallExpression node
28
+ * @param {Object} node - AST CallExpression node
29
+ * @param {string} source - Analytics provider source
30
+ * @returns {EventData} Extracted event data
31
+ */
32
+ function extractEventData(node, source) {
33
+ const strategy = EXTRACTION_STRATEGIES[source] || EXTRACTION_STRATEGIES.default;
34
+ return strategy(node);
35
+ }
36
+
37
+ /**
38
+ * Extracts Google Analytics event data
39
+ * @param {Object} node - CallExpression node
40
+ * @returns {EventData}
41
+ */
42
+ function extractGoogleAnalyticsEvent(node) {
43
+ if (!node.arguments || node.arguments.length < 3) {
44
+ return { eventName: null, propertiesNode: null };
45
+ }
46
+
47
+ // gtag('event', 'event_name', { properties })
48
+ const eventName = getStringValue(node.arguments[1]);
49
+ const propertiesNode = node.arguments[2];
50
+
51
+ return { eventName, propertiesNode };
52
+ }
53
+
54
+ /**
55
+ * Extracts Snowplow event data
56
+ * @param {Object} node - CallExpression node
57
+ * @returns {EventData}
58
+ */
59
+ function extractSnowplowEvent(node) {
60
+ if (!node.arguments || node.arguments.length === 0) {
61
+ return { eventName: null, propertiesNode: null };
62
+ }
63
+
64
+ // tracker.track(buildStructEvent({ action: 'event_name', ... }))
65
+ const firstArg = node.arguments[0];
66
+
67
+ if (firstArg.type === NODE_TYPES.CALL_EXPRESSION &&
68
+ firstArg.arguments.length > 0) {
69
+ const structEventArg = firstArg.arguments[0];
70
+
71
+ if (structEventArg.type === NODE_TYPES.OBJECT_EXPRESSION) {
72
+ const actionProperty = findPropertyByKey(structEventArg, 'action');
73
+ const eventName = actionProperty ? getStringValue(actionProperty.value) : null;
74
+
75
+ return { eventName, propertiesNode: structEventArg };
76
+ }
77
+ }
78
+
79
+ return { eventName: null, propertiesNode: null };
80
+ }
81
+
82
+ /**
83
+ * Extracts mParticle event data
84
+ * @param {Object} node - CallExpression node
85
+ * @returns {EventData}
86
+ */
87
+ function extractMparticleEvent(node) {
88
+ if (!node.arguments || node.arguments.length < 3) {
89
+ return { eventName: null, propertiesNode: null };
90
+ }
91
+
92
+ // mParticle.logEvent('event_name', mParticle.EventType.Navigation, { properties })
93
+ const eventName = getStringValue(node.arguments[0]);
94
+ const propertiesNode = node.arguments[2];
95
+
96
+ return { eventName, propertiesNode };
97
+ }
98
+
99
+ /**
100
+ * Default event extraction for standard providers
101
+ * @param {Object} node - CallExpression node
102
+ * @returns {EventData}
103
+ */
104
+ function extractDefaultEvent(node) {
105
+ if (!node.arguments || node.arguments.length < 2) {
106
+ return { eventName: null, propertiesNode: null };
107
+ }
108
+
109
+ // provider.track('event_name', { properties })
110
+ const eventName = getStringValue(node.arguments[0]);
111
+ const propertiesNode = node.arguments[1];
112
+
113
+ return { eventName, propertiesNode };
114
+ }
115
+
116
+ /**
117
+ * Processes extracted event data into final event object
118
+ * @param {EventData} eventData - Raw event data
119
+ * @param {string} source - Analytics source
120
+ * @param {string} filePath - File path
121
+ * @param {number} line - Line number
122
+ * @param {string} functionName - Containing function name
123
+ * @returns {Object|null} Processed event object or null
124
+ */
125
+ function processEventData(eventData, source, filePath, line, functionName) {
126
+ const { eventName, propertiesNode } = eventData;
127
+
128
+ if (!eventName || !propertiesNode || propertiesNode.type !== NODE_TYPES.OBJECT_EXPRESSION) {
129
+ return null;
130
+ }
131
+
132
+ let properties = extractProperties(propertiesNode);
133
+
134
+ // Special handling for Snowplow: remove 'action' from properties
135
+ if (source === 'snowplow' && properties.action) {
136
+ delete properties.action;
137
+ }
138
+
139
+ return {
140
+ eventName,
141
+ source,
142
+ properties,
143
+ filePath,
144
+ line,
145
+ functionName
146
+ };
147
+ }
148
+
149
+ /**
150
+ * Gets string value from an AST node
151
+ * @param {Object} node - AST node
152
+ * @returns {string|null} String value or null
153
+ */
154
+ function getStringValue(node) {
155
+ if (!node) return null;
156
+ if (node.type === NODE_TYPES.LITERAL && typeof node.value === 'string') {
157
+ return node.value;
158
+ }
159
+ return null;
160
+ }
161
+
162
+ /**
163
+ * Finds a property by key in an ObjectExpression
164
+ * @param {Object} objectNode - ObjectExpression node
165
+ * @param {string} key - Property key to find
166
+ * @returns {Object|null} Property node or null
167
+ */
168
+ function findPropertyByKey(objectNode, key) {
169
+ if (!objectNode.properties) return null;
170
+
171
+ return objectNode.properties.find(prop =>
172
+ prop.key && (prop.key.name === key || prop.key.value === key)
173
+ );
174
+ }
175
+
176
+ module.exports = {
177
+ extractEventData,
178
+ processEventData
179
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * @fileoverview Central export for all extractor modules
3
+ * @module analyze/javascript/extractors
4
+ */
5
+
6
+ const { extractEventData, processEventData } = require('./event-extractor');
7
+ const { extractProperties } = require('./property-extractor');
8
+
9
+ module.exports = {
10
+ extractEventData,
11
+ processEventData,
12
+ extractProperties
13
+ };
@@ -0,0 +1,172 @@
1
+ /**
2
+ * @fileoverview Property extraction from AST nodes
3
+ * @module analyze/javascript/extractors/property-extractor
4
+ */
5
+
6
+ const { NODE_TYPES } = require('../constants');
7
+
8
+ /**
9
+ * Property structure representation
10
+ * @typedef {Object} PropertySchema
11
+ * @property {string} type - The property type (string, number, boolean, object, array, any)
12
+ * @property {PropertySchema} [properties] - Nested properties for objects
13
+ * @property {Object} [items] - Item type information for arrays
14
+ */
15
+
16
+ /**
17
+ * Extracts properties from an ObjectExpression node
18
+ * @param {Object} node - AST ObjectExpression node
19
+ * @returns {Object.<string, PropertySchema>} Extracted properties with their schemas
20
+ */
21
+ function extractProperties(node) {
22
+ if (!node || node.type !== NODE_TYPES.OBJECT_EXPRESSION) {
23
+ return {};
24
+ }
25
+
26
+ const properties = {};
27
+
28
+ node.properties.forEach((prop) => {
29
+ const key = getPropertyKey(prop);
30
+ if (!key) return;
31
+
32
+ const schema = extractPropertySchema(prop.value);
33
+ if (schema) {
34
+ properties[key] = schema;
35
+ }
36
+ });
37
+
38
+ return properties;
39
+ }
40
+
41
+ /**
42
+ * Gets the key name from a property node
43
+ * @param {Object} prop - Property node
44
+ * @returns {string|null} Property key or null
45
+ */
46
+ function getPropertyKey(prop) {
47
+ if (!prop.key) return null;
48
+ return prop.key.name || prop.key.value || null;
49
+ }
50
+
51
+ /**
52
+ * Extracts schema information from a property value
53
+ * @param {Object} valueNode - AST node representing the property value
54
+ * @returns {PropertySchema|null} Property schema or null
55
+ */
56
+ function extractPropertySchema(valueNode) {
57
+ if (!valueNode) return null;
58
+
59
+ switch (valueNode.type) {
60
+ case NODE_TYPES.OBJECT_EXPRESSION:
61
+ return extractObjectSchema(valueNode);
62
+
63
+ case NODE_TYPES.ARRAY_EXPRESSION:
64
+ return extractArraySchema(valueNode);
65
+
66
+ case NODE_TYPES.LITERAL:
67
+ return extractLiteralSchema(valueNode);
68
+
69
+ default:
70
+ return { type: 'any' };
71
+ }
72
+ }
73
+
74
+ /**
75
+ * Extracts schema for object expressions
76
+ * @param {Object} node - ObjectExpression node
77
+ * @returns {PropertySchema}
78
+ */
79
+ function extractObjectSchema(node) {
80
+ return {
81
+ type: 'object',
82
+ properties: extractProperties(node)
83
+ };
84
+ }
85
+
86
+ /**
87
+ * Extracts schema for array expressions
88
+ * @param {Object} node - ArrayExpression node
89
+ * @returns {PropertySchema}
90
+ */
91
+ function extractArraySchema(node) {
92
+ const itemType = inferArrayItemType(node.elements);
93
+
94
+ return {
95
+ type: 'array',
96
+ items: {
97
+ type: itemType
98
+ }
99
+ };
100
+ }
101
+
102
+ /**
103
+ * Extracts schema for literal values
104
+ * @param {Object} node - Literal node
105
+ * @returns {PropertySchema}
106
+ */
107
+ function extractLiteralSchema(node) {
108
+ const valueType = typeof node.value;
109
+
110
+ // Handle null and undefined
111
+ if (node.value === null || valueType === 'undefined') {
112
+ return { type: 'any' };
113
+ }
114
+
115
+ // Handle valid primitive types
116
+ if (['string', 'number', 'boolean'].includes(valueType)) {
117
+ return { type: valueType };
118
+ }
119
+
120
+ // Default to 'any' for other types
121
+ return { type: 'any' };
122
+ }
123
+
124
+ /**
125
+ * Infers the item type of an array from its elements
126
+ * @param {Array} elements - Array of AST nodes
127
+ * @returns {string} Inferred type or 'any'
128
+ */
129
+ function inferArrayItemType(elements) {
130
+ if (!elements || elements.length === 0) {
131
+ return 'any';
132
+ }
133
+
134
+ const types = new Set();
135
+
136
+ elements.forEach(element => {
137
+ if (!element) return;
138
+
139
+ const elementType = getElementType(element);
140
+ types.add(elementType);
141
+ });
142
+
143
+ // If all elements have the same type, use that type
144
+ if (types.size === 1) {
145
+ return Array.from(types)[0];
146
+ }
147
+
148
+ // Mixed types default to 'any'
149
+ return 'any';
150
+ }
151
+
152
+ /**
153
+ * Gets the type of an array element
154
+ * @param {Object} element - AST node
155
+ * @returns {string} Element type
156
+ */
157
+ function getElementType(element) {
158
+ switch (element.type) {
159
+ case NODE_TYPES.LITERAL:
160
+ return typeof element.value;
161
+ case NODE_TYPES.OBJECT_EXPRESSION:
162
+ return 'object';
163
+ case NODE_TYPES.ARRAY_EXPRESSION:
164
+ return 'array';
165
+ default:
166
+ return 'any';
167
+ }
168
+ }
169
+
170
+ module.exports = {
171
+ extractProperties
172
+ };
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @fileoverview JavaScript analytics tracking analyzer - main entry point
3
+ * @module analyze/javascript
4
+ */
5
+
6
+ const { parseFile, findTrackingEvents, FileReadError, ParseError } = require('./parser');
7
+
8
+ /**
9
+ * Analyzes a JavaScript file for analytics tracking calls
10
+ * @param {string} filePath - Path to the JavaScript file to analyze
11
+ * @param {string} [customFunction] - Optional custom function name to detect
12
+ * @returns {Array<Object>} Array of tracking events found in the file
13
+ */
14
+ function analyzeJsFile(filePath, customFunction) {
15
+ const events = [];
16
+
17
+ try {
18
+ // Parse the file into an AST
19
+ const ast = parseFile(filePath);
20
+
21
+ // Find and extract tracking events
22
+ const foundEvents = findTrackingEvents(ast, filePath, customFunction);
23
+ events.push(...foundEvents);
24
+
25
+ } catch (error) {
26
+ if (error instanceof FileReadError) {
27
+ console.error(`Error reading file ${filePath}: ${error.originalError.message}`);
28
+ } else if (error instanceof ParseError) {
29
+ console.error(`Error parsing file ${filePath}: ${error.originalError.message}`);
30
+ } else {
31
+ console.error(`Unexpected error analyzing ${filePath}: ${error.message}`);
32
+ }
33
+ }
34
+
35
+ return events;
36
+ }
37
+
38
+ module.exports = { analyzeJsFile };
@@ -0,0 +1,126 @@
1
+ /**
2
+ * @fileoverview AST parsing and walking module
3
+ * @module analyze/javascript/parser
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const acorn = require('acorn');
8
+ const jsx = require('acorn-jsx');
9
+ const walk = require('acorn-walk');
10
+ const { extend } = require('acorn-jsx-walk');
11
+ const { PARSER_OPTIONS, NODE_TYPES } = require('./constants');
12
+ const { detectAnalyticsSource } = require('./detectors');
13
+ const { extractEventData, processEventData } = require('./extractors');
14
+ const { findWrappingFunction } = require('./utils/function-finder');
15
+
16
+ // Extend walker to support JSX
17
+ extend(walk.base);
18
+
19
+ // Configure parser with JSX support
20
+ const parser = acorn.Parser.extend(jsx());
21
+
22
+ /**
23
+ * Error thrown when file cannot be read
24
+ */
25
+ class FileReadError extends Error {
26
+ constructor(filePath, originalError) {
27
+ super(`Failed to read file: ${filePath}`);
28
+ this.name = 'FileReadError';
29
+ this.filePath = filePath;
30
+ this.originalError = originalError;
31
+ }
32
+ }
33
+
34
+ /**
35
+ * Error thrown when file cannot be parsed
36
+ */
37
+ class ParseError extends Error {
38
+ constructor(filePath, originalError) {
39
+ super(`Failed to parse file: ${filePath}`);
40
+ this.name = 'ParseError';
41
+ this.filePath = filePath;
42
+ this.originalError = originalError;
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Parses a JavaScript file and returns its AST
48
+ * @param {string} filePath - Path to the JavaScript file
49
+ * @returns {Object} Parsed AST
50
+ * @throws {FileReadError} If file cannot be read
51
+ * @throws {ParseError} If file cannot be parsed
52
+ */
53
+ function parseFile(filePath) {
54
+ let code;
55
+
56
+ try {
57
+ code = fs.readFileSync(filePath, 'utf8');
58
+ } catch (error) {
59
+ throw new FileReadError(filePath, error);
60
+ }
61
+
62
+ try {
63
+ return parser.parse(code, PARSER_OPTIONS);
64
+ } catch (error) {
65
+ throw new ParseError(filePath, error);
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Walks the AST and finds analytics tracking calls
71
+ * @param {Object} ast - Parsed AST
72
+ * @param {string} filePath - Path to the file being analyzed
73
+ * @param {string} [customFunction] - Custom function name to detect
74
+ * @returns {Array<Object>} Array of found events
75
+ */
76
+ function findTrackingEvents(ast, filePath, customFunction) {
77
+ const events = [];
78
+
79
+ walk.ancestor(ast, {
80
+ [NODE_TYPES.CALL_EXPRESSION]: (node, ancestors) => {
81
+ try {
82
+ const event = extractTrackingEvent(node, ancestors, filePath, customFunction);
83
+ if (event) {
84
+ events.push(event);
85
+ }
86
+ } catch (error) {
87
+ console.error(`Error processing node in ${filePath}:`, error.message);
88
+ }
89
+ }
90
+ });
91
+
92
+ return events;
93
+ }
94
+
95
+ /**
96
+ * Extracts tracking event from a CallExpression node
97
+ * @param {Object} node - CallExpression node
98
+ * @param {Array<Object>} ancestors - Ancestor nodes
99
+ * @param {string} filePath - File path
100
+ * @param {string} [customFunction] - Custom function name
101
+ * @returns {Object|null} Extracted event or null
102
+ */
103
+ function extractTrackingEvent(node, ancestors, filePath, customFunction) {
104
+ // Detect the analytics source
105
+ const source = detectAnalyticsSource(node, customFunction);
106
+ if (source === 'unknown') {
107
+ return null;
108
+ }
109
+
110
+ // Extract event data based on the source
111
+ const eventData = extractEventData(node, source);
112
+
113
+ // Get location and context information
114
+ const line = node.loc.start.line;
115
+ const functionName = findWrappingFunction(node, ancestors);
116
+
117
+ // Process the event data into final format
118
+ return processEventData(eventData, source, filePath, line, functionName);
119
+ }
120
+
121
+ module.exports = {
122
+ parseFile,
123
+ findTrackingEvents,
124
+ FileReadError,
125
+ ParseError
126
+ };
@@ -0,0 +1,123 @@
1
+ /**
2
+ * @fileoverview Utilities for finding function context in AST
3
+ * @module analyze/javascript/utils/function-finder
4
+ */
5
+
6
+ const { NODE_TYPES } = require('../constants');
7
+
8
+ /**
9
+ * Finds the name of the function that wraps a given node
10
+ * @param {Object} node - The AST node to find the wrapper for
11
+ * @param {Array<Object>} ancestors - Array of ancestor nodes from acorn-walk
12
+ * @returns {string} The function name or 'global' if not in a function
13
+ */
14
+ function findWrappingFunction(node, ancestors) {
15
+ // Traverse ancestors from closest to furthest
16
+ for (let i = ancestors.length - 1; i >= 0; i--) {
17
+ const current = ancestors[i];
18
+ const functionName = extractFunctionName(current, node, ancestors[i - 1]);
19
+
20
+ if (functionName) {
21
+ return functionName;
22
+ }
23
+ }
24
+
25
+ return 'global';
26
+ }
27
+
28
+ /**
29
+ * Extracts function name from different AST node types
30
+ * @param {Object} current - Current ancestor node
31
+ * @param {Object} node - Original node being analyzed
32
+ * @param {Object} [parent] - Parent of current node
33
+ * @returns {string|null} Function name or null if not a function context
34
+ */
35
+ function extractFunctionName(current, node, parent) {
36
+ switch (current.type) {
37
+ case NODE_TYPES.VARIABLE_DECLARATOR:
38
+ return handleVariableDeclarator(current, node);
39
+
40
+ case NODE_TYPES.FUNCTION_DECLARATION:
41
+ return current.id ? current.id.name : 'anonymous';
42
+
43
+ case NODE_TYPES.METHOD_DEFINITION:
44
+ return current.key.name || 'anonymous';
45
+
46
+ case NODE_TYPES.PROPERTY:
47
+ return handleObjectProperty(current, node);
48
+
49
+ case NODE_TYPES.EXPORT_NAMED:
50
+ return handleNamedExport(current);
51
+
52
+ default:
53
+ return null;
54
+ }
55
+ }
56
+
57
+ /**
58
+ * Handles variable declarator nodes (const/let/var declarations)
59
+ * @param {Object} declarator - VariableDeclarator node
60
+ * @param {Object} node - Original node being analyzed
61
+ * @returns {string|null} Function name or null
62
+ */
63
+ function handleVariableDeclarator(declarator, node) {
64
+ // Direct assignment: const myFunc = () => {}
65
+ if (declarator.init === node) {
66
+ return declarator.id.name;
67
+ }
68
+
69
+ // Function expression assignment
70
+ if (declarator.init && isFunctionNode(declarator.init)) {
71
+ return declarator.id.name;
72
+ }
73
+
74
+ return null;
75
+ }
76
+
77
+ /**
78
+ * Handles object property nodes (methods in object literals)
79
+ * @param {Object} property - Property node
80
+ * @param {Object} node - Original node being analyzed
81
+ * @returns {string|null} Function name or null
82
+ */
83
+ function handleObjectProperty(property, node) {
84
+ if (property.value === node || isFunctionNode(property.value)) {
85
+ return property.key.name || property.key.value || 'anonymous';
86
+ }
87
+ return null;
88
+ }
89
+
90
+ /**
91
+ * Handles named export declarations
92
+ * @param {Object} exportNode - ExportNamedDeclaration node
93
+ * @returns {string|null} Function name or null
94
+ */
95
+ function handleNamedExport(exportNode) {
96
+ if (!exportNode.declaration || !exportNode.declaration.declarations) {
97
+ return null;
98
+ }
99
+
100
+ const declaration = exportNode.declaration.declarations[0];
101
+ if (declaration && isFunctionNode(declaration.init)) {
102
+ return declaration.id.name;
103
+ }
104
+
105
+ return null;
106
+ }
107
+
108
+ /**
109
+ * Checks if a node is a function (arrow function or function expression)
110
+ * @param {Object} node - AST node to check
111
+ * @returns {boolean}
112
+ */
113
+ function isFunctionNode(node) {
114
+ return node && (
115
+ node.type === NODE_TYPES.ARROW_FUNCTION ||
116
+ node.type === NODE_TYPES.FUNCTION_EXPRESSION ||
117
+ node.type === NODE_TYPES.FUNCTION_DECLARATION
118
+ );
119
+ }
120
+
121
+ module.exports = {
122
+ findWrappingFunction
123
+ };